#!/bin/sh

log_write() {
	local facility=kern.$1
	logger -t AppArmor -p $facility "$2"
}

AA_STATUS=/usr/sbin/aa-status
SECURITYFS=/sys/kernel/security
SFS_MOUNTPOINT="${SECURITYFS}/apparmor"
PARSER=/sbin/apparmor_parser
PARSER_OPTS=
ADDITIONAL_PROFILE_DIR=

[ -d /etc/apparmor.d ] && PROFILE_DIRS=/etc/apparmor.d ||
	log_write warning "Unable to find profiles: /etc/apparmor.d"

[ -n "$ADDITIONAL_PROFILE_DIR" ] && [ -d "$ADDITIONAL_PROFILE_DIR" ] &&
	PROFILE_DIRS="$PROFILE_DIRS $ADDITIONAL_PROFILE_DIR"

dir_is_empty() {
	[ "$(du -s $1 | cut -f 1)" -eq 0 ] && return 0 || return 1
}

profiles_loaded_count() {

	[ -f ${SFS_MOUNTPOINT}/profiles ] &&
		return $(cat "${SFS_MOUNTPOINT}/profiles" | wc -l) || return 0
}

is_profiles_loaded() {

	[ -f ${SFS_MOUNTPOINT}/profiles ] && {
		rc=$(cat "${SFS_MOUNTPOINT}/profiles" | wc -l)
		[ "$rc" -ne 0 ] && return 0 || return 1
	}
	return 1
}

is_container_with_internal_policy() {

	local ns_stacked_path="${SFS_MOUNTPOINT}/.ns_stacked"
	local ns_name_path="${SFS_MOUNTPOINT}/.ns_name"
	local ns_stacked
	local ns_name

	if ! [ -f "$ns_stacked_path" ] || ! [ -f "$ns_name_path" ]; then
		return 1
	fi

	read -r ns_stacked < "$ns_stacked_path"
	if [ "$ns_stacked" != "yes" ]; then
		return 1
	fi

	# LXD and LXC set up AppArmor namespaces starting with "lxd-" and
	# "lxc-", respectively. Return non-zero for all other namespace
	# identifiers.

	read -r ns_name < "$ns_name_path"
	if [ "${ns_name#lxd-*}" = "$ns_name" ] && \
	   [ "${ns_name#lxc-*}" = "$ns_name" ]; then
		return 1
	fi

	return 0
}

skip_profile() {

	local profile="$1"

	if [ "${profile%.rpmnew}"   != "$profile" ] || \
	   [ "${profile%.rpmsave}"  != "$profile" ] || \
	   [ "${profile%.orig}"     != "$profile" ] || \
	   [ "${profile%.rej}"      != "$profile" ] || \
	   [ "${profile%\~}"        != "$profile" ] ; then
		return 1
	fi

	# Silently ignore the dpkg, pacman, ipk and xbps files

	if [ "${profile%.dpkg-new}"     != "$profile" ] || \
	   [ "${profile%.dpkg-old}"     != "$profile" ] || \
	   [ "${profile%.dpkg-dist}"    != "$profile" ] || \
	   [ "${profile%.dpkg-bak}"     != "$profile" ] || \
	   [ "${profile%.dpkg-remove}"  != "$profile" ] || \
	   [ "${profile%.ipk}"          != "$profile" ] || \
	   [ "${profile%.ipk-new}"      != "$profile" ] || \
	   [ "${profile%.ipk-old}"      != "$profile" ] || \
	   [ "${profile%.ipk-dist}"     != "$profile" ] || \
	   [ "${profile%.ipk-bak}"      != "$profile" ] || \
	   [ "${profile%.ipk-remove}"   != "$profile" ] || \
	   [ "${profile%.pacsave}"      != "$profile" ] || \
	   [ "${profile%.pacnew}"       != "$profile" ] ; then
		return 2
	fi

	$(echo "$profile" | grep -E -q '^.+\.new-[0-9\.]+_[0-9]+$'); [ "$?" -eq 0 ] && return 2

	return 0
}

