#!/bin/sh
# ----------------------------------------------------------------------
#    Copyright (c) 1999-2008 NOVELL (All rights reserved)
#    Copyright (c) 2009-2012 Canonical Ltd. (All rights reserved)
#
#    This program is free software; you can redistribute it and/or
#    modify it under the terms of version 2 of the GNU General Public
#    License published by the Free Software Foundation.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, contact Novell, Inc.
# ----------------------------------------------------------------------
# rc.apparmor.functions by Steve Beattie
#
# NOTE: rc.apparmor initscripts that source this file need to implement
# the following set of functions:
#	aa_action
#	aa_log_action_start
#	aa_log_action_end
#	aa_log_success_msg
#	aa_log_warning_msg
#	aa_log_failure_msg
#	aa_log_skipped_msg
#	aa_log_daemon_msg
#	aa_log_end_msg

# Some nice defines that we use

CONFIG_DIR=/etc/apparmor
MODULE=apparmor
OLD_MODULE=subdomain
if [ -f "${CONFIG_DIR}/${MODULE}.conf" ] ; then
	APPARMOR_CONF="${CONFIG_DIR}/${MODULE}.conf"
elif [ -f "${CONFIG_DIR}/${OLD_MODULE}.conf" ] ; then
	APPARMOR_CONF="${CONFIG_DIR}/${OLD_MODULE}.conf"
elif [ -f "/etc/immunix/subdomain.conf" ] ; then
	aa_log_warning_msg "/etc/immunix/subdomain.conf is deprecated, use ${CONFIG_DIR}/subdomain.conf instead"
	APPARMOR_CONF="/etc/immunix/subdomain.conf"
elif [ -f "/etc/subdomain.conf" ] ; then
	aa_log_warning_msg "/etc/subdomain.conf is deprecated, use ${CONFIG_DIR}/subdomain.conf instead"
	APPARMOR_CONF="/etc/subdomain.conf"
else
	aa_log_warning_msg "Unable to find config file in ${CONFIG_DIR}, installation problem?"
fi

# Read configuration options from /etc/subdomain.conf, default is to
# warn if subdomain won't load.
SUBDOMAIN_MODULE_PANIC="warn"
SUBDOMAIN_ENABLE_OWLSM="no"
APPARMOR_ENABLE_AAEVENTD="no"

if [ -f "${APPARMOR_CONF}" ] ; then
	#parse the conf file to see what we should do
	. "${APPARMOR_CONF}"
fi

PARSER=/sbin/apparmor_parser

# SUBDOMAIN_DIR and APPARMOR_DIR might be defined in subdomain.conf|apparmor.conf
if [ -d "${APPARMOR_DIR}" ] ; then
	PROFILE_DIR=${APPARMOR_DIR}
elif [ -d "${SUBDOMAIN_DIR}" ] ; then
	PROFILE_DIR=${SUBDOMAIN_DIR}
elif [ -d /etc/apparmor.d ] ; then
	PROFILE_DIR=/etc/apparmor.d
elif [ -d /etc/subdomain.d ] ; then
	PROFILE_DIR=/etc/subdomain.d
fi
ABSTRACTIONS="-I${PROFILE_DIR}"
AA_EV_BIN=/usr/sbin/aa-eventd
AA_EV_PIDFILE=/var/run/aa-eventd.pid
AA_STATUS=/usr/sbin/aa-status
SD_EV_BIN=/usr/sbin/sd-event-dispatch.pl
SD_EV_PIDFILE=/var/run/sd-event-dispatch.init.pid
SD_STATUS=/usr/sbin/subdomain_status
SECURITYFS=/sys/kernel/security

SUBDOMAINFS_MOUNTPOINT=$(grep subdomainfs /etc/fstab  | \
	sed -e 's|^[[:space:]]*[^[:space:]]\+[[:space:]]\+\(/[^[:space:]]*\)[[:space:]]\+subdomainfs.*$|\1|' 2> /dev/null)

# keep exit status from parser during profile load.  0 is good, 1 is bad
STATUS=0

# Test if the apparmor "module" is present.
is_apparmor_present() {
	local modules=$1
	shift

	while [ $# -gt 0 ] ; do
		modules="$modules|$1"
		shift
	done

	# check for subdomainfs version of module
	grep -qE "^($modules)[[:space:]]" /proc/modules

	[ $? -ne 0 -a -d /sys/module/apparmor ]

	return $?
}

# This set of patterns to skip needs to be kept in sync with
# AppArmor.pm::isSkippableFile()
# returns 0 if profile should NOT be skipped
# returns 1 on verbose skip
# returns 2 on silent skip
skip_profile() {
	local profile=$1
	if [ "${profile%.rpmnew}" != "${profile}" -o \
	     "${profile%.rpmsave}" != "${profile}" -o \
	     -e "${PROFILE_DIR}/disable/`basename ${profile}`" -o \
	     "${profile%\~}" != "${profile}" ] ; then
		return 1
	fi
	# Silently ignore the dpkg files
	if [ "${profile%.dpkg-new}" != "${profile}" -o \
	     "${profile%.dpkg-old}" != "${profile}" -o \
	     "${profile%.dpkg-dist}" != "${profile}" -o \
	     "${profile%.dpkg-bak}" != "${profile}" ] ; then
		return 2
	fi

	return 0
}

force_complain() {
	local profile=$1

	# if profile not in complain mode
	if ! egrep -q "^/.*[ \t]+flags[ \t]*=[ \t]*\([ \t]*complain[ \t]*\)[ \t]+{" $profile ; then
		local link="${PROFILE_DIR}/force-complain/`basename ${profile}`"
		if [ -e "$link" ] ; then
			aa_log_warning_msg "found $link, forcing complain mode"
			return 0
		fi
	fi

	return 1
}

parse_profiles() {
	# get parser arg
	case "$1" in
		load)
			PARSER_ARGS="--add"
			PARSER_MSG="Loading AppArmor profiles "
			;;
		reload)
			PARSER_ARGS="--replace"
			PARSER_MSG="Reloading AppArmor profiles "
			;;
		*)
			aa_log_failure_msg "required 'load' or 'reload'"
			exit 1
			;;
	esac
	aa_log_action_start "$PARSER_MSG"
	# run the parser on all of the apparmor profiles
	if [ ! -f "$PARSER" ]; then
		aa_log_failure_msg "AppArmor parser not found"
		aa_log_action_end 1
		exit 1
	fi

	if [ ! -d "$PROFILE_DIR" ]; then
		aa_log_failure_msg "Profile directory not found"
		aa_log_action_end 1
		exit 1
	fi

	if [ -z "$(ls $PROFILE_DIR/)" ]; then
		aa_log_failure_msg "No profiles found"
		aa_log_action_end 1
		return 1
	fi

	for profile in $PROFILE_DIR/*; do
		skip_profile "${profile}"
		skip=$?
		# Ignore skip status == 2 (silent skip)
		if [ "$skip" -eq 1 ] ; then
			aa_log_skipped_msg "$profile"
			logger -t "AppArmor(init)" -p daemon.warn "Skipping profile $profile"
			STATUS=2
			continue
		elif [ "$skip" -ne 0 ]; then
			continue
		fi
		if [ -f "${profile}" ] ; then
			COMPLAIN=""
			if force_complain "${profile}" ; then
				COMPLAIN="-C"
			fi
			$PARSER $ABSTRACTIONS $PARSER_ARGS $COMPLAIN "$profile" > /dev/null
			if [ $? -ne 0 ]; then
				aa_log_failure_msg "$profile failed to load"
				STATUS=1
			fi
		fi
	done
	if [ $STATUS -eq 2 ]; then
		STATUS=0
	fi
	aa_log_action_end "$STATUS"
	return $STATUS
}

profiles_names_list() {
	# run the parser on all of the apparmor profiles
	if [ ! -f "$PARSER" ]; then
		aa_log_failure_msg "- AppArmor parser not found"
		exit 1
	fi

	if [ ! -d "$PROFILE_DIR" ]; then
		aa_log_failure_msg "- Profile directory not found"
		exit 1
	fi

	for profile in $PROFILE_DIR/*; do
	        if skip_profile "${profile}" && [ -f "${profile}" ] ; then
			LIST_ADD=$($PARSER $ABSTRACTIONS -N "$profile" )
			if [ $? -eq 0 ]; then
				echo "$LIST_ADD"
			fi
		fi
	done
}

failstop_system() {
	level=$(runlevel | cut -d" " -f2)
	if [ $level -ne "1" ] ; then
		aa_log_failure_msg "- could not start AppArmor.  Changing to runlevel 1"
		telinit 1;
		return -1;
	fi
	aa_log_failure_msg "- could not start AppArmor."
	return -1
}

module_panic() {
	# the module failed to load, determine what action should be taken

	case "$SUBDOMAIN_MODULE_PANIC" in
		"warn"|"WARN")
			return 1 ;;
		"panic"|"PANIC") failstop_system
			rc=$?
			return $rc ;;
		*) aa_log_failure_msg "- invalid AppArmor module fail option"
			return -1 ;;
	esac
}

is_apparmor_loaded() {
	if ! is_securityfs_mounted ; then
		mount_securityfs
	fi

	mount_subdomainfs

	if [ -f "${SECURITYFS}/${MODULE}/profiles" ]; then
		SFS_MOUNTPOINT="${SECURITYFS}/${MODULE}"
		return 0
	fi

	if [ -f "${SECURITYFS}/${OLD_MODULE}/profiles" ]; then
		SFS_MOUNTPOINT="${SECURITYFS}/${OLD_MODULE}"
		return 0
	fi

	if [ -f "${SUBDOMAINFS_MOUNTPOINT}/profiles" ]; then
		SFS_MOUNTPOINT=${SUBDOMAINFS_MOUNTPOINT}
		return 0
	fi

	# check for subdomainfs version of module
	is_apparmor_present apparmor subdomain

	return $?
}

is_securityfs_mounted() {
	test -d ${SECURITYFS} -a -d /sys/fs/cgroup/systemd || grep -q securityfs /proc/filesystems && grep -q securityfs /proc/mounts
	return $?
}

mount_securityfs() {
	if grep -q securityfs /proc/filesystems ; then
		aa_action "Mounting securityfs on ${SECURITYFS}" \
				mount -t securityfs securityfs "${SECURITYFS}"
		return $?
	fi
	return 0
}


mount_subdomainfs() {
	# for backwords compatibility
	if grep -q subdomainfs /proc/filesystems && \
	   ! grep -q subdomainfs /proc/mounts && \
	   [ -n "${SUBDOMAINFS_MOUNTPOINT}" ]; then
		aa_action "Mounting subdomainfs on ${SUBDOMAINFS_MOUNTPOINT}" \
				mount "${SUBDOMAINFS_MOUNTPOINT}"
		return $?
	fi
	return 0
}

unmount_subdomainfs() {
	SUBDOMAINFS=$(grep subdomainfs /proc/mounts  | cut -d" " -f2 2> /dev/null)
	if [ -n "${SUBDOMAINFS}" ]; then
		aa_action "Unmounting subdomainfs" umount ${SUBDOMAINFS}
	fi
}

load_module() {
	local rc=0
	if modinfo -F filename apparmor > /dev/null 2>&1 ; then
		MODULE=apparmor
	elif modinfo -F filename ${OLD_MODULE} > /dev/null 2>&1 ; then
		MODULE=${OLD_MODULE}
	fi

	if ! is_apparmor_present apparmor subdomain ; then
		aa_action "Loading AppArmor module" /sbin/modprobe -q $MODULE $1
		rc=$?
		if [ $rc -ne 0 ] ; then
			module_panic
			rc=$?
			if [ $rc -ne 0 ] ; then
				exit $rc
			fi
		fi
	fi

	if ! is_apparmor_loaded ; then
		return 1
	fi

	return $rc
}

apparmor_start() {
	aa_log_daemon_msg "Starting AppArmor"
	if ! is_apparmor_loaded ; then
		load_module
		rc=$?
		if [ $rc -ne 0 ] ; then
			aa_log_end_msg $rc
			return $rc
		fi
	fi

	if [ ! -w "$SFS_MOUNTPOINT/.load" ] ; then
		aa_log_failure_msg "Loading AppArmor profiles - failed, Do you have the correct privileges?"
		aa_log_end_msg 1
		return 1
	fi

	configure_owlsm

	# if there is anything in the profiles file don't load
	if ! read line < "$SFS_MOUNTPOINT/profiles"; then
		parse_profiles load
	else
		aa_log_skipped_msg ": already loaded with profiles."
		return 0
	fi
	aa_log_end_msg 0
	return 0
}

remove_profiles() {

	# removing profiles as we directly read from subdomainfs
	# doesn't work, since we are removing entries which screws up
	# our position.  Lets hope there are never enough profiles to
	# overflow the variable
	if ! is_apparmor_loaded ; then
		aa_log_failure_msg "AppArmor module is not loaded"
		return 1
	fi

	if [ ! -w "$SFS_MOUNTPOINT/.remove" ] ; then
		aa_log_failure_msg "Root privileges not available"
		return 1
	fi

	if [ ! -x "${PARSER}" ] ; then
		aa_log_failure_msg "Unable to execute AppArmor parser"
		return 1
	fi

	retval=0
	# We filter child profiles as removing the parent will remove
	# the children
	sed -e "s/ (\(enforce\|complain\))$//" "$SFS_MOUNTPOINT/profiles" | \
	LC_COLLATE=C sort | grep -v // | while read profile ; do
		echo -n "$profile" > "$SFS_MOUNTPOINT/.remove"
		rc=$?
		if [ ${rc} -ne 0 ] ; then 
			retval=${rc}
		fi
	done
	return ${retval}
}

apparmor_stop() {
	aa_log_daemon_msg "Unloading AppArmor profiles "
	remove_profiles
	rc=$?
	aa_log_end_msg $rc
	return $rc
}

apparmor_kill() {
	aa_log_daemon_msg "Unloading AppArmor modules "
	if ! is_apparmor_loaded ; then
		aa_log_failure_msg "AppArmor module is not loaded"
		return 1
	fi

	unmount_subdomainfs
	if is_apparmor_present apparmor ; then
		MODULE=apparmor
	elif is_apparmor_present subdomain ; then
		MODULE=subdomain
	else
		aa_log_failure_msg "AppArmor is builtin"
		return 1
	fi
	/sbin/modprobe -qr $MODULE
	rc=$?
	aa_log_end_msg $rc
	return $rc
}

__apparmor_restart() {
	if [ ! -w "$SFS_MOUNTPOINT/.load" ] ; then
		aa_log_failure_msg "Loading AppArmor profiles - failed, Do you have the correct privileges?"
		return 4
	fi

	aa_log_daemon_msg "Restarting AppArmor"

	configure_owlsm
	parse_profiles reload

	rc=$?
	aa_log_end_msg $rc
	return $rc
}

apparmor_restart() {
	if ! is_apparmor_loaded ; then
		apparmor_start
		rc=$?
		return $rc
	fi

	__apparmor_restart
	return $?
}

apparmor_try_restart() {
	if ! is_apparmor_loaded ; then
		return 0
	fi

	__apparmor_restart
	return $?
}

configure_owlsm () {
	if [ "${SUBDOMAIN_ENABLE_OWLSM}" = "yes" -a -f ${SFS_MOUNTPOINT}/control/owlsm ] ; then
		# Sigh, the "sh -c" is necessary for the SuSE aa_action
		# and it can't be abstracted out as a seperate function, as
		# that breaks under RedHat's action, which needs a
		# binary to invoke.
		aa_action "Enabling OWLSM extension" sh -c "echo -n \"1\" > \"${SFS_MOUNTPOINT}/control/owlsm\""
	elif [ -f "${SFS_MOUNTPOINT}/control/owlsm" ] ; then
		aa_action "Disabling OWLSM extension" sh -c "echo -n \"0\" > \"${SFS_MOUNTPOINT}/control/owlsm\""
	fi
}

apparmor_status () {
	if test -x ${AA_STATUS} ; then
		${AA_STATUS} --verbose
		return $?
	fi
	if test -x ${SD_STATUS} ; then
		${SD_STATUS} --verbose
		return $?
	fi
	if ! is_apparmor_loaded ; then
		echo "AppArmor is not loaded."
		rc=1
	else
		echo "AppArmor is enabled."
		rc=0
	fi
	echo "Install the apparmor-utils package to receive more detailed"
	echo "status information here (or examine ${SFS_MOUNTPOINT} directly)."

	return $rc
}
