#!/bin/sh
# PCP QA Test No. 917
# SELinux Testing
#
# Copyright (c) 2017-2018 Red Hat Inc.  All Rights Reserved.
#

seq=`basename $0`
echo "QA output created by $seq"

# get standard environment, filters and checks
. ./common.product
. ./common.filter
. ./common.check

policy_name="pcpupstream"
policy_file="$PCP_VAR_DIR/selinux/$policy_name.pp"
which sedismod >/dev/null 2>&1 || _notrun "sedismod tool not installed (module disassembly)"
which semodule >/dev/null 2>&1 || _notrun "semodule tool not installed"
which seinfo >/dev/null 2>&1 || _notrun "seinfo tool not installed"
( seinfo -t 2>&1 | grep 'Default policy search failed: No such file or directory' >/dev/null ) && _notrun "seinfo version bad: can't load default policy"
[ -f "$policy_file" ] || _notrun "upstream policy package not installed"
$sudo semodule -l 2>&1 | grep -q $policy_name || _notrun "upstream policy package not loaded"
[ -f $PCP_INC_DIR/builddefs ] || _notrun "No $PCP_INC_DIR/builddefs"

seinfo --common >/dev/null 2>&1
if [ $? -eq 0 ]
then
    common_flag="--common"
else
    common_flag=""
fi

_filter_semodule()
{
    awk '{ print $1 }'
}

_filter_sedismod()
{
    awk '
/^--- begin avrule block ---/	{ body=1 }
	{ if (body) print $0 }' | \
    sed \
	-e '/^--- begin avrule block ---/d' \
	-e '/^decl [0-9[0-9]*/d' \
	-e '/pcp_pmwebd_t/d' \
	-e '/pcp_pmmgr_t/d' \
	-e '/^Command/d' \
    #end
}

_filter_outfile()
{
    sed -f $tmp.sed
}

# As of Fedora 34, sedismod(1) is sorting the attributes ... to
# accommodate older versions of sedismod(1) and keep $seq.out in the
# same order we use the function below to sort both $seq.out.in and
# sedismod(1) output
#
# so we turn (for example)
#   allow [pcp_pmproxy_t] [shadow_t] : [file] { ioctl read getattr lock open };
# into
#   allow [pcp_pmproxy_t] [shadow_t] : [file] { getattr ioctl lock open read };
_sort_attr()
{
    $PCP_AWK_PROG '
$1 == "allow"	{
		    inattr = 0
		    for (f = 1; f <= NF; f++) {
			if (inattr == 0 && $f == "{") {
			    out = out " " $f
			    inattr = 1
			    continue
			}
			if (inattr > 0 && $f == "};") {
			    # asort() is a GNU awk extension ... the list
			    # is short, so linear search is probably OK
			    for (a = 1; a < inattr; a++) {
				pick = -1
				for (p = 1; p < inattr; p++) {
				    if (attrs[p] != "") {
					if (pick == -1) {
					    # first unselected one
					    pick = p
					}
					else if (attrs[p] < attrs[pick]) {
					    # smallest unselected one, so far
					    pick = p
					}
				    }
				}
				out = out " " attrs[pick]
				attrs[pick] = ""
			    }
			    print "  " out " };"
			    next
			}
			if (inattr) {
			    attrs[inattr] = $f
			    inattr++
			}
			else {
			    if (f == 1)
				out = $f
			    else
				out = out " " $f
			}
		    }
		}
		{ print }'
}

status=1	# failure is the default!
$sudo rm -rf $tmp $tmp.* $seq.full
trap "cd $here; $sudo rm -rf $tmp $tmp.*; exit \$status" 0 1 2 3 15

# use logic from configure.ac to build list of optional types that are
# not present on this system and need to be culled from $seq.out.in
#
seinfo -t >$tmp.types
echo '/^#/d' >$tmp.sed
echo '/^!/s// /' >>$tmp.sed
for type in container_runtime_t nsfs_t docker_var_lib_t unreserved_port_t \
	    tracefs_t unconfined_service_t numad_t container_var_run_t \
	    virt_var_run_t proc_security_t sbd_exec_t kmod_exec_t \
	    dma_device_t glusterd_log_t mdadm_conf_t \
	    # end
do
    if grep "^[ 	][ 	]*$type\$" $tmp.types >/dev/null
    then
	:
    else
	echo "/^  *$type\$/d" >>$tmp.sed
	# and some missing types => associated rules need to be culled or
	# edited
	#
	case "$type"
	in
	    nsfs_t)
		echo '/allow \[pcp_pmcd_t] \[nsfs_t]/d' >>$tmp.sed
		;;
	    unreserved_port_t)
		echo '/allow \[pcp_pmcd_t] \[unreserved_port_t]/d' >>$tmp.sed
		echo '/allow \[pcp_pmlogger_t] \[unreserved_port_t]/d' >>$tmp.sed
		;;
	    tracefs_t)
		echo '/allow \[pcp_pmcd_t] \[tracefs_t]/d' >>$tmp.sed
		;;
	    unconfined_service_t)
		echo '/allow \[pcp_pmcd_t] \[unconfined_service_t]/d' >>$tmp.sed
		echo '/allow \[pcp_pmlogger_t] \[unconfined_service_t]/d' >>$tmp.sed
		echo '/allow \[pcp_pmie_t] \[unconfined_service_t]/d' >>$tmp.sed
		;;
	    numad_t)
		echo '/allow \[pcp_pmcd_t] \[numad_t]/d' >>$tmp.sed
		;;
	    container_var_run_t)
		echo '/allow \[pcp_pmcd_t] \[container_var_run_t]/d' >>$tmp.sed
		;;
	    virt_var_run_t)
		echo '/allow \[pcp_pmcd_t] \[virt_var_run_t]/d' >>$tmp.sed
		;;
	    proc_security_t)
		echo '/allow \[pcp_pmcd_t] \[proc_security_t]/d' >>$tmp.sed
		;;
	    sbd_exec_t)
		echo '/allow \[pcp_pmcd_t] \[sbd_exec_t]/d' >>$tmp.sed
		;;
	    kmod_exec_t)
		echo '/allow \[pcp_pmcd_t] \[kmod_exec_t]/d' >>$tmp.sed
		;;
	    dma_device_t)
		echo '/allow \[pcp_pmcd_t] \[dma_device_t]/d' >>$tmp.sed
		;;
	    mdadm_conf_t)
		echo '/allow \[pcp_pmcd_t] \[mdadm_conf_t\]/d' >>$tmp.sed
		;;
	esac
    fi