__parse_profiles_dir() {

	local parser_cmd="$1"
	local profile_dir="$2"
	local status=0

	[ -x "$PARSER" ] || {
		log_write err "Unable to execute AppArmor parser"
		return 1
	}

	[ -d "$profile_dir" ] || {
		log_write warning "AppArmor profiles not found: $profile_dir"
		return 1
	}

	dir_is_empty "$profile_dir"; [ "$?" -eq 0 ] && {
		log_write err "No profiles found in $profile_dir"
		return 1
	}

	local nprocs=$(cat /proc/cpuinfo |grep "processor\t:"|wc -l)
	local rc=0
	local xargs_args=""
	[ "$nprocs" -ge 2 ] && xargs_args="--max-procs=$nprocs"

	"$PARSER" $PARSER_OPTS "$parser_cmd" -- "$profile_dir" || {

		for profile in "$profile_dir"/*; do
			skip_profile "$profile"
			skip=$?
			[ "$skip" -ne 0 ] && {
				[ "$skip" -ne 2 ] && log_write info "Skipped loading profile $profile"
				continue
			}
			[ -f "$profile" ] || continue
			echo "$profile"
		done | \

		# Use xargs to parallelize calls to the parser over all CPUs

		/usr/libexec/xargs-findutils -n1 -d"\n" $xargs_args \
			"$PARSER" $PARSER_OPTS "$parser_cmd" --

		[ "$?" -ne 0 ] && {
			rc=1
			log_write err "At least one profile failed to load"
		}
	}

	return $rc
}

parse_profiles() {

	case "$1" in
		load)
			PARSER_CMD="--add"
			PARSER_MSG="Loading profiles"
			;;
		reload)
			PARSER_CMD="--replace"
			PARSER_MSG="Reloading profiles"
			;;
		*)
			log_write err "Unknown parameter $1"
			log_write info "parse_profiles parameter must be either 'load' or 'reload'"
			return 1
			;;
	esac

	log_write info "$PARSER_MSG"

	[ -w "$SFS_MOUNTPOINT/.load" ] || {
		log_write err "${SFS_MOUNTPOINT}/.load not writable"
		return 1
	}

	[ -f "$PARSER" ] || {
		log_write err "AppArmor parser not found"
		return 1
	}

	# run parser on all profiles
	local rc=0
	for profile_dir in $PROFILE_DIRS; do
		__parse_profiles_dir "$PARSER_CMD" "$profile_dir" || rc=$?
	done

	return $rc
}

is_apparmor_loaded() {

	is_securityfs_mounted; [ "$?" -eq 0 ] || {
		mount_securityfs
	}

	[ -f "${SFS_MOUNTPOINT}/profiles" ] && return 0
	[ -d /sys/module/apparmor ] && return 0 || return 1
}

is_securityfs_mounted() {

	[ -d "$SECURITYFS" ] && {
		grep -q securityfs /proc/filesystems && grep -q securityfs /proc/mounts
		return $?
	}
	return 1
}

mount_securityfs() {

	local rc=0

	grep -q securityfs /proc/filesystems; [ "$?" -eq 0 ] && {
		mount -t securityfs securityfs "$SECURITYFS"
		rc=$?
		[ "$rc" -eq 0 ] && log_write info "Mounting securityfs" ||
			log_write err "Failed to mount securityfs"
	}
	return $rc
}

apparmor_start() {

	local announced=0
	is_securityfs_mounted; [ "$?" -ne 0 ] && {
		log_write info "Starting AppArmor"
		announced=1
		mount_securityfs; [ "$?" -eq 0 ] || return $?
	}

	is_apparmor_loaded; [ "$?" -eq 0 ] || {
		[ "$announced" -eq 0 ] && log_write info "Starting AppArmor"
		announced=1
		log_write err "AppArmor kernel support is not present"
		return 1
	}

	[ -d /var/lib/apparmor ] || mkdir -p /var/lib/apparmor > /dev/null

	is_profiles_loaded; [ "$?" -eq 0 ] || {
		[ "$announced" -eq 0 ] && log_write info "Starting AppArmor"
		announced=1
		parse_profiles load
		return $?
	} || {
		parse_profiles reload
		return $?
	}
}

remove_profiles() {

	log_write info "Unloading profiles"

	is_apparmor_loaded; [ "$?" -eq 0 ] || {
		log_write err "AppArmor kernel support is not present"
		return 1
	}

	[ -w "$SFS_MOUNTPOINT/.remove" ] || {
		log_write err "${SFS_MOUNTPOINT}/.remove not writable"
		return 1
	}

	[ -x "$PARSER" ] || {
		log_write err "Unable to execute AppArmor parser"
		return 1
	}

	local rc=0

	sed -e "s/ (\(enforce\|complain\))$//" "$SFS_MOUNTPOINT/profiles" | \
	LC_COLLATE=C sort | grep -v // | {
		while read -r profile ; do
			printf "%s" "$profile" > "$SFS_MOUNTPOINT/.remove"
			result=$?
			[ "$result" -eq 0 ] || rc=$result
		done
	}
	return $rc
}

apparmor_stop() {

	is_apparmor_loaded; [ "$?" -eq 0 ] || return 1
	is_profiles_loaded; [ "$?" -eq 0 ] && {
		log_write info "Stopping AppArmor"
		remove_profiles
		return $?
	} || return 0
}

apparmor_restart() {

	is_profiles_loaded; [ "$?" -eq 0 ] || {
		apparmor_start
		return $?
	}

	is_apparmor_loaded; [ "$?" -eq 0 ] || {
		apparmor_start
		return $?
	}

	log_write info "Restarting AppArmor"
	parse_profiles reload
	return $?
}

apparmor_reload() {

	is_profiles_loaded; [ "$?" -eq 0 ] || {
		apparmor_start
		return $?
	}

	is_apparmor_loaded; [ "$?" -eq 0 ] || {
		apparmor_start
		return $?
	}

	log_write info "Reloading AppArmor"
	parse_profiles reload
	return $?
}

apparmor_list_profiles() {

	is_apparmor_loaded; [ "$?" -eq 0 ] || {
		echo "AppArmor kernel support is not present"
		return 1
	}

        [ -x "$PARSER" ] || {
                echo "Unable to execute AppArmor parser"
		return 1
	}

	# run parser on all profiles
	for profile_dir in $PROFILE_DIRS; do
		[ -d "$profile_dir" ] || {
			echo "AppArmor profiles not found: $profile_dir"
			continue
		}

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


apparmor_status() {

	is_apparmor_loaded; [ "$?" -eq 0 ] || {
		echo "AppArmor kernel support is not present"
		return 1
	}

	[ -x "$AA_STATUS" ] && {
		"$AA_STATUS" --verbose
		return $?
	}

	echo "AppArmor is enabled."
	echo "Install apparmor-utils to receive more detailed status"
	echo "information or examine $SFS_MOUNTPOINT directly."

	return 0
}