#!/bin/sh
PATH="/usr/bin:/bin:/usr/sbin:/sbin"

set -e

xslt_processor="xsltproc --nonet --novalid --maxdepth 25000"
tmpdir=`mktemp -d`
checker_dir="/usr/share/dbus-tools/policychecker"
schematron_dir="/usr/share/dbus-tools/policychecker/xslt"
conf_path=("/usr/share/dbus-1" "/etc/dbus-1")
sub_conf_path=("system.d" "session.d")
bus_type=-1
config_file=-1
checker_include_all=0
verbose_mode=0
schema_file="$checker_dir/rules.xsl"

system_privileges_file="$tmpdir/privileges_system"
conf_privileges_file="$tmpdir/privileges_conf"
cynara_db="/var/cynara/db"

cleanup() {
	rm -rf $tmpdir
	exit $1
}

trap cleanup 0

usage() {
	echo "Usage: $0 [-v] [-p] [-s|-u|filename]"
	echo ""
	echo -e "\tfilename    dbus policy configuration file"
	echo -e "\t-s          system bus"
	echo -e "\t-u          session bus"
	echo -e "\t-d          enable verbose mode"
	echo -e "\t-v          include every iso xsls"
	echo -e "\t-p          enable profile mode"
}

# use "checker opt + config-file"
# getopts doesn't support - "checker config-file + opt"
while getopts :sudvp opt
do	case "$opt" in
	s)	if [ $bus_type -eq -1 ]; then
			bus_type=0
			echo "check system bus"
		fi
		;;
	u)	if [ $bus_type -eq -1 ]; then
			bus_type=1
			echo "check session bus"
		fi
		;;
	d)	echo "enable verbose mode"
		verbose_mode=1
		;;
	v)	echo "include every xsl. iso_dsdl_include.xsl"
		checker_include_all=1
		;;
	p)	echo "enable profile mode"
		xslt_processor="$xslt_processor --profile"
		;;
	?)	echo "Unknown arg:$OPTARG"
		usage
		exit 1
		;;
	esac
done

shift $(( OPTIND - 1 ))

if [ $bus_type -eq -1 ]; then
	if [ "$#" -ne 1 ]; then
		echo "unknown opts: $@"
		usage
		exit 1
	fi

	config_file=$1
	if [ ! -f $config_file ]; then
		echo "config file '$config_file' does not exist"
		usage
		exit 1
	fi
fi

if [ ! -d $cynara_db ]; then
	echo "Cynara database not found"
	exit 1
fi

# Cynara privilege formats can be various.
# For example,
# SR: http://tizen.org/privilege/${privilege_name}
# DA: http://samsung.com/tizen/privilege/${privilege_name}
# IM: http://developer.samsung.com/tizen/privilege/${privilege_name}
# VD: http://developer.samsung.com/privilege/${privilege_name}
PRIVILEGES="
http://tizen.org/privilege
http://samsung.com/tizen/privilege
http://developer.samsung.com/tizen/privilege
http://developer.samsung.com/privilege
"

