# /lib/apparmor/functions for Debian -*- shell-script -*-
# ----------------------------------------------------------------------
#    Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007
#     NOVELL (All rights reserved)
#    Copyright (c) 2008-2018 Canonical, Ltd.
#
#    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.
# ----------------------------------------------------------------------
# Authors:
#  Kees Cook <kees@ubuntu.com>

PROFILES="/etc/apparmor.d"
PROFILES_VAR="/var/lib/snapd/apparmor/profiles"
PARSER="/sbin/apparmor_parser"
PARSER_ARGS="-O no-expr-simplify"
SECURITYFS="/sys/kernel/security"
export AA_SFS="$SECURITYFS/apparmor"

# Suppress warnings when booting in quiet mode
quiet_arg=""
[ "${QUIET:-no}" = yes ] && quiet_arg="-q"
[ "${quiet:-n}" = y ] && quiet_arg="-q"

foreach_configured_profile() {
	rc_all="0"
	for pdir in "$PROFILES" "$PROFILES_VAR" ; do
		if [ ! -d "$pdir" ]; then
			continue
		fi
		num=`find "$pdir" -type f ! -name '*.md5sums' | wc -l`
		if [ "$num" = "0" ]; then
			continue
		fi

		"$PARSER" "$@" $PARSER_ARGS -- "$pdir" || {
			rc_all="$?"
			# FIXME: when the parser properly handles broken
			# profiles (LP: #1377338), remove this if statement.
			# For now, if the xargs returns with error, just run
			# through everything with -n1. (This could be broken
			# out and refactored, but this is temporary so make it
			# easy to understand and revert)
			if [ "$rc_all" != "0" ]; then
				(ls -1 "$pdir" | \
				egrep -v '(\.dpkg-(new|old|dist|bak|remove)|~)$' | \
				while read profile; do
					if [ -f "$pdir"/"$profile" ]; then
						echo "$pdir"/"$profile"
					fi
				done) | \
				xargs -n1 -d"\n" -P$(getconf _NPROCESSORS_ONLN) "$PARSER" "$@" $PARSER_ARGS -- || {
					rc_all="$?"
				}
			fi
		}
	done

	return $rc_all
}

load_configured_profiles() {
	foreach_configured_profile $quiet_arg --write-cache --replace
}

# Checks to see if the current container is capable of having internal AppArmor
# profiles that should be loaded. Callers of this function should have already
# verified that they're running inside of a container environment with
# something like `systemd-detect-virt --container`.
#
# The only known container environments capable of supporting internal policy
# are LXD and LXC environment.
#
# Returns 0 if the container environment is capable of having its own internal
# policy and non-zero otherwise.
#
# IMPORTANT: This function will return 0 in the case of a non-LXD/non-LXC
# system container technology being nested inside of a LXD/LXC container that
# utilized an AppArmor namespace and profile stacking. The reason 0 will be
# returned is because .ns_stacked will be "yes" and .ns_name will still match
# "lx[dc]-*" since the nested system container technology will not have set up
# a new AppArmor profile namespace. This will result in the nested system
# container's boot process to experience failed policy loads but the boot
# process should continue without any loss of functionality. This is an
# unsupported configuration that cannot be properly handled by this function.
is_container_with_internal_policy() {
	local ns_stacked_path="${AA_SFS}/.ns_stacked"
	local ns_name_path="${AA_SFS}/.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
}