done

# now the class ones ... also using logic from configure.ac
#
if seinfo -x --class=cap_userns $common_flag 2>&1 \
   | grep '^[ 	][ 	]*sys_ptrace$' >/dev/null
then
    :
else
    echo '/allow \[pcp_pmie_t] .*\[cap_userns]/d' >>$tmp.sed
    echo '/allow \[pcp_pmcd_t] .*\[cap_userns]/d' >>$tmp.sed
fi

if seinfo -x --class=file $common_flag 2>&1 \
   | grep '^[ 	][ 	]*map$' >/dev/null
then
    :
elif seinfo -x --common file 2>&1 \
   | grep '^[ 	][ 	]*map$' >/dev/null
then
    :
else
    # if no map, need to cull these one as map is the only permission
    #
    echo '/allow \[pcp_pmcd_t] \[ldconfig_exec_t] : \[file].* map/d' >>$tmp.sed
    echo '/allow \[pcp_pmcd_t] \[default_t] : \[file].* map/d' >>$tmp.sed
    # strip "map" from permissions for others
    #
    echo '/\[pcp_pmie_exec_t] .*\[file]/s/ map / /' >>$tmp.sed
    echo '/\[pcp_pmcd_t] .*\[file]/s/ map / /' >>$tmp.sed
    echo '/\[pcp_pmie_t] .*\[hostname_exec_t]/s/ map / /' >>$tmp.sed
    echo '/\[pcp_pmcd_t] \[fsadm_exec_t]/s/ map / /' >>$tmp.sed
    echo '/\[pcp_pmcd_t] \[default_t]/s/ map / /' >>$tmp.sed
    echo '/\[pcp_pmcd_t] \[pcp_pmie_exec_t]/s/ map / /' >>$tmp.sed
    echo '/\[pcp_pmcd_t] \[pcp_tmp_t]/s/ map / /' >>$tmp.sed
    echo '/\[pcp_pmcd_t] \[ping_exec_t]/s/ map / /' >>$tmp.sed
fi

if seinfo -x --class=bpf $common_flag 2>&1 \
   | egrep '^[ 	][ 	]*(class |)bpf$' >/dev/null
then
    :
else
    echo '/allow \[pcp_pmcd_t] .*\[bpf]/d' >>$tmp.sed
fi

if seinfo -x --class=capability2 $common_flag 2>&1 \
   | grep '^[ 	][ 	]*syslog$' >/dev/null
then
    :
else
    # need full rule here because > 1 rule for this class
    #
    echo '/allow \[pcp_pmcd_t\] .*\[capability2\] { syslog };/d' >>$tmp.sed
fi

if seinfo -x --class=capability2 $common_flag 2>&1 \
   | grep '^[ 	][ 	]*bpf$' >/dev/null
then
    :