for p in $PRIVILEGES; do
	grep $p $cynara_db/* | cut -d\; -f3 | sort -u >> $system_privileges_file
done

function check_policy_file(){
	config_file="$1"

	echo "Checking D-Bus policy file: $config_file"

	# TEST 1/3: check cynara privileges existence (there are too many to perform this check using xsltproc)
	$xslt_processor $checker_dir/extract_privilege.xsl $config_file | sort -u > $conf_privileges_file
	grep -Fxv -f $system_privileges_file $conf_privileges_file | while read line ; do echo "FAILED(cynara) no privilege in cynara db: $line" ; exit 1; done

	# TEST 2/3: check allow/deny duplicates (impossible to do directly with xpath 1.0, I don't know how to embed it into schematron config)
	$xslt_processor $checker_dir/same.xsl $config_file

	# TEST 3/3: apply schematron rules

	# build a test (@user = x or @user = y or ...) at runtime
	prepare_test() {
		echo $(getent $1 | sort -r | awk -F: '{entries[n++] = $1} END { while (n>0) {printf "@'"$2"' = '\''%s'\''%s", entries[n-1], (n > 1 ? " or " : ""); n--} }')
	}

	users_test=$(prepare_test passwd user)
	groups_test=$(prepare_test group group)

	tmpname="$tmpdir/$(basename $schema_file)"

	cat $schema_file | sed -e "s/USERS_TEST/$users_test/g" -e "s/GROUPS_TEST/$groups_test/g" > $tmpname.0

	if [ $checker_include_all -eq 1 ]; then
		$xslt_processor $schematron_dir/iso_dsdl_include.xsl $tmpname.0 > $tmpname.1
		$xslt_processor $schematron_dir/iso_abstract_expand.xsl $tmpname.1 > $tmpname.2
	else
		$xslt_processor $schematron_dir/iso_abstract_expand.xsl $tmpname.0 > $tmpname.2
	fi
	$xslt_processor $schematron_dir/iso_svrl_for_xslt1.xsl $tmpname.2 > $tmpname.3
	$xslt_processor $tmpname.3 $config_file > $tmpname.4
	$xslt_processor $checker_dir/report.xsl $tmpname.4

	# end-of-output, a new line for pretty printing
	echo
}

# print_matched_xml (str filename, int policyindex, str allow/deny, int allowindex)
# print_matched_xml "$filename" $policy "" 0
function print_matched_xml(){
	local cnt_policy=0
	local cnt_allow=0
	local cnt_deny=0
	local filename="$1"
	local policy_index=$2
	local allowdeny="$3"
	local allow_index=$4
	local found_policy_tag=0
	local print_to_end=0
	local line_cnt=0
	local is_comment=0
	local reg1="^[[:blank:]]*<!--.*$"
	local reg2="^[[:blank:]]*<!--.*-->[[:blank:]]*$"
	local reg3="^.*-->[[:blank:]]*$"
	local reg_start_allow="^[[:blank:]]*<$allowdeny.*$"
	local reg_end_tag=".*/>[[:blank:]]*$"
	local reg_start_policy="^[[:blank:]]*<policy.*$"
	local reg_end_policy="^.*</policy[[:blank:]]*>[[:blank:]]*$"

	#echo "printline: $filename $policy_index $allowdeny $allow_index"
	while IFS= read -r line
	do
		line_cnt=$((line_cnt+1))

		# ignore comment
		if [ $is_comment -eq 1 ]; then
			if [[ $line =~ $reg3 ]]; then
				is_comment=0
			fi
			continue
		fi
		# ignore comment
		if [[ $line =~ $reg1 ]]; then
			if [[ ! $line =~ $reg2 ]]; then
				is_comment=1
			fi
			continue
		fi

		# print multiple line
		if [ $print_to_end -eq 1 ]; then
			echo "$filename:$line_cnt: $line"
			if [[ "$line" =~ $reg_end_tag ]]; then
				print_to_end=0
			fi
			continue
		fi

		# end of policy
		if [ -z "$allowdeny" ] && [ $found_policy_tag -eq 1 ]; then
			echo "$filename:$line_cnt: $line"
			if [[ "$line" =~ $reg_end_policy ]]; then
				break
			fi
			continue
		fi

		# is matched policy ?
		if [[ "$line" =~ $reg_start_policy ]]; then
			cnt_policy=$((cnt_policy+1))
			cnt_allow=0
			cnt_deny=0
			found_policy_tag=0
			if [ $cnt_policy -eq $policy_index ]; then
				echo "$filename:$line_cnt: $line"
				found_policy_tag=1
			fi
			continue
		fi

		if [ $found_policy_tag -eq 0 ]; then
			continue
		fi

		# find matched allow or deny
		if [[ "$line" =~ $reg_start_allow ]]; then
			cnt_allow=$((cnt_allow+1))
			if [ $allow_index -eq 0 ] || [ $cnt_allow -eq $allow_index ]; then
				echo "$filename:$line_cnt: $line"
				if [[ ! "$line" =~ $reg_end_tag ]]; then
					print_to_end=1
					continue
				fi
			fi
		fi
	done < "$filename"
}

# print_err_info (str filename, str line)
function print_err_info(){
	local filename=$1
	local line=$2
	local ipolicy=0
	local allowdeny=0
	local iallowdeny=0

	# line contain a word 'policy' ? "FAILED(assert) at /busconfig/policy[1]/allow[1] ..."
	if [[ "$line" =~ ^(FAILED).*/policy(\[([0-9]{1,2})\])?(/(allow|deny)(\[([0-9]{1,2})\])?)?[[:blank:]]+.*$ ]]; then
		ipolicy=${BASH_REMATCH[3]}
		allowdeny=${BASH_REMATCH[5]}
		if [ ! -z ${BASH_REMATCH[7]} ]; then
			iallowdeny=${BASH_REMATCH[7]}
		fi

		print_matched_xml "$filename" $ipolicy "$allowdeny" $iallowdeny
		echo ""
		return
	fi

	echo "$line"
}

function verbose_mode(){
	local filename=$1
	local result=$2

	IFS=$'\n'
	lines=($result)
	IFS=' '
	for line in "${lines[@]}"; do
		echo $line
		if [[ "$line" =~ ^(FAILED).*$ ]]; then
			print_err_info "$filename" "$line"
		fi
	done
}

function check_policy_dir() {
	for d in "${conf_path[@]}"; do
		echo "$d/${sub_conf_path[$bus_type]}"
		target_path="$d/${sub_conf_path[$bus_type]}/*.conf"
		for f in $target_path; do
			if [ -f $f ]; then
				result=$(check_policy_file "$f")
				if [ $verbose_mode -eq 1 ]; then
					verbose_mode "$f" "$result"
				else
					echo "$result"
				fi
			fi
		done
	done
}

if [ $bus_type -eq -1 ]; then
	#check_policy_file "$config_file"
	result=$(check_policy_file "$config_file")
	if [ $verbose_mode -eq 1 ]; then
		verbose_mode "$config_file" "$result"
	else
		echo "$result"
	fi
else
	check_policy_dir
fi

exit 0

