# -*-Shell-script-*-
#
# functions     This file contains functions to be used by most or all
#               audit remediation shell scripts in the scap-security-guide's
#               RHEL/6/input/fixes/bash/ directory.
#


# Function to fix syscall audit rule for given system call. It is
# based on example audit syscall rule definitions as outlined in
# /usr/share/doc/audit-2.3.7/stig.rules file provided with the audit
# package. It will combine multiple system calls belonging to the same
# syscall group into one audit rule (rather than to create audit rule per
# different system call) to avoid audit infrastructure performance penalty
# in the case of 'one-audit-rule-definition-per-one-system-call'. See:
#
#   https://www.redhat.com/archives/linux-audit/2014-November/msg00009.html
#
# for further details.
#
# Expects four arguments (each of them is required) in the form of:
# * audit rules' pattern		audit rule skeleton for same syscall
# * syscall group			greatest common string this rule shares
# 					with other rules from the same group
# * architecture			architecture this rule is intended for
# * full form of new rule to add	expected full form of audit rule as to be
# 					added into audit.rules file
#
# Note: The first three arguments are used to determine how many existing
# audit rules will be inspected for resemblance with the new audit rule
# (4-th argument) the function is going to add. The rule's similarity check
# is performed to optimize audit.rules definition (merge syscalls of the same
# group into one rule) to avoid the "single-syscall-per-audit-rule" performance
# penalty.
#
# Example call:
#
function fix_audit_syscall_rule {

# Load function arguments into local variables
local pattern="$1"
local group="$2"
local arch="$3"
local full_rule="$4"

# Define selected contants
local readonly AUDIT_RULES="/etc/audit/audit.rules"
# Indicator that we want to append $full_rule into audit.rules by default
local append_expected_rule=0

# Filter existing /etc/audit.rules rules' definitions to select those that:
# * follow the rule pattern, and
# * meet the hardware architecture requirement, and
# * are current syscall group specific
IFS=$'\n' existing_rules=($(sed -e "/${pattern}/!d" -e "/${arch}/!d" -e "/${group}/!d"  $AUDIT_RULES))
# Reset IFS back to default
unset $IFS

# Process rules found case-by-case
for rule in ${existing_rules[@]}
do
	# Found rule is for same arch & key, but differs (e.g. in count of -S arguments)
	if [ ${rule} != ${full_rule} ]
	then
		# If so, isolate just '(-S \w)+' substring of that rule
		rule_syscalls=$(echo $rule | grep -o -P '(-S \w+ )+')
		# Check if list of '-S syscall' arguments of that rule is subset
		# of '-S syscall' list of expected $full_rule
		if [ $(echo $full_rule | grep -- $rule_syscalls) ]
		then
			# Rule is covered (i.e. the list of -S syscalls for this rule is
			# subset of -S syscalls of $full_rule => existing rule can be deleted
			# Thus delete the rule from audit.rules & our array
			sed -i -e "/$rule/d" $AUDIT_RULES
			existing_rules=(${existing_rules[@]//$rule/})
		else
			# Rule isn't covered by $full_rule - it besides -S syscall arguments
			# for this group contains also -S syscall arguments for other syscall
			# group. Example: '-S lchown -S fchmod -S fchownat' => group='chown'
			# since 'lchown' & 'fchownat' share 'chown' substring
			# Therefore:
			# * 1) delete the original rule from audit.rules
			# (original '-S lchown -S fchmod -S fchownat' rule would be deleted)
			# * 2) delete the -S syscall arguments for this syscall group, but
			# keep those not belonging to this syscall group
			# (original '-S lchown -S fchmod -S fchownat' would become '-S fchmod'
			# * 3) append the modified (filtered) rule again into audit.rules
			# if the same rule not already present
			#
			# 1) Delete the original rule
			sed -i -e "/$rule/d" $AUDIT_RULES
			# 2) Delete syscalls for this group, but keep those from other groups
			# Convert current rule syscall's string into array splitting by '-S' delimiter
			IFS=$'-S' read -a rule_syscalls_as_array <<< "$rule_syscalls"
			# Reset IFS back to default
			unset $IFS
			# Declare new empty string to hold '-S syscall' arguments from other groups
			new_syscalls_for_rule=''
			# Walk through existing '-S syscall' arguments
			for syscall_arg in ${rule_syscalls_as_array[@]}
			do
				# If the '-S syscall' doesn't belong to current group add it to the new list
				# (together with adding '-S' delimiter back for each of such item found)
				if [ $(echo $syscall_arg | grep -v -- $group) ]
				then
					new_syscalls_for_rule="$new_syscalls_for_rule -S $syscall_arg"
				fi
			done
			# Replace original '-S syscall' list with the new one for this rule
			updated_rule=$(echo $rule | sed "s/$rule_syscalls/$new_syscalls_for_rule/g")
			# Squeeze repeated whitespace characters in rule definition (if any) into one
			updated_rule=$(echo $updated_rule | tr -s '[:space:]')
			# 3) Append the modified / filtered rule again into audit.rules
			#    (but only in case it's not present yet to prevent duplicate definitions)
			if [ ! $(grep -- $updated_rule $AUDIT_RULES) ]
			then
				echo $updated_rule >> $AUDIT_RULES
			fi
		fi
	else
		# /etc/audit/audit.rules already contains the expected rule form for this
		# architecture & key => don't insert it second time
		append_expected_rule=1
	fi
done

# We deleted all rules that were subset of the expected one for this arch & key.
# Also isolated rules containing system calls not from this system calls group.
# Now append the expected rule if it's not present in audit.rules yet
if [[ ${append_expected_rule} -eq "0" ]]
then
	echo $full_rule >> $AUDIT_RULES
fi
}



# Function to fix audit file system object watch rule for given path:
# * if rule exists, also verifies the -w bits match the requirements
# * if rule doesn't exist yet, appends expected form to audit.rules
#
# Expects three arguments (each of them is required) in the form of:
# * path                        	value of -w audit rule's argument
# * required access bits        	value of -p audit rule's argument
# * key                         	value of -k audit rule's argument
#
# Example call:
#
#       fix_audit_watch_rule "/etc/localtime" "wa" "audit_time_rules"
#
function fix_audit_watch_rule {

# Load function arguments into local variables
local path="$1"
local required_access_bits="$2"
local key="$3"

# Define selected contants
local readonly AUDIT_RULES="/etc/audit/audit.rules"

# Check if audit watch file system object rule for given path already present
if grep -q -P -- "[\s]*-w[\s]+$path" $AUDIT_RULES
then
        # Rule is found => verify yet if existing rule definition contains
        # all of the required access type bits

        # Escape slashes in path for use in sed pattern below
        local esc_path=${path//$'/'/$'\/'}
        # Define BRE whitespace class shortcut
        local sp="[[:space:]]"
        # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
        current_access_bits=$(sed -ne "s/$sp*-w$sp\+$esc_path$sp\+-p$sp\+\([rxwa]\{1,4\}\).*/\1/p" $AUDIT_RULES)
        # Split required access bits string into characters array
        # (to check bit's presence for one bit at a time)
        for access_bit in $(echo $required_access_bits | grep -o .)
        do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! $(echo $current_access_bits | grep -q "$access_bit")
                then
                        # Concatenate the existing mask with the missing bit
                        current_access_bits="$current_access_bits$access_bit"
                fi
        done
        # Propagate the updated rule's access bits (original + the required
        # ones) back into the /etc/audit/audit.rules file for that rule
        sed -i "s/\($sp*-w$sp\+$esc_path$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)/\1$current_access_bits\3/" $AUDIT_RULES
else
        # Rule isn't present yet. Append it at the end of /etc/audit/audit.rules file
        # with proper key

        echo "-w $path -p $required_access_bits -k $key" >> $AUDIT_RULES
fi
}