else
    # need full rule here because > 1 rule for this class
    #
    echo '/allow \[pcp_pmcd_t\] .*\[capability2\] { bpf };/d' >>$tmp.sed
fi

# rawip_socket is indeed nested and only present if the icmp_socket class
# IS NOT defined AND the rawip_socket class IS defined
# (see the selinux GNUlocaldefs file) ... so in the other cases we need
# to cull the rawip_socket line from $seq.out.in
#
if seinfo -x --class=icmp_socket $common_flag 2>&1 \
   | egrep '^[ 	][ 	]*(class |)icmp_socket$' >/dev/null
then
    echo '/allow \[pcp_pmcd_t\] .*\[rawip_socket\]/d' >>$tmp.sed
else
    echo '/allow \[pcp_pmcd_t\] .*\[icmp_socket\]/d' >>$tmp.sed
    if seinfo -x --class=rawip_socket $common_flag 2>&1 \
       | egrep '^[ 	][ 	]*(class |)rawip_socket$' >/dev/null
    then
	:
    else
	echo '/allow \[pcp_pmcd_t\] .*\[rawip_socket\]/d' >>$tmp.sed
    fi
fi

if seinfo -x --class=netlink_generic_socket $common_flag 2>&1 \
   | egrep '^[ 	][ 	]*(class |)netlink_generic_socket$' >/dev/null
then
    :
else
    echo '/allow \[pcp_pmcd_t\] .*\[netlink_generic_socket\]/d' >>$tmp.sed
fi

if seinfo -a 2>&1 \
   | grep '^[ 	][ 	]*non_auth_file_type$' >/dev/null
then
    echo '/allow \[pcp_domain] \[non_security_file_type]/d' >>$tmp.sed
else
    echo '/allow \[pcp_domain] \[non_auth_file_type]/d' >>$tmp.sed
fi

if grep 'PCP_SELINUX_FILES_MMAP_ALL_FILES[ 	]*=[ 	]*true' $PCP_INC_DIR/builddefs >/dev/null 2>&1
then
    :
else
    # from expansion of files_mmap_all_files(pcp_domain)
    #
    echo '/allow \[pcp_domain] \[file_type] : \[file].* map/d' >>$tmp.sed
fi

if grep 'PCP_SELINUX_LOGGING_WATCH_ALL_LOG_DIRS_PATH[ 	]*=[ 	]*true' $PCP_INC_DIR/builddefs >/dev/null 2>&1
then
    :
else
    # from expansion of logging_watch_all_log_dirs_path(pcp_domain)
    #
    echo '/allow \[pcp_domain] \[root_t] : \[dir] /d' >>$tmp.sed
    echo '/allow \[pcp_domain] \[var_t] : \[dir] /d' >>$tmp.sed
    echo '/allow \[pcp_domain] \[logfile] : \[dir] /d' >>$tmp.sed
    # and these two are from the expansion of logging_watch_journal_dir(pcp_domain)
    #
    echo '/allow \[pcp_domain] \[syslogd_var_run_t] : \[dir] /d' >>$tmp.sed
    echo '/allow \[pcp_pmcd_t] \[syslogd_var_run_t] : \[file] {  *}/d' >>$tmp.sed
fi

if seinfo -x --class=lockdown $common_flag 2>&1 \
   | egrep '^[ 	][ 	]*(class |)lockdown$' >/dev/null
then
    :
else
    echo '/allow \[pcp_pmcd_t\] .*\[lockdown\]/d' >>$tmp.sed
fi

if seinfo -x --class=netlink_tcpdiag_socket $common_flag 2>&1 \
   | egrep '^[ 	][ 	]*(class |)netlink_tcpdiag_socket$' >/dev/null
then
    :
else
    echo '/allow \[pcp_pmcd_t\] .*\[netlink_tcpdiag_socket\]/d' >>$tmp.sed
fi

cat $tmp.sed >>$seq.full
echo "--- end of sed ---" >>$seq.full

cat $seq.out.in \
| _filter_outfile \
| _sort_attr >$seq.out

echo "Checking that pcpupstream policy module has been properly installed"
echo "--- semodule -l ---" >>$seq.full
# two styles of semodule -l output need to be accommodated
# pcpupstream\t<version>\n
# pcpupstream\n
#
$sudo semodule -l \
| tee -a $seq.full \
| sed -n \
    -e '/^pcpupstream[ 	]/s/[ 	].*//' \
    -e '/^pcpupstream$/p' \
# end
echo "--- end of semodule -l ---" >>$seq.full
# real QA test starts here
echo "Checking policies."
printf '1\nq\n' \
| sedismod $policy_file \
| tee -a $seq.full \
| _filter_sedismod \
| _sort_attr

# success, all done
status=0
exit
