diff --git a/deprecated/rc.aaeventd.redhat b/deprecated/rc.aaeventd.redhat deleted file mode 100644 index b0a7cc701..000000000 --- a/deprecated/rc.aaeventd.redhat +++ /dev/null @@ -1,137 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------- -# Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007 -# NOVELL (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 by Steve Beattie -# -# /etc/init.d/aaeventd -# and its symbolic link -# /sbin/rcaaeventd -# -# chkconfig: 2345 01 99 -# description: AppArmor Notification and Reporting daemon -# -### BEGIN INIT INFO -# Provides: aaeventd -# Required-Start: apparmor -# Required-Stop: -# Default-Start: 2 3 5 -# Default-Stop: -# Short-Description: AppArmor Notification and Reporting -# Description: AppArmor Notification and Reporting daemon -### END INIT INFO -APPARMOR_FUNCTIONS=/lib/apparmor/rc.apparmor.functions - -# source function library -if [ -f /etc/init.d/functions ]; then - . /etc/init.d/functions -elif [ -f /etc/rc.d/init.d/functions ]; then - . /etc/rc.d/init.d/functions -elif [ -f /lib/lsb/init-functions ]; then - . /lib/lsb/init-functions -else - exit 0 -fi - -sd_log_success_msg() { - echo -n "$*" - success - echo -} - -sd_log_warning_msg() { - echo -n "$*" - warning - echo -} - -sd_log_skipped_msg() { - echo -n "$*" - warning - echo -} - -sd_log_failure_msg() { - echo -n "$*" - failure - echo -} - -sd_action() { - STRING=$1 - shift - action "${STRING} " "$@" - return $? -} - -start_aa_event() { - if [ -x "$AA_EV_BIN" -a "${APPARMOR_ENABLE_AAEVENTD}" = "yes" ] ; then - sd_action "Starting AppArmor Event daemon" daemon --pidfile $AA_EV_PIDFILE $AA_EV_BIN -p $AA_EV_PIDFILE - elif [ -x "$SD_EV_BIN" -a "${APPARMOR_ENABLE_AAEVENTD}" = "yes" ] ; then - sd_action "Starting AppArmor Event daemon" daemon --pidfile $SD_EV_PIDFILE $SD_EV_BIN -p $SD_EV_PIDFILE - fi -} - -stop_aa_event() { - if [ -x "$AA_EV_BIN" -a -f "$AA_EV_PIDFILE" ] ; then - sd_action "Shutting down AppArmor Event daemon" killproc -p $AA_EV_PIDFILE -INT $AA_EV_BIN - fi - if [ -f "$SD_EV_PIDFILE" ] ; then - sd_action "Shutting down AppArmor Event daemon" killproc -p $SD_EV_PIDFILE -INT $SD_EV_BIN - fi -} - -usage() { - echo "Usage: $0 {start|stop|restart|try-restart|reload|force-reload|status}" -} - -# source apparmor function library -if [ -f "${APPARMOR_FUNCTIONS}" ]; then - . ${APPARMOR_FUNCTIONS} -else - sd_log_failure_msg "Unable to find AppArmor initscript functions" - exit 1 -fi - -case "$1" in - start) - start_aa_event - rc=$? - ;; - stop) - stop_aa_event - rc=$? - ;; - restart|reload|force-reload|try-restart) - stop_aa_event - start_aa_event - rc=$? - ;; - status) - echo -n "Checking for service AppArmor Event daemon:" - if [ "${APPARMOR_ENABLE_AAEVENTD}" = "yes" ]; then - /sbin/checkproc -p $AA_EV_PIDFILE $AA_EV_BIN - rc_status -v - else - rc_status -u - fi - ;; - *) - usage - exit 1 - ;; -esac -exit $rc diff --git a/deprecated/rc.aaeventd.suse b/deprecated/rc.aaeventd.suse deleted file mode 100644 index 5add5ed2c..000000000 --- a/deprecated/rc.aaeventd.suse +++ /dev/null @@ -1,133 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------- -# Copyright (c) 1999, 2000, 2001, 2002, 2003 2004, 2005, 2006, 2007 -# NOVELL (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 by Steve Beattie -# -# /etc/init.d/aaeventd -# and its symbolic link -# /sbin/rcaaeventd -# -# chkconfig: 2345 01 99 -# description: AppArmor Notification and Reporting daemon -# -### BEGIN INIT INFO -# Provides: aaeventd -# Required-Start: apparmor -# Required-Stop: $null -# Default-Start: 2 3 5 -# Default-Stop: -# Short-Description: AppArmor Notification and Reporting -# Description: AppArmor Notification and Reporting daemon -### END INIT INFO -APPARMOR_FUNCTIONS=/lib/apparmor/rc.apparmor.functions - -# source function library -if [ -f /etc/init.d/functions ]; then - . /etc/init.d/functions -elif [ -f /etc/rc.d/init.d/functions ]; then - . /etc/rc.d/init.d/functions -elif [ -f /lib/lsb/init-functions ]; then - . /lib/lsb/init-functions -else - exit 0 -fi - -# Ugh, SUSE doesn't implement action -sd_action() { - STRING=$1 - shift - "$@" - rc=$? - if [ $rc -eq 0 ] ; then - log_success_msg $"$STRING " - else - log_failure_msg $"$STRING " - fi - return $rc -} - -sd_log_success_msg() { - log_success_msg $* -} - -sd_log_warning_msg() { - log_warning_msg $* -} - -sd_log_failure_msg() { - log_failure_msg $* -} - -usage() { - echo "Usage: $0 {start|stop|restart|try-restart|reload|force-reload|status}" -} - -start_aa_event() { - if [ -x "$AA_EV_BIN" -a "${APPARMOR_ENABLE_AAEVENTD}" = "yes" ] ; then - sd_action "Starting AppArmor Event daemon" startproc -p $AA_EV_PIDFILE $AA_EV_BIN -p $AA_EV_PIDFILE - elif [ -x "$SD_EV_BIN" -a "${APPARMOR_ENABLE_AAEVENTD}" = "yes" ] ; then - sd_action "Starting AppArmor Event daemon" startproc -p $SD_EV_PIDFILE $SD_EV_BIN -p $SD_EV_PIDFILE - fi -} - -stop_aa_event() { - if [ -x "$AA_EV_BIN" -a -f "$AA_EV_PIDFILE" ] ; then - sd_action "Shutting down AppArmor Event daemon" killproc -G -p $AA_EV_PIDFILE -INT $AA_EV_BIN - fi - if [ -f "$SD_EV_PIDFILE" ] ; then - sd_action "Shutting down AppArmor Event daemon" killproc -G -p $SD_EV_PIDFILE -INT $SD_EV_BIN - fi -} - -# source apparmor function library -if [ -f "${APPARMOR_FUNCTIONS}" ]; then - . ${APPARMOR_FUNCTIONS} -else - sd_log_failure_msg "Unable to find AppArmor initscript functions" - exit 1 -fi - -case "$1" in - start) - start_aa_event - rc=$? - ;; - stop) - stop_aa_event - rc=$? - ;; - restart|reload|force-reload|try-restart) - stop_aa_event - start_aa_event - rc=$? - ;; - status) - echo -n "Checking for service AppArmor Event daemon:" - if [ "${APPARMOR_ENABLE_AAEVENTD}" = "yes" ]; then - /sbin/checkproc -p $AA_EV_PIDFILE $AA_EV_BIN - rc_status -v - else - rc_status -u - fi - ;; - *) - usage - exit 1 - ;; -esac -exit $rc - diff --git a/deprecated/utils/Immunix/AppArmor.pm b/deprecated/utils/Immunix/AppArmor.pm deleted file mode 100755 index 42c0bd362..000000000 --- a/deprecated/utils/Immunix/AppArmor.pm +++ /dev/null @@ -1,6860 +0,0 @@ -# ---------------------------------------------------------------------- -# Copyright (c) 2006 Novell, Inc. All Rights Reserved. -# Copyright (c) 2010 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 as 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. -# -# To contact Novell about this file by physical or electronic mail, -# you may find current contact information at www.novell.com. -# ---------------------------------------------------------------------- - -package Immunix::AppArmor; - -use strict; -use warnings; - -use Carp; -use Cwd qw(cwd realpath); -use File::Basename; -use File::Temp qw/ tempfile tempdir /; -use Data::Dumper; - -use Locale::gettext; -use POSIX; -use Storable qw(dclone); - -use Term::ReadKey; - -use Immunix::Severity; -use Immunix::Repository; -use Immunix::Config; -use LibAppArmor; - -require Exporter; -our @ISA = qw(Exporter); -our @EXPORT = qw( - %sd - %qualifiers - %include - %helpers - - $filename - $profiledir - $parser - $logger - $UI_Mode - $running_under_genprof - - which - getprofilefilename - get_full_path - fatal_error - get_pager - - getprofileflags - setprofileflags - complain - enforce - - autodep - reload - - UI_GetString - UI_GetFile - UI_YesNo - UI_ShortMessage - UI_LongMessage - - UI_Important - UI_Info - UI_PromptUser - display_changes - getkey - - do_logprof_pass - - loadincludes - readprofile - readprofiles - writeprofile - serialize_profile - attach_profile_data - parse_repo_profile - activate_repo_profiles - - check_for_subdomain - - setup_yast - shutdown_yast - GetDataFromYast - SendDataToYast - - checkProfileSyntax - checkIncludeSyntax - check_qualifiers - - isSkippableFile - isSkippableDir -); - -our $confdir = "/etc/apparmor"; - -our $running_under_genprof = 0; - -our $DEBUGGING; - -our $unimplemented_warning = 0; - -# keep track of if we're running under yast or not - default to text mode -our $UI_Mode = "text"; - -our $sevdb; - -# initialize Term::ReadLine if it's available -our $term; -eval { - require Term::ReadLine; - import Term::ReadLine; - $term = new Term::ReadLine 'AppArmor'; -}; - -# initialize the local poo -setlocale(LC_MESSAGES, "") - unless defined(LC_MESSAGES); -textdomain("apparmor-utils"); - -# where do we get our log messages from? -our $filename; - -our $cfg; -our $repo_cfg; - -our $parser; -our $ldd; -our $logger; -our $profiledir; -our $extraprofiledir; - -# we keep track of the included profile fragments with %include -my %include; - -my %existing_profiles; - -our $seenevents = 0; - - -# these are globs that the user specifically entered. we'll keep track of -# them so that if one later matches, we'll suggest it again. -our @userglobs; - -### THESE VARIABLES ARE USED WITHIN LOGPROF -our %t; -our %transitions; -our %sd; # we keep track of the original profiles in %sd -our %original_sd; -our %extras; # inactive profiles from extras - -my @log; -my %pid; - -my %seen; -my %profilechanges; -my %prelog; -my %log; -my %changed; -my @created; -my %skip; -our %helpers; # we want to preserve this one between passes - -### THESE VARIABLES ARE USED WITHIN LOGPROF - -my %filelist; # file level stuff including variables in config files - -my $AA_MAY_EXEC = 1; -my $AA_MAY_WRITE = 2; -my $AA_MAY_READ = 4; -my $AA_MAY_APPEND = 8; -my $AA_MAY_LINK = 16; -my $AA_MAY_LOCK = 32; -my $AA_EXEC_MMAP = 64; -my $AA_EXEC_UNSAFE = 128; -my $AA_EXEC_INHERIT = 256; -my $AA_EXEC_UNCONFINED = 512; -my $AA_EXEC_PROFILE = 1024; -my $AA_EXEC_CHILD = 2048; -my $AA_EXEC_NT = 4096; -my $AA_LINK_SUBSET = 8192; - -my $AA_OTHER_SHIFT = 14; -my $AA_USER_MASK = 16384 -1; - -my $AA_EXEC_TYPE = $AA_MAY_EXEC | $AA_EXEC_UNSAFE | $AA_EXEC_INHERIT | - $AA_EXEC_UNCONFINED | $AA_EXEC_PROFILE | $AA_EXEC_CHILD | $AA_EXEC_NT; - -my $ALL_AA_EXEC_TYPE = $AA_EXEC_TYPE; - -my %MODE_HASH = ( - x => $AA_MAY_EXEC, - X => $AA_MAY_EXEC, - w => $AA_MAY_WRITE, - W => $AA_MAY_WRITE, - r => $AA_MAY_READ, - R => $AA_MAY_READ, - a => $AA_MAY_APPEND, - A => $AA_MAY_APPEND, - l => $AA_MAY_LINK, - L => $AA_MAY_LINK, - k => $AA_MAY_LOCK, - K => $AA_MAY_LOCK, - m => $AA_EXEC_MMAP, - M => $AA_EXEC_MMAP, -# Unsafe => 128, - i => $AA_EXEC_INHERIT, - I => $AA_EXEC_INHERIT, - u => $AA_EXEC_UNCONFINED + $AA_EXEC_UNSAFE, # U + Unsafe - U => $AA_EXEC_UNCONFINED, - p => $AA_EXEC_PROFILE + $AA_EXEC_UNSAFE, # P + Unsafe - P => $AA_EXEC_PROFILE, - c => $AA_EXEC_CHILD + $AA_EXEC_UNSAFE, - C => $AA_EXEC_CHILD, - n => $AA_EXEC_NT + $AA_EXEC_UNSAFE, - N => $AA_EXEC_NT, - ); - - -# Currently only used by netdomain but there's no reason it couldn't -# be extended to support other types. -my %operation_types = ( - - # Old socket names - "socket_create", => "net", - "socket_post_create" => "net", - "socket_bind" => "net", - "socket_connect" => "net", - "socket_listen" => "net", - "socket_accept" => "net", - "socket_sendmsg" => "net", - "socket_recvmsg" => "net", - "socket_getsockname" => "net", - "socket_getpeername" => "net", - "socket_getsockopt" => "net", - "socket_setsockopt" => "net", - "socket_shutdown" => "net", - - # New socket names - "create" => "net", - "post_create" => "net", - "bind" => "net", - "connect" => "net", - "listen" => "net", - "accept" => "net", - "sendmsg" => "net", - "recvmsg" => "net", - "getsockname" => "net", - "getpeername" => "net", - "getsockopt" => "net", - "setsockopt" => "net", - "sock_shutdown" => "net", -); - -sub optype($) { - my $op = shift; - my $type = $operation_types{$op}; - - return "unknown" if !defined($type); - return $type; -} - -sub debug ($) { - my $message = shift; - chomp($message); - - print DEBUG "$message\n" if $DEBUGGING; -} - -my %arrows = ( A => "UP", B => "DOWN", C => "RIGHT", D => "LEFT" ); - -sub getkey() { - # change to raw mode - ReadMode(4); - - my $key = ReadKey(0); - - # decode arrow key control sequences - if ($key eq "\x1B") { - $key = ReadKey(0); - if ($key eq "[") { - $key = ReadKey(0); - if ($arrows{$key}) { - $key = $arrows{$key}; - } - } - } - - # return to cooked mode - ReadMode(0); - return $key; -} - -BEGIN { - # set things up to log extra info if they want... - if ($ENV{LOGPROF_DEBUG}) { - $DEBUGGING = 1; - open(DEBUG, ">>$ENV{LOGPROF_DEBUG}"); - my $oldfd = select(DEBUG); - $| = 1; - select($oldfd); - } else { - $DEBUGGING = 0; - } -} - -END { - $DEBUGGING && debug "Exiting..."; - - # close the debug log if necessary - close(DEBUG) if $DEBUGGING; -} - -# returns true if the specified program contains references to LD_PRELOAD or -# LD_LIBRARY_PATH to give the PX/UX code better suggestions -sub check_for_LD_XXX ($) { - my $file = shift; - - return undef unless -f $file; - - # limit our checking to programs/scripts under 10k to speed things up a bit - my $size = -s $file; - return undef unless ($size && $size < 10000); - - my $found = undef; - if (open(F, $file)) { - while () { - $found = 1 if /LD_(PRELOAD|LIBRARY_PATH)/; - } - close(F); - } - - return $found; -} - -sub fatal_error ($) { - my $message = shift; - - my $details = "$message\n"; - - if ($DEBUGGING) { - - # we'll include the stack backtrace if we're debugging... - $details = Carp::longmess($message); - - # write the error to the log - print DEBUG $details; - } - - # we'll just shoot ourselves in the head if it was one of the yast - # interface functions that ran into an error. it gets really ugly if - # the yast frontend goes away and we try to notify the user of that - # problem by trying to send the yast frontend a pretty dialog box - my $caller = (caller(1))[3]; - - exit 1 if defined($caller) && $caller =~ /::(Send|Get)Data(To|From)Yast$/; - - # tell the user what the hell happened - UI_Important($details); - - # make sure the frontend exits cleanly... - shutdown_yast(); - - # die a horrible flaming death - exit 1; -} - -sub setup_yast() { - - # set up the yast connection if we're running under yast... - if ($ENV{YAST_IS_RUNNING}) { - - # load the yast module if available. - eval { require ycp; }; - unless ($@) { - import ycp; - - $UI_Mode = "yast"; - - # let the frontend know that we're starting - SendDataToYast({ - type => "initial_handshake", - status => "backend_starting" - }); - - # see if the frontend is just starting up also... - my ($ypath, $yarg) = GetDataFromYast(); - unless ($yarg - && (ref($yarg) eq "HASH") - && ($yarg->{type} eq "initial_handshake") - && ($yarg->{status} eq "frontend_starting")) - { - - # something's broken, die a horrible, painful death - fatal_error "Yast frontend is out of sync from backend agent."; - } - $DEBUGGING && debug "Initial handshake ok"; - - # the yast connection seems to be working okay - return 1; - } - - } - - # couldn't init yast - return 0; -} - -sub shutdown_yast() { - if ($UI_Mode eq "yast") { - SendDataToYast({ type => "final_shutdown" }); - my ($ypath, $yarg) = GetDataFromYast(); - } -} - -sub check_for_subdomain () { - - my ($support_subdomainfs, $support_securityfs); - if (open(MOUNTS, "/proc/filesystems")) { - while () { - $support_subdomainfs = 1 if m/subdomainfs/; - $support_securityfs = 1 if m/securityfs/; - } - close(MOUNTS); - } - - my $sd_mountpoint = ""; - if (open(MOUNTS, "/proc/mounts")) { - while () { - if ($support_subdomainfs) { - $sd_mountpoint = $1 if m/^\S+\s+(\S+)\s+subdomainfs\s/; - } elsif ($support_securityfs) { - if (m/^\S+\s+(\S+)\s+securityfs\s/) { - if (-e "$1/apparmor") { - $sd_mountpoint = "$1/apparmor"; - } elsif (-e "$1/subdomain") { - $sd_mountpoint = "$1/subdomain"; - } - } - } - } - close(MOUNTS); - } - - # make sure that subdomain is actually mounted there - $sd_mountpoint = undef unless -f "$sd_mountpoint/profiles"; - - return $sd_mountpoint; -} - -sub check_for_apparmor () { - return check_for_subdomain(); -} - -sub which ($) { - my $file = shift; - - foreach my $dir (split(/:/, $ENV{PATH})) { - return "$dir/$file" if -x "$dir/$file"; - } - - return undef; -} - -# we need to convert subdomain regexps to perl regexps -sub convert_regexp ($) { - my $regexp = shift; - - # escape regexp-special characters we don't support - $regexp =~ s/(? 64) { - fatal_error "Followed too many symlinks resolving $originalpath"; - } - - # split out the directory/file components - if ($path =~ m/^(.*)\/(.+)$/) { - my ($dir, $file) = ($1, $2); - - # figure out where the link is pointing... - my $link = readlink($path); - if ($link =~ /^\//) { - # if it's an absolute link, just replace it - $path = $link; - } else { - # if it's relative, let abs_path handle it - $path = $dir . "/$link"; - } - } - } - - return realpath($path); -} - -sub findexecutable ($) { - my $bin = shift; - - my $fqdbin; - if (-e $bin) { - $fqdbin = get_full_path($bin); - chomp($fqdbin); - } else { - if ($bin !~ /\//) { - my $which = which($bin); - if ($which) { - $fqdbin = get_full_path($which); - } - } - } - - unless ($fqdbin && -e $fqdbin) { - return undef; - } - - return $fqdbin; -} - -sub name_to_prof_filename($) { - my $bin = shift; - my $filename; - - unless ($bin =~ /^($profiledir)/) { - my $fqdbin = findexecutable($bin); - if ($fqdbin) { - $filename = getprofilefilename($fqdbin); - return ($filename, $fqdbin) if -f $filename; - } - } - - if ($bin =~ /^$profiledir(.*)/) { - my $profile = $1; - return ($bin, $profile); - } elsif ($bin =~ /^\//) { - $filename = getprofilefilename($bin); - return ($filename, $bin); - } else { - # not an absolute path try it as a profile_ - $bin = $1 if ($bin !~ /^profile_(.*)/); - $filename = getprofilefilename($bin); - return ($filename, "profile_${bin}"); - } - return undef; -} - -sub complain ($) { - my $bin = shift; - - return if (!$bin); - - my ($filename, $name) = name_to_prof_filename($bin) - or fatal_error(sprintf(gettext('Can\'t find %s.'), $bin)); - - UI_Info(sprintf(gettext('Setting %s to complain mode.'), $name)); - - setprofileflags($filename, "complain"); -} - -sub enforce ($) { - my $bin = shift; - - return if (!$bin); - - my ($filename, $name) = name_to_prof_filename($bin) - or fatal_error(sprintf(gettext('Can\'t find %s.'), $bin)); - - UI_Info(sprintf(gettext('Setting %s to enforce mode.'), $name)); - - setprofileflags($filename, ""); -} - -sub head ($) { - my $file = shift; - - my $first = ""; - if (open(FILE, $file)) { - $first = ; - close(FILE); - } - - return $first; -} - -sub get_output ($@) { - my ($program, @args) = @_; - - my $ret = -1; - - my $pid; - my @output; - - if (-x $program) { - $pid = open(KID_TO_READ, "-|"); - unless (defined $pid) { - fatal_error "can't fork: $!"; - } - - if ($pid) { - while () { - chomp; - push @output, $_; - } - close(KID_TO_READ); - $ret = $?; - } else { - ($>, $)) = ($<, $(); - open(STDERR, ">&STDOUT") - || fatal_error "can't dup stdout to stderr"; - exec($program, @args) || fatal_error "can't exec program: $!"; - - # NOTREACHED - } - } - - return ($ret, @output); -} - -sub get_reqs ($) { - my $file = shift; - - my @reqs; - my ($ret, @ldd) = get_output($ldd, $file); - - if ($ret == 0) { - for my $line (@ldd) { - last if $line =~ /not a dynamic executable/; - last if $line =~ /cannot read header/; - last if $line =~ /statically linked/; - - # avoid new kernel 2.6 poo - next if $line =~ /linux-(gate|vdso(32|64)).so/; - - if ($line =~ /^\s*\S+ => (\/\S+)/) { - push @reqs, $1; - } elsif ($line =~ /^\s*(\/\S+)/) { - push @reqs, $1; - } - } - } - - return @reqs; -} - -sub handle_binfmt ($$) { - my ($profile, $fqdbin) = @_; - - my %reqs; - my @reqs = get_reqs($fqdbin); - - while (my $library = shift @reqs) { - - $library = get_full_path($library); - - push @reqs, get_reqs($library) unless $reqs{$library}++; - - # does path match anything pulled in by includes in original profile? - my $combinedmode = match_prof_incs_to_path($profile, 'allow', $library); - - # if we found any matching entries, do the modes match? - next if $combinedmode; - - $library = globcommon($library); - chomp $library; - next unless $library; - - $profile->{allow}{path}->{$library}{mode} |= str_to_mode("mr"); - $profile->{allow}{path}->{$library}{audit} |= 0; - } -} - -sub get_inactive_profile($) { - my $fqdbin = shift; - if ( $extras{$fqdbin} ) { - return {$fqdbin => $extras{$fqdbin}}; - } -} - - - -sub create_new_profile($) { - my $fqdbin = shift; - - my $profile; - $profile = { - $fqdbin => { - flags => "complain", - include => { "abstractions/base" => 1 }, - } - }; - - # if the executable exists on this system, pull in extra dependencies - if (-f $fqdbin) { - my $hashbang = head($fqdbin); - if ($hashbang && $hashbang =~ /^#!\s*(\S+)/) { - my $interpreter = get_full_path($1); - $profile->{$fqdbin}{allow}{path}->{$fqdbin}{mode} |= str_to_mode("r"); - $profile->{$fqdbin}{allow}{path}->{$fqdbin}{audit} |= 0; - $profile->{$fqdbin}{allow}{path}->{$interpreter}{mode} |= str_to_mode("ix"); - $profile->{$fqdbin}{allow}{path}->{$interpreter}{audit} |= 0; - if ($interpreter =~ /perl/) { - $profile->{$fqdbin}{include}->{"abstractions/perl"} = 1; - } elsif ($interpreter =~ m/\/bin\/(bash|dash|sh)/) { - $profile->{$fqdbin}{include}->{"abstractions/bash"} = 1; - } elsif ($interpreter =~ m/python/) { - $profile->{$fqdbin}{include}->{"abstractions/python"} = 1; - } elsif ($interpreter =~ m/ruby/) { - $profile->{$fqdbin}{include}->{"abstractions/ruby"} = 1; - } - handle_binfmt($profile->{$fqdbin}, $interpreter); - } else { - $profile->{$fqdbin}{allow}{path}->{$fqdbin}{mode} |= str_to_mode("mr"); - $profile->{$fqdbin}{allow}{path}->{$fqdbin}{audit} |= 0; - handle_binfmt($profile->{$fqdbin}, $fqdbin); - } - } - - # create required infrastructure hats if it's a known change_hat app - for my $hatglob (keys %{$cfg->{required_hats}}) { - if ($fqdbin =~ /$hatglob/) { - for my $hat (sort split(/\s+/, $cfg->{required_hats}{$hatglob})) { - $profile->{$hat} = { flags => "complain" }; - } - } - } - push @created, $fqdbin; - $DEBUGGING && debug( Data::Dumper->Dump([$profile], [qw(*profile)])); - return { $fqdbin => $profile }; -} - -sub delete_profile ($) { - my $profile = shift; - my $profilefile = getprofilefilename( $profile ); - if ( -e $profilefile ) { - unlink( $profilefile ); - } - if ( defined $sd{$profile} ) { - delete $sd{$profile}; - } -} - -sub get_profile($) { - my $fqdbin = shift; - my $profile_data; - - my $distro = $cfg->{repository}{distro}; - my $repo_url = $cfg->{repository}{url}; - my @profiles; - my %profile_hash; - - if (repo_is_enabled()) { - my $results; - UI_BusyStart( gettext("Connecting to repository.....") ); - - my ($status_ok,$ret) = - fetch_profiles_by_name($repo_url, $distro, $fqdbin ); - UI_BusyStop(); - if ( $status_ok ) { - %profile_hash = %$ret; - } else { - my $errmsg = - sprintf(gettext("WARNING: Error fetching profiles from the repository:\n%s\n"), - $ret?$ret:gettext("UNKNOWN ERROR")); - UI_Important( $errmsg ); - } - } - - my $inactive_profile = get_inactive_profile($fqdbin); - if ( defined $inactive_profile && $inactive_profile ne "" ) { - # set the profile to complain mode - my $uname = gettext( "Inactive local profile for ") . $fqdbin; - $inactive_profile->{$fqdbin}{$fqdbin}{flags} = "complain"; - # inactive profiles store where they came from - delete $inactive_profile->{$fqdbin}{$fqdbin}{filename}; - $profile_hash{$uname} = - { - "username" => $uname, - "profile_type" => "INACTIVE_LOCAL", - "profile" => serialize_profile($inactive_profile->{$fqdbin}, - $fqdbin - ), - "profile_data" => $inactive_profile, - }; - } - - return undef if ( keys %profile_hash == 0 ); # No repo profiles, no inactive - # profile - my @options; - my @tmp_list; - my $preferred_present = 0; - my $preferred_user = $cfg->{repository}{preferred_user} || "NOVELL"; - - foreach my $p ( keys %profile_hash ) { - if ( $profile_hash{$p}->{username} eq $preferred_user ) { - $preferred_present = 1; - } else { - push @tmp_list, $profile_hash{$p}->{username}; - } - } - - if ( $preferred_present ) { - push @options, $preferred_user; - } - push @options, @tmp_list; - - my $q = {}; - $q->{headers} = []; - push @{ $q->{headers} }, gettext("Profile"), $fqdbin; - - $q->{functions} = [ "CMD_VIEW_PROFILE", "CMD_USE_PROFILE", - "CMD_CREATE_PROFILE", "CMD_ABORT", "CMD_FINISHED" ]; - - $q->{default} = "CMD_VIEW_PROFILE"; - - $q->{options} = [@options]; - $q->{selected} = 0; - - my ($p, $ans, $arg); - do { - ($ans, $arg) = UI_PromptUser($q); - $p = $profile_hash{$options[$arg]}; - for (my $i = 0; $i < scalar(@options); $i++) { - if ($options[$i] eq $options[$arg]) { - $q->{selected} = $i; - } - } - - if ($ans eq "CMD_VIEW_PROFILE") { - if ($UI_Mode eq "yast") { - SendDataToYast( - { - type => "dialog-view-profile", - user => $options[$arg], - profile => $p->{profile}, - profile_type => $p->{profile_type} - } - ); - my ($ypath, $yarg) = GetDataFromYast(); - } else { - my $pager = get_pager(); - open(PAGER, "| $pager"); - print PAGER gettext("Profile submitted by") . - " $options[$arg]:\n\n" . $p->{profile} . "\n\n"; - close(PAGER); - } - } elsif ($ans eq "CMD_USE_PROFILE") { - if ( $p->{profile_type} eq "INACTIVE_LOCAL" ) { - $profile_data = $p->{profile_data}; - push @created, $fqdbin; # This really is ugly here - # need to find a better place to mark - # this as newly created - } else { - $profile_data = - parse_repo_profile($fqdbin, $repo_url, $p); - } - } - } until ($ans =~ /^CMD_(USE_PROFILE|CREATE_PROFILE)$/); - - return $profile_data; -} - -sub activate_repo_profiles ($$$) { - my ($url,$profiles,$complain) = @_; - - readprofiles(); - eval { - for my $p ( @$profiles ) { - my $pname = $p->[0]; - my $profile_data = parse_repo_profile( $pname, $url, $p->[1] ); - attach_profile_data(\%sd, $profile_data); - writeprofile($pname); - if ( $complain ) { - my $filename = getprofilefilename($pname); - setprofileflags($filename, "complain"); - UI_Info(sprintf(gettext('Setting %s to complain mode.'), - $pname)); - } - } - }; - # if there were errors.... - if ($@) { - $@ =~ s/\n$//; - print STDERR sprintf(gettext("Error activating profiles: %s\n"), $@); - } -} - -sub autodep_base($$) { - my ($bin, $pname) = @_; - %extras = (); - - $bin = $pname if (! $bin) && ($pname =~ /^\//); - - unless ($repo_cfg || not defined $cfg->{repository}{url}) { - $repo_cfg = read_config("repository.conf"); - if ( (not defined $repo_cfg->{repository}) || - ($repo_cfg->{repository}{enabled} eq "later") ) { - UI_ask_to_enable_repo(); - } - } - - my $fqdbin; - if ($bin) { - # findexecutable() might fail if we're running on a different system - # than the logs were collected on. ugly. we'll just hope for the best. - $fqdbin = findexecutable($bin) || $bin; - - # try to make sure we have a full path in case findexecutable failed - return unless $fqdbin =~ /^\//; - - # ignore directories - return if -d $fqdbin; - } - - $pname = $fqdbin if $fqdbin; - - my $profile_data; - - readinactiveprofiles(); # need to read the profiles to see if an - # inactive local profile is present - $profile_data = eval { get_profile($pname) }; - # propagate any errors we hit inside the get_profile call - if ($@) { die $@; } - - unless ($profile_data) { - $profile_data = create_new_profile($pname); - } - - my $file = getprofilefilename($pname); - - # stick the profile into our data structure. - attach_profile_data(\%sd, $profile_data); - # and store a "clean" version also so we can display the changes we've - # made during this run - attach_profile_data(\%original_sd, $profile_data); - - if (-f "$profiledir/tunables/global") { - unless (exists $filelist{$file}) { - $filelist{$file} = { }; - } - $filelist{$file}{include}{'tunables/global'} = 1; # sorry - } - - # write out the profile... - writeprofile_ui_feedback($pname); -} - -sub autodep ($) { - my $bin = shift; - return autodep_base($bin, ""); -} - -sub getprofilefilename ($) { - my $profile = shift; - - my $filename = $profile; - if ($filename =~ /^\//) { - $filename =~ s/^\///; # strip leading / - } else { - $filename = "profile_$filename"; - } - $filename =~ s/\//./g; # convert /'s to .'s - - return "$profiledir/$filename"; -} - -sub setprofileflags ($$) { - my $filename = shift; - my $newflags = shift; - - if (open(PROFILE, "$filename")) { - if (open(NEWPROFILE, ">$filename.new")) { - while () { - if (m/^(\s*)(("??\/.+?"??)|(profile\s+("??.+?"??)))\s+(flags=\(.+\)\s+)*\{\s*$/) { - my ($space, $binary, $flags) = ($1, $2, $6); - - if ($newflags) { - $_ = "$space$binary flags=($newflags) {\n"; - } else { - $_ = "$space$binary {\n"; - } - } elsif (m/^(\s*\^\S+)\s+(flags=\(.+\)\s+)*\{\s*$/) { - my ($hat, $flags) = ($1, $2); - - if ($newflags) { - $_ = "$hat flags=($newflags) {\n"; - } else { - $_ = "$hat {\n"; - } - } - print NEWPROFILE; - } - close(NEWPROFILE); - rename("$filename.new", "$filename"); - } - close(PROFILE); - } -} - -sub profile_exists($) { - my $program = shift || return 0; - - # if it's already in the cache, return true - return 1 if $existing_profiles{$program}; - - # if the profile exists, mark it in the cache and return true - my $profile = getprofilefilename($program); - if (-e $profile) { - $existing_profiles{$program} = 1; - return 1; - } - - # couldn't find a profile, so we'll return false - return 0; -} - -sub sync_profiles() { - - my ($user, $pass) = get_repo_user_pass(); - return unless ( $user && $pass ); - - my @repo_profiles; - my @changed_profiles; - my @new_profiles; - my $serialize_opts = { }; - my ($status_ok,$ret) = - fetch_profiles_by_user($cfg->{repository}{url}, - $cfg->{repository}{distro}, - $user - ); - if ( !$status_ok ) { - my $errmsg = - sprintf(gettext("WARNING: Error syncronizing profiles with the repository:\n%s\n"), - $ret?$ret:gettext("UNKNOWN ERROR")); - UI_Important($errmsg); - return; - } else { - my $users_repo_profiles = $ret; - $serialize_opts->{NO_FLAGS} = 1; - # - # Find changes made to non-repo profiles - # - for my $profile (sort keys %sd) { - if (is_repo_profile($sd{$profile}{$profile})) { - push @repo_profiles, $profile; - } - if ( grep(/^$profile$/, @created) ) { - my $p_local = serialize_profile($sd{$profile}, - $profile, - $serialize_opts); - if ( not defined $users_repo_profiles->{$profile} ) { - push @new_profiles, [ $profile, $p_local, "" ]; - } else { - my $p_repo = $users_repo_profiles->{$profile}->{profile}; - if ( $p_local ne $p_repo ) { - push @changed_profiles, [ $profile, $p_local, $p_repo ]; - } - } - } - } - - # - # Find changes made to local profiles with repo metadata - # - if (@repo_profiles) { - for my $profile (@repo_profiles) { - my $p_local = serialize_profile($sd{$profile}, - $profile, - $serialize_opts); - if ( not exists $users_repo_profiles->{$profile} ) { - push @new_profiles, [ $profile, $p_local, "" ]; - } else { - my $p_repo = ""; - if ( $sd{$profile}{$profile}{repo}{user} eq $user ) { - $p_repo = $users_repo_profiles->{$profile}->{profile}; - } else { - my ($status_ok,$ret) = - fetch_profile_by_id($cfg->{repository}{url}, - $sd{$profile}{$profile}{repo}{id} - ); - if ( $status_ok ) { - $p_repo = $ret->{profile}; - } else { - my $errmsg = - sprintf( - gettext("WARNING: Error syncronizing profiles with the repository:\n%s\n"), - $ret?$ret:gettext("UNKNOWN ERROR")); - UI_Important($errmsg); - next; - } - } - if ( $p_repo ne $p_local ) { - push @changed_profiles, [ $profile, $p_local, $p_repo ]; - } - } - } - } - - if ( @changed_profiles ) { - submit_changed_profiles( \@changed_profiles ); - } - if ( @new_profiles ) { - submit_created_profiles( \@new_profiles ); - } - } -} - -sub submit_created_profiles($) { - my $new_profiles = shift; - my $url = $cfg->{repository}{url}; - - if ($UI_Mode eq "yast") { - my $title = gettext("New profiles"); - my $explanation = - gettext("Please choose the newly created profiles that you would". - " like\nto store in the repository"); - yast_select_and_upload_profiles($title, - $explanation, - $new_profiles); - } else { - my $title = - gettext("Submit newly created profiles to the repository"); - my $explanation = - gettext("Would you like to upload the newly created profiles?"); - console_select_and_upload_profiles($title, - $explanation, - $new_profiles); - } -} - -sub submit_changed_profiles($) { - my $changed_profiles = shift; - my $url = $cfg->{repository}{url}; - if (@$changed_profiles) { - if ($UI_Mode eq "yast") { - my $explanation = - gettext("Select which of the changed profiles you would". - " like to upload\nto the repository"); - my $title = gettext("Changed profiles"); - yast_select_and_upload_profiles($title, - $explanation, - $changed_profiles); - } else { - my $title = - gettext("Submit changed profiles to the repository"); - my $explanation = - gettext("The following profiles from the repository were". - " changed.\nWould you like to upload your changes?"); - console_select_and_upload_profiles($title, - $explanation, - $changed_profiles); - } - } -} - -sub yast_select_and_upload_profiles($$$) { - - my ($title, $explanation, $profiles_ref) = @_; - my $url = $cfg->{repository}{url}; - my %profile_changes; - my @profiles = @$profiles_ref; - - foreach my $prof (@profiles) { - $profile_changes{ $prof->[0] } = - get_profile_diff($prof->[2], $prof->[1]); - } - - my (@selected_profiles, $changelog, $changelogs, $single_changelog); - SendDataToYast( - { - type => "dialog-select-profiles", - title => $title, - explanation => $explanation, - default_select => "false", - disable_ask_upload => "true", - profiles => \%profile_changes - } - ); - my ($ypath, $yarg) = GetDataFromYast(); - if ($yarg->{STATUS} eq "cancel") { - return; - } else { - my $selected_profiles_ref = $yarg->{PROFILES}; - @selected_profiles = @$selected_profiles_ref; - $changelogs = $yarg->{CHANGELOG}; - if (defined $changelogs->{SINGLE_CHANGELOG}) { - $changelog = $changelogs->{SINGLE_CHANGELOG}; - $single_changelog = 1; - } - } - - for my $profile (@selected_profiles) { - my ($user, $pass) = get_repo_user_pass(); - my $profile_string = serialize_profile($sd{$profile}, $profile); - if (!$single_changelog) { - $changelog = $changelogs->{$profile}; - } - my ($status_ok, $ret) = upload_profile( $url, - $user, - $pass, - $cfg->{repository}{distro}, - $profile, - $profile_string, - $changelog - ); - if ($status_ok) { - my $newprofile = $ret; - my $newid = $newprofile->{id}; - set_repo_info($sd{$profile}{$profile}, $url, $user, $newid); - writeprofile_ui_feedback($profile); - } else { - my $errmsg = - sprintf( - gettext("WARNING: An error occured while uploading the profile %s\n%s\n"), - $profile, $ret?$ret:gettext("UNKNOWN ERROR")); - UI_Important( $errmsg ); - } - } - UI_Info(gettext("Uploaded changes to repository.")); - - # Check to see if unselected profiles should be marked as local only - # this is outside of the main repo code as we want users to be able to mark - # profiles as local only even if they aren't able to connect to the repo. - if (defined $yarg->{NEVER_ASK_AGAIN}) { - my @unselected_profiles; - foreach my $prof (@profiles) { - if ( grep(/^$prof->[0]$/, @selected_profiles) == 0 ) { - push @unselected_profiles, $prof->[0]; - } - } - set_profiles_local_only( @unselected_profiles ); - } -} - -sub console_select_and_upload_profiles($$$) { - my ($title, $explanation, $profiles_ref) = @_; - my $url = $cfg->{repository}{url}; - my @profiles = @$profiles_ref; - my $q = {}; - $q->{title} = $title; - $q->{headers} = [ gettext("Repository"), $url, ]; - - $q->{explanation} = $explanation; - - $q->{functions} = [ "CMD_UPLOAD_CHANGES", - "CMD_VIEW_CHANGES", - "CMD_ASK_LATER", - "CMD_ASK_NEVER", - "CMD_ABORT", ]; - - $q->{default} = "CMD_VIEW_CHANGES"; - - $q->{options} = [ map { $_->[0] } @profiles ]; - $q->{selected} = 0; - - my ($ans, $arg); - do { - ($ans, $arg) = UI_PromptUser($q); - - if ($ans eq "CMD_VIEW_CHANGES") { - display_changes($profiles[$arg]->[2], $profiles[$arg]->[1]); - } - } until $ans =~ /^CMD_(UPLOAD_CHANGES|ASK_NEVER|ASK_LATER)/; - - if ($ans eq "CMD_ASK_NEVER") { - set_profiles_local_only( map { $_->[0] } @profiles ); - } elsif ($ans eq "CMD_UPLOAD_CHANGES") { - my $changelog = UI_GetString(gettext("Changelog Entry: "), ""); - my ($user, $pass) = get_repo_user_pass(); - if ($user && $pass) { - for my $p_data (@profiles) { - my $profile = $p_data->[0]; - my $profile_string = $p_data->[1]; - my ($status_ok,$ret) = - upload_profile( $url, - $user, - $pass, - $cfg->{repository}{distro}, - $profile, - $profile_string, - $changelog - ); - if ($status_ok) { - my $newprofile = $ret; - my $newid = $newprofile->{id}; - set_repo_info($sd{$profile}{$profile}, $url, $user, $newid); - writeprofile_ui_feedback($profile); - UI_Info( - sprintf(gettext("Uploaded %s to repository."), $profile) - ); - } else { - my $errmsg = - sprintf( - gettext("WARNING: An error occured while uploading the profile %s\n%s\n"), - $profile, $ret?$ret:gettext("UNKNOWN ERROR")); - UI_Important( $errmsg ); - } - } - } else { - UI_Important(gettext("Repository Error\n" . - "Registration or Signin was unsuccessful. User login\n" . - "information is required to upload profiles to the\n" . - "repository. These changes have not been sent.\n")); - } - } -} - -# -# Mark the profiles passed in @profiles as local only -# and don't prompt to upload changes to the repository -# -sub set_profiles_local_only(@) { - my @profiles = @_; - for my $profile (@profiles) { - $sd{$profile}{$profile}{repo}{neversubmit} = 1; - writeprofile_ui_feedback($profile); - } -} - -########################################################################## -# Here are the console/yast interface functions - -sub UI_Info ($) { - my $text = shift; - - $DEBUGGING && debug "UI_Info: $UI_Mode: $text"; - - if ($UI_Mode eq "text") { - print "$text\n"; - } else { - ycp::y2milestone($text); - } -} - -sub UI_Important ($) { - my $text = shift; - - $DEBUGGING && debug "UI_Important: $UI_Mode: $text"; - - if ($UI_Mode eq "text") { - print "\n$text\n"; - } else { - SendDataToYast({ type => "dialog-error", message => $text }); - my ($path, $yarg) = GetDataFromYast(); - } -} - -sub UI_YesNo ($$) { - my $text = shift; - my $default = shift; - - $DEBUGGING && debug "UI_YesNo: $UI_Mode: $text $default"; - - my $ans; - if ($UI_Mode eq "text") { - - my $yes = gettext("(Y)es"); - my $no = gettext("(N)o"); - - # figure out our localized hotkeys - my $usrmsg = "PromptUser: " . gettext("Invalid hotkey for"); - $yes =~ /\((\S)\)/ or fatal_error "$usrmsg '$yes'"; - my $yeskey = lc($1); - $no =~ /\((\S)\)/ or fatal_error "$usrmsg '$no'"; - my $nokey = lc($1); - - print "\n$text\n"; - if ($default eq "y") { - print "\n[$yes] / $no\n"; - } else { - print "\n$yes / [$no]\n"; - } - $ans = getkey() || (($default eq "y") ? $yeskey : $nokey); - - # convert back from a localized answer to english y or n - $ans = (lc($ans) eq $yeskey) ? "y" : "n"; - } else { - - SendDataToYast({ type => "dialog-yesno", question => $text }); - my ($ypath, $yarg) = GetDataFromYast(); - $ans = $yarg->{answer} || $default; - - } - - return $ans; -} - -sub UI_YesNoCancel ($$) { - my $text = shift; - my $default = shift; - - $DEBUGGING && debug "UI_YesNoCancel: $UI_Mode: $text $default"; - - my $ans; - if ($UI_Mode eq "text") { - - my $yes = gettext("(Y)es"); - my $no = gettext("(N)o"); - my $cancel = gettext("(C)ancel"); - - # figure out our localized hotkeys - my $usrmsg = "PromptUser: " . gettext("Invalid hotkey for"); - $yes =~ /\((\S)\)/ or fatal_error "$usrmsg '$yes'"; - my $yeskey = lc($1); - $no =~ /\((\S)\)/ or fatal_error "$usrmsg '$no'"; - my $nokey = lc($1); - $cancel =~ /\((\S)\)/ or fatal_error "$usrmsg '$cancel'"; - my $cancelkey = lc($1); - - $ans = "XXXINVALIDXXX"; - while ($ans !~ /^(y|n|c)$/) { - print "\n$text\n"; - if ($default eq "y") { - print "\n[$yes] / $no / $cancel\n"; - } elsif ($default eq "n") { - print "\n$yes / [$no] / $cancel\n"; - } else { - print "\n$yes / $no / [$cancel]\n"; - } - - $ans = getkey(); - - if ($ans) { - # convert back from a localized answer to english y or n - $ans = lc($ans); - if ($ans eq $yeskey) { - $ans = "y"; - } elsif ($ans eq $nokey) { - $ans = "n"; - } elsif ($ans eq $cancelkey) { - $ans = "c"; - } - } else { - $ans = $default; - } - } - } else { - - SendDataToYast({ type => "dialog-yesnocancel", question => $text }); - my ($ypath, $yarg) = GetDataFromYast(); - $ans = $yarg->{answer} || $default; - - } - - return $ans; -} - -sub UI_GetString ($$) { - my $text = shift; - my $default = shift; - - $DEBUGGING && debug "UI_GetString: $UI_Mode: $text $default"; - - my $string; - if ($UI_Mode eq "text") { - - if ($term) { - $string = $term->readline($text, $default); - } else { - local $| = 1; - print "$text"; - $string = ; - chomp($string); - } - - } else { - - SendDataToYast({ - type => "dialog-getstring", - label => $text, - default => $default - }); - my ($ypath, $yarg) = GetDataFromYast(); - $string = $yarg->{string}; - - } - return $string; -} - -sub UI_GetFile ($) { - my $f = shift; - - $DEBUGGING && debug "UI_GetFile: $UI_Mode"; - - my $filename; - if ($UI_Mode eq "text") { - - local $| = 1; - print "$f->{description}\n"; - $filename = ; - chomp($filename); - - } else { - - $f->{type} = "dialog-getfile"; - - SendDataToYast($f); - my ($ypath, $yarg) = GetDataFromYast(); - if ($yarg->{answer} eq "okay") { - $filename = $yarg->{filename}; - } - } - - return $filename; -} - -sub UI_BusyStart ($) { - my $message = shift; - $DEBUGGING && debug "UI_BusyStart: $UI_Mode"; - - if ($UI_Mode eq "text") { - UI_Info( $message ); - } else { - SendDataToYast({ - type => "dialog-busy-start", - message => $message, - }); - my ($ypath, $yarg) = GetDataFromYast(); - } -} - -sub UI_BusyStop() { - $DEBUGGING && debug "UI_BusyStop: $UI_Mode"; - - if ($UI_Mode ne "text") { - SendDataToYast({ type => "dialog-busy-stop" }); - my ($ypath, $yarg) = GetDataFromYast(); - } -} - - -my %CMDS = ( - CMD_ALLOW => "(A)llow", - CMD_OTHER => "(M)ore", - CMD_AUDIT_NEW => "Audi(t)", - CMD_AUDIT_OFF => "Audi(t) off", - CMD_AUDIT_FULL => "Audit (A)ll", - CMD_OTHER => "(O)pts", - CMD_USER_ON => "(O)wner permissions on", - CMD_USER_OFF => "(O)wner permissions off", - CMD_DENY => "(D)eny", - CMD_ABORT => "Abo(r)t", - CMD_FINISHED => "(F)inish", - CMD_ix => "(I)nherit", - CMD_px => "(P)rofile", - CMD_px_safe => "(P)rofile Clean Exec", - CMD_cx => "(C)hild", - CMD_cx_safe => "(C)hild Clean Exec", - CMD_nx => "(N)ame", - CMD_nx_safe => "(N)amed Clean Exec", - CMD_ux => "(U)nconfined", - CMD_ux_safe => "(U)nconfined Clean Exec", - CMD_pix => "(P)rofile ix", - CMD_pix_safe => "(P)rofile ix Clean Exec", - CMD_cix => "(C)hild ix", - CMD_cix_safe => "(C)hild ix Cx Clean Exec", - CMD_nix => "(N)ame ix", - CMD_nix_safe => "(N)ame ix", - CMD_EXEC_IX_ON => "(X)ix", - CMD_EXEC_IX_OFF => "(X)ix", - CMD_SAVE => "(S)ave Changes", - CMD_CONTINUE => "(C)ontinue Profiling", - CMD_NEW => "(N)ew", - CMD_GLOB => "(G)lob", - CMD_GLOBEXT => "Glob w/(E)xt", - CMD_ADDHAT => "(A)dd Requested Hat", - CMD_USEDEFAULT => "(U)se Default Hat", - CMD_SCAN => "(S)can system log for AppArmor events", - CMD_HELP => "(H)elp", - CMD_VIEW_PROFILE => "(V)iew Profile", - CMD_USE_PROFILE => "(U)se Profile", - CMD_CREATE_PROFILE => "(C)reate New Profile", - CMD_UPDATE_PROFILE => "(U)pdate Profile", - CMD_IGNORE_UPDATE => "(I)gnore Update", - CMD_SAVE_CHANGES => "(S)ave Changes", - CMD_UPLOAD_CHANGES => "(U)pload Changes", - CMD_VIEW_CHANGES => "(V)iew Changes", - CMD_VIEW => "(V)iew", - CMD_ENABLE_REPO => "(E)nable Repository", - CMD_DISABLE_REPO => "(D)isable Repository", - CMD_ASK_NEVER => "(N)ever Ask Again", - CMD_ASK_LATER => "Ask Me (L)ater", - CMD_YES => "(Y)es", - CMD_NO => "(N)o", - CMD_ALL_NET => "Allow All (N)etwork", - CMD_NET_FAMILY => "Allow Network Fa(m)ily", - CMD_OVERWRITE => "(O)verwrite Profile", - CMD_KEEP => "(K)eep Profile", - CMD_CONTINUE => "(C)ontinue", -); - -sub UI_PromptUser ($) { - my $q = shift; - - my ($cmd, $arg); - if ($UI_Mode eq "text") { - - ($cmd, $arg) = Text_PromptUser($q); - - } else { - - $q->{type} = "wizard"; - - SendDataToYast($q); - my ($ypath, $yarg) = GetDataFromYast(); - - $cmd = $yarg->{selection} || "CMD_ABORT"; - $arg = $yarg->{selected}; - } - - if ($cmd eq "CMD_ABORT") { - confirm_and_abort(); - $cmd = "XXXINVALIDXXX"; - } elsif ($cmd eq "CMD_FINISHED") { - confirm_and_finish(); - $cmd = "XXXINVALIDXXX"; - } - - if (wantarray) { - return ($cmd, $arg); - } else { - return $cmd; - } -} - - -sub UI_ShortMessage($$) { - my ($headline, $message) = @_; - - SendDataToYast( - { - type => "short-dialog-message", - headline => $headline, - message => $message - } - ); - my ($ypath, $yarg) = GetDataFromYast(); -} - -sub UI_LongMessage($$) { - my ($headline, $message) = @_; - - $headline = "MISSING" if not defined $headline; - $message = "MISSING" if not defined $message; - - SendDataToYast( - { - type => "long-dialog-message", - headline => $headline, - message => $message - } - ); - my ($ypath, $yarg) = GetDataFromYast(); -} - -########################################################################## -# here are the interface functions to send data back and forth between -# the yast frontend and the perl backend - -# this is super ugly, but waits for the next ycp Read command and sends data -# back to the ycp front end. - -sub SendDataToYast($) { - my $data = shift; - - $DEBUGGING && debug "SendDataToYast: Waiting for YCP command"; - - while () { - $DEBUGGING && debug "SendDataToYast: YCP: $_"; - my ($ycommand, $ypath, $yargument) = ycp::ParseCommand($_); - - if ($ycommand && $ycommand eq "Read") { - - if ($DEBUGGING) { - my $debugmsg = Data::Dumper->Dump([$data], [qw(*data)]); - debug "SendDataToYast: Sending--\n$debugmsg"; - } - - ycp::Return($data); - return 1; - - } else { - - $DEBUGGING && debug "SendDataToYast: Expected 'Read' but got-- $_"; - - } - } - - # if we ever break out here, something's horribly wrong. - fatal_error "SendDataToYast: didn't receive YCP command before connection died"; -} - -# this is super ugly, but waits for the next ycp Write command and grabs -# whatever the ycp front end gives us - -sub GetDataFromYast() { - - $DEBUGGING && debug "GetDataFromYast: Waiting for YCP command"; - - while () { - $DEBUGGING && debug "GetDataFromYast: YCP: $_"; - my ($ycmd, $ypath, $yarg) = ycp::ParseCommand($_); - - if ($DEBUGGING) { - my $debugmsg = Data::Dumper->Dump([$yarg], [qw(*data)]); - debug "GetDataFromYast: Received--\n$debugmsg"; - } - - if ($ycmd && $ycmd eq "Write") { - - ycp::Return("true"); - return ($ypath, $yarg); - - } else { - $DEBUGGING && debug "GetDataFromYast: Expected 'Write' but got-- $_"; - } - } - - # if we ever break out here, something's horribly wrong. - fatal_error "GetDataFromYast: didn't receive YCP command before connection died"; -} - -sub confirm_and_abort() { - my $ans = UI_YesNo(gettext("Are you sure you want to abandon this set of profile changes and exit?"), "n"); - if ($ans eq "y") { - UI_Info(gettext("Abandoning all changes.")); - shutdown_yast(); - foreach my $prof (@created) { - delete_profile($prof); - } - exit 0; - } -} - -sub confirm_and_finish() { - die "FINISHING\n"; -} - -sub build_x_functions($$$) { - my ($default, $options, $exec_toggle) = @_; - my @{list}; - if ($exec_toggle) { - push @list, "CMD_ix" if $options =~ /i/; - push @list, "CMD_pix" if $options =~ /p/ and $options =~ /i/; - push @list, "CMD_cix" if $options =~ /c/ and $options =~ /i/; - push @list, "CMD_nix" if $options =~ /n/ and $options =~ /i/; - push @list, "CMD_ux" if $options =~ /u/; - } else { - push @list, "CMD_ix" if $options =~ /i/; - push @list, "CMD_px" if $options =~ /p/; - push @list, "CMD_cx" if $options =~ /c/; - push @list, "CMD_nx" if $options =~ /n/; - push @list, "CMD_ux" if $options =~ /u/; - } - if ($exec_toggle) { - push @list, "CMD_EXEC_IX_OFF" if $options =~/p|c|n/; - } else { - push @list, "CMD_EXEC_IX_ON" if $options =~/p|c|n/; - } - push @list, "CMD_DENY", "CMD_ABORT", "CMD_FINISHED"; - return @list; -} - -########################################################################## -# this is the hideously ugly function that descends down the flow/event -# trees that we've generated by parsing the logfile - -sub handlechildren($$$); - -sub handlechildren($$$) { - my $profile = shift; - my $hat = shift; - my $root = shift; - - my @entries = @$root; - for my $entry (@entries) { - fatal_error "$entry is not a ref" if not ref($entry); - - if (ref($entry->[0])) { - handlechildren($profile, $hat, $entry); - } else { - - my @entry = @$entry; - my $type = shift @entry; - - if ($type eq "fork") { - my ($pid, $p, $h) = @entry; - - if ( ($p !~ /null(-complain)*-profile/) - && ($h !~ /null(-complain)*-profile/)) - { - $profile = $p; - $hat = $h; - } - - if ($hat) { - $profilechanges{$pid} = $profile . "//" . $hat; - } else { - $profilechanges{$pid} = $profile; - } - } elsif ($type eq "unknown_hat") { - my ($pid, $p, $h, $sdmode, $uhat) = @entry; - - if ($p !~ /null(-complain)*-profile/) { - $profile = $p; - } - - if ($sd{$profile}{$uhat}) { - $hat = $uhat; - next; - } - - my $new_p = update_repo_profile($sd{$profile}{$profile}); - if ( $new_p and - UI_SelectUpdatedRepoProfile($profile, $new_p) and - $sd{$profile}{$uhat} ) { - $hat = $uhat; - next; - } - - # figure out what our default hat for this application is. - my $defaulthat; - for my $hatglob (keys %{$cfg->{defaulthat}}) { - $defaulthat = $cfg->{defaulthat}{$hatglob} - if $profile =~ /$hatglob/; - } - # keep track of previous answers for this run... - my $context = $profile; - $context .= " -> ^$uhat"; - my $ans = $transitions{$context} || "XXXINVALIDXXX"; - - while ($ans !~ /^CMD_(ADDHAT|USEDEFAULT|DENY)$/) { - my $q = {}; - $q->{headers} = []; - push @{ $q->{headers} }, gettext("Profile"), $profile; - if ($defaulthat) { - push @{ $q->{headers} }, gettext("Default Hat"), $defaulthat; - } - push @{ $q->{headers} }, gettext("Requested Hat"), $uhat; - - $q->{functions} = []; - push @{ $q->{functions} }, "CMD_ADDHAT"; - push @{ $q->{functions} }, "CMD_USEDEFAULT" if $defaulthat; - push @{$q->{functions}}, "CMD_DENY", "CMD_ABORT", - "CMD_FINISHED"; - - $q->{default} = ($sdmode eq "PERMITTING") ? "CMD_ADDHAT" : "CMD_DENY"; - - $seenevents++; - - $ans = UI_PromptUser($q); - - } - $transitions{$context} = $ans; - - if ($ans eq "CMD_ADDHAT") { - $hat = $uhat; - $sd{$profile}{$hat}{flags} = $sd{$profile}{$profile}{flags}; - } elsif ($ans eq "CMD_USEDEFAULT") { - $hat = $defaulthat; - } elsif ($ans eq "CMD_DENY") { - return; - } - - } elsif ($type eq "capability") { - my ($pid, $p, $h, $prog, $sdmode, $capability) = @entry; - - if ( ($p !~ /null(-complain)*-profile/) - && ($h !~ /null(-complain)*-profile/)) - { - $profile = $p; - $hat = $h; - } - - # print "$pid $profile $hat $prog $sdmode capability $capability\n"; - - next unless $profile && $hat; - - $prelog{$sdmode}{$profile}{$hat}{capability}{$capability} = 1; - } elsif (($type eq "path") || ($type eq "exec")) { - my ($pid, $p, $h, $prog, $sdmode, $mode, $detail, $to_name) = @entry; - - $mode = 0 unless ($mode); - - if ( ($p !~ /null(-complain)*-profile/) - && ($h !~ /null(-complain)*-profile/)) - { - $profile = $p; - $hat = $h; - } - - next unless $profile && $hat && $detail; - my $domainchange = ($type eq "exec") ? "change" : "nochange"; - - # escape special characters that show up in literal paths - $detail =~ s/(\[|\]|\+|\*|\{|\})/\\$1/g; - - # we need to give the Execute dialog if they're requesting x - # access for something that's not a directory - we'll force - # a "ix" Path dialog for directories - my $do_execute = 0; - my $exec_target = $detail; - - if ($mode & str_to_mode("x")) { - if (-d $exec_target) { - $mode &= (~$ALL_AA_EXEC_TYPE); - $mode |= str_to_mode("ix"); - } else { - $do_execute = 1; - } - } - - if ($mode & $AA_MAY_LINK) { - if ($detail =~ m/^from (.+) to (.+)$/) { - my ($path, $target) = ($1, $2); - - my $frommode = str_to_mode("lr"); - if (defined $prelog{$sdmode}{$profile}{$hat}{path}{$path}) { - $frommode |= $prelog{$sdmode}{$profile}{$hat}{path}{$path}; - } - $prelog{$sdmode}{$profile}{$hat}{path}{$path} = $frommode; - - my $tomode = str_to_mode("lr"); - if (defined $prelog{$sdmode}{$profile}{$hat}{path}{$target}) { - $tomode |= $prelog{$sdmode}{$profile}{$hat}{path}{$target}; - } - $prelog{$sdmode}{$profile}{$hat}{path}{$target} = $tomode; - - # print "$pid $profile $hat $prog $sdmode $path:$frommode -> $target:$tomode\n"; - } else { - next; - } - } elsif ($mode) { - my $path = $detail; - - if (defined $prelog{$sdmode}{$profile}{$hat}{path}{$path}) { - $mode |= $prelog{$sdmode}{$profile}{$hat}{path}{$path}; - } - $prelog{$sdmode}{$profile}{$hat}{path}{$path} = $mode; - - # print "$pid $profile $hat $prog $sdmode $mode $path\n"; - } - - if ($do_execute) { - next if ( profile_known_exec($sd{$profile}{$hat}, - "exec", $exec_target ) ); - - my $p = update_repo_profile($sd{$profile}{$profile}); - - if ($to_name) { - next if ( $to_name and - UI_SelectUpdatedRepoProfile($profile, $p) and - profile_known_exec($sd{$profile}{$hat}, - "exec", $to_name ) ); - } else { - next if ( UI_SelectUpdatedRepoProfile($profile, $p) and - profile_known_exec($sd{$profile}{$hat}, - "exec", $exec_target ) ); - } - - my $context = $profile; - $context .= "^$hat" if $profile ne $hat; - $context .= " -> $exec_target"; - my $ans = $transitions{$context} || ""; - - my ($combinedmode, $combinedaudit, $cm, $am, @m); - $combinedmode = 0; - $combinedaudit = 0; - - # does path match any regexps in original profile? - ($cm, $am, @m) = rematchfrag($sd{$profile}{$hat}, 'allow', $exec_target); - $combinedmode |= $cm if $cm; - $combinedaudit |= $am if $am; - - # find the named transition if is present - if ($combinedmode & str_to_mode("x")) { - my $nt_name; - foreach my $entry (@m) { - if ($sd{$profile}{$hat}{allow}{path}{$entry}{to}) { - $nt_name = $sd{$profile}{$hat}{allow}{path}{$entry}{to}; - last; - } - } - if ($to_name and $nt_name and ($to_name ne $nt_name)) { - #fatal_error "transition name from " - } elsif ($nt_name) { - $to_name = $nt_name; - } - } - - # does path match anything pulled in by includes in - # original profile? - ($cm, $am, @m) = match_prof_incs_to_path($sd{$profile}{$hat}, 'allow', $exec_target); - $combinedmode |= $cm if $cm; - $combinedaudit |= $am if $am; - if ($combinedmode & str_to_mode("x")) { - my $nt_name; - foreach my $entry (@m) { - if ($sd{$profile}{$hat}{allow}{path}{$entry}{to}) { - $nt_name = $sd{$profile}{$hat}{allow}{path}{$entry}{to}; - last; - } - } - if ($to_name and $nt_name and ($to_name ne $nt_name)) { - #fatal_error "transition name from " - } elsif ($nt_name) { - $to_name = $nt_name; - } - } - - - #nx does not exist in profiles. It does in log - #files however. The log parsing routines will convert - #it to its profile form. - #nx is internally represented by cx/px/cix/pix + to_name - my $exec_mode = 0; - if (contains($combinedmode, "pix")) { - if ($to_name) { - $ans = "CMD_nix"; - } else { - $ans = "CMD_pix"; - } - $exec_mode = str_to_mode("pixr"); - } elsif (contains($combinedmode, "cix")) { - if ($to_name) { - $ans = "CMD_nix"; - } else { - $ans = "CMD_cix"; - } - $exec_mode = str_to_mode("cixr"); - } elsif (contains($combinedmode, "Pix")) { - if ($to_name) { - $ans = "CMD_nix_safe"; - } else { - $ans = "CMD_pix_safe"; - } - $exec_mode = str_to_mode("Pixr"); - } elsif (contains($combinedmode, "Cix")) { - if ($to_name) { - $ans = "CMD_nix_safe"; - } else { - $ans = "CMD_cix_safe"; - } - $exec_mode = str_to_mode("Cixr"); - } elsif (contains($combinedmode, "ix")) { - $ans = "CMD_ix"; - $exec_mode = str_to_mode("ixr"); - } elsif (contains($combinedmode, "px")) { - if ($to_name) { - $ans = "CMD_nx"; - } else { - $ans = "CMD_px"; - } - $exec_mode = str_to_mode("px"); - } elsif (contains($combinedmode, "cx")) { - if ($to_name) { - $ans = "CMD_nx"; - } else { - $ans = "CMD_cx"; - } - $exec_mode = str_to_mode("cx"); - } elsif (contains($combinedmode, "ux")) { - $ans = "CMD_ux"; - $exec_mode = str_to_mode("ux"); - } elsif (contains($combinedmode, "Px")) { - if ($to_name) { - $ans = "CMD_nx_safe"; - } else { - $ans = "CMD_px_safe"; - } - $exec_mode = str_to_mode("Px"); - } elsif (contains($combinedmode, "Cx")) { - if ($to_name) { - $ans = "CMD_nx_safe"; - } else { - $ans = "CMD_cx_safe"; - } - $exec_mode = str_to_mode("Cx"); - } elsif (contains($combinedmode, "Ux")) { - $ans = "CMD_ux_safe"; - $exec_mode = str_to_mode("Ux"); - } else { - my $options = $cfg->{qualifiers}{$exec_target} || "ipcnu"; - fatal_error "$entry has transition name but not transition mode" if $to_name; - - # force "ix" as the only option when the profiled - # program executes itself - $options = "i" if $exec_target eq $profile; - - # for now don't allow hats to cx - $options =~ s/c// if $hat and $hat ne $profile; - - # we always need deny... - $options .= "d"; - - # figure out what our default option should be... - my $default; - if ($options =~ /p/ - && -e getprofilefilename($exec_target)) - { - $default = "CMD_px"; - } elsif ($options =~ /i/) { - $default = "CMD_ix"; - } elsif ($options =~ /c/) { - $default = "CMD_cx"; - } elsif ($options =~ /n/) { - $default = "CMD_nx"; - } else { - $default = "CMD_DENY"; - } - - # ugh, this doesn't work if someone does an ix before - # calling this particular child process. at least - # it's only a hint instead of mandatory to get this - # right. - my $parent_uses_ld_xxx = check_for_LD_XXX($profile); - - my $severity = $sevdb->rank($exec_target, "x"); - - # build up the prompt... - my $q = {}; - $q->{headers} = []; - push @{ $q->{headers} }, gettext("Profile"), combine_name($profile, $hat); - if ($prog && $prog ne "HINT") { - push @{ $q->{headers} }, gettext("Program"), $prog; - } - # $to_name should NOT exist here other wise we know what - # mode we are supposed to be transitioning to - # which is handled above. - push @{ $q->{headers} }, gettext("Execute"), $exec_target; - push @{ $q->{headers} }, gettext("Severity"), $severity; - - $q->{functions} = []; - - my $prompt = "\n$context\n"; - my $exec_toggle = 0; - - push @{ $q->{functions} }, build_x_functions($default, $options, $exec_toggle); - - $options = join("|", split(//, $options)); - - $seenevents++; - - while ($ans !~ m/^CMD_(ix|px|cx|nx|pix|cix|nix|px_safe|cx_safe|nx_safe|pix_safe|cix_safe|nix_safe|ux|ux_safe|EXEC_TOGGLE|DENY)$/) { - $ans = UI_PromptUser($q); - - if ($ans =~ /CMD_EXEC_IX_/) { - $exec_toggle = !$exec_toggle; - - $q->{functions} = [ ]; - push @{ $q->{functions} }, build_x_functions($default, $options, $exec_toggle); - $ans = ""; - next; - } - if ($ans =~ /CMD_(nx|nix)/) { - my $arg = $exec_target; - - my $ynans = "n"; - if ($profile eq $hat) { - $ynans = UI_YesNo("Are you specifying a transition to a local profile?", "n"); - } - - if ($ynans eq "y") { - if ($ans eq "CMD_nx") { - $ans = "CMD_cx"; - } else { - $ans = "CMD_cix"; - } - } else { - if ($ans eq "CMD_nx") { - $ans = "CMD_px"; - } else { - $ans = "CMD_pix"; - } - } - $to_name = UI_GetString(gettext("Enter profile name to transition to: "), $arg); - } - if ($ans =~ /CMD_ix/) { - $exec_mode = str_to_mode("ix"); - } elsif ($ans =~ /CMD_(px|cx|nx|pix|cix|nix)/) { - my $match = $1; - $exec_mode = str_to_mode($match); - my $px_default = "n"; - my $px_mesg = gettext("Should AppArmor sanitize the environment when\nswitching profiles?\n\nSanitizing the environment is more secure,\nbut some applications depend on the presence\nof LD_PRELOAD or LD_LIBRARY_PATH."); - if ($parent_uses_ld_xxx) { - $px_mesg = gettext("Should AppArmor sanitize the environment when\nswitching profiles?\n\nSanitizing the environment is more secure,\nbut this application appears to use LD_PRELOAD\nor LD_LIBRARY_PATH and clearing these could\ncause functionality problems."); - } - my $ynans = UI_YesNo($px_mesg, $px_default); - $ans = "CMD_$match"; - if ($ynans eq "y") { - $exec_mode &= ~($AA_EXEC_UNSAFE | ($AA_EXEC_UNSAFE << $AA_OTHER_SHIFT)); - } - } elsif ($ans eq "CMD_ux") { - $exec_mode = str_to_mode("ux"); - my $ynans = UI_YesNo(sprintf(gettext("Launching processes in an unconfined state is a very\ndangerous operation and can cause serious security holes.\n\nAre you absolutely certain you wish to remove all\nAppArmor protection when executing \%s?"), $exec_target), "n"); - if ($ynans eq "y") { - my $ynans = UI_YesNo(gettext("Should AppArmor sanitize the environment when\nrunning this program unconfined?\n\nNot sanitizing the environment when unconfining\na program opens up significant security holes\nand should be avoided if at all possible."), "y"); - if ($ynans eq "y") { - $exec_mode &= ~($AA_EXEC_UNSAFE | ($AA_EXEC_UNSAFE << $AA_OTHER_SHIFT)); - } - } else { - $ans = "INVALID"; - } - } - } - $transitions{$context} = $ans; - - if ($ans =~ /CMD_(ix|px|cx|nx|pix|cix|nix)/) { - # if we're inheriting, things'll bitch unless we have r - if ($exec_mode & str_to_mode("i")) { - $exec_mode |= str_to_mode("r"); - } - - } else { - if ($ans eq "CMD_DENY") { - $sd{$profile}{$hat}{deny}{path}{$exec_target}{mode} |= str_to_mode("x"); - - $sd{$profile}{$hat}{deny}{path}{$exec_target}{audit} |= 0; - $changed{$profile} = 1; - # skip all remaining events if they say to deny - # the exec - return if $domainchange eq "change"; - } - - } - - unless ($ans eq "CMD_DENY") { -# ???? if its defined in the prelog we shouldn't have asked - if (defined $prelog{PERMITTING}{$profile}{$hat}{path}{$exec_target}) { -# $exec_mode = $prelog{PERMITTING}{$profile}{$hat}{path}{$exec_target}; - } - - $prelog{PERMITTING}{$profile}{$hat}{path}{$exec_target} |= $exec_mode; - $log{PERMITTING}{$profile} = {}; - $sd{$profile}{$hat}{allow}{path}{$exec_target}{mode} |= $exec_mode; - $sd{$profile}{$hat}{allow}{path}{$exec_target}{audit} |= 0; - $sd{$profile}{$hat}{allow}{path}{$exec_target}{to} = $to_name if ($to_name); - - # mark this profile as changed - $changed{$profile} = 1; - - if ($exec_mode & str_to_mode("i")) { - if ($exec_target =~ /perl/) { - $sd{$profile}{$hat}{include}{"abstractions/perl"} = 1; - } elsif ($detail =~ m/\/bin\/(bash|sh)/) { - $sd{$profile}{$hat}{include}{"abstractions/bash"} = 1; - } - my $hashbang = head($exec_target); - if ($hashbang =~ /^#!\s*(\S+)/) { - my $interpreter = get_full_path($1); - $sd{$profile}{$hat}{path}->{$interpreter}{mode} |= str_to_mode("ix"); - $sd{$profile}{$hat}{path}->{$interpreter}{audit} |= 0; - if ($interpreter =~ /perl/) { - $sd{$profile}{$hat}{include}{"abstractions/perl"} = 1; - } elsif ($interpreter =~ m/\/bin\/(bash|sh)/) { - $sd{$profile}{$hat}{include}{"abstractions/bash"} = 1; - } - } - } - } - } - - # print "$pid $profile $hat EXEC $exec_target $ans $exec_mode\n"; - - # update our tracking info based on what kind of change - # this is... - if ($ans eq "CMD_ix") { - if ($hat) { - $profilechanges{$pid} = $profile . "//" . $hat; - } else { - $profilechanges{$pid} = $profile; - } - } elsif ($ans =~ /^CMD_(px|nx|pix|nix)/) { - $exec_target = $to_name if ($to_name); - if ($sdmode eq "PERMITTING") { - if ($domainchange eq "change") { - $profile = $exec_target; - $hat = $exec_target; - $profilechanges{$pid} = $profile; - } - } - # if they want to use px, make sure a profile - # exists for the target. - unless (-e getprofilefilename($exec_target)) { - my $ynans = "y"; - if ($exec_mode & str_to_mode("i")) { - $ynans = UI_YesNo(sprintf(gettext("A profile for %s does not exist. Create one?"), $exec_target), "n"); - } - if ($ynans eq "y") { - $helpers{$exec_target} = "enforce"; - if ($to_name) { - autodep_base("", $exec_target); - } else { - autodep_base($exec_target, ""); - } - reload_base($exec_target); - } - } - } elsif ($ans =~ /^CMD_(cx|cix)/) { - $exec_target = $to_name if ($to_name); - if ($sdmode eq "PERMITTING") { - if ($domainchange eq "change") { - $profilechanges{$pid} = "${profile}//${exec_target}"; -# $profile = $exec_target; -# $hat = $exec_target; - } - } - - # if they want to use cx, make sure a profile - # exists for the target. - unless ($sd{$profile}{$exec_target}) { - my $ynans = "y"; - if ($exec_mode & str_to_mode("i")) { - $ynans = UI_YesNo(sprintf(gettext("A local profile for %s does not exist. Create one?"), $exec_target), "n"); - } - if ($ynans eq "y") { - $hat = $exec_target; - # keep track of profile flags - #$profile_data->{$profile}{$hat}{flags} = ; - - # we have seen more than a declaration so clear it - $sd{$profile}{$hat}{'declared'} = 0; - $sd{$profile}{$hat}{profile} = 1; - - # Otherwise sub-profiles end up getting - # put in enforce mode with genprof - $sd{$profile}{$hat}{flags} = $sd{$profile}{$profile}{flags} if $profile ne $hat; - - # autodep our new child - my $stub_profile = create_new_profile($hat); - - $sd{$profile}{$hat}{flags} = 'complain'; - $sd{$profile}{$hat}{allow}{path} = { }; - if (defined $stub_profile->{$hat}{$hat}{allow}{path}) { - $sd{$profile}{$hat}{allow}{path} = $stub_profile->{$hat}{$hat}{allow}{path}; - } - $sd{$profile}{$hat}{include} = { }; - if (defined $stub_profile->{$hat}{$hat}{include}) { - $sd{$profile}{$hat}{include} = $stub_profile->{$hat}{$hat}{include}; - } - $sd{$profile}{$hat}{allow}{netdomain} = { }; - my $file = $sd{$profile}{$profile}{filename}; - $filelist{$file}{profiles}{$profile}{$hat} = 1; - - } - } - } elsif ($ans =~ /^CMD_ux/) { - $profilechanges{$pid} = "unconfined"; - return if $domainchange eq "change"; - } - } - } elsif ( $type eq "netdomain" ) { - my ($pid, $p, $h, $prog, $sdmode, $family, $sock_type, $protocol) = - @entry; - - if ( ($p !~ /null(-complain)*-profile/) - && ($h !~ /null(-complain)*-profile/)) - { - $profile = $p; - $hat = $h; - } - - next unless $profile && $hat; - $prelog{$sdmode} - {$profile} - {$hat} - {netdomain} - {$family} - {$sock_type} = 1 unless ( !$family || !$sock_type ); - - } - } - } -} - -sub add_to_tree ($$$@) { - my ($pid, $parent, $type, @event) = @_; - if ( $DEBUGGING ) { - my $eventmsg = Data::Dumper->Dump([@event], [qw(*event)]); - $eventmsg =~ s/\n/ /g; - debug " add_to_tree: pid [$pid] type [$type] event [ $eventmsg ]"; - } - - unless (exists $pid{$pid}) { - my $profile = $event[0]; - my $hat = $event[1]; - if ($parent && exists $pid{$parent}) { - # fork entry is missing fake one so that fork tracking will work - $hat ||= "null-complain-profile"; - my $arrayref = []; - push @{ $pid{$parent} }, $arrayref; - $pid{$pid} = $arrayref; - push @{$arrayref}, [ "fork", $pid, $profile, $hat ]; - } else { - my $arrayref = []; - push @log, $arrayref; - $pid{$pid} = $arrayref; - } - } - - push @{ $pid{$pid} }, [ $type, $pid, @event ]; -} - -# -# variables used in the logparsing routines -# -our $LOG; -our $next_log_entry; -our $logmark; -our $seenmark; -my $RE_LOG_v2_0_syslog = qr/SubDomain/; -my $RE_LOG_v2_1_syslog = qr/kernel:\s+(\[[\d\.\s]+\]\s+)?(audit\([\d\.\:]+\):\s+)?type=150[1-6]/; -my $RE_LOG_v2_6_syslog = qr/kernel:\s+(\[[\d\.\s]+\]\s+)?type=\d+\s+audit\([\d\.\:]+\):\s+apparmor=/; -my $RE_LOG_v2_0_audit = - qr/type=(APPARMOR|UNKNOWN\[1500\]) msg=audit\([\d\.\:]+\):/; -my $RE_LOG_v2_1_audit = - qr/type=(UNKNOWN\[150[1-6]\]|APPARMOR_(AUDIT|ALLOWED|DENIED|HINT|STATUS|ERROR))/; -my $RE_LOG_v2_6_audit = - qr/type=AVC\s+(msg=)?audit\([\d\.\:]+\):\s+apparmor=/; - -sub prefetch_next_log_entry() { - # if we already have an existing cache entry, something's broken - if ($next_log_entry) { - print STDERR "Already had next log entry: $next_log_entry"; - } - - # read log entries until we either hit the end or run into an - # AA event message format we recognize - do { - $next_log_entry = <$LOG>; - $DEBUGGING && debug "prefetch_next_log_entry: next_log_entry = " . ($next_log_entry ? $next_log_entry : "empty"); - } until (!$next_log_entry || $next_log_entry =~ m{ - $RE_LOG_v2_0_syslog | - $RE_LOG_v2_0_audit | - $RE_LOG_v2_1_audit | - $RE_LOG_v2_1_syslog | - $RE_LOG_v2_6_syslog | - $RE_LOG_v2_6_audit | - $logmark - }x); -} - -sub get_next_log_entry() { - # make sure we've got a next log entry if there is one - prefetch_next_log_entry() unless $next_log_entry; - - # save a copy of the next log entry... - my $log_entry = $next_log_entry; - - # zero out our cache of the next log entry - $next_log_entry = undef; - - return $log_entry; -} - -sub peek_at_next_log_entry() { - # make sure we've got a next log entry if there is one - prefetch_next_log_entry() unless $next_log_entry; - - # return a copy of the next log entry without pulling it out of the cache - return $next_log_entry; -} - -sub throw_away_next_log_entry() { - $next_log_entry = undef; -} - -sub parse_log_record_v_2_0 ($$) { - my ($record, $last) = @_; - $DEBUGGING && debug "parse_log_record_v_2_0: $record"; - - # What's this early out for? As far as I can tell, parse_log_record_v_2_0 - # won't ever be called without something in $record - return $last if ( ! $record ); - - $_ = $record; - - if (s/(PERMITTING|REJECTING)-SYSLOGFIX/$1/) { - s/%%/%/g; - } - - if (m/LOGPROF-HINT unknown_hat (\S+) pid=(\d+) profile=(.+) active=(.+)/) { - my ($uhat, $pid, $profile, $hat) = ($1, $2, $3, $4); - - $last = $&; - - # we want to ignore entries for profiles that don't exist - # they're most likely broken entries or old entries for - # deleted profiles - return $& - if ( ($profile ne 'null-complain-profile') - && (!profile_exists($profile))); - - add_to_tree($pid, 0, "unknown_hat", $profile, $hat, - "PERMITTING", $uhat); - } elsif (m/LOGPROF-HINT (unknown_profile|missing_mandatory_profile) image=(.+) pid=(\d+) profile=(.+) active=(.+)/) { - my ($image, $pid, $profile, $hat) = ($2, $3, $4, $5); - - return $& if $last =~ /PERMITTING x access to $image/; - $last = $&; - - # we want to ignore entries for profiles that don't exist - # they're most likely broken entries or old entries for - # deleted profiles - return $& - if ( ($profile ne 'null-complain-profile') - && (!profile_exists($profile))); - - add_to_tree($pid, 0, "exec", $profile, $hat, "HINT", "PERMITTING", "x", $image); - - } elsif (m/(PERMITTING|REJECTING) (\S+) access (.+) \((.+)\((\d+)\) profile (.+) active (.+)\)/) { - my ($sdmode, $mode, $detail, $prog, $pid, $profile, $hat) = - ($1, $2, $3, $4, $5, $6, $7); - - if ($mode eq "link") { - $mode = "l"; - } - if (!validate_log_mode($mode)) { - fatal_error(sprintf(gettext('Log contains unknown mode %s.'), $mode)); - } - - my $domainchange = "nochange"; - if ($mode =~ /x/) { - - # we need to try to check if we're doing a domain transition - if ($sdmode eq "PERMITTING") { - my $following = peek_at_next_log_entry(); - - if ($following && ($following =~ m/changing_profile/)) { - $domainchange = "change"; - throw_away_next_log_entry(); - } - } - } else { - - # we want to ignore duplicates for things other than executes... - return $& if $seen{$&}; - $seen{$&} = 1; - } - - $last = $&; - - # we want to ignore entries for profiles that don't exist - # they're most likely broken entries or old entries for - # deleted profiles - if (($profile ne 'null-complain-profile') - && (!profile_exists($profile))) - { - return $&; - } - - # currently no way to stick pipe mediation in a profile, ignore - # any messages like this - return $& if $detail =~ /to pipe:/; - - # strip out extra extended attribute info since we don't - # currently have a way to specify it in the profile and - # instead just need to provide the access to the base filename - $detail =~ s/\s+extended attribute \S+//; - - # kerberos code checks to see if the krb5.conf file is world - # writable in a stupid way so we'll ignore any w accesses to - # krb5.conf - return $& if (($detail eq "to /etc/krb5.conf") && contains($mode, "w")); - - # strip off the (deleted) tag that gets added if it's a - # deleted file - $detail =~ s/\s+\(deleted\)$//; - - # next if (($detail =~ /to \/lib\/ld-/) && ($mode =~ /x/)); - - $detail =~ s/^to\s+//; - - if ($domainchange eq "change") { - add_to_tree($pid, 0, "exec", $profile, $hat, $prog, - $sdmode, str_to_mode($mode), $detail); - } else { - add_to_tree($pid, 0, "path", $profile, $hat, $prog, - $sdmode, str_to_mode($mode), $detail); - } - - } elsif (m/(PERMITTING|REJECTING) (?:mk|rm)dir on (.+) \((.+)\((\d+)\) profile (.+) active (.+)\)/) { - my ($sdmode, $path, $prog, $pid, $profile, $hat) = - ($1, $2, $3, $4, $5, $6); - - # we want to ignore duplicates for things other than executes... - return $& if $seen{$&}++; - - $last = $&; - - # we want to ignore entries for profiles that don't exist - # they're most likely broken entries or old entries for - # deleted profiles - return $& - if ( ($profile ne 'null-complain-profile') - && (!profile_exists($profile))); - - add_to_tree($pid, 0, "path", $profile, $hat, $prog, $sdmode, - "w", $path); - - } elsif (m/(PERMITTING|REJECTING) xattr (\S+) on (.+) \((.+)\((\d+)\) profile (.+) active (.+)\)/) { - my ($sdmode, $xattr_op, $path, $prog, $pid, $profile, $hat) = - ($1, $2, $3, $4, $5, $6, $7); - - # we want to ignore duplicates for things other than executes... - return $& if $seen{$&}++; - - $last = $&; - - # we want to ignore entries for profiles that don't exist - # they're most likely broken entries or old entries for - # deleted profiles - return $& - if ( ($profile ne 'null-complain-profile') - && (!profile_exists($profile))); - - my $xattrmode; - if ($xattr_op eq "get" || $xattr_op eq "list") { - $xattrmode = "r"; - } elsif ($xattr_op eq "set" || $xattr_op eq "remove") { - $xattrmode = "w"; - } - - if ($xattrmode) { - add_to_tree($pid, 0, "path", $profile, $hat, $prog, $sdmode, - str_to_mode($xattrmode), $path); - } - - } elsif (m/(PERMITTING|REJECTING) attribute \((.*?)\) change to (.+) \((.+)\((\d+)\) profile (.+) active (.+)\)/) { - my ($sdmode, $change, $path, $prog, $pid, $profile, $hat) = - ($1, $2, $3, $4, $5, $6, $7); - - # we want to ignore duplicates for things other than executes... - return $& if $seen{$&}; - $seen{$&} = 1; - - $last = $&; - - # we want to ignore entries for profiles that don't exist - # they're most likely broken entries or old entries for - # deleted profiles - return $& - if ( ($profile ne 'null-complain-profile') - && (!profile_exists($profile))); - - # kerberos code checks to see if the krb5.conf file is world - # writable in a stupid way so we'll ignore any w accesses to - # krb5.conf - return $& if $path eq "/etc/krb5.conf"; - - add_to_tree($pid, 0, "path", $profile, $hat, $prog, $sdmode, - str_to_mode("w"), $path); - - } elsif (m/(PERMITTING|REJECTING) access to capability '(\S+)' \((.+)\((\d+)\) profile (.+) active (.+)\)/) { - my ($sdmode, $capability, $prog, $pid, $profile, $hat) = - ($1, $2, $3, $4, $5, $6); - - return $& if $seen{$&}; - - $seen{$&} = 1; - $last = $&; - - # we want to ignore entries for profiles that don't exist - they're - # most likely broken entries or old entries for deleted profiles - return $& - if ( ($profile ne 'null-complain-profile') - && (!profile_exists($profile))); - - add_to_tree($pid, 0, "capability", $profile, $hat, $prog, - $sdmode, $capability); - - } elsif (m/Fork parent (\d+) child (\d+) profile (.+) active (.+)/ - || m/LOGPROF-HINT fork pid=(\d+) child=(\d+) profile=(.+) active=(.+)/ - || m/LOGPROF-HINT fork pid=(\d+) child=(\d+)/) - { - my ($parent, $child, $profile, $hat) = ($1, $2, $3, $4); - - $profile ||= "null-complain-profile"; - $hat ||= "null-complain-profile"; - - $last = $&; - - # we want to ignore entries for profiles that don't exist - # they're most likely broken entries or old entries for - # deleted profiles - return $& - if ( ($profile ne 'null-complain-profile') - && (!profile_exists($profile))); - - my $arrayref = []; - if (exists $pid{$parent}) { - push @{ $pid{$parent} }, $arrayref; - } else { - push @log, $arrayref; - } - $pid{$child} = $arrayref; - push @{$arrayref}, [ "fork", $child, $profile, $hat ]; - } else { - $DEBUGGING && debug "UNHANDLED: $_"; - } - return $last; -} - -sub parse_log_record ($) { - my $record = shift; - $DEBUGGING && debug "parse_log_record: $record"; - my $e = parse_event($record); - - return $e; -} - - -sub add_event_to_tree ($) { - my $e = shift; - - my $sdmode = $e->{sdmode}?$e->{sdmode}:"UNKNOWN"; - if ( $e->{type} ) { - if ( $e->{type} =~ /(UNKNOWN\[1501\]|APPARMOR_AUDIT|1501)/ ) { - $sdmode = "AUDIT"; - } elsif ( $e->{type} =~ /(UNKNOWN\[1502\]|APPARMOR_ALLOWED|1502)/ ) { - $sdmode = "PERMITTING"; - } elsif ( $e->{type} =~ /(UNKNOWN\[1503\]|APPARMOR_DENIED|1503)/ ) { - $sdmode = "REJECTING"; - } elsif ( $e->{type} =~ /(UNKNOWN\[1504\]|APPARMOR_HINT|1504)/ ) { - $sdmode = "HINT"; - } elsif ( $e->{type} =~ /(UNKNOWN\[1505\]|APPARMOR_STATUS|1505)/ ) { - $sdmode = "STATUS"; - } elsif ( $e->{type} =~ /(UNKNOWN\[1506\]|APPARMOR_ERROR|1506)/ ) { - $sdmode = "ERROR"; - } else { - $sdmode = "UNKNOWN"; - } - } - return if ( $sdmode =~ /UNKNOWN|AUDIT|STATUS|ERROR/ ); - return if ($e->{operation} =~ /profile_set/); - - my ($profile, $hat); - - # The version of AppArmor that was accepted into the mainline kernel - # issues audit events for things like change_hat while unconfined. - # Previous versions just returned -EPERM without the audit so the - # events wouldn't have been picked up here. - return if (!$e->{profile}); - - # just convert new null profile style names to old before we begin processing - # profile and name can contain multiple layers of null- but all we care about - # currently is single level. - if ($e->{profile} =~ m/\/\/null-/) { - $e->{profile} = "null-complain-profile"; - } - ($profile, $hat) = split /\/\//, $e->{profile}; - if ( $e->{operation} eq "change_hat" ) { - #screen out change_hat events that aren't part of learning, as before - #AppArmor 2.4 these events only happend as hints during learning - return if ($sdmode ne "HINT" && $sdmode ne "PERMITTING"); - ($profile, $hat) = split /\/\//, $e->{name}; - } - $hat = $profile if ( !$hat ); - # TODO - refactor add_to_tree as prog is no longer supplied - # HINT is from previous format where prog was not - # consistently passed - my $prog = "HINT"; - - return if ($profile ne 'null-complain-profile' && !profile_exists($profile)); - - if ($e->{operation} eq "exec") { - if ( defined $e->{info} && $e->{info} eq "mandatory profile missing" ) { - add_to_tree( $e->{pid}, - $e->{parent}, - "exec", - $profile, - $hat, - $sdmode, - "PERMITTING", - $e->{denied_mask}, - $e->{name}, - $e->{name2} - ); - } elsif ( defined $e->{name2} && $e->{name2} =~ m/\/\/null-/) { - add_to_tree( $e->{pid}, - $e->{parent}, - "exec", - $profile, - $hat, - $prog, - $sdmode, - $e->{denied_mask}, - $e->{name}, - "" - ); - } elsif (defined $e->{name}) { - add_to_tree( $e->{pid}, - $e->{parent}, - "exec", - $profile, - $hat, - $prog, - $sdmode, - $e->{denied_mask}, - $e->{name}, - "" - ); - } else { - $DEBUGGING && debug "add_event_to_tree: dropped exec event in $e->{profile}"; - } - } elsif ($e->{operation} =~ m/file_/) { - add_to_tree( $e->{pid}, - $e->{parent}, - "path", - $profile, - $hat, - $prog, - $sdmode, - $e->{denied_mask}, - $e->{name}, - "", - ); - } elsif ($e->{operation} eq "open" || - $e->{operation} eq "truncate" || - $e->{operation} eq "mkdir" || - $e->{operation} eq "mknod" || - $e->{operation} eq "rename_src" || - $e->{operation} eq "rename_dest" || - $e->{operation} =~ m/^(unlink|rmdir|symlink_create|link)$/) { - add_to_tree( $e->{pid}, - $e->{parent}, - "path", - $profile, - $hat, - $prog, - $sdmode, - $e->{denied_mask}, - $e->{name}, - "", - ); - } elsif ($e->{operation} eq "capable") { - add_to_tree( $e->{pid}, - $e->{parent}, - "capability", - $profile, - $hat, - $prog, - $sdmode, - $e->{name} - ); - } elsif ($e->{operation} =~ m/xattr/ || - $e->{operation} eq "setattr") { - add_to_tree( $e->{pid}, - $e->{parent}, - "path", - $profile, - $hat, - $prog, - $sdmode, - $e->{denied_mask}, - $e->{name}, - "" - ); - } elsif ($e->{operation} =~ m/inode_/) { - my $is_domain_change = 0; - - if ($e->{operation} eq "inode_permission" && - $e->{denied_mask} & $AA_MAY_EXEC && - $sdmode eq "PERMITTING") { - - my $following = peek_at_next_log_entry(); - if ($following) { - my $entry = parse_log_record($following); - if ($entry && - $entry->{info} && - $entry->{info} eq "set profile" ) { - - $is_domain_change = 1; - throw_away_next_log_entry(); - } - } - } - - if ($is_domain_change) { - add_to_tree( $e->{pid}, - $e->{parent}, - "exec", - $profile, - $hat, - $prog, - $sdmode, - $e->{denied_mask}, - $e->{name}, - $e->{name2} - ); - } else { - add_to_tree( $e->{pid}, - $e->{parent}, - "path", - $profile, - $hat, - $prog, - $sdmode, - $e->{denied_mask}, - $e->{name}, - "" - ); - } - } elsif ($e->{operation} eq "sysctl") { - add_to_tree( $e->{pid}, - $e->{parent}, - "path", - $profile, - $hat, - $prog, - $sdmode, - $e->{denied_mask}, - $e->{name}, - "" - ); - } elsif ($e->{operation} eq "clone") { - my ($parent, $child) = ($e->{pid}, $e->{task}); - $profile ||= "null-complain-profile"; - $hat ||= "null-complain-profile"; - my $arrayref = []; - if (exists $pid{$parent}) { - push @{ $pid{$parent} }, $arrayref; - } else { - push @log, $arrayref; - } - $pid{$child} = $arrayref; - push @{$arrayref}, [ "fork", $child, $profile, $hat ]; - } elsif (optype($e->{operation}) eq "net") { - add_to_tree( $e->{pid}, - $e->{parent}, - "netdomain", - $profile, - $hat, - $prog, - $sdmode, - $e->{family}, - $e->{sock_type}, - $e->{protocol}, - ); - } elsif ($e->{operation} eq "change_hat") { - add_to_tree($e->{pid}, $e->{parent}, "unknown_hat", $profile, $hat, $sdmode, $hat); - } else { - if ( $DEBUGGING ) { - my $msg = Data::Dumper->Dump([$e], [qw(*event)]); - debug "UNHANDLED: $msg"; - } - } -} - -sub read_log($) { - $logmark = shift; - $seenmark = $logmark ? 0 : 1; - my $last; - my $event_type; - - # okay, done loading the previous profiles, get on to the good stuff... - open($LOG, $filename) - or fatal_error "Can't read AppArmor logfile $filename: $!"; - while ($_ = get_next_log_entry()) { - chomp; - - $DEBUGGING && debug "read_log: $_"; - - $seenmark = 1 if /$logmark/; - - $DEBUGGING && debug "read_log: seenmark = $seenmark"; - next unless $seenmark; - - my $last_match = ""; # v_2_0 syslog record parsing requires - # the previous aa record in the mandatory profile - # case - # all we care about is apparmor messages - if (/$RE_LOG_v2_0_syslog/ || /$RE_LOG_v2_0_audit/) { - $last_match = parse_log_record_v_2_0( $_, $last_match ); - } else { - my $event = parse_log_record($_); - add_event_to_tree($event) if ( $event ); - } - } - close($LOG); - $logmark = ""; -} - - -sub UI_SelectUpdatedRepoProfile ($$) { - - my ($profile, $p) = @_; - my $distro = $cfg->{repository}{distro}; - my $url = $sd{$profile}{$profile}{repo}{url}; - my $user = $sd{$profile}{$profile}{repo}{user}; - my $id = $sd{$profile}{$profile}{repo}{id}; - my $updated = 0; - - if ($p) { - my $q = { }; - $q->{headers} = [ - "Profile", $profile, - "User", $user, - "Old Revision", $id, - "New Revision", $p->{id}, - ]; - $q->{explanation} = - gettext( "An updated version of this profile has been found in the profile repository. Would you like to use it?"); - $q->{functions} = [ - "CMD_VIEW_CHANGES", "CMD_UPDATE_PROFILE", "CMD_IGNORE_UPDATE", - "CMD_ABORT", "CMD_FINISHED" - ]; - - my $ans; - do { - $ans = UI_PromptUser($q); - - if ($ans eq "CMD_VIEW_CHANGES") { - my $oldprofile = serialize_profile($sd{$profile}, $profile); - my $newprofile = $p->{profile}; - display_changes($oldprofile, $newprofile); - } - } until $ans =~ /^CMD_(UPDATE_PROFILE|IGNORE_UPDATE)/; - - if ($ans eq "CMD_UPDATE_PROFILE") { - eval { - my $profile_data = - parse_profile_data($p->{profile}, getprofilefilename($profile), 0); - if ($profile_data) { - attach_profile_data(\%sd, $profile_data); - $changed{$profile} = 1; - } - - set_repo_info($sd{$profile}{$profile}, $url, $user, $p->{id}); - - UI_Info( - sprintf( - gettext("Updated profile %s to revision %s."), - $profile, $p->{id} - ) - ); - }; - - if ($@) { - UI_Info(gettext("Error parsing repository profile.")); - } else { - $updated = 1; - } - } - } - return $updated; -} - -sub UI_repo_signup() { - - my ($url, $res, $save_config, $newuser, $user, $pass, $email, $signup_okay); - $url = $cfg->{repository}{url}; - do { - if ($UI_Mode eq "yast") { - SendDataToYast( - { - type => "dialog-repo-sign-in", - repo_url => $url - } - ); - my ($ypath, $yarg) = GetDataFromYast(); - $email = $yarg->{email}; - $user = $yarg->{user}; - $pass = $yarg->{pass}; - $newuser = $yarg->{newuser}; - $save_config = $yarg->{save_config}; - if ($yarg->{cancelled} && $yarg->{cancelled} eq "y") { - return; - } - $DEBUGGING && debug("AppArmor Repository: \n\t " . - ($newuser eq "1") ? - "New User\n\temail: [" . $email . "]" : - "Signin" . "\n\t user[" . $user . "]" . - "password [" . $pass . "]\n"); - } else { - $newuser = UI_YesNo(gettext("Create New User?"), "n"); - $user = UI_GetString(gettext("Username: "), $user); - $pass = UI_GetString(gettext("Password: "), $pass); - $email = UI_GetString(gettext("Email Addr: "), $email) - if ($newuser eq "y"); - $save_config = UI_YesNo(gettext("Save Configuration? "), "y"); - } - - if ($newuser eq "y") { - my ($status_ok,$res) = user_register($url, $user, $pass, $email); - if ($status_ok) { - $signup_okay = 1; - } else { - my $errmsg = - gettext("The Profile Repository server returned the following error:") . - "\n" . $res?$res:gettext("UNKOWN ERROR") . "\n" . - gettext("Please re-enter registration information or contact the administrator."); - UI_Important(gettext("Login Error\n") . $errmsg); - } - } else { - my ($status_ok,$res) = user_login($url, $user, $pass); - if ($status_ok) { - $signup_okay = 1; - } else { - my $errmsg = gettext("Login failure\n Please check username and password and try again.") . "\n" . $res; - UI_Important($errmsg); - } - } - } until $signup_okay; - - $repo_cfg->{repository}{user} = $user; - $repo_cfg->{repository}{pass} = $pass; - $repo_cfg->{repository}{email} = $email; - - write_config("repository.conf", $repo_cfg) if ( $save_config eq "y" ); - - return ($user, $pass); -} - -sub UI_ask_to_enable_repo() { - - my $q = { }; - return if ( not defined $cfg->{repository}{url} ); - $q->{headers} = [ - gettext("Repository"), $cfg->{repository}{url}, - ]; - $q->{explanation} = gettext( "Would you like to enable access to the -profile repository?" ); $q->{functions} = [ "CMD_ENABLE_REPO", -"CMD_DISABLE_REPO", "CMD_ASK_LATER", ]; - - my $cmd; - do { - $cmd = UI_PromptUser($q); - } until $cmd =~ /^CMD_(ENABLE_REPO|DISABLE_REPO|ASK_LATER)/; - - if ($cmd eq "CMD_ENABLE_REPO") { - $repo_cfg->{repository}{enabled} = "yes"; - } elsif ($cmd eq "CMD_DISABLE_REPO") { - $repo_cfg->{repository}{enabled} = "no"; - } elsif ($cmd eq "CMD_ASK_LATER") { - $repo_cfg->{repository}{enabled} = "later"; - } - - eval { write_config("repository.conf", $repo_cfg) }; - if ($@) { - fatal_error($@); - } -} - - -sub UI_ask_to_upload_profiles() { - - my $q = { }; - $q->{headers} = [ - gettext("Repository"), $cfg->{repository}{url}, - ]; - $q->{explanation} = - gettext( "Would you like to upload newly created and changed profiles to - the profile repository?" ); - $q->{functions} = [ - "CMD_YES", "CMD_NO", "CMD_ASK_LATER", - ]; - - my $cmd; - do { - $cmd = UI_PromptUser($q); - } until $cmd =~ /^CMD_(YES|NO|ASK_LATER)/; - - if ($cmd eq "CMD_NO") { - $repo_cfg->{repository}{upload} = "no"; - } elsif ($cmd eq "CMD_YES") { - $repo_cfg->{repository}{upload} = "yes"; - } elsif ($cmd eq "CMD_ASK_LATER") { - $repo_cfg->{repository}{upload} = "later"; - } - - eval { write_config("repository.conf", $repo_cfg) }; - if ($@) { - fatal_error($@); - } -} - - -sub parse_repo_profile($$$) { - my ($fqdbin, $repo_url, $profile) = @_; - - my $profile_data = eval { - parse_profile_data($profile->{profile}, getprofilefilename($fqdbin), 0); - }; - if ($@) { - print STDERR "PARSING ERROR: $@\n"; - $profile_data = undef; - } - - if ($profile_data) { - set_repo_info($profile_data->{$fqdbin}{$fqdbin}, $repo_url, - $profile->{username}, $profile->{id}); - } - - return $profile_data; -} - - -sub set_repo_info($$$$) { - my ($profile_data, $repo_url, $username, $id) = @_; - - # save repository metadata - $profile_data->{repo}{url} = $repo_url; - $profile_data->{repo}{user} = $username; - $profile_data->{repo}{id} = $id; -} - - -sub is_repo_profile($) { - my $profile_data = shift; - - return $profile_data->{repo}{url} && - $profile_data->{repo}{user} && - $profile_data->{repo}{id}; -} - - -sub get_repo_user_pass() { - my ($user, $pass); - - if ($repo_cfg) { - $user = $repo_cfg->{repository}{user}; - $pass = $repo_cfg->{repository}{pass}; - } - - unless ($user && $pass) { - ($user, $pass) = UI_repo_signup(); - } - - return ($user, $pass); -} - - -sub get_preferred_user ($) { - my $repo_url = shift; - return $cfg->{repository}{preferred_user} || "NOVELL"; -} - - -sub repo_is_enabled () { - my $enabled; - if ($cfg->{repository}{url} && - $repo_cfg && - $repo_cfg->{repository}{enabled} && - $repo_cfg->{repository}{enabled} eq "yes") { - $enabled = 1; - } - return $enabled; -} - - -sub update_repo_profile($) { - my $profile = shift; - - return undef if ( not is_repo_profile($profile) ); - my $distro = $cfg->{repository}{distro}; - my $url = $profile->{repo}{url}; - my $user = $profile->{repo}{user}; - my $id = $profile->{repo}{id}; - - UI_BusyStart( gettext("Connecting to repository.....") ); - my ($status_ok,$res) = fetch_newer_profile( $url, - $distro, - $user, - $id, - $profile->{name} - ); - UI_BusyStop(); - if ( ! $status_ok ) { - my $errmsg = - sprintf( - gettext("WARNING: Profile update check failed\nError Detail:\n%s"), - defined $res?$res:gettext("UNKNOWN ERROR")); - UI_Important($errmsg); - $res = undef; - } - return( $res ); -} - -sub UI_ask_mode_toggles ($$$) { - my ($audit_toggle, $owner_toggle, $oldmode) = @_; - my $q = { }; - $q->{headers} = [ ]; -# "Repository", $cfg->{repository}{url}, -# ]; - $q->{explanation} = gettext( "Change mode modifiers"); - - if ($audit_toggle) { - $q->{functions} = [ "CMD_AUDIT_OFF" ]; - } else { - $q->{functions} = [ "CMD_AUDIT_NEW" ]; - push @{$q->{functions}}, "CMD_AUDIT_FULL" if ($oldmode); - } - - if ($owner_toggle) { - push @{$q->{functions}}, "CMD_USER_OFF"; - } else { - push @{$q->{functions}}, "CMD_USER_ON"; - } - push @{$q->{functions}}, "CMD_CONTINUE"; - - my $cmd; - do { - $cmd = UI_PromptUser($q); - } until $cmd =~ /^CMD_(AUDIT_OFF|AUDIT_NEW|AUDIT_FULL|USER_ON|USER_OFF|CONTINUE)/; - - if ($cmd eq "CMD_AUDIT_OFF") { - $audit_toggle = 0; - } elsif ($cmd eq "CMD_AUDIT_NEW") { - $audit_toggle = 1; - } elsif ($cmd eq "CMD_AUDIT_FULL") { - $audit_toggle = 2; - } elsif ($cmd eq "CMD_USER_ON") { - $owner_toggle = 1; - } elsif ($cmd eq "CMD_USER_OFF") { - $owner_toggle = 0; -# $owner_toggle++; -# $owner_toggle++ if (!$oldmode && $owner_toggle == 2); -# $owner_toggle = 0 if ($owner_toggle > 3); - } - return ($audit_toggle, $owner_toggle); -} - -sub ask_the_questions() { - my $found; # do the magic foo-foo - for my $sdmode (sort keys %log) { - - # let them know what sort of changes we're about to list... - if ($sdmode eq "PERMITTING") { - UI_Info(gettext("Complain-mode changes:")); - } elsif ($sdmode eq "REJECTING") { - UI_Info(gettext("Enforce-mode changes:")); - } else { - - # if we're not permitting and not rejecting, something's broken. - # most likely the code we're using to build the hash tree of log - # entries - this should never ever happen - fatal_error(sprintf(gettext('Invalid mode found: %s'), $sdmode)); - } - - for my $profile (sort keys %{ $log{$sdmode} }) { - my $p = update_repo_profile($sd{$profile}{$profile}); - UI_SelectUpdatedRepoProfile($profile, $p) if ( $p ); - - $found++; - - # this sorts the list of hats, but makes sure that the containing - # profile shows up in the list first to keep the question order - # rational - my @hats = - grep { $_ ne $profile } keys %{ $log{$sdmode}{$profile} }; - unshift @hats, $profile - if defined $log{$sdmode}{$profile}{$profile}; - - for my $hat (@hats) { - - # step through all the capabilities first... - for my $capability (sort keys %{ $log{$sdmode}{$profile}{$hat}{capability} }) { - - # we don't care about it if we've already added it to the - # profile - next if profile_known_capability($sd{$profile}{$hat}, - $capability); - - my $severity = $sevdb->rank(uc("cap_$capability")); - - my $defaultoption = 1; - my @options = (); - my @newincludes; - @newincludes = matchcapincludes($sd{$profile}{$hat}, - $capability); - - - my $q = {}; - - if (@newincludes) { - push @options, - map { "#include <$_>" } sort(uniq(@newincludes)); - } - - if ( @options ) { - push @options, "capability $capability"; - $q->{options} = [@options]; - $q->{selected} = $defaultoption - 1; - } - - $q->{headers} = []; - push @{ $q->{headers} }, gettext("Profile"), combine_name($profile, $hat); - push @{ $q->{headers} }, gettext("Capability"), $capability; - push @{ $q->{headers} }, gettext("Severity"), $severity; - - my $audit_toggle = 0; - $q->{functions} = [ - "CMD_ALLOW", "CMD_DENY", "CMD_AUDIT_NEW", "CMD_ABORT", "CMD_FINISHED" - ]; - - # complain-mode events default to allow - enforce defaults - # to deny - $q->{default} = ($sdmode eq "PERMITTING") ? "CMD_ALLOW" : "CMD_DENY"; - - $seenevents++; - my $done = 0; - while ( not $done ) { - # what did the grand exalted master tell us to do? - my ($ans, $selected) = UI_PromptUser($q); - - if ($ans =~ /^CMD_AUDIT/) { - $audit_toggle = !$audit_toggle; - my $audit = ""; - if ($audit_toggle) { - $q->{functions} = [ - "CMD_ALLOW", "CMD_DENY", "CMD_AUDIT_OFF", "CMD_ABORT", "CMD_FINISHED" - ]; - $audit = "audit "; - } else { - $q->{functions} = [ - "CMD_ALLOW", "CMD_DENY", "CMD_AUDIT_NEW", "CMD_ABORT", "CMD_FINISHED" - ]; - } - $q->{headers} = []; - push @{ $q->{headers} }, gettext("Profile"), combine_name($profile, $hat); - push @{ $q->{headers} }, gettext("Capability"), $audit . $capability; - push @{ $q->{headers} }, gettext("Severity"), $severity; - - } if ($ans eq "CMD_ALLOW") { - - # they picked (a)llow, so... - - my $selection = $options[$selected]; - $done = 1; - if ($selection && - $selection =~ m/^#include <(.+)>$/) { - my $deleted = 0; - my $inc = $1; - $deleted = delete_duplicates($sd{$profile}{$hat}, - $inc - ); - $sd{$profile}{$hat}{include}{$inc} = 1; - - $changed{$profile} = 1; - UI_Info(sprintf( - gettext('Adding #include <%s> to profile.'), - $inc)); - UI_Info(sprintf( - gettext('Deleted %s previous matching profile entries.'), - $deleted)) if $deleted; - } - # stick the capability into the profile - $sd{$profile}{$hat}{allow}{capability}{$capability}{set} = 1; - $sd{$profile}{$hat}{allow}{capability}{$capability}{audit} = $audit_toggle; - - # mark this profile as changed - $changed{$profile} = 1; - $done = 1; - # give a little feedback to the user - UI_Info(sprintf(gettext('Adding capability %s to profile.'), $capability)); - } elsif ($ans eq "CMD_DENY") { - $sd{$profile}{$hat}{deny}{capability}{$capability}{set} = 1; - # mark this profile as changed - $changed{$profile} = 1; - UI_Info(sprintf(gettext('Denying capability %s to profile.'), $capability)); - $done = 1; - } else { - redo; - } - } - } - - # and then step through all of the path entries... - for my $path (sort keys %{ $log{$sdmode}{$profile}{$hat}{path} }) { - - my $mode = $log{$sdmode}{$profile}{$hat}{path}{$path}; - - # do original profile lookup once. - - my $allow_mode = 0; - my $allow_audit = 0; - my $deny_mode = 0; - my $deny_audit = 0; - - my ($fmode, $famode, $imode, $iamode, @fm, @im, $cm, $am, $cam, @m); - ($fmode, $famode, @fm) = rematchfrag($sd{$profile}{$hat}, 'allow', $path); - $allow_mode |= $fmode if $fmode; - $allow_audit |= $famode if $famode; - ($imode, $iamode, @im) = match_prof_incs_to_path($sd{$profile}{$hat}, 'allow', $path); - $allow_mode |= $imode if $imode; - $allow_audit |= $iamode if $iamode; - - ($cm, $cam, @m) = rematchfrag($sd{$profile}{$hat}, 'deny', $path); - $deny_mode |= $cm if $cm; - $deny_audit |= $cam if $cam; - ($cm, $cam, @m) = match_prof_incs_to_path($sd{$profile}{$hat}, 'deny', $path); - $deny_mode |= $cm if $cm; - $deny_audit |= $cam if $cam; - - if ($deny_mode & $AA_MAY_EXEC) { - $deny_mode |= $ALL_AA_EXEC_TYPE; - } - - # mask off the modes that have been denied - $mode &= ~$deny_mode; - $allow_mode &= ~$deny_mode; - - # if we had an access(X_OK) request or some other kind of - # event that generates a "PERMITTING x" syslog entry, - # first check if it was already dealt with by a i/p/x - # question due to a exec(). if not, ask about adding ix - # permission. - if ($mode & $AA_MAY_EXEC) { - - # get rid of the access() markers. - $mode &= (~$ALL_AA_EXEC_TYPE); - - unless ($allow_mode & $allow_mode & $AA_MAY_EXEC) { - $mode |= str_to_mode("ix"); - } - } - - # if we had an mmap(PROT_EXEC) request, first check if we - # already have added an ix rule to the profile - if ($mode & $AA_EXEC_MMAP) { - # ix implies m. don't ask if they want to add an "m" - # rule when we already have a matching ix rule. - if ($allow_mode && contains($allow_mode, "ix")) { - $mode &= (~$AA_EXEC_MMAP); - } - } - - next unless $mode; - - - my @matches; - - if ($fmode) { - push @matches, @fm; - } - if ($imode) { - push @matches, @im; - } - - unless ($allow_mode && mode_contains($allow_mode, $mode)) { - - my $defaultoption = 1; - my @options = (); - - # check the path against the available set of include - # files - my @newincludes; - my $includevalid; - for my $incname (keys %include) { - $includevalid = 0; - - # don't suggest it if we're already including it, - # that's dumb - next if $sd{$profile}{$hat}{$incname}; - - # only match includes that can be suggested to - # the user - if ($cfg->{settings}{custom_includes}) { - for my $incm (split(/\s+/, - $cfg->{settings}{custom_includes}) - ) { - $includevalid = 1 if $incname =~ /$incm/; - } - } - $includevalid = 1 if $incname =~ /abstractions/; - next if ($includevalid == 0); - - ($cm, $am, @m) = match_include_to_path($incname, 'allow', $path); - if ($cm && mode_contains($cm, $mode)) { - #make sure it doesn't deny $mode - my $dm = match_include_to_path($incname, 'deny', $path); - unless (($mode & $dm) || (grep { $_ eq "/**" } @m)) { - push @newincludes, $incname; - } - } - } - - - # did any match? add them to the option list... - if (@newincludes) { - push @options, - map { "#include <$_>" } - sort(uniq(@newincludes)); - } - - # include the literal path in the option list... - push @options, $path; - - # match the current path against the globbing list in - # logprof.conf - my @globs = globcommon($path); - if (@globs) { - push @matches, @globs; - } - - # suggest any matching globs the user manually entered - for my $userglob (@userglobs) { - push @matches, $userglob - if matchliteral($userglob, $path); - } - - # we'll take the cheesy way and order the suggested - # globbing list by length, which is usually right, - # but not always always - push @options, - sort { length($b) <=> length($a) } - grep { $_ ne $path } - uniq(@matches); - $defaultoption = $#options + 1; - - my $severity = $sevdb->rank($path, mode_to_str($mode)); - - my $audit_toggle = 0; - my $owner_toggle = $cfg->{settings}{default_owner_prompt}; - my $done = 0; - while (not $done) { - - my $q = {}; - $q->{headers} = []; - push @{ $q->{headers} }, gettext("Profile"), combine_name($profile, $hat); - push @{ $q->{headers} }, gettext("Path"), $path; - - # merge in any previous modes from this run - if ($allow_mode) { - my $str; -#print "mode: " . print_mode($mode) . " allow: " . print_mode($allow_mode) . "\n"; - $mode |= $allow_mode; - my $tail; - my $prompt_mode; - if ($owner_toggle == 0) { - $prompt_mode = flatten_mode($mode); - $tail = " " . gettext("(owner permissions off)"); - } elsif ($owner_toggle == 1) { - $prompt_mode = $mode; - $tail = ""; - } elsif ($owner_toggle == 2) { - $prompt_mode = $allow_mode | owner_flatten_mode($mode & ~$allow_mode); - $tail = " " . gettext("(force new perms to owner)"); - } else { - $prompt_mode = owner_flatten_mode($mode); - $tail = " " . gettext("(force all rule perms to owner)"); - } - - if ($audit_toggle == 1) { - $str = mode_to_str_user($allow_mode); - $str .= ", " if ($allow_mode); - $str .= "audit " . mode_to_str_user($prompt_mode & ~$allow_mode) . $tail; - } elsif ($audit_toggle == 2) { - $str = "audit " . mode_to_str_user($prompt_mode) . $tail; - } else { - $str = mode_to_str_user($prompt_mode) . $tail; - } - push @{ $q->{headers} }, gettext("Old Mode"), mode_to_str_user($allow_mode); - push @{ $q->{headers} }, gettext("New Mode"), $str; - } else { - my $str = ""; - if ($audit_toggle) { - $str = "audit "; - } - my $tail; - my $prompt_mode; - if ($owner_toggle == 0) { - $prompt_mode = flatten_mode($mode); - $tail = " " . gettext("(owner permissions off)"); - } elsif ($owner_toggle == 1) { - $prompt_mode = $mode; - $tail = ""; - } else { - $prompt_mode = owner_flatten_mode($mode); - $tail = " " . gettext("(force perms to owner)"); - } - $str .= mode_to_str_user($prompt_mode) . $tail; - push @{ $q->{headers} }, gettext("Mode"), $str; - } - push @{ $q->{headers} }, gettext("Severity"), $severity; - - $q->{options} = [@options]; - $q->{selected} = $defaultoption - 1; - - $q->{functions} = [ - "CMD_ALLOW", "CMD_DENY", "CMD_GLOB", "CMD_GLOBEXT", "CMD_NEW", - "CMD_ABORT", "CMD_FINISHED", "CMD_OTHER" - ]; - - $q->{default} = - ($sdmode eq "PERMITTING") - ? "CMD_ALLOW" - : "CMD_DENY"; - - $seenevents++; - # if they just hit return, use the default answer - my ($ans, $selected) = UI_PromptUser($q); - - if ($ans eq "CMD_OTHER") { - - ($audit_toggle, $owner_toggle) = UI_ask_mode_toggles($audit_toggle, $owner_toggle, $allow_mode); - } elsif ($ans eq "CMD_USER_TOGGLE") { - $owner_toggle++; - $owner_toggle++ if (!$allow_mode && $owner_toggle == 2); - $owner_toggle = 0 if ($owner_toggle > 3); - } elsif ($ans eq "CMD_ALLOW") { - $path = $options[$selected]; - $done = 1; - if ($path =~ m/^#include <(.+)>$/) { - my $inc = $1; - my $deleted = 0; - - $deleted = delete_duplicates($sd{$profile}{$hat}, - $inc ); - - # record the new entry - $sd{$profile}{$hat}{include}{$inc} = 1; - - $changed{$profile} = 1; - UI_Info(sprintf(gettext('Adding #include <%s> to profile.'), $inc)); - UI_Info(sprintf(gettext('Deleted %s previous matching profile entries.'), $deleted)) if $deleted; - } else { - if ($sd{$profile}{$hat}{allow}{path}{$path}{mode}) { - $mode |= $sd{$profile}{$hat}{allow}{path}{$path}{mode}; - } - - my $deleted = 0; - for my $entry (keys %{ $sd{$profile}{$hat}{allow}{path} }) { - - next if $path eq $entry; - - if (matchregexp($path, $entry)) { - - # regexp matches, add it's mode to - # the list to check against - if (mode_contains($mode, - $sd{$profile}{$hat}{allow}{path}{$entry}{mode})) { - delete $sd{$profile}{$hat}{allow}{path}{$entry}; - $deleted++; - } - } - } - - # record the new entry - if ($owner_toggle == 0) { - $mode = flatten_mode($mode); - } elsif ($owner_toggle == 1) { - $mode = $mode; - } elsif ($owner_toggle == 2) { - $mode = $allow_mode | owner_flatten_mode($mode & ~$allow_mode); - } elsif ($owner_toggle == 3) { - $mode = owner_flatten_mode($mode); - } - $sd{$profile}{$hat}{allow}{path}{$path}{mode} |= $mode; - my $tmpmode = ($audit_toggle == 1) ? $mode & ~$allow_mode : 0; - $tmpmode = ($audit_toggle == 2) ? $mode : $tmpmode; - $sd{$profile}{$hat}{allow}{path}{$path}{audit} |= $tmpmode; - - $changed{$profile} = 1; - UI_Info(sprintf(gettext('Adding %s %s to profile.'), $path, mode_to_str_user($mode))); - UI_Info(sprintf(gettext('Deleted %s previous matching profile entries.'), $deleted)) if $deleted; - } - } elsif ($ans eq "CMD_DENY") { - # record the new entry - $sd{$profile}{$hat}{deny}{path}{$path}{mode} |= $mode & ~$allow_mode; - $sd{$profile}{$hat}{deny}{path}{$path}{audit} |= 0; - - $changed{$profile} = 1; - - # go on to the next entry without saving this - # one - $done = 1; - } elsif ($ans eq "CMD_NEW") { - my $arg = $options[$selected]; - if ($arg !~ /^#include/) { - $ans = UI_GetString(gettext("Enter new path: "), $arg); - if ($ans) { - unless (matchliteral($ans, $path)) { - my $ynprompt = gettext("The specified path does not match this log entry:") . "\n\n"; - $ynprompt .= " " . gettext("Log Entry") . ": $path\n"; - $ynprompt .= " " . gettext("Entered Path") . ": $ans\n\n"; - $ynprompt .= gettext("Do you really want to use this path?") . "\n"; - - # we default to no if they just hit return... - my $key = UI_YesNo($ynprompt, "n"); - - next if $key eq "n"; - } - - # save this one for later - push @userglobs, $ans; - - push @options, $ans; - $defaultoption = $#options + 1; - } - } - } elsif ($ans eq "CMD_GLOB") { - - # do globbing if they don't have an include - # selected - my $newpath = $options[$selected]; - chomp $newpath ; - unless ($newpath =~ /^#include/) { - # is this entry directory specific - if ( $newpath =~ m/\/$/ ) { - # do we collapse to /* or /**? - if ($newpath =~ m/\/\*{1,2}\/$/) { - $newpath =~ - s/\/[^\/]+\/\*{1,2}\/$/\/\*\*\//; - } else { - $newpath =~ s/\/[^\/]+\/$/\/\*\//; - } - } else { - # do we collapse to /* or /**? - if ($newpath =~ m/\/\*{1,2}$/) { - $newpath =~ s/\/[^\/]+\/\*{1,2}$/\/\*\*/; - } else { - $newpath =~ s/\/[^\/]+$/\/\*/; - } - } - if (not grep { $newpath eq $_ } @options) { - push @options, $newpath; - $defaultoption = $#options + 1; - } - } - } elsif ($ans eq "CMD_GLOBEXT") { - - # do globbing if they don't have an include - # selected - my $newpath = $options[$selected]; - unless ($newpath =~ /^#include/) { - # do we collapse to /*.ext or /**.ext? - if ($newpath =~ m/\/\*{1,2}\.[^\/]+$/) { - $newpath =~ s/\/[^\/]+\/\*{1,2}(\.[^\/]+)$/\/\*\*$1/; - } else { - $newpath =~ s/\/[^\/]+(\.[^\/]+)$/\/\*$1/; - } - if (not grep { $newpath eq $_ } @options) { - push @options, $newpath; - $defaultoption = $#options + 1; - } - } - } elsif ($ans =~ /\d/) { - $defaultoption = $ans; - } - } - } - } - - # and then step through all of the netdomain entries... - for my $family (sort keys %{$log{$sdmode} - {$profile} - {$hat} - {netdomain}}) { - - # TODO - severity handling for net toggles - #my $severity = $sevdb->rank(); - for my $sock_type (sort keys %{$log{$sdmode} - {$profile} - {$hat} - {netdomain} - {$family}}) { - - # we don't care about it if we've already added it to the - # profile - next if ( profile_known_network($sd{$profile}{$hat}, - $family, - $sock_type)); - my $defaultoption = 1; - my @options = (); - my @newincludes; - @newincludes = matchnetincludes($sd{$profile}{$hat}, - $family, - $sock_type); - - my $q = {}; - - if (@newincludes) { - push @options, - map { "#include <$_>" } sort(uniq(@newincludes)); - } - - if ( @options ) { - push @options, "network $family $sock_type"; - $q->{options} = [@options]; - $q->{selected} = $defaultoption - 1; - } - - $q->{headers} = []; - push @{ $q->{headers} }, - gettext("Profile"), - combine_name($profile, $hat); - push @{ $q->{headers} }, - gettext("Network Family"), - $family; - push @{ $q->{headers} }, - gettext("Socket Type"), - $sock_type; - - my $audit_toggle = 0; - - $q->{functions} = [ - "CMD_ALLOW", - "CMD_DENY", - "CMD_AUDIT_NEW", - "CMD_ABORT", - "CMD_FINISHED" - ]; - - # complain-mode events default to allow - enforce defaults - # to deny - $q->{default} = ($sdmode eq "PERMITTING") ? "CMD_ALLOW" : - "CMD_DENY"; - - $seenevents++; - - # what did the grand exalted master tell us to do? - my $done = 0; - while ( not $done ) { - my ($ans, $selected) = UI_PromptUser($q); - if ($ans =~ /^CMD_AUDIT/) { - $audit_toggle = !$audit_toggle; - my $audit = $audit_toggle ? "audit " : ""; - if ($audit_toggle) { - $q->{functions} = [ - "CMD_ALLOW", - "CMD_DENY", - "CMD_AUDIT_OFF", - "CMD_ABORT", - "CMD_FINISHED" - ]; - } else { - $q->{functions} = [ - "CMD_ALLOW", - "CMD_DENY", - "CMD_AUDIT_NEW", - "CMD_ABORT", - "CMD_FINISHED" - ]; - } - $q->{headers} = []; - push @{ $q->{headers} }, - gettext("Profile"), - combine_name($profile, $hat); - push @{ $q->{headers} }, - gettext("Network Family"), - $audit . $family; - push @{ $q->{headers} }, - gettext("Socket Type"), - $sock_type; - } elsif ($ans eq "CMD_ALLOW") { - my $selection = $options[$selected]; - $done = 1; - if ($selection && - $selection =~ m/^#include <(.+)>$/) { - my $inc = $1; - my $deleted = 0; - $deleted = delete_duplicates($sd{$profile}{$hat}, - $inc - ); - # record the new entry - $sd{$profile}{$hat}{include}{$inc} = 1; - - $changed{$profile} = 1; - UI_Info( - sprintf( - gettext('Adding #include <%s> to profile.'), - $inc)); - UI_Info( - sprintf( - gettext('Deleted %s previous matching profile entries.'), - $deleted)) if $deleted; - } else { - - # stick the whole rule into the profile - $sd{$profile} - {$hat} - {allow} - {netdomain} - {audit} - {$family} - {$sock_type} = $audit_toggle; - - $sd{$profile} - {$hat} - {allow} - {netdomain} - {rule} - {$family} - {$sock_type} = 1; - - # mark this profile as changed - $changed{$profile} = 1; - - # give a little feedback to the user - UI_Info(sprintf( - gettext('Adding network access %s %s to profile.'), - $family, - $sock_type - ) - ); - } - } elsif ($ans eq "CMD_DENY") { - $done = 1; - # record the new entry - $sd{$profile} - {$hat} - {deny} - {netdomain} - {rule} - {$family} - {$sock_type} = 1; - - $changed{$profile} = 1; - UI_Info(sprintf( - gettext('Denying network access %s %s to profile.'), - $family, - $sock_type - ) - ); - } else { - redo; - } - } - } - } - } - } - } -} - -sub delete_net_duplicates($$) { - my ($netrules, $incnetrules) = @_; - my $deleted = 0; - if ( $incnetrules && $netrules ) { - my $incnetglob = defined $incnetrules->{all}; - - # See which if any profile rules are matched by the include and can be - # deleted - for my $fam ( keys %$netrules ) { - if ( $incnetglob || (ref($incnetrules->{rule}{$fam}) ne "HASH" && - $incnetrules->{rule}{$fam} == 1)) { # include allows - # all net or - # all fam - if ( ref($netrules->{rule}{$fam}) eq "HASH" ) { - $deleted += ( keys %{$netrules->{rule}{$fam}} ); - } else { - $deleted++; - } - delete $netrules->{rule}{$fam}; - } elsif ( ref($netrules->{rule}{$fam}) ne "HASH" && - $netrules->{rule}{$fam} == 1 ){ - next; # profile has all family - } else { - for my $socket_type ( keys %{$netrules->{rule}{$fam}} ) { - if ( defined $incnetrules->{$fam}{$socket_type} ) { - delete $netrules->{$fam}{$socket_type}; - $deleted++; - } - } - } - } - } - return $deleted; -} - -sub delete_cap_duplicates ($$) { - my ($profilecaps, $inccaps) = @_; - my $deleted = 0; - if ( $profilecaps && $inccaps ) { - for my $capname ( keys %$profilecaps ) { - if ( defined $inccaps->{$capname}{set} && $inccaps->{$capname}{set} == 1 ) { - delete $profilecaps->{$capname}; - $deleted++; - } - } - } - return $deleted; -} - -sub delete_path_duplicates ($$$) { - my ($profile, $incname, $allow) = @_; - my $deleted = 0; - - for my $entry (keys %{ $profile->{$allow}{path} }) { - next if $entry eq "#include <$incname>"; - my ($cm, $am, @m) = match_include_to_path($incname, $allow, $entry); - if ($cm - && mode_contains($cm, $profile->{$allow}{path}{$entry}{mode}) - && mode_contains($am, $profile->{$allow}{path}{$entry}{audit})) - { - delete $profile->{$allow}{path}{$entry}; - $deleted++; - } - } - return $deleted; -} - -sub delete_duplicates (\%$) { - my ( $profile, $incname ) = @_; - my $deleted = 0; - - # don't cross delete allow rules covered by denied rules as the coverage - # may not be complete. ie. want to deny a subset of allow, allow a subset - # of deny with different perms. - - ## network rules - $deleted += delete_net_duplicates($profile->{allow}{netdomain}, $include{$incname}{$incname}{allow}{netdomain}); - $deleted += delete_net_duplicates($profile->{deny}{netdomain}, $include{$incname}{$incname}{deny}{netdomain}); - - ## capabilities - $deleted += delete_cap_duplicates($profile->{allow}{capability}, - $include{$incname}{$incname}{allow}{capability}); - $deleted += delete_cap_duplicates($profile->{deny}{capability}, - $include{$incname}{$incname}{deny}{capability}); - - ## paths - $deleted += delete_path_duplicates($profile, $incname, 'allow'); - $deleted += delete_path_duplicates($profile, $incname, 'deny'); - - return $deleted; -} - -sub matchnetinclude ($$$) { - my ($incname, $family, $type) = @_; - - my @matches; - - # scan the include fragments for this profile looking for matches - my @includelist = ($incname); - my @checked; - while (my $name = shift @includelist) { - push @checked, $name; - return 1 - if netrules_access_check($include{$name}{$name}{allow}{netdomain}, $family, $type); - # if this fragment includes others, check them too - if (keys %{ $include{$name}{$name}{include} } && - (grep($name, @checked) == 0) ) { - push @includelist, keys %{ $include{$name}{$name}{include} }; - } - } - return 0; -} - -sub matchcapincludes (\%$) { - my ($profile, $cap) = @_; - - # check the path against the available set of include - # files - my @newincludes; - my $includevalid; - for my $incname (keys %include) { - $includevalid = 0; - - # don't suggest it if we're already including it, - # that's dumb - next if $profile->{include}{$incname}; - - # only match includes that can be suggested to - # the user - if ($cfg->{settings}{custom_includes}) { - for my $incm (split(/\s+/, - $cfg->{settings}{custom_includes})) { - $includevalid = 1 if $incname =~ /$incm/; - } - } - $includevalid = 1 if $incname =~ /abstractions/; - next if ($includevalid == 0); - - push @newincludes, $incname - if ( defined $include{$incname}{$incname}{allow}{capability}{$cap}{set} && - $include{$incname}{$incname}{allow}{capability}{$cap}{set} == 1 ); - } - return @newincludes; -} - -sub matchnetincludes (\%$$) { - my ($profile, $family, $type) = @_; - - # check the path against the available set of include - # files - my @newincludes; - my $includevalid; - for my $incname (keys %include) { - $includevalid = 0; - - # don't suggest it if we're already including it, - # that's dumb - next if $profile->{include}{$incname}; - - # only match includes that can be suggested to - # the user - if ($cfg->{settings}{custom_includes}) { - for my $incm (split(/\s+/, $cfg->{settings}{custom_includes})) { - $includevalid = 1 if $incname =~ /$incm/; - } - } - $includevalid = 1 if $incname =~ /abstractions/; - next if ($includevalid == 0); - - push @newincludes, $incname - if matchnetinclude($incname, $family, $type); - } - return @newincludes; -} - - -sub do_logprof_pass($) { - my $logmark = shift || ""; - - # zero out the state variables for this pass... - %t = ( ); - %transitions = ( ); - %seen = ( ); - %sd = ( ); - %profilechanges = ( ); - %prelog = ( ); - @log = ( ); - %log = ( ); - %changed = ( ); - %skip = ( ); - %filelist = ( ); - - UI_Info(sprintf(gettext('Reading log entries from %s.'), $filename)); - UI_Info(sprintf(gettext('Updating AppArmor profiles in %s.'), $profiledir)); - - readprofiles(); - unless ($sevdb) { - $sevdb = new Immunix::Severity("$confdir/severity.db", gettext("unknown -")); - } - - # we need to be able to break all the way out of deep into subroutine calls - # if they select "Finish" so we can take them back out to the genprof prompt - eval { - unless ($repo_cfg || not defined $cfg->{repository}{url}) { - $repo_cfg = read_config("repository.conf"); - unless ($repo_cfg->{repository}{enabled} && - ($repo_cfg->{repository}{enabled} eq "yes" || - $repo_cfg->{repository}{enabled} eq "no")) { - UI_ask_to_enable_repo(); - } - } - - read_log($logmark); - - for my $root (@log) { - handlechildren(undef, undef, $root); - } - - for my $pid (sort { $a <=> $b } keys %profilechanges) { - setprocess($pid, $profilechanges{$pid}); - } - - collapselog(); - - ask_the_questions(); - - if ($UI_Mode eq "yast") { - if (not $running_under_genprof) { - if ($seenevents) { - my $w = { type => "wizard" }; - $w->{explanation} = gettext("The profile analyzer has completed processing the log files.\n\nAll updated profiles will be reloaded"); - $w->{functions} = [ "CMD_ABORT", "CMD_FINISHED" ]; - SendDataToYast($w); - my $foo = GetDataFromYast(); - } else { - my $w = { type => "wizard" }; - $w->{explanation} = gettext("No unhandled AppArmor events were found in the system log."); - $w->{functions} = [ "CMD_ABORT", "CMD_FINISHED" ]; - SendDataToYast($w); - my $foo = GetDataFromYast(); - } - } - } - }; - - my $finishing = 0; - if ($@) { - if ($@ =~ /FINISHING/) { - $finishing = 1; - } else { - die $@; - } - } - - save_profiles(); - - if (repo_is_enabled()) { - if ( (not defined $repo_cfg->{repository}{upload}) || - ($repo_cfg->{repository}{upload} eq "later") ) { - UI_ask_to_upload_profiles(); - } - if ($repo_cfg->{repository}{upload} eq "yes") { - sync_profiles(); - } - @created = (); - } - - # if they hit "Finish" we need to tell the caller that so we can exit - # all the way instead of just going back to the genprof prompt - return $finishing ? "FINISHED" : "NORMAL"; -} - -sub save_profiles() { - # make sure the profile changes we've made are saved to disk... - my @changed = sort keys %changed; - # - # first make sure that profiles in %changed are active (or actual profiles - # in %sd) - this is to handle the sloppiness of setting profiles as changed - # when they are parsed in the case of legacy hat code that we want to write - # out in an updated format - foreach my $profile_name ( keys %changed ) { - if ( ! is_active_profile( $profile_name ) ) { - delete $changed{ $profile_name }; - } - } - @changed = sort keys %changed; - - if (@changed) { - if ($UI_Mode eq "yast") { - my (@selected_profiles, $title, $explanation, %profile_changes); - foreach my $prof (@changed) { - my $oldprofile = serialize_profile($original_sd{$prof}, $prof); - my $newprofile = serialize_profile($sd{$prof}, $prof); - - $profile_changes{$prof} = get_profile_diff($oldprofile, - $newprofile); - } - $explanation = gettext("Select which profile changes you would like to save to the\nlocal profile set"); - $title = gettext("Local profile changes"); - SendDataToYast( - { - type => "dialog-select-profiles", - title => $title, - explanation => $explanation, - default_select => "true", - get_changelog => "false", - profiles => \%profile_changes - } - ); - my ($ypath, $yarg) = GetDataFromYast(); - if ($yarg->{STATUS} eq "cancel") { - return; - } else { - my $selected_profiles_ref = $yarg->{PROFILES}; - for my $profile (@$selected_profiles_ref) { - writeprofile_ui_feedback($profile); - reload_base($profile); - } - } - } else { - my $q = {}; - $q->{title} = "Changed Local Profiles"; - $q->{headers} = []; - - $q->{explanation} = - gettext( "The following local profiles were changed. Would you like to save them?"); - - $q->{functions} = [ "CMD_SAVE_CHANGES", - "CMD_VIEW_CHANGES", - "CMD_ABORT", ]; - - $q->{default} = "CMD_VIEW_CHANGES"; - - $q->{options} = [@changed]; - $q->{selected} = 0; - - my ($p, $ans, $arg); - do { - ($ans, $arg) = UI_PromptUser($q); - - if ($ans eq "CMD_VIEW_CHANGES") { - my $which = $changed[$arg]; - my $oldprofile = - serialize_profile($original_sd{$which}, $which); - my $newprofile = serialize_profile($sd{$which}, $which); - display_changes($oldprofile, $newprofile); - } - - } until $ans =~ /^CMD_SAVE_CHANGES/; - - for my $profile (sort keys %changed) { - writeprofile_ui_feedback($profile); - reload_base($profile); - } - } - } -} - - -sub get_pager() { - - if ( $ENV{PAGER} and (-x "/usr/bin/$ENV{PAGER}" || - -x "/usr/sbin/$ENV{PAGER}" ) - ) { - return $ENV{PAGER}; - } else { - return "less" - } -} - - -sub display_text($$) { - my ($header, $body) = @_; - my $pager = get_pager(); - if (open(PAGER, "| $pager")) { - print PAGER "$header\n\n$body"; - close(PAGER); - } -} - -sub get_profile_diff($$) { - my ($oldprofile, $newprofile) = @_; - my $oldtmp = new File::Temp(UNLINK => 0); - print $oldtmp $oldprofile; - close($oldtmp); - - my $newtmp = new File::Temp(UNLINK => 0); - print $newtmp $newprofile; - close($newtmp); - - my $difftmp = new File::Temp(UNLINK => 0); - my @diff; - system("diff -u $oldtmp $newtmp > $difftmp"); - while (<$difftmp>) { - push(@diff, $_) unless (($_ =~ /^(---|\+\+\+)/) || - ($_ =~ /^\@\@.*\@\@$/)); - } - unlink($difftmp); - unlink($oldtmp); - unlink($newtmp); - return join("", @diff); -} - -sub display_changes($$) { - my ($oldprofile, $newprofile) = @_; - - my $oldtmp = new File::Temp( UNLINK => 0 ); - print $oldtmp $oldprofile; - close($oldtmp); - - my $newtmp = new File::Temp( UNLINK => 0 ); - print $newtmp $newprofile; - close($newtmp); - - my $difftmp = new File::Temp(UNLINK => 0); - my @diff; - system("diff -u $oldtmp $newtmp > $difftmp"); - if ($UI_Mode eq "yast") { - while (<$difftmp>) { - push(@diff, $_) unless (($_ =~ /^(---|\+\+\+)/) || - ($_ =~ /^\@\@.*\@\@$/)); - } - UI_LongMessage(gettext("Profile Changes"), join("", @diff)); - } else { - system("less $difftmp"); - } - - unlink($difftmp); - unlink($oldtmp); - unlink($newtmp); -} - -sub setprocess ($$) { - my ($pid, $profile) = @_; - - # don't do anything if the process exited already... - return unless -e "/proc/$pid/attr/current"; - - return unless open(CURR, "/proc/$pid/attr/current"); - my $current = ; - return unless $current; - chomp $current; - close(CURR); - - # only change null profiles - return unless $current =~ /null(-complain)*-profile/; - - return unless open(STAT, "/proc/$pid/stat"); - my $stat = ; - chomp $stat; - close(STAT); - - return unless $stat =~ /^\d+ \((\S+)\) /; - my $currprog = $1; - - open(CURR, ">/proc/$pid/attr/current") or return; - print CURR "setprofile $profile"; - close(CURR); -} - -sub collapselog () { - for my $sdmode (keys %prelog) { - for my $profile (keys %{ $prelog{$sdmode} }) { - for my $hat (keys %{ $prelog{$sdmode}{$profile} }) { - for my $path (keys %{ $prelog{$sdmode}{$profile}{$hat}{path} }) { - - my $mode = $prelog{$sdmode}{$profile}{$hat}{path}{$path}; - - # we want to ignore anything from the log that's already - # in the profile - my $combinedmode = 0; - - # is it in the original profile? - if ($sd{$profile}{$hat}{allow}{path}{$path}) { - $combinedmode |= $sd{$profile}{$hat}{allow}{path}{$path}{mode}; - } - - # does path match any regexps in original profile? - $combinedmode |= rematchfrag($sd{$profile}{$hat}, 'allow', $path); - - # does path match anything pulled in by includes in - # original profile? - $combinedmode |= match_prof_incs_to_path($sd{$profile}{$hat}, 'allow', $path); - - # if we found any matching entries, do the modes match? - unless ($combinedmode && mode_contains($combinedmode, $mode)) { - - # merge in any previous modes from this run - if ($log{$sdmode}{$profile}{$hat}{$path}) { - $mode |= $log{$sdmode}{$profile}{$hat}{path}{$path}; - } - - # record the new entry - $log{$sdmode}{$profile}{$hat}{path}{$path} = $mode; - } - } - - for my $capability (keys %{ $prelog{$sdmode}{$profile}{$hat}{capability} }) { - - # if we don't already have this capability in the profile, - # add it - unless ($sd{$profile}{$hat}{allow}{capability}{$capability}{set}) { - $log{$sdmode}{$profile}{$hat}{capability}{$capability} = 1; - } - } - - # Network toggle handling - my $ndref = $prelog{$sdmode}{$profile}{$hat}{netdomain}; - for my $family ( keys %{$ndref} ) { - for my $sock_type ( keys %{$ndref->{$family}} ) { - unless ( profile_known_network($sd{$profile}{$hat}, - $family, $sock_type)) { - $log{$sdmode} - {$profile} - {$hat} - {netdomain} - {$family} - {$sock_type}=1; - } - } - } - } - } - } -} - -sub profilemode ($) { - my $mode = shift; - - my $modifier = ($mode =~ m/[iupUP]/)[0]; - if ($modifier) { - $mode =~ s/[iupUPx]//g; - $mode .= $modifier . "x"; - } - - return $mode; -} - -# kinky. -sub commonprefix (@) { (join("\0", @_) =~ m/^([^\0]*)[^\0]*(\0\1[^\0]*)*$/)[0] } -sub commonsuffix (@) { reverse(((reverse join("\0", @_)) =~ m/^([^\0]*)[^\0]*(\0\1[^\0]*)*$/)[0]); } - -sub uniq (@) { - my %seen; - my @result = sort grep { !$seen{$_}++ } @_; - return @result; -} - -our $MODE_MAP_RE = "r|w|l|m|k|a|x|i|u|p|c|n|I|U|P|C|N"; -our $LOG_MODE_RE = "r|w|l|m|k|a|x|ix|ux|px|cx|nx|pix|cix|Ix|Ux|Px|PUx|Cx|Nx|Pix|Cix"; -our $PROFILE_MODE_RE = "r|w|l|m|k|a|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix"; -our $PROFILE_MODE_NT_RE = "r|w|l|m|k|a|x|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix"; -our $PROFILE_MODE_DENY_RE = "r|w|l|m|k|a|x"; - -sub split_log_mode($) { - my $mode = shift; - my $user = ""; - my $other = ""; - - if ($mode =~ /(.*?)::(.*)/) { - $user = $1 if ($1); - $other = $2 if ($2); - } else { - $user = $mode; - $other = $mode; - } - return ($user, $other); -} - -sub map_log_mode ($) { - my $mode = shift; - return $mode; -# $mode =~ s/(.*l.*)::.*/$1/ge; -# $mode =~ s/.*::(.*l.*)/$1/ge; -# $mode =~ s/:://; -# return $mode; -# return $1; -} - -sub hide_log_mode($) { - my $mode = shift; - - $mode =~ s/:://; - return $mode; -} - -sub validate_log_mode ($) { - my $mode = shift; - - return ($mode =~ /^($LOG_MODE_RE)+$/) ? 1 : 0; -} - -sub validate_profile_mode ($$$) { - my ($mode, $allow, $nt_name) = @_; - - if ($allow eq 'deny') { - return ($mode =~ /^($PROFILE_MODE_DENY_RE)+$/) ? 1 : 0; - } elsif ($nt_name) { - return ($mode =~ /^($PROFILE_MODE_NT_RE)+$/) ? 1 : 0; - } - - return ($mode =~ /^($PROFILE_MODE_RE)+$/) ? 1 : 0; -} - -# modes internally are stored as a bit Mask -sub sub_str_to_mode($) { - my $str = shift; - my $mode = 0; - - return 0 if (not $str); - - while ($str =~ s/(${MODE_MAP_RE})//) { - my $tmp = $1; -#print "found mode $1\n"; - - if ($tmp && $MODE_HASH{$tmp}) { - $mode |= $MODE_HASH{$tmp}; - } else { -#print "found mode $tmp\n"; - } - } - -#my $tmp = mode_to_str($mode); -#print "parsed_mode $mode\n"; - return $mode; -} - -sub print_mode ($) { - my $mode = shift; - - my ($user, $other) = split_mode($mode); - my $str = sub_mode_to_str($user) . "::" . sub_mode_to_str($other); - - return $str; -} - -sub str_to_mode ($) { - my $str = shift; - - return 0 if (not $str); - - my ($user, $other) = split_log_mode($str); - -#print "str: $str user: $user, other $other\n"; - # we only allow user or all - $user = $other if (!$user); - - my $mode = sub_str_to_mode($user); - $mode |= (sub_str_to_mode($other) << $AA_OTHER_SHIFT); - -#print "user: $user " .sub_str_to_mode($user) . " other: $other " . (sub_str_to_mode($other) << $AA_OTHER_SHIFT) . " mode = $mode\n"; - - return $mode; -} - -sub log_str_to_mode($$$) { - my ($profile, $str, $nt_name) = @_; - - my $mode = str_to_mode($str); - - # this will cover both nx and nix - if (contains($mode, "Nx")) { - # need to transform to px, cx - - if ($nt_name =~ /(.+?)\/\/(.+?)/) { - my ($lprofile, $lhat) = @_; - my $tmode = 0; - if ($profile eq $profile) { - if ($mode & ($AA_MAY_EXEC)) { - $tmode = str_to_mode("Cx::"); - } - if ($mode & ($AA_MAY_EXEC << $AA_OTHER_SHIFT)) { - $tmode |= str_to_mode("Cx"); - } - $nt_name = $lhat; - } else { - if ($mode & ($AA_MAY_EXEC)) { - $tmode = str_to_mode("Px::"); - } - if ($mode & ($AA_MAY_EXEC << $AA_OTHER_SHIFT)) { - $tmode |= str_to_mode("Px"); - } - $nt_name = $lhat; - } - $mode = ($mode & ~(str_to_mode("Nx"))); - $mode |= $tmode; - } - } - return ($mode, $nt_name); -} - -sub split_mode ($) { - my $mode = shift; - - my $user = $mode & $AA_USER_MASK; - my $other = ($mode >> $AA_OTHER_SHIFT) & $AA_USER_MASK; - - return ($user, $other); -} - -sub is_user_mode ($) { - my $mode = shift; - - my ($user, $other) = split_mode($mode); - - if ($user && !$other) { - return 1; - } - return 0; -} - -sub sub_mode_to_str($) { - my $mode = shift; - my $str = ""; - - # "w" implies "a" - $mode &= (~$AA_MAY_APPEND) if ($mode & $AA_MAY_WRITE); - $str .= "m" if ($mode & $AA_EXEC_MMAP); - $str .= "r" if ($mode & $AA_MAY_READ); - $str .= "w" if ($mode & $AA_MAY_WRITE); - $str .= "a" if ($mode & $AA_MAY_APPEND); - $str .= "l" if ($mode & $AA_MAY_LINK); - $str .= "k" if ($mode & $AA_MAY_LOCK); - - # modes P and C *must* come before I and U; otherwise syntactically - # invalid profiles result - if ($mode & ($AA_EXEC_PROFILE | $AA_EXEC_NT)) { - if ($mode & $AA_EXEC_UNSAFE) { - $str .= "p"; - } else { - $str .= "P"; - } - } - if ($mode & $AA_EXEC_CHILD) { - if ($mode & $AA_EXEC_UNSAFE) { - $str .= "c"; - } else { - $str .= "C"; - } - } - - # modes P and C *must* come before I and U; otherwise syntactically - # invalid profiles result - if ($mode & $AA_EXEC_UNCONFINED) { - if ($mode & $AA_EXEC_UNSAFE) { - $str .= "u"; - } else { - $str .= "U"; - } - } - $str .= "i" if ($mode & $AA_EXEC_INHERIT); - - $str .= "x" if ($mode & $AA_MAY_EXEC); - - return $str; -} - -sub flatten_mode ($) { - my $mode = shift; - - return 0 if (!$mode); - - $mode = ($mode & $AA_USER_MASK) | (($mode >> $AA_OTHER_SHIFT) & $AA_USER_MASK); - $mode |= ($mode << $AA_OTHER_SHIFT); -} - -sub mode_to_str ($) { - my $mode = shift; - $mode = flatten_mode($mode); - return sub_mode_to_str($mode); -} - -sub owner_flatten_mode($) { - my $mode = shift; - $mode = flatten_mode($mode) & $AA_USER_MASK; - return $mode; -} - -sub mode_to_str_user ($) { - my $mode = shift; - - my ($user, $other) = split_mode($mode); - - my $str = ""; - $user = 0 if (!$user); - $other = 0 if (!$other); - - if ($user & ~$other) { - # more user perms than other - $str = sub_mode_to_str($other). " + " if ($other); - $str .= "owner " . sub_mode_to_str($user & ~$other); - } elsif (is_user_mode($mode)) { - $str = "owner " . sub_mode_to_str($user); - } else { - $str = sub_mode_to_str(flatten_mode($mode)); - } - return $str; -} - -sub mode_contains ($$) { - my ($mode, $subset) = @_; - - # "w" implies "a" - if ($mode & $AA_MAY_WRITE) { - $mode |= $AA_MAY_APPEND; - } - if ($mode & ($AA_MAY_WRITE << $AA_OTHER_SHIFT)) { - $mode |= ($AA_MAY_APPEND << $AA_OTHER_SHIFT); - } - - # "?ix" implies "m" - if ($mode & $AA_EXEC_INHERIT) { - $mode |= $AA_EXEC_MMAP; - } - if ($mode & ($AA_EXEC_INHERIT << $AA_OTHER_SHIFT)) { - $mode |= ($AA_EXEC_MMAP << $AA_OTHER_SHIFT); - } - - return (($mode & $subset) == $subset); -} - -sub contains ($$) { - my ($mode, $str) = @_; - - return mode_contains($mode, str_to_mode($str)); -} - -# isSkippableFile - return true if filename matches something that -# should be skipped (rpm backup files, dotfiles, emacs backup files -# Annoyingly, this needs to be kept in sync with the skipped files -# in the apparmor initscript. -sub isSkippableFile($) { - my $path = shift; - - return ($path =~ /(^|\/)\.[^\/]*$/ - || $path =~ /\.rpm(save|new)$/ - || $path =~ /\.dpkg-(old|new)$/ - || $path =~ /\.swp$/ - || $path =~ /\~$/); -} - -# isSkippableDir - return true if directory matches something that -# should be skipped (cache directory, symlink directories, etc.) -sub isSkippableDir($) { - my $path = shift; - - return ($path eq "disable" - || $path eq "cache" - || $path eq "force-complain"); -} - -sub checkIncludeSyntax($) { - my $errors = shift; - - if (opendir(SDDIR, $profiledir)) { - my @incdirs = grep { (!/^\./) && (-d "$profiledir/$_") } readdir(SDDIR); - close(SDDIR); - while (my $id = shift @incdirs) { - next if isSkippableDir($id); - if (opendir(SDDIR, "$profiledir/$id")) { - for my $path (grep { !/^\./ } readdir(SDDIR)) { - chomp($path); - next if isSkippableFile($path); - if (-f "$profiledir/$id/$path") { - my $file = "$id/$path"; - $file =~ s/$profiledir\///; - eval { loadinclude($file); }; - if ( defined $@ && $@ ne "" ) { - push @$errors, $@; - } - } elsif (-d "$id/$path") { - push @incdirs, "$id/$path"; - } - } - closedir(SDDIR); - } - } - } - return $errors; -} - -sub checkProfileSyntax ($) { - my $errors = shift; - - # Check the syntax of profiles - - opendir(SDDIR, $profiledir) - or fatal_error "Can't read AppArmor profiles in $profiledir."; - for my $file (grep { -f "$profiledir/$_" } readdir(SDDIR)) { - next if isSkippableFile($file); - my $err = readprofile("$profiledir/$file", \&printMessageErrorHandler, 1); - if (defined $err and $err ne "") { - push @$errors, $err; - } - } - closedir(SDDIR); - return $errors; -} - -sub printMessageErrorHandler ($) { - my $message = shift; - return $message; -} - -sub readprofiles () { - opendir(SDDIR, $profiledir) - or fatal_error "Can't read AppArmor profiles in $profiledir."; - for my $file (grep { -f "$profiledir/$_" } readdir(SDDIR)) { - next if isSkippableFile($file); - readprofile("$profiledir/$file", \&fatal_error, 1); - } - closedir(SDDIR); -} - -sub readinactiveprofiles () { - return if ( ! -e $extraprofiledir ); - opendir(ESDDIR, $extraprofiledir) or - fatal_error "Can't read AppArmor profiles in $extraprofiledir."; - for my $file (grep { -f "$extraprofiledir/$_" } readdir(ESDDIR)) { - next if $file =~ /\.rpm(save|new)|README$/; - readprofile("$extraprofiledir/$file", \&fatal_error, 0); - } - closedir(ESDDIR); -} - -sub readprofile ($$$) { - my $file = shift; - my $error_handler = shift; - my $active_profile = shift; - if (open(SDPROF, "$file")) { - local $/; - my $data = ; - close(SDPROF); - - eval { - my $profile_data = parse_profile_data($data, $file, 0); - if ($profile_data && $active_profile) { - attach_profile_data(\%sd, $profile_data); - attach_profile_data(\%original_sd, $profile_data); - } elsif ( $profile_data ) { - attach_profile_data(\%extras, $profile_data); - } - }; - - # if there were errors loading the profile, call the error handler - if ($@) { - $@ =~ s/\n$//; - return &$error_handler($@); - } - } else { - $DEBUGGING && debug "readprofile: can't read $file - skipping"; - } -} - -sub attach_profile_data($$) { - my ($profiles, $profile_data) = @_; - - # make deep copies of the profile data so that if we change one set of - # profile data, we're not changing others because of sharing references - for my $p ( keys %$profile_data) { - $profiles->{$p} = dclone($profile_data->{$p}); - } -} - -sub parse_profile_data($$$) { - my ($data, $file, $do_include) = @_; - - - my ($profile_data, $profile, $hat, $in_contained_hat, $repo_data, - @parsed_profiles); - my $initial_comment = ""; - - if ($do_include) { - $profile = $file; - $hat = $file; - } - - for (split(/\n/, $data)) { - chomp; - - # we don't care about blank lines - next if /^\s*$/; - - # start of a profile... - if (m/^\s*(("??\/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$/) { - # if we run into the start of a profile while we're already in a - # profile, something's wrong... - if ($profile) { - unless (($profile eq $hat) and $4) { - die "$profile profile in $file contains syntax errors.\n"; - } - } - - # we hit the start of a profile, keep track of it... - if ($profile && ($profile eq $hat) && $4) { - # local profile - $hat = $4; - $in_contained_hat = 1; - $profile_data->{$profile}{$hat}{profile} = 1; - } else { - $profile = $2 || $4; - # hat is same as profile name if we're not in a hat - ($profile, $hat) = split /\/\//, $profile; - $in_contained_hat = 0; - if ($hat) { - $profile_data->{$profile}{$hat}{external} = 1; - } - - $hat ||= $profile; - } - - my $flags = $7; - - # deal with whitespace in profile and hat names. - $profile = strip_quotes($profile); - $hat = strip_quotes($hat) if $hat; - - # save off the name and filename - $profile_data->{$profile}{$hat}{name} = $profile; - $profile_data->{$profile}{$hat}{filename} = $file; - $filelist{$file}{profiles}{$profile}{$hat} = 1; - - # keep track of profile flags - $profile_data->{$profile}{$hat}{flags} = $flags; - - $profile_data->{$profile}{$hat}{allow}{netdomain} = { }; - $profile_data->{$profile}{$hat}{allow}{path} = { }; - - # store off initial comment if they have one - $profile_data->{$profile}{$hat}{initial_comment} = $initial_comment - if $initial_comment; - $initial_comment = ""; - - if ($repo_data) { - $profile_data->{$profile}{$profile}{repo}{url} = $repo_data->{url}; - $profile_data->{$profile}{$profile}{repo}{user} = $repo_data->{user}; - $profile_data->{$profile}{$profile}{repo}{id} = $repo_data->{id}; - $repo_data = undef; - } - - } elsif (m/^\s*\}\s*(#.*)?$/) { # end of a profile... - - # if we hit the end of a profile when we're not in one, something's - # wrong... - if (not $profile) { - die sprintf(gettext('%s contains syntax errors.'), $file) . "\n"; - } - - if ($in_contained_hat) { - $hat = $profile; - $in_contained_hat = 0; - } else { - push @parsed_profiles, $profile; - # mark that we're outside of a profile now... - $profile = undef; - } - - $initial_comment = ""; - - } elsif (m/^\s*(audit\s+)?(deny\s+)?capability(\s+(\S+))?\s*,\s*(#.*)?$/) { # capability entry - if (not $profile) { - die sprintf(gettext('%s contains syntax errors.'), $file) . "\n"; - } - - my $audit = $1 ? 1 : 0; - my $allow = $2 ? 'deny' : 'allow'; - $allow = 'deny' if ($2); - my $capability = $3 ? $3 : 'all'; - $profile_data->{$profile}{$hat}{$allow}{capability}{$capability}{set} = 1; - $profile_data->{$profile}{$hat}{$allow}{capability}{$capability}{audit} = $audit; - } elsif (m/^\s*set capability\s+(\S+)\s*,\s*(#.*)?$/) { # capability entry - if (not $profile) { - die sprintf(gettext('%s contains syntax errors.'), $file) . "\n"; - } - - my $capability = $1; - $profile_data->{$profile}{$hat}{set_capability}{$capability} = 1; - - } elsif (m/^\s*(audit\s+)?(deny\s+)?link\s+(((subset)|(<=))\s+)?([\"\@\/].*?"??)\s+->\s*([\"\@\/].*?"??)\s*,\s*(#.*)?$/) { # for now just keep link - if (not $profile) { - die sprintf(gettext('%s contains syntax errors.'), $file) . "\n"; - } - my $audit = $1 ? 1 : 0; - my $allow = $2 ? 'deny' : 'allow'; - - my $subset = $4; - my $link = strip_quotes($7); - my $value = strip_quotes($8); - $profile_data->{$profile}{$hat}{$allow}{link}{$link}{to} = $value; - $profile_data->{$profile}{$hat}{$allow}{link}{$link}{mode} |= $AA_MAY_LINK; - if ($subset) { - $profile_data->{$profile}{$hat}{$allow}{link}{$link}{mode} |= $AA_LINK_SUBSET; - } - if ($audit) { - $profile_data->{$profile}{$hat}{$allow}{link}{$link}{audit} |= $AA_LINK_SUBSET; - } else { - $profile_data->{$profile}{$hat}{$allow}{link}{$link}{audit} |= 0; - } - - } elsif (m/^\s*change_profile\s+->\s*("??.+?"??),(#.*)?$/) { # for now just keep change_profile - if (not $profile) { - die sprintf(gettext('%s contains syntax errors.'), $file) . "\n"; - } - my $cp = strip_quotes($1); - - $profile_data->{$profile}{$hat}{change_profile}{$cp} = 1; - } elsif (m/^\s*alias\s+("??.+?"??)\s+->\s*("??.+?"??)\s*,(#.*)?$/) { # never do anything with aliases just keep them - my $from = strip_quotes($1); - my $to = strip_quotes($2); - - if ($profile) { - $profile_data->{$profile}{$hat}{alias}{$from} = $to; - } else { - unless (exists $filelist{$file}) { - $filelist{$file} = { }; - } - $filelist{$file}{alias}{$from} = $to; - } - - } elsif (m/^\s*set\s+rlimit\s+(.+)\s+<=\s*(.+)\s*,(#.*)?$/) { # never do anything with rlimits just keep them - if (not $profile) { - die sprintf(gettext('%s contains syntax errors.'), $file) . "\n"; - } - my $from = $1; - my $to = $2; - - $profile_data->{$profile}{$hat}{rlimit}{$from} = $to; - - } elsif (/^\s*(\$\{?[[:alpha:]][[:alnum:]_]*\}?)\s*=\s*(true|false)\s*,?\s*(#.*)?$/i) { # boolean definition - if (not $profile) { - die sprintf(gettext('%s contains syntax errors.'), $file) . "\n"; - } - my $bool_var = $1; - my $value = $2; - - $profile_data->{$profile}{$hat}{lvar}{$bool_var} = $value; - } elsif (/^\s*(@\{?[[:alpha:]][[:alnum:]_]+\}?)\s*\+?=\s*(.+?)\s*,?\s*(#.*)?$/) { # variable additions both += and = doesn't mater - my $list_var = strip_quotes($1); - my $value = strip_quotes($2); - - if ($profile) { - unless (exists $profile_data->{$profile}{$hat}{lvar}) { - # create lval hash by sticking an empty list into list_var - my @empty = (); - $profile_data->{$profile}{$hat}{lvar}{$list_var} = \@empty; - } - - store_list_var($profile_data->{$profile}{$hat}{lvar}, $list_var, $value); - } else { - unless (exists $filelist{$file}{lvar}) { - # create lval hash by sticking an empty list into list_var - my @empty = (); - $filelist{$file}{lvar}{$list_var} = \@empty; - } - - store_list_var($filelist{$file}{lvar}, $list_var, $value); - } - } elsif (m/^\s*if\s+(not\s+)?(\$\{?[[:alpha:]][[:alnum:]_]*\}?)\s*\{\s*(#.*)?$/) { # conditional -- boolean - } elsif (m/^\s*if\s+(not\s+)?defined\s+(@\{?[[:alpha:]][[:alnum:]_]+\}?)\s*\{\s*(#.*)?$/) { # conditional -- variable defined - } elsif (m/^\s*if\s+(not\s+)?defined\s+(\$\{?[[:alpha:]][[:alnum:]_]+\}?)\s*\{\s*(#.*)?$/) { # conditional -- boolean defined - } elsif (m/^\s*(audit\s+)?(deny\s+)?(owner\s+)?(file|([\"\@\/].*?)\s+(\S+))(\s+->\s*(.*?))?\s*,\s*(#.*)?$/) { # path entry - if (not $profile) { - die sprintf(gettext('%s contains syntax errors.'), $file) . "\n"; - } - - my $audit = $1 ? 1 : 0; - my $allow = $2 ? 'deny' : 'allow'; - my $user = $3 ? 1 : 0; - my ($path, $mode, $nt_name) = ($5, $6, $8); - my $file_keyword = 0; - my $use_mode = 1; - - if ($4 eq "file") { - $path = "/{**,}"; - $file_keyword = 1; - if (!$mode) { - # what the parser uses, but we don't care - $mode = "rwixlka"; - $use_mode = 0; - } - } - - # strip off any trailing spaces. - $path =~ s/\s+$//; - $nt_name =~ s/\s+$// if $nt_name; - - $path = strip_quotes($path); - $nt_name = strip_quotes($nt_name) if $nt_name; - - # make sure they don't have broken regexps in the profile - my $p_re = convert_regexp($path); - eval { "foo" =~ m/^$p_re$/; }; - if ($@) { - die sprintf(gettext('Profile %s contains invalid regexp %s.'), - $file, $path) . "\n"; - } - - if (!validate_profile_mode($mode, $allow, $nt_name)) { - fatal_error(sprintf(gettext('Profile %s contains invalid mode %s.'), $file, $mode)); - } - - $profile_data->{$profile}{$hat}{$allow}{path}{$path}{use_mode} = $use_mode; - $profile_data->{$profile}{$hat}{$allow}{path}{$path}{file_keyword} = 1 if $file_keyword; - - my $tmpmode; - if ($user) { - $tmpmode = str_to_mode("${mode}::"); - } else { - $tmpmode = str_to_mode($mode); - } - - $profile_data->{$profile}{$hat}{$allow}{path}{$path}{mode} |= $tmpmode; - $profile_data->{$profile}{$hat}{$allow}{path}{$path}{to} = $nt_name if $nt_name; - if ($audit) { - $profile_data->{$profile}{$hat}{$allow}{path}{$path}{audit} |= $tmpmode; - } else { - $profile_data->{$profile}{$hat}{$allow}{path}{$path}{audit} |= 0; - } - } elsif (m/^\s*#include <(.+)>\s*$/) { # include stuff - my $include = $1; - - if ($profile) { - $profile_data->{$profile}{$hat}{include}{$include} = 1; - } else { - unless (exists $filelist{$file}) { - $filelist{$file} = { }; - } - $filelist{$file}{include}{$include} = 1; - } - - # include is a dir - if (-d "$profiledir/$include") { - if (opendir(SDINCDIR, "$profiledir/$include")) { - for my $path (readdir(SDINCDIR)) { - chomp($path); - next if isSkippableFile($path); - if (-f "$profiledir/$include/$path") { - my $file = "$include/$path"; - $file =~ s/$profiledir\///; - my $ret = eval { loadinclude($file); }; - if ($@) { die $@; } - return $ret if ( $ret != 0 ); - } - } - } - closedir(SDINCDIR); - } else { - # try to load the include... - my $ret = eval { loadinclude($include); }; - # propagate errors up the chain - if ($@) { die $@; } - return $ret if ( $ret != 0 ); - } - } elsif (/^\s*(audit\s+)?(deny\s+)?network(.*)/) { - if (not $profile) { - die sprintf(gettext('%s contains syntax errors.'), $file) . "\n"; - } - my $audit = $1 ? 1 : 0; - my $allow = $2 ? 'deny' : 'allow'; - my $network = $3; - - unless ($profile_data->{$profile}{$hat}{$allow}{netdomain}{rule}) { - $profile_data->{$profile}{$hat}{$allow}{netdomain}{rule} = { }; - } - - if ($network =~ /\s+(\S+)\s+(\S+)\s*,\s*(#.*)?$/ ) { - my $fam = $1; - my $type = $2; - $profile_data->{$profile}{$hat}{$allow}{netdomain}{rule}{$fam}{$type} = 1; - $profile_data->{$profile}{$hat}{$allow}{netdomain}{audit}{$fam}{$type} = $audit; - } elsif ( $network =~ /\s+(\S+)\s*,\s*(#.*)?$/ ) { - my $fam = $1; - $profile_data->{$profile}{$hat}{$allow}{netdomain}{rule}{$fam} = 1; - $profile_data->{$profile}{$hat}{$allow}{netdomain}{audit}{$fam} = $audit; - } else { - $profile_data->{$profile}{$hat}{$allow}{netdomain}{rule}{all} = 1; - $profile_data->{$profile}{$hat}{$allow}{netdomain}{audit}{all} = $audit; - } - } elsif (/^\s*(tcp_connect|tcp_accept|udp_send|udp_receive)/) { -# just ignore and drop old style network -# die sprintf(gettext('%s contains old style network rules.'), $file) . "\n"; - - } elsif (m/^\s*\^(\"??.+?\"??)\s*,\s*(#.*)?$/) { - if (not $profile) { - die "$file contains syntax errors."; - } - # change_hat declaration - needed to change_hat to an external - # hat - $hat = $1; - $hat = $1 if $hat =~ /^"(.+)"$/; - - #store we have a declaration if the hat hasn't been seen - $profile_data->{$profile}{$hat}{'declared'} = 1 - unless exists($profile_data->{$profile}{$hat}{declared}); - - } elsif (m/^\s*\^(\"??.+?\"??)\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$/) { - # start of embedded hat syntax hat definition - # read in and mark as changed so that will be written out in the new - # format - - # if we hit the start of a contained hat when we're not in a profile - # something is wrong... - if (not $profile) { - die sprintf(gettext('%s contains syntax errors.'), $file) . "\n"; - } - - $in_contained_hat = 1; - - # we hit the start of a hat inside the current profile - $hat = $1; - my $flags = $4; - - # strip quotes. - $hat = $1 if $hat =~ /^"(.+)"$/; - - # keep track of profile flags - $profile_data->{$profile}{$hat}{flags} = $flags; - # we have seen more than a declaration so clear it - $profile_data->{$profile}{$hat}{'declared'} = 0; - $profile_data->{$profile}{$hat}{allow}{path} = { }; - $profile_data->{$profile}{$hat}{allow}{netdomain} = { }; - - # store off initial comment if they have one - $profile_data->{$profile}{$hat}{initial_comment} = $initial_comment - if $initial_comment; - $initial_comment = ""; - #don't mark profile as changed just because it has an embedded - #hat. - #$changed{$profile} = 1; - - $filelist{$file}{profiles}{$profile}{$hat} = 1; - - } elsif (/^\s*\#/) { - # we only currently handle initial comments - if (not $profile) { - # ignore vim syntax highlighting lines - next if /^\s*\# vim:syntax/; - # ignore Last Modified: lines - next if /^\s*\# Last Modified:/; - if (/^\s*\# REPOSITORY: (\S+) (\S+) (\S+)$/) { - $repo_data = { url => $1, user => $2, id => $3 }; - } elsif (/^\s*\# REPOSITORY: NEVERSUBMIT$/) { - $repo_data = { neversubmit => 1 }; - } else { - $initial_comment .= "$_\n"; - } - } - } elsif (/^\s*(audit\s+)?(deny\s+)?(owner\s+)?(capability|dbus|file|mount|pivot_root|remount|umount|signal|unix|ptrace)/) { - # ignore valid rules that are currently unsupported by AppArmor.pm - if (! defined $profile_data->{$profile}{$hat}{unsupported_rules}) { - $profile_data->{$profile}{$hat}{unsupported_rules} = []; - } - $_ =~ s/^\s+|\s+$//g; - push @{$profile_data->{$profile}{$hat}{unsupported_rules}}, $_ ; - - } else { - # we hit something we don't understand in a profile... - die sprintf(gettext('%s contains syntax errors. Line [%s]'), $file, $_) . "\n"; - } - } - - # - # Cleanup : add required hats if not present in the - # parsed profiles - # -if (not $do_include) { - for my $hatglob (keys %{$cfg->{required_hats}}) { - for my $parsed_profile ( sort @parsed_profiles ) { - if ($parsed_profile =~ /$hatglob/) { - for my $hat (split(/\s+/, $cfg->{required_hats}{$hatglob})) { - unless ($profile_data->{$parsed_profile}{$hat}) { - $profile_data->{$parsed_profile}{$hat} = { }; - } - } - } - } - } - -} # if we're still in a profile when we hit the end of the file, it's bad - if ($profile and not $do_include) { - die "Reached the end of $file while we were still inside the $profile profile.\n"; - } - - return $profile_data; -} - -sub eliminate_duplicates(@) { - my @data =@_; - - my %set = map { $_ => 1 } @_; - @data = keys %set; - - return @data; -} - -sub separate_vars($) { - my $vs = shift; - my @data; - -# while ($vs =~ /\s*(((\"([^\"]|\\\"))+?\")|\S*)\s*(.*)$/) { - while ($vs =~ /\s*((\".+?\")|([^\"]\S+))\s*(.*)$/) { - my $tmp = $1; - push @data, strip_quotes($tmp); - $vs = $4; - } - - return @data; -} - -sub is_active_profile ($) { - my $pname = shift; - if ( $sd{$pname} ) { - return 1; - } else { - return 0; - } -} - -sub store_list_var (\%$$) { - my ($vars, $list_var, $value) = @_; - - my @vlist = (separate_vars($value)); - -# if (exists $profile_data->{$profile}{$hat}{lvar}{$list_var}) { -# @vlist = (@vlist, @{$profile_data->{$profile}{$hat}{lvar}{$list_var}}); -# } -# -# @vlist = eliminate_duplicates(@vlist); -# $profile_data->{$profile}{$hat}{lvar}{$list_var} = \@vlist; - - if (exists $vars->{$list_var}) { - @vlist = (@vlist, @{$vars->{$list_var}}); - } - - @vlist = eliminate_duplicates(@vlist); - $vars->{$list_var} = \@vlist; - - -} - -sub strip_quotes ($) { - my $data = shift; - $data = $1 if $data =~ /^\"(.*)\"$/; - return $data; -} - -sub quote_if_needed ($) { - my $data = shift; - $data = "\"$data\"" if $data =~ /\s/; - - return $data; -} - -sub escape ($) { - my $dangerous = shift; - - $dangerous = strip_quotes($dangerous); - - $dangerous =~ s/((?" unless ( $is_hat ); - if ($write_flags and $profile_data->{flags}) { - push @data, "${pre}$name flags=($profile_data->{flags}) {"; - } else { - push @data, "${pre}$name {"; - } - - return @data; -} - -sub qin_trans ($) { - my $value = shift; - return quote_if_needed($value); -} - -sub write_single ($$$$$$) { - my ($profile_data, $depth, $allow, $name, $prefix, $tail) = @_; - my $ref; - my @data; - - if ($allow) { - $ref = $profile_data->{$allow}; - if ($allow eq 'deny') { - $allow .= " "; - } else { - $allow = ""; - } - } else { - $ref = $profile_data; - $allow = ""; - } - - my $pre = " " x $depth; - - - # dump out the data - if (exists $ref->{$name}) { - for my $key (sort keys %{$ref->{$name}}) { - my $qkey = quote_if_needed($key); - push @data, "${pre}${allow}${prefix}${qkey}${tail}"; - } - push @data, "" if keys %{$ref->{$name}}; - } - - return @data; -} - -sub write_pair ($$$$$$$$) { - my ($profile_data, $depth, $allow, $name, $prefix, $sep, $tail, $fn) = @_; - my $ref; - my @data; - - if ($allow) { - $ref = $profile_data->{$allow}; - if ($allow eq 'deny') { - $allow .= " "; - } else { - $allow = ""; - } - } else { - $ref = $profile_data; - $allow = ""; - } - - my $pre = " " x $depth; - - # dump out the data - if (exists $ref->{$name}) { - for my $key (sort keys %{$ref->{$name}}) { - my $value = &{$fn}($ref->{$name}{$key}); - push @data, "${pre}${allow}${prefix}${key}${sep}${value}${tail}"; - } - push @data, "" if keys %{$ref->{$name}}; - } - - return @data; -} - -sub writeincludes ($$) { - my ($prof_data, $depth) = @_; - - return write_single($prof_data, $depth,'', 'include', "#include <", ">"); -} - -sub writechange_profile ($$) { - my ($prof_data, $depth) = @_; - - return write_single($prof_data, $depth, '', 'change_profile', "change_profile -> ", ","); -} - -sub writealiases ($$) { - my ($prof_data, $depth) = @_; - - return write_pair($prof_data, $depth, '', 'alias', "alias ", " -> ", ",", \&qin_trans); -} - -sub writerlimits ($$) { - my ($prof_data, $depth) = @_; - - return write_pair($prof_data, $depth, '', 'rlimit', "set rlimit ", " <= ", ",", \&qin_trans); -} - -# take a list references and process it -sub var_transform($) { - my $ref = shift; - my @in = @{$ref}; - my @data; - - foreach my $value (@in) { - push @data, quote_if_needed($value); - } - - return join " ", @data; -} - -sub writelistvars ($$) { - my ($prof_data, $depth) = @_; - - return write_pair($prof_data, $depth, '', 'lvar', "", " = ", "", \&var_transform); -} - -sub writecap_rules ($$$) { - my ($profile_data, $depth, $allow) = @_; - - my $allowstr = $allow eq 'deny' ? 'deny ' : ''; - my $pre = " " x $depth; - - my @data; - if (exists $profile_data->{$allow}{capability}) { - my $audit; - if (exists $profile_data->{$allow}{capability}{all}) { - $audit = ($profile_data->{$allow}{capability}{all}{audit}) ? 'audit ' : ''; - push @data, "${pre}${audit}${allowstr}capability,"; - } - for my $cap (sort keys %{$profile_data->{$allow}{capability}}) { - next if ($cap eq "all"); - my $audit = ($profile_data->{$allow}{capability}{$cap}{audit}) ? 'audit ' : ''; - if ($profile_data->{$allow}{capability}{$cap}{set}) { - push @data, "${pre}${audit}${allowstr}capability ${cap},"; - } - } - push @data, ""; - } - - return @data; -} - -sub writecapabilities ($$) { - my ($prof_data, $depth) = @_; - my @data; - push @data, write_single($prof_data, $depth, '', 'set_capability', "set capability ", ","); - push @data, writecap_rules($prof_data, $depth, 'deny'); - push @data, writecap_rules($prof_data, $depth, 'allow'); - return @data; -} - -sub writenet_rules ($$$) { - my ($profile_data, $depth, $allow) = @_; - - my $allowstr = $allow eq 'deny' ? 'deny ' : ''; - - my $pre = " " x $depth; - my $audit = ""; - - my @data; - # dump out the netdomain entries... - if (exists $profile_data->{$allow}{netdomain}) { - if ( $profile_data->{$allow}{netdomain}{rule} && - $profile_data->{$allow}{netdomain}{rule}{all}) { - $audit = "audit " if $profile_data->{$allow}{netdomain}{audit}{all}; - push @data, "${pre}${audit}network,"; - } else { - for my $fam (sort keys %{$profile_data->{$allow}{netdomain}{rule}}) { - if ( $profile_data->{$allow}{netdomain}{rule}{$fam} == 1 ) { - $audit = "audit " if $profile_data->{$allow}{netdomain}{audit}{$fam}; - push @data, "${pre}${audit}${allowstr}network $fam,"; - } else { - for my $type - (sort keys %{$profile_data->{$allow}{netdomain}{rule}{$fam}}) { - $audit = "audit " if $profile_data->{$allow}{netdomain}{audit}{$fam}{$type}; - push @data, "${pre}${audit}${allowstr}network $fam $type,"; - } - } - } - } - push @data, "" if %{$profile_data->{$allow}{netdomain}}; - } - return @data; - -} - -sub writenetdomain ($$) { - my ($prof_data, $depth) = @_; - my @data; - - push @data, writenet_rules($prof_data, $depth, 'deny'); - push @data, writenet_rules($prof_data, $depth, 'allow'); - - return @data; -} - -sub writelink_rules ($$$) { - my ($profile_data, $depth, $allow) = @_; - - my $allowstr = $allow eq 'deny' ? 'deny ' : ''; - my $pre = " " x $depth; - - my @data; - if (exists $profile_data->{$allow}{link}) { - for my $path (sort keys %{$profile_data->{$allow}{link}}) { - my $to = $profile_data->{$allow}{link}{$path}{to}; - my $subset = ($profile_data->{$allow}{link}{$path}{mode} & $AA_LINK_SUBSET) ? 'subset ' : ''; - my $audit = ($profile_data->{$allow}{link}{$path}{audit}) ? 'audit ' : ''; - # deal with whitespace in path names - $path = quote_if_needed($path); - $to = quote_if_needed($to); - push @data, "${pre}${audit}${allowstr}link ${subset}${path} -> ${to},"; - } - push @data, ""; - } - - return @data; -} - -sub writelinks ($$) { - my ($profile_data, $depth) = @_; - my @data; - - push @data, writelink_rules($profile_data, $depth, 'deny'); - push @data, writelink_rules($profile_data, $depth, 'allow'); - - return @data; -} - -sub writepath_rules ($$$) { - my ($profile_data, $depth, $allow) = @_; - - my $allowstr = $allow eq 'deny' ? 'deny ' : ''; - my $pre = " " x $depth; - - my @data; - if (exists $profile_data->{$allow}{path}) { - for my $path (sort keys %{$profile_data->{$allow}{path}}) { - my $mode = $profile_data->{$allow}{path}{$path}{mode}; - my $audit = $profile_data->{$allow}{path}{$path}{audit}; - my $tail = ""; - $tail = " -> " . $profile_data->{$allow}{path}{$path}{to} if ($profile_data->{$allow}{path}{$path}{to}); - my ($user, $other) = split_mode($mode); - my ($user_audit, $other_audit) = split_mode($audit); - # determine whether the rule contains any owner only components - - while ($user || $other) { - my $ownerstr = ""; - my ($tmpmode, $tmpaudit) = 0; - if ($user & ~$other) { - # user contains bits not set in other - $ownerstr = "owner "; - $tmpmode = $user & ~$other; - $tmpaudit = $user_audit; - $user &= ~$tmpmode; -# } elsif ($other & ~$user) { -# $ownerstr = "other "; -# $tmpmode = $other & ~$user; -# $tmpaudit = $other_audit; -# $other &= ~$tmpmode; - } else { - if ($user_audit & ~$other_audit & $user) { - $ownerstr = "owner "; - $tmpaudit = $user_audit & ~$other_audit & $user; - $tmpmode = $user & $tmpaudit; - $user &= ~$tmpmode; -# } elsif ($other_audit & ~$user_audit & $other) { -# $ownerstr = "other "; -# $tmpaudit = $other_audit & ~$user_audit & $other; -# $tmpmode = $other & $tmpaudit; -# $other &= ~$tmpmode; - } else { - # user == other && user_audit == other_audit - $ownerstr = ""; -#include exclusive other for now -# $tmpmode = $user; -# $tmpaudit = $user_audit; - $tmpmode = $user | $other; - $tmpaudit = $user_audit | $other_audit; - $user &= ~$tmpmode; - $other &= ~$tmpmode; - } - } - - if ($tmpmode & $tmpaudit) { - my $modestr = mode_to_str($tmpmode & $tmpaudit); - if ($path =~ /\s/) { - push @data, "${pre}audit ${allowstr}${ownerstr}\"$path\" ${modestr}${tail},"; - } else { - push @data, "${pre}audit ${allowstr}${ownerstr}$path ${modestr}${tail},"; - } - $tmpmode &= ~$tmpaudit; - } - my $kw = $profile_data->{$allow}{path}{$path}{file_keyword}; - my $use_mode = $profile_data->{$allow}{path}{$path}{use_mode}; - if ($kw) { - my $modestr = ""; - $modestr = " " . mode_to_str($tmpmode) if $use_mode; - push @data, "${pre}${allowstr}${ownerstr}file${modestr}${tail},"; - } elsif ($tmpmode) { - my $modestr = mode_to_str($tmpmode); - if ($path =~ /\s/) { - push @data, "${pre}${allowstr}${ownerstr}\"$path\" ${modestr}${tail},"; - } else { - push @data, "${pre}${allowstr}${ownerstr}$path ${modestr}${tail},"; - } - } - } - - } - push @data, ""; - } - - return @data; -} - -sub writepaths ($$) { - my ($prof_data, $depth) = @_; - - my @data; - push @data, writepath_rules($prof_data, $depth, 'deny'); - push @data, writepath_rules($prof_data, $depth, 'allow'); - - return @data; -} - -sub writeunsupportedrules ($$) { - my ($prof_data, $depth) = @_; - - my @data; - my $pre = " " x $depth; - - if (defined $prof_data->{unsupported_rules}) { - - for my $rule (@{$prof_data->{unsupported_rules}}){ - push @data, "${pre}${rule}"; - } - - push @data, ""; - } - - return @data; - -} - -sub write_rules ($$) { - my ($prof_data, $depth) = @_; - - my @data; - push @data, writealiases($prof_data, $depth); - push @data, writelistvars($prof_data, $depth); - push @data, writeincludes($prof_data, $depth); - push @data, writerlimits($prof_data, $depth); - push @data, writecapabilities($prof_data, $depth); - push @data, writenetdomain($prof_data, $depth); - push @data, writeunsupportedrules($prof_data, $depth); ## Legacy support for unknown/new rules - push @data, writelinks($prof_data, $depth); - push @data, writepaths($prof_data, $depth); - push @data, writechange_profile($prof_data, $depth); - - return @data; -} - -sub writepiece ($$$$$); -sub writepiece ($$$$$) { - my ($profile_data, $depth, $name, $nhat, $write_flags) = @_; - - my $pre = ' ' x $depth; - my @data; - my $wname; - my $inhat = 0; - if ($name eq $nhat) { - $wname = $name; - } else { - $wname = "$name//$nhat"; - $name = $nhat; - $inhat = 1; - } - push @data, writeheader($profile_data->{$name}, $depth, $wname, 0, $write_flags); - push @data, write_rules($profile_data->{$name}, $depth + 1); - - my $pre2 = ' ' x ($depth + 1); - # write external hat declarations - for my $hat (grep { $_ ne $name } sort keys %{$profile_data}) { - if ($profile_data->{$hat}{declared}) { - push @data, "${pre2}^$hat,"; - } - } - - if (!$inhat) { - # write embedded hats - for my $hat (grep { $_ ne $name } sort keys %{$profile_data}) { - if ((not $profile_data->{$hat}{external}) and - (not $profile_data->{$hat}{declared})) { - push @data, ""; - if ($profile_data->{$hat}{profile}) { - push @data, map { "$_" } writeheader($profile_data->{$hat}, - $depth + 1, $hat, - 1, $write_flags); - } else { - push @data, map { "$_" } writeheader($profile_data->{$hat}, - $depth + 1, "^$hat", - 1, $write_flags); - } - push @data, map { "$_" } write_rules($profile_data->{$hat}, - $depth + 2); - push @data, "${pre2}}"; - } - } - push @data, "${pre}}"; - - #write external hats - for my $hat (grep { $_ ne $name } sort keys %{$profile_data}) { - if (($name eq $nhat) and $profile_data->{$hat}{external}) { - push @data, ""; - push @data, map { " $_" } writepiece($profile_data, $depth - 1, - $name, $hat, $write_flags); - push @data, " }"; - } - } - } - return @data; -} - -sub serialize_profile($$$) { - my ($profile_data, $name, $options) = @_; - - my $string = ""; - my $include_metadata = 0; # By default don't write out metadata - my $include_flags = 1; - if ( $options and ref($options) eq "HASH" ) { - $include_metadata = 1 if ( defined $options->{METADATA} ); - $include_flags = 0 if ( defined $options->{NO_FLAGS} ); - } - - if ($include_metadata) { - # keep track of when the file was last updated - $string .= "# Last Modified: " . localtime(time) . "\n"; - - # print out repository metadata - if ($profile_data->{$name}{repo} && - $profile_data->{$name}{repo}{url} && - $profile_data->{$name}{repo}{user} && - $profile_data->{$name}{repo}{id}) { - my $repo = $profile_data->{$name}{repo}; - $string .= "# REPOSITORY: $repo->{url} $repo->{user} $repo->{id}\n"; - } elsif ($profile_data->{$name}{repo}{neversubmit}) { - $string .= "# REPOSITORY: NEVERSUBMIT\n"; - } - } - - # print out initial comment - if ($profile_data->{$name}{initial_comment}) { - my $comment = $profile_data->{$name}{initial_comment}; - $comment =~ s/\\n/\n/g; - $string .= "$comment\n"; - } - - #bleah this is stupid the data structure needs to be reworked - my $filename = getprofilefilename($name); - my @data; - if ($filelist{$filename}) { - push @data, writealiases($filelist{$filename}, 0); - push @data, writelistvars($filelist{$filename}, 0); - push @data, writeincludes($filelist{$filename}, 0); - } - - -# XXX - FIXME -# -# # dump variables defined in this file -# if ($variables{$filename}) { -# for my $var (sort keys %{$variables{$filename}}) { -# if ($var =~ m/^@/) { -# my @values = sort @{$variables{$filename}{$var}}; -# @values = map { escape($_) } @values; -# my $values = join (" ", @values); -# print SDPROF "$var = "; -# print SDPROF $values; -# } elsif ($var =~ m/^\$/) { -# print SDPROF "$var = "; -# print SDPROF ${$variables{$filename}{$var}}; -# } elsif ($var =~ m/^\#/) { -# my $inc = $var; -# $inc =~ s/^\#//; -# print SDPROF "#include <$inc>"; -# } -# print SDPROF "\n"; -# } -# } - - push @data, writepiece($profile_data, 0, $name, $name, $include_flags); - $string .= join("\n", @data); - - return "$string\n"; -} - -sub writeprofile_ui_feedback ($) { - my $profile = shift; - UI_Info(sprintf(gettext('Writing updated profile for %s.'), $profile)); - writeprofile($profile); -} - -sub writeprofile ($) { - my ($profile) = shift; - - my $filename = $sd{$profile}{$profile}{filename} || getprofilefilename($profile); - - open(SDPROF, ">$filename") or - fatal_error "Can't write new AppArmor profile $filename: $!"; - my $serialize_opts = { }; - $serialize_opts->{METADATA} = 1; - - #make sure to write out all the profiles in the file - my $profile_string = serialize_profile($sd{$profile}, $profile, $serialize_opts); - print SDPROF $profile_string; - close(SDPROF); - - # mark the profile as up-to-date - delete $changed{$profile}; - $original_sd{$profile} = dclone($sd{$profile}); -} - -sub getprofileflags($) { - my $filename = shift; - - my $flags = "enforce"; - - if (open(PROFILE, "$filename")) { - while () { - if (m/^\s*\/\S+\s+flags=\((.+)\)\s+{\s*$/) { - $flags = $1; - close(PROFILE); - return $flags; - } - } - close(PROFILE); - } - - return $flags; -} - - -sub matchliteral($$) { - my ($sd_regexp, $literal) = @_; - - my $p_regexp = convert_regexp($sd_regexp); - - # check the log entry against our converted regexp... - my $matches = eval { $literal =~ /^$p_regexp$/; }; - - # doesn't match if we've got a broken regexp - return undef if $@; - - return $matches; -} - -# test if profile has exec rule for $exec_target -sub profile_known_exec (\%$$) { - my ($profile, $type, $exec_target) = @_; - if ( $type eq "exec" ) { - my ($cm, $am, @m); - - # test denies first - ($cm, $am, @m) = rematchfrag($profile, 'deny', $exec_target); - if ($cm & $AA_MAY_EXEC) { - return -1; - } - ($cm, $am, @m) = match_prof_incs_to_path($profile, 'deny', $exec_target); - if ($cm & $AA_MAY_EXEC) { - return -1; - } - - # now test the generally longer allow lists - ($cm, $am, @m) = rematchfrag($profile, 'allow', $exec_target); - if ($cm & $AA_MAY_EXEC) { - return 1; - } - - ($cm, $am, @m) = match_prof_incs_to_path($profile, 'allow', $exec_target); - if ($cm & $AA_MAY_EXEC) { - return 1; - } - } - return 0; -} - -sub profile_known_capability (\%$) { - my ($profile, $capname) = @_; - - return -1 if $profile->{deny}{capability}{$capname}{set}; - return 1 if $profile->{allow}{capability}{$capname}{set}; - for my $incname ( keys %{$profile->{include}} ) { - return -1 if $include{$incname}{$incname}{deny}{capability}{$capname}{set}; - return 1 if $include{$incname}{$incname}{allow}{capability}{$capname}{set}; - } - return 0; -} - -sub profile_known_network (\%$$) { - my ($profile, $family, $sock_type) = @_; - - return -1 if netrules_access_check( $profile->{deny}{netdomain}, - $family, $sock_type); - return 1 if netrules_access_check( $profile->{allow}{netdomain}, - $family, $sock_type); - - for my $incname ( keys %{$profile->{include}} ) { - return -1 if netrules_access_check($include{$incname}{$incname}{deny}{netdomain}, - $family, $sock_type); - return 1 if netrules_access_check($include{$incname}{$incname}{allow}{netdomain}, - $family, $sock_type); - } - - return 0; -} - -sub netrules_access_check ($$$) { - my ($netrules, $family, $sock_type) = @_; - return 0 if ( not defined $netrules ); - my %netrules = %$netrules; - my $all_net = defined $netrules{rule}{all}; - my $all_net_family = defined $netrules{rule}{$family} && $netrules{rule}{$family} == 1; - my $net_family_sock = defined $netrules{rule}{$family} && - ref($netrules{rule}{$family}) eq "HASH" && - defined $netrules{rule}{$family}{$sock_type}; - - if ( $all_net || $all_net_family || $net_family_sock ) { - return 1; - } else { - return 0; - } -} - -sub reload_base($) { - my $bin = shift; - - # don't try to reload profile if AppArmor is not running - return unless check_for_subdomain(); - - my $filename = getprofilefilename($bin); - - system("/bin/cat '$filename' | $parser -I$profiledir -r >/dev/null 2>&1"); -} - -sub reload ($) { - my $bin = shift; - - # don't reload the profile if the corresponding executable doesn't exist - my $fqdbin = findexecutable($bin) or return; - - return reload_base($fqdbin); -} - -sub read_include_from_file($) { - my $which = shift; - - my $data; - if (open(INCLUDE, "$profiledir/$which")) { - local $/; - $data = ; - close(INCLUDE); - } - - return $data; -} - -sub get_include_data($) { - my $which = shift; - - my $data = read_include_from_file($which); - unless($data) { - fatal_error "Can't find include file $which: $!"; - } - return $data; -} - -sub loadinclude($) { - my $which = shift; - - # don't bother loading it again if we already have - return 0 if $include{$which}{$which}; - - my @loadincludes = ($which); - while (my $incfile = shift @loadincludes) { - - my $data = get_include_data($incfile); - my $incdata = parse_profile_data($data, $incfile, 1); - if ($incdata) { - attach_profile_data(\%include, $incdata); - } - } - return 0; -} - -sub rematchfrag ($$$) { - my ($frag, $allow, $path) = @_; - - my $combinedmode = 0; - my $combinedaudit = 0; - my @matches; - - for my $entry (keys %{ $frag->{$allow}{path} }) { - - my $regexp = convert_regexp($entry); - - # check the log entry against our converted regexp... - if ($path =~ /^$regexp$/) { - - # regexp matches, add it's mode to the list to check against - $combinedmode |= $frag->{$allow}{path}{$entry}{mode}; - $combinedaudit |= $frag->{$allow}{path}{$entry}{audit}; - push @matches, $entry; - } - } - - return wantarray ? ($combinedmode, $combinedaudit, @matches) : $combinedmode; -} - -sub match_include_to_path ($$$) { - my ($incname, $allow, $path) = @_; - - my $combinedmode = 0; - my $combinedaudit = 0; - my @matches; - - my @includelist = ( $incname ); - while (my $incfile = shift @includelist) { - my $ret = eval { loadinclude($incfile); }; - if ($@) { fatal_error $@; } - my ($cm, $am, @m) = rematchfrag($include{$incfile}{$incfile}, $allow, $path); - if ($cm) { - $combinedmode |= $cm; - $combinedaudit |= $am; - push @matches, @m; - } - - # check if a literal version is in the current include fragment - if ($include{$incfile}{$incfile}{$allow}{path}{$path}) { - $combinedmode |= $include{$incfile}{$incfile}{$allow}{path}{$path}{mode}; - $combinedaudit |= $include{$incfile}{$incfile}{$allow}{path}{$path}{audit}; - } - - # if this fragment includes others, check them too - if (keys %{ $include{$incfile}{$incfile}{include} }) { - push @includelist, keys %{ $include{$incfile}{$incfile}{include} }; - } - } - - return wantarray ? ($combinedmode, $combinedaudit, @matches) : $combinedmode; -} - -sub match_prof_incs_to_path ($$$) { - my ($frag, $allow, $path) = @_; - - my $combinedmode = 0; - my $combinedaudit = 0; - my @matches; - - # scan the include fragments for this profile looking for matches - my @includelist = keys %{ $frag->{include} }; - while (my $include = shift @includelist) { - my ($cm, $am, @m) = match_include_to_path($include, $allow, $path); - if ($cm) { - $combinedmode |= $cm; - $combinedaudit |= $am; - push @matches, @m; - } - } - - return wantarray ? ($combinedmode, $combinedaudit, @matches) : $combinedmode; -} - -#find includes that match the path to suggest -sub suggest_incs_for_path($$$) { - my ($incname, $path, $allow) = @_; - - - my $combinedmode = 0; - my $combinedaudit = 0; - my @matches; - - # scan the include fragments looking for matches - my @includelist = ($incname); - while (my $include = shift @includelist) { - my ($cm, $am, @m) = rematchfrag($include{$include}{$include}, 'allow', $path); - if ($cm) { - $combinedmode |= $cm; - $combinedaudit |= $am; - push @matches, @m; - } - - # check if a literal version is in the current include fragment - if ($include{$include}{$include}{allow}{path}{$path}) { - $combinedmode |= $include{$include}{$include}{allow}{path}{$path}{mode}; - $combinedaudit |= $include{$include}{$include}{allow}{path}{$path}{audit}; - } - - # if this fragment includes others, check them too - if (keys %{ $include{$include}{$include}{include} }) { - push @includelist, keys %{ $include{$include}{$include}{include} }; - } - } - - if ($combinedmode) { - return wantarray ? ($combinedmode, $combinedaudit, @matches) : $combinedmode; - } else { - return; - } -} - -sub check_qualifiers($) { - my $program = shift; - - if ($cfg->{qualifiers}{$program}) { - unless($cfg->{qualifiers}{$program} =~ /p/) { - fatal_error(sprintf(gettext("\%s is currently marked as a program that should not have it's own profile. Usually, programs are marked this way if creating a profile for them is likely to break the rest of the system. If you know what you're doing and are certain you want to create a profile for this program, edit the corresponding entry in the [qualifiers] section in /etc/apparmor/logprof.conf."), $program)); - } - } -} - -sub loadincludes() { - if (opendir(SDDIR, $profiledir)) { - my @incdirs = grep { (!/^\./) && (-d "$profiledir/$_") } readdir(SDDIR); - close(SDDIR); - - while (my $id = shift @incdirs) { - next if isSkippableDir($id); - if (opendir(SDDIR, "$profiledir/$id")) { - for my $path (readdir(SDDIR)) { - chomp($path); - next if isSkippableFile($path); - if (-f "$profiledir/$id/$path") { - my $file = "$id/$path"; - $file =~ s/$profiledir\///; - my $ret = eval { loadinclude($file); }; - if ($@) { fatal_error $@; } - } elsif (-d "$id/$path") { - push @incdirs, "$id/$path"; - } - } - closedir(SDDIR); - } - } - } -} - -sub globcommon ($) { - my $path = shift; - - my @globs; - - # glob library versions in both foo-5.6.so and baz.so.9.2 form - if ($path =~ m/[\d\.]+\.so$/ || $path =~ m/\.so\.[\d\.]+$/) { - my $libpath = $path; - $libpath =~ s/[\d\.]+\.so$/*.so/; - $libpath =~ s/\.so\.[\d\.]+$/.so.*/; - push @globs, $libpath if $libpath ne $path; - } - - for my $glob (keys %{$cfg->{globs}}) { - if ($path =~ /$glob/) { - my $globbedpath = $path; - $globbedpath =~ s/$glob/$cfg->{globs}{$glob}/g; - push @globs, $globbedpath if $globbedpath ne $path; - } - } - - if (wantarray) { - return sort { length($b) <=> length($a) } uniq(@globs); - } else { - my @list = sort { length($b) <=> length($a) } uniq(@globs); - return $list[$#list]; - } -} - -# this is an ugly, nasty function that attempts to see if one regexp -# is a subset of another regexp -sub matchregexp ($$) { - my ($new, $old) = @_; - - # bail out if old pattern has {foo,bar,baz} stuff in it - return undef if $old =~ /\{.*(\,.*)*\}/; - - # are there any regexps at all in the old pattern? - if ($old =~ /\[.+\]/ or $old =~ /\*/ or $old =~ /\?/) { - - # convert {foo,baz} to (foo|baz) - $new =~ y/\{\}\,/\(\)\|/ if $new =~ /\{.*\,.*\}/; - - # \001 == SD_GLOB_RECURSIVE - # \002 == SD_GLOB_SIBLING - - $new =~ s/\*\*/\001/g; - $new =~ s/\*/\002/g; - - $old =~ s/\*\*/\001/g; - $old =~ s/\*/\002/g; - - # strip common prefix - my $prefix = commonprefix($new, $old); - if ($prefix) { - - # make sure we don't accidentally gobble up a trailing * or ** - $prefix =~ s/(\001|\002)$//; - $new =~ s/^$prefix//; - $old =~ s/^$prefix//; - } - - # strip common suffix - my $suffix = commonsuffix($new, $old); - if ($suffix) { - - # make sure we don't accidentally gobble up a leading * or ** - $suffix =~ s/^(\001|\002)//; - $new =~ s/$suffix$//; - $old =~ s/$suffix$//; - } - - # if we boiled the differences down to a ** in the new entry, it matches - # whatever's in the old entry - return 1 if $new eq "\001"; - - # if we've paired things down to a * in new, old matches if there are no - # slashes left in the path - return 1 if ($new eq "\002" && $old =~ /^[^\/]+$/); - - # we'll bail out if we have more globs in the old version - return undef if $old =~ /\001|\002/; - - # see if we can match * globs in new against literal elements in old - $new =~ s/\002/[^\/]*/g; - - return 1 if $old =~ /^$new$/; - - } else { - - my $new_regexp = convert_regexp($new); - - # check the log entry against our converted regexp... - return 1 if $old =~ /^$new_regexp$/; - - } - - return undef; -} - -sub combine_name($$) { return ($_[0] eq $_[1]) ? $_[0] : "$_[0]^$_[1]"; } -sub split_name ($) { my ($p, $h) = split(/\^/, $_[0]); $h ||= $p; ($p, $h); } - -########################## -# -# prompt_user($headers, $functions, $default, $options, $selected); -# -# $headers: -# a required arrayref made up of "key, value" pairs in the order you'd -# like them displayed to user -# -# $functions: -# a required arrayref of the different options to display at the bottom -# of the prompt like "(A)llow", "(D)eny", and "Ba(c)on". the character -# contained by ( and ) will be used as the key to select the specified -# option. -# -# $default: -# a required character which is the default "key" to enter when they -# just hit enter -# -# $options: -# an optional arrayref of the choices like the glob suggestions to be -# presented to the user -# -# $selected: -# specifies which option is currently selected -# -# when prompt_user() is called without an $options list, it returns a -# single value which is the key for the specified "function". -# -# when prompt_user() is called with an $options list, it returns an array -# of two elements, the key for the specified function as well as which -# option was currently selected -####################################################################### - -sub Text_PromptUser ($) { - my $question = shift; - - my $title = $question->{title}; - my $explanation = $question->{explanation}; - - my @headers = (@{ $question->{headers} }); - my @functions = (@{ $question->{functions} }); - - my $default = $question->{default}; - my $options = $question->{options}; - my $selected = $question->{selected} || 0; - - my $helptext = $question->{helptext}; - - push @functions, "CMD_HELP" if $helptext; - - my %keys; - my @menu_items; - for my $cmd (@functions) { - - # make sure we know about this particular command - my $cmdmsg = "PromptUser: " . gettext("Unknown command") . " $cmd"; - fatal_error $cmdmsg unless $CMDS{$cmd}; - - # grab the localized text to use for the menu for this command - my $menutext = gettext($CMDS{$cmd}); - - # figure out what the hotkey for this menu item is - my $menumsg = "PromptUser: " . - gettext("Invalid hotkey in") . - " '$menutext'"; - $menutext =~ /\((\S)\)/ or fatal_error $menumsg; - - # we want case insensitive comparisons so we'll force things to - # lowercase - my $key = lc($1); - - # check if we're already using this hotkey for this prompt - my $hotkeymsg = "PromptUser: " . - gettext("Duplicate hotkey for") . - " $cmd: $menutext"; - fatal_error $hotkeymsg if $keys{$key}; - - # keep track of which command they're picking if they hit this hotkey - $keys{$key} = $cmd; - - if ($default && $default eq $cmd) { - $menutext = "[$menutext]"; - } - - push @menu_items, $menutext; - } - - # figure out the key for the default option - my $default_key; - if ($default && $CMDS{$default}) { - my $defaulttext = gettext($CMDS{$default}); - - # figure out what the hotkey for this menu item is - my $defmsg = "PromptUser: " . - gettext("Invalid hotkey in default item") . - " '$defaulttext'"; - $defaulttext =~ /\((\S)\)/ or fatal_error $defmsg; - - # we want case insensitive comparisons so we'll force things to - # lowercase - $default_key = lc($1); - - my $defkeymsg = "PromptUser: " . - gettext("Invalid default") . - " $default"; - fatal_error $defkeymsg unless $keys{$default_key}; - } - - my $widest = 0; - my @poo = @headers; - while (my $header = shift @poo) { - my $value = shift @poo; - $widest = length($header) if length($header) > $widest; - } - $widest++; - - my $format = '%-' . $widest . "s \%s\n"; - - my $function_regexp = '^('; - $function_regexp .= join("|", keys %keys); - $function_regexp .= '|\d' if $options; - $function_regexp .= ')$'; - - my $ans = "XXXINVALIDXXX"; - while ($ans !~ /$function_regexp/i) { - # build up the prompt... - my $prompt = "\n"; - - $prompt .= "= $title =\n\n" if $title; - - if (@headers) { - my @poo = @headers; - while (my $header = shift @poo) { - my $value = shift @poo; - $prompt .= sprintf($format, "$header:", $value); - } - $prompt .= "\n"; - } - - if ($explanation) { - $prompt .= "$explanation\n\n"; - } - - if ($options) { - for (my $i = 0; $options->[$i]; $i++) { - my $f = ($selected == $i) ? ' [%d - %s]' : ' %d - %s '; - $prompt .= sprintf("$f\n", $i + 1, $options->[$i]); - } - $prompt .= "\n"; - } - $prompt .= join(" / ", @menu_items); - print "$prompt\n"; - - # get their input... - $ans = lc(getkey()); - - if ($ans) { - # handle escape sequences so you can up/down in the list - if ($ans eq "up") { - - if ($options && ($selected > 0)) { - $selected--; - } - $ans = "XXXINVALIDXXX"; - - } elsif ($ans eq "down") { - - if ($options && ($selected < (scalar(@$options) - 1))) { - $selected++; - } - $ans = "XXXINVALIDXXX"; - - } elsif ($keys{$ans} && $keys{$ans} eq "CMD_HELP") { - - print "\n$helptext\n"; - $ans = "XXXINVALIDXXX"; - - } elsif (ord($ans) == 10) { - - # pick the default if they hit return... - $ans = $default_key; - - } elsif ($options && ($ans =~ /^\d$/)) { - - # handle option poo - if ($ans > 0 && $ans <= scalar(@$options)) { - $selected = $ans - 1; - } - $ans = "XXXINVALIDXXX"; - } - } - - if ($keys{$ans} && $keys{$ans} eq "CMD_HELP") { - print "\n$helptext\n"; - $ans = "again"; - } - } - - # pull our command back from our hotkey map - $ans = $keys{$ans} if $keys{$ans}; - return ($ans, $selected); - -} - -# Parse event record into key-value pairs -sub parse_event($) { - my %ev = (); - my $msg = shift; - chomp($msg); - my $event = LibAppArmor::parse_record($msg); - my ($rmask, $dmask); - - $DEBUGGING && debug("parse_event: $msg"); - - $ev{'resource'} = LibAppArmor::aa_log_record::swig_info_get($event); - $ev{'active_hat'} = LibAppArmor::aa_log_record::swig_active_hat_get($event); - $ev{'sdmode'} = LibAppArmor::aa_log_record::swig_event_get($event); - $ev{'time'} = LibAppArmor::aa_log_record::swig_epoch_get($event); - $ev{'operation'} = LibAppArmor::aa_log_record::swig_operation_get($event); - $ev{'profile'} = LibAppArmor::aa_log_record::swig_profile_get($event); - $ev{'name'} = LibAppArmor::aa_log_record::swig_name_get($event); - $ev{'name2'} = LibAppArmor::aa_log_record::swig_name2_get($event); - $ev{'attr'} = LibAppArmor::aa_log_record::swig_attribute_get($event); - $ev{'parent'} = LibAppArmor::aa_log_record::swig_parent_get($event); - $ev{'pid'} = LibAppArmor::aa_log_record::swig_pid_get($event); - $ev{'task'} = LibAppArmor::aa_log_record::swig_task_get($event); - $ev{'info'} = LibAppArmor::aa_log_record::swig_info_get($event); - $dmask = LibAppArmor::aa_log_record::swig_denied_mask_get($event); - $rmask = LibAppArmor::aa_log_record::swig_requested_mask_get($event); - $ev{'magic_token'} = - LibAppArmor::aa_log_record::swig_magic_token_get($event); - - # NetDomain - if ( $ev{'operation'} && optype($ev{'operation'}) eq "net" ) { - $ev{'family'} = - LibAppArmor::aa_log_record::swig_net_family_get($event); - $ev{'protocol'} = - LibAppArmor::aa_log_record::swig_net_protocol_get($event); - $ev{'sock_type'} = - LibAppArmor::aa_log_record::swig_net_sock_type_get($event); - } - - LibAppArmor::free_record($event); - - if ($ev{'operation'} && $ev{'operation'} =~ /^(capable|dbus|mount|pivotroot|umount)/) { - $DEBUGGING && debug("parser_event: previous event IGNORED"); - return( undef ); - } - - #map new c and d to w as logprof doesn't support them yet - if ($rmask) { - $rmask =~ s/c/w/g; - $rmask =~ s/d/w/g; - } - if ($dmask) { - $dmask =~ s/c/w/g; - $dmask =~ s/d/w/g; - } - - if ($rmask && !validate_log_mode(hide_log_mode($rmask))) { - fatal_error(sprintf(gettext('Log contains unknown mode %s.'), - $rmask)); - } - - if ($dmask && !validate_log_mode(hide_log_mode($dmask))) { - fatal_error(sprintf(gettext('Log contains unknown mode %s.'), - $dmask)); - } -#print "str_to_mode deny $dmask = " . str_to_mode($dmask) . "\n" if ($dmask); -#print "str_to_mode req $rmask = " . str_to_mode($rmask) . "\n" if ($rmask); - - my ($mask, $name); - ($mask, $name) = log_str_to_mode($ev{profile}, $dmask, $ev{name2}); - $ev{'denied_mask'} = $mask; - $ev{name2} = $name; - - ($mask, $name) = log_str_to_mode($ev{profile}, $rmask, $ev{name2}); - $ev{'request_mask'} = $mask; - $ev{name2} = $name; - - if ( ! $ev{'time'} ) { $ev{'time'} = time; } - - # remove null responses - for (keys(%ev)) { - if ( ! $ev{$_} || $ev{$_} !~ /[\/\w]+/) { delete($ev{$_}); } - } - - if ( $ev{'sdmode'} ) { - #0 = invalid, 1 = error, 2 = AUDIT, 3 = ALLOW/PERMIT, - #4 = DENIED/REJECTED, 5 = HINT, 6 = STATUS/config change - if ( $ev{'sdmode'} == 0 ) { $ev{'sdmode'} = "UNKNOWN"; } - elsif ( $ev{'sdmode'} == 1 ) { $ev{'sdmode'} = "ERROR"; } - elsif ( $ev{'sdmode'} == 2 ) { $ev{'sdmode'} = "AUDITING"; } - elsif ( $ev{'sdmode'} == 3 ) { $ev{'sdmode'} = "PERMITTING"; } - elsif ( $ev{'sdmode'} == 4 ) { $ev{'sdmode'} = "REJECTING"; } - elsif ( $ev{'sdmode'} == 5 ) { $ev{'sdmode'} = "HINT"; } - elsif ( $ev{'sdmode'} == 6 ) { $ev{'sdmode'} = "STATUS"; } - else { delete($ev{'sdmode'}); } - } - if ( $ev{sdmode} ) { - $DEBUGGING && debug( Data::Dumper->Dump([%ev], [qw(*event)])); - return \%ev; - } else { - return( undef ); - } -} - -############################################################################### -# required initialization - -$cfg = read_config("logprof.conf"); -if ((not defined $cfg->{settings}{default_owner_prompt})) { - $cfg->{settings}{default_owner_prompt} = 0; -} - -$profiledir = find_first_dir($cfg->{settings}{profiledir}) || "/etc/apparmor.d"; -unless (-d $profiledir) { fatal_error "Can't find AppArmor profiles."; } - -$extraprofiledir = find_first_dir($cfg->{settings}{inactive_profiledir}) || -"/usr/share/apparmor/extra-profiles/"; - -$parser = find_first_file($cfg->{settings}{parser}) || "/sbin/apparmor_parser"; -unless (-x $parser) { fatal_error "Can't find apparmor_parser."; } - -$filename = find_first_file($cfg->{settings}{logfiles}) || "/var/log/syslog"; -unless (-f $filename) { fatal_error "Can't find system log."; } - -$ldd = find_first_file($cfg->{settings}{ldd}) || "/usr/bin/ldd"; -unless (-x $ldd) { fatal_error "Can't find ldd."; } - -$logger = find_first_file($cfg->{settings}{logger}) || "/bin/logger"; -unless (-x $logger) { fatal_error "Can't find logger."; } - -1; - diff --git a/deprecated/utils/Immunix/Config.pm b/deprecated/utils/Immunix/Config.pm deleted file mode 100644 index 0869a5e01..000000000 --- a/deprecated/utils/Immunix/Config.pm +++ /dev/null @@ -1,124 +0,0 @@ -# ---------------------------------------------------------------------- -# Copyright (c) 2006 Novell, Inc. 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 as 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. -# -# To contact Novell about this file by physical or electronic mail, -# you may find current contact information at www.novell.com. -# ---------------------------------------------------------------------- - -package Immunix::Config; - -use strict; -use warnings; - -use Carp; -use Cwd qw(cwd realpath); -use File::Basename; -use File::Temp qw/ tempfile tempdir /; -use Data::Dumper; -use Locale::gettext; -use POSIX; - -require Exporter; -our @ISA = qw(Exporter); -our @EXPORT = qw( - read_config - write_config - find_first_file - find_first_dir -); - -our $confdir = "/etc/apparmor"; - -# config vars -our $cfg; -our $repo_cfg; - -sub read_config { - my $filename = shift; - my $config; - - if (open(CONF, "$confdir/$filename")) { - my $which; - while () { - chomp; - # ignore comments - next if /^\s*#/; - if (m/^\[(\S+)\]/) { - $which = $1; - } elsif (m/^\s*(\S+)\s*=\s*(.*)\s*$/) { - my ($key, $value) = ($1, $2); - $config->{$which}{$key} = $value; - } - } - close(CONF); - } - - # LP: #692406 - # Explicitly disable the repository until there is an alternative, since - # the OpenSUSE site went away - if ($filename eq "repository.conf") { - $config->{repository}{enabled} = "no"; - } - - return $config; -} - -sub write_config { - my ($filename, $config) = @_; - if (open(my $CONF, ">$confdir/$filename")) { - for my $section (sort keys %$config) { - print $CONF "[$section]\n"; - - for my $key (sort keys %{$config->{$section}}) { - print $CONF " $key = $config->{$section}{$key}\n" - if ($config->{$section}{$key}); - } - } - chmod(0600, $CONF); - close($CONF); - } else { - die "Can't write config file $filename: $!"; - } -} - -sub find_first_file { - my $list = shift; - return if ( not defined $list ); - my $filename; - for my $f (split(/\s+/, $list)) { - if (-f $f) { - $filename = $f; - last; - } - } - - return $filename; -} - -sub find_first_dir { - my $list = shift; - return if ( not defined $list ); - my $dirname; - for my $f (split(/\s+/, $list)) { - if (-d $f) { - $dirname = $f; - last; - } - } - - return $dirname; -} - -1; diff --git a/deprecated/utils/Immunix/Reports.pm b/deprecated/utils/Immunix/Reports.pm deleted file mode 100755 index 47fbd08e8..000000000 --- a/deprecated/utils/Immunix/Reports.pm +++ /dev/null @@ -1,2024 +0,0 @@ -# ------------------------------------------------------------------ -# -# Copyright (C) 2005-2006 Novell/SUSE -# -# 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. -# -# ------------------------------------------------------------------ - -package Immunix::Reports; - -################################################################################ -# /usr/lib/perl5/site_perl/Reports.pm -# -# - Parses /var/log/messages for AppArmor messages -# - Writes results to .html or comma-delimited (.csv) files (Optional) -# -# Requires: -# Immunix::Events; -# Time::Local (temporary) -# -# Input (Optional): -# -Start Date|End Date (Month, Day, Year, Time) -# -Program Name -# -Profile Name -# -PID -# -Denied Resources -# -################################################################################ - -use strict; - -use DBI; -use DBD::SQLite; -use Locale::gettext; -use POSIX; -use ycp; - -setlocale(LC_MESSAGES, ""); -textdomain("Reports"); - -my $eventDb = '/var/log/apparmor/events.db'; -my $numEvents = 1000; - -sub YcpDebug ($$) { - - my $argList = ""; - #my ($script, $args) = @_; - my $script = shift; - my $args = shift; - - if ($args && ref($args) eq "HASH") { - - for (sort keys(%$args) ) { - $argList .= "$_ is ..$args->{$_}.., " if $args->{$_}; - } - - } elsif ($args && ref($args) eq "ARRAY") { - - for my $row (@$args) { - for (sort keys(%$row) ) { - $argList .= "$_ is ..$row->{$_}.., " if $row->{$_}; - } - } - } elsif ( $args ) { - $argList = $args; - } else { - my $prob = ref($args); - $argList = "Type not supported for printing debug: $prob"; - } - - ycp::y2milestone("[apparmor $script] vars: $argList"); - -} - -sub month2Num { - my $lexMon = shift; - my $months = { - "Jan" => '01', - "Feb" => '02', - "Mar" => '03', - "Apr" => '04', - "May" => '05', - "Jun" => '06', - "Jul" => '07', - "Aug" => '08', - "Sep" => '09', - "Oct" => '10', - "Nov" => '11', - "Dec" => '12' - }; - - my $numMonth = $months->{$lexMon}; - - return $numMonth; -} - -sub num2Month { - my $monthNum = shift; - - my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); - my $lexMonth = $months[ ($monthNum - 1) ]; - - return $lexMonth; -} - -# Converts Epoch Time to Formatted Date String -sub getDate { - my $epTime = shift; - - my $date = localtime($epTime); - - my ($day, $mon, $mondate, $time, $year) = split(/\s+/, $date); - my ($hour, $min, $sec) = split(/:/, $time); - - $mon = month2Num($mon); - - # we want 2 digits for easier reading - $mon = sprintf("%02d", $mon); - $mondate = sprintf("%02d", $mondate); - - my $newDate = "$year-$mon-$mondate $time"; - return $newDate; -} - -sub round { - my $num = shift; - $num = sprintf("%.2f", $num); - return ("$num"); -} - -# round up -sub pageRound { - my $num = shift; - my $pnum = int($num); - - if ($pnum < $num) { - $pnum++; - } - - return $pnum; -} - -sub checkFileExists { - my $file = shift; - - if ($file && -e $file) { - return 1; - } else { - return 0; - } -} - -sub getNetSockList { - - my @netsockList = ( - "All", "UNIX Domain Sockets", "IP v4", "Radio AX.25", - "Novell IPX", "Appletalk", "Radio NET/ROM", - "Multiprotocol Bridge", "ATM PVC", "X.25", "IP v6", - "Radio X.25 PLP", "DECnet", "NetBEUI", "Security Callback", - "PF Key Management", "Netlink", "Packet", "Ash", - "Econet", "ATM SVC", "Filler (Ignore)", "Linux SNA", - "IRDA", "PPPoX", "Wanpipe", "Linux LLC", - "Filler (Ignore)", "Filler (Ignore)", "Filler (Ignore)", "TIPC", - "Bluetooth", "IUCV", "RxRPC" - ); - - - return \@netsockList; -} - -sub netsock_name2num { - - my $sockName = shift; - my $sockNum = ''; - my $netsockList = getNetSockList(); - - my $i; - - for ($i = 0; $i < @$netsockList; $i++) { - last if $_ eq @$netsockList[$i]; - } - - if ($i < @$netsockList) { - $sockNum = $i; - } - - return $sockNum; -} - -sub netsock_num2name { - - my $sockNum = shift; - my $sockName = undef; - my $netsockList = getNetSockList(); - - if ( $sockNum < @$netsockList) { - $sockName = @$netsockList[$sockNum]; - } - - if ( $sockName eq "Filler (Ignore)" ) { - $sockName = undef; - } - - return $sockName; -} - -sub matchFailed ($$) { - - my $args = shift; - my $rec = shift; - - # Check filters - if ($args->{'pid'} && $args->{'pid'} ne '-') { - return(1) unless ($args->{'pid'} eq $rec->{'pid'}); - } - if ( $args->{'severity'} - && $args->{'severity'} ne "00" - && $args->{'severity'} ne '-') - { - if ($args->{'severity'} eq "U") { $args->{'severity'} = '-1'; } - return(1) unless ($args->{'severity'} eq $rec->{'severity'}); - } - if ($args->{'mode_deny'} && $args->{'mode_deny'} ne '-') { - return(1) unless ($args->{'mode_deny'} eq $rec->{'mode_deny'}); - } - if ($args->{'mode_req'} && $args->{'mode_req'} ne '-') { - return(1) unless ($args->{'mode_req'} eq $rec->{'mode_req'}); - } - - if ($args->{'resource'} && $args->{'resource'} ne '-') { - return(1) unless ($args->{'resource'} eq $rec->{'resource'}); - } - if ($args->{'sdmode'} && $args->{'sdmode'} ne '-') { - # Needs reversal of comparison for sdmode - return(1) unless ($rec->{'sdmode'} =~ /$args->{'sdmode'}/); - } - if ($args->{'op'} && $args->{'op'} ne '-') { - return(1) unless ($args->{'op'} eq $rec->{'op'}); - } - if ($args->{'attr'} && $args->{'attr'} ne '-') { - return(1) unless ($args->{'attr'} eq $rec->{'attr'}); - } - if ($args->{'name_alt'} && $args->{'name_alt'} ne '-') { - return(1) unless ($args->{'name_alt'} eq $rec->{'name_alt'}); - } - if ($args->{'net_family'} && $args->{'net_family'} ne '-') { - return(1) unless ($args->{'net_family'} eq $rec->{'net_family'}); - } - if ($args->{'net_proto'} && $args->{'net_proto'} ne '-') { - return(1) unless ($args->{'net_proto'} eq $rec->{'net_proto'}); - } - if ($args->{'net_socktype'} && $args->{'net_socktype'} ne '-') { - return(1) unless ($args->{'net_socktype'} eq $rec->{'net_socktype'}); - } - - return 0; -} - -# Translate mode & sdmode for parsing -sub rewriteModes { - my $filts = shift; - - # Mode wrangling - Rewrite for better matches - for ('mode_req','mode_deny') { - - if ($filts->{$_} && $filts->{$_} ne "All") { - - my @mode = (); - my $tmpMode = undef; - - @mode = split(//, $filts->{$_}); - - if (@mode > 0) { - #$tmpMode = join("|", @mode); - $tmpMode = join("", @mode); - if ($tmpMode =~ /m/) { - $tmpMode =~ s/m//g; - $tmpMode = "m" . $tmpMode; - } - } else { - delete($filts->{$_}); - } - - if ($tmpMode) { - $filts->{$_} = $tmpMode; - } - } - } - - # Rewrite sdmode for more flexible matches - if ($filts->{'sdmode'} && $filts->{'sdmode'} ne "All") { - my @tmpMode = (); - if ($filts->{'sdmode'} =~ /[pP]/) { push(@tmpMode, 'PERMIT'); } - if ($filts->{'sdmode'} =~ /[rR]/) { push(@tmpMode, 'REJECT'); } - if ($filts->{'sdmode'} =~ /[aA]/) { push(@tmpMode, 'AUDIT'); } - if (@tmpMode > 0) { - $filts->{'sdmode'} = join('|', @tmpMode); - } else { - delete($filts->{'sdmode'}); - } - } - - return $filts; -} - -sub getFilterList ($) { - - my $args = shift; - my $filts = undef; - - if ($args->{'prog'}) { $filts->{'prog'} = $args->{'prog'}; } - if ($args->{'profile'}) { $filts->{'profile'} = $args->{'profile'}; } - if ($args->{'pid'}) { $filts->{'pid'} = $args->{'pid'}; } - if ($args->{'resource'}) { $filts->{'resource'} = $args->{'resource'}; } - if ($args->{'severity'}) { $filts->{'severity'} = $args->{'severity'}; } - if ($args->{'sdmode'}) { $filts->{'sdmode'} = $args->{'sdmode'}; } - if ($args->{'mode_req'}) { $filts->{'mode_req'} = $args->{'mode_req'}; } - if ($args->{'mode_deny'}) { $filts->{'mode_deny'} = $args->{'mode_deny'}; } - if ($args->{'op'}) { $filts->{'op'} = $args->{'op'}; } - if ($args->{'attr'}) { $filts->{'attr'} = $args->{'attr'}; } - if ($args->{'name_alt'}) { $filts->{'name_alt'} = $args->{'name_alt'}; } - if ($args->{'net_family'}) { $filts->{'net_family'} = $args->{'net_family'}; } - if ($args->{'net_proto'}) { $filts->{'net_proto'} = $args->{'net_proto'}; } - if ($args->{'net_socktype'}) { $filts->{'net_socktype'} = $args->{'net_socktype'}; } - - for (sort(keys(%$filts))) { - if ($filts->{$_} eq '-' || $filts->{$_} eq 'All') { - delete($filts->{$_}); - } - } - return $filts; -} - -sub enableEventD { - - # make sure the eventd is enabled before we do any reports - my $need_enable = 0; - if (open(SDCONF, "/etc/apparmor/subdomain.conf")) { - while () { - if (/^\s*APPARMOR_ENABLE_AAEVENTD\s*=\s*(\S+)\s*$/) { - my $flag = lc($1); - - # strip quotes from the value if present - $flag = $1 if $flag =~ /^"(\S+)"$/; - $need_enable = 1 if $flag ne "yes"; - } - } - close(SDCONF); - } - - # if the eventd isn't enabled, we'll turn it on the first time they - # run a report and start it up - if something fails for some reason, - # we should just fall through and the db check should correctly tell - # the caller that the db isn't initialized correctly - if ($need_enable) { - my $old = "/etc/apparmor/subdomain.conf"; - my $new = "/etc/apparmor/subdomain.conf.$$"; - if (open(SDCONF, $old)) { - if (open(SDCONFNEW, ">$new")) { - my $foundit = 0; - - while () { - if (/^\s*APPARMOR_ENABLE_AAEVENTD\s*=/) { - print SDCONFNEW "APPARMOR_ENABLE_AAEVENTD=\"yes\"\n"; - - $foundit = 1; - } else { - print SDCONFNEW; - } - } - - unless ($foundit) { - print SDCONFNEW "APPARMOR_ENABLE_AAEVENTD=\"yes\"\n"; - } - - close(SDCONFNEW); - - # if we were able to overwrite the old config - # config file with the new stuff, we'll kick - # the init script to start up aa-eventd - if (rename($new, $old)) { - if (-e "/sbin/rcaaeventd") { - system("/sbin/rcaaeventd restart >/dev/null 2>&1"); - } else { - system("/sbin/rcapparmor restart >/dev/null 2>&1"); - } - } - } - close(SDCONF); - } - - } - - return $need_enable; -} - -# Check that events db exists and is populated -# - Returns 1 for good db, 0 for bad db -sub checkEventDb { - my $count = undef; - my $eventDb = '/var/log/apparmor/events.db'; - - # make sure the event daemon is enabled - if (enableEventD()) { - - my $now = time; - - # wait until the event db appears or we hit 1 min - while (!-e $eventDb) { - sleep 2; - return 0 if ((time - $now) >= 60); - } - - # wait until it stops changing or we hit 1 min - the event - # daemon flushes events to the db every five seconds. - my $last_modified = 0; - my $modified = (stat($eventDb))[9]; - while ($last_modified != $modified) { - sleep 10; - last if ((time - $now) >= 60); - $last_modified = $modified; - $modified = (stat($eventDb))[9]; - } - } - - my $query = "SELECT count(*) FROM events "; - - # Pull stuff from db - my $dbh = DBI->connect("dbi:SQLite:dbname=$eventDb", "", "", { RaiseError => 1, AutoCommit => 1 }); - - eval { - my $sth = $dbh->prepare($query); - $sth->execute; - $count = $sth->fetchrow_array(); - - $sth->finish; - }; - - if ($@) { - ycp::y2error(sprintf(gettext("DBI Execution failed: %s."), $DBI::errstr)); - return; - } - - $dbh->disconnect(); - - if ($count && $count > 0) { - return 1; - } else { - return 0; - } -} - -# Called from ag_reports_parse -sub getNumPages ($) { - my $args = shift; - my $db = (); - my $numPages = 0; - my $count = 0; - my $type = undef; - my $eventRep = "/var/log/apparmor/reports/events.rpt"; - - # Figure out whether we want db count or file parse - if ($args->{'type'}) { - if ($args->{'type'} eq 'sir' || $args->{'type'} eq 'ess-multi') { - $type = 'db'; - } elsif ($args->{'type'} eq 'ess') { - return 1; # ess reports have one page by definition - } else { - $type = 'arch'; # archived or file - } - } - - # Parse sdmode & mode labels - if ($args->{'sdmode'}) { - $args->{'sdmode'} =~ s/\&//g; - $args->{'sdmode'} =~ s/\://g; - $args->{'sdmode'} =~ s/\s//g; - $args->{'sdmode'} =~ s/AccessType//g; - - if ($args->{'sdmode'} eq "All") { - delete($args->{'sdmode'}); - } - } - - if ($args->{'mode_req'}) { - $args->{'mode_req'} =~ s/\&//g; - $args->{'mode_req'} =~ s/Mode\://g; - $args->{'mode_req'} =~ s/\s//g; - - if ($args->{'mode_req'} eq "All") { - delete($args->{'mode_req'}); - } - } - ######################################## - - $args = rewriteModes($args); - - if ($type && $type eq 'db') { - - my $start = undef; - my $end = undef; - - if ($args->{'startTime'} && $args->{'startTime'} > 0) { - $start = $args->{'startTime'}; - } - - if ($args->{'endTime'} && $args->{'endTime'} > 0) { - $end = $args->{'endTime'}; - } - - my $query = "SELECT count(*) FROM events "; - - # We need filter information for getting a correct count - my $filts = getFilterList($args); - my $midQuery = getQueryFilters($filts, $start, $end); - if ($midQuery) { $query .= "$midQuery"; } - # Pull stuff from db - my $dbh = DBI->connect("dbi:SQLite:dbname=$eventDb", "", "", { RaiseError => 1, AutoCommit => 1 }); - - eval { - my $sth = $dbh->prepare($query); - $sth->execute; - $count = $sth->fetchrow_array(); - - $sth->finish; - }; - - if ($@) { - ycp::y2error(sprintf(gettext("DBI Execution failed: %s."), $DBI::errstr)); - return; - } - - $dbh->disconnect(); - - $numPages = pageRound($count / $numEvents); - if ($numPages < 1) { $numPages = 1; } - - } elsif ($type && $type eq 'arch') { - - if (open(REP, "<$eventRep")) { - - while () { - if (/^Page/) { - $numPages++; - } else { - $count++; - } - } - - close REP; - - } else { - ycp::y2error(sprintf(gettext("Couldn't open file: %s."), $eventRep)); - } - - } else { - ycp::y2error(gettext("No type value passed. Unable to determine page count.")); - return ("1"); - } - - if ($numPages < 1) { $numPages = 1; } - - my $numCheck = int($count / $numEvents); - - if ($numPages < $numCheck) { - $numPages = $numCheck; - } - - return ($numPages); -} - -sub getEpochFromNum { - my $date = shift; - my $place = shift || undef; # Used to set default $sec if undef - - my ($numMonth, $numDay, $time, $year) = split(/\s+/, $date); - my ($hour, $min, $sec) = '0'; - my $junk = undef; - - if ($time =~ /:/) { - ($hour, $min, $sec, $junk) = split(/\:/, $time); - if (!$hour || $hour eq "") { $hour = '0'; } - if (!$min || $min eq "") { $min = '0'; } - if (!$sec || $sec eq "") { - if ($place eq 'end') { - $sec = '59'; - } else { - $sec = '0'; - } - } - } - - $numMonth--; # Months start from 0 for epoch translation - - if (!$year) { $year = (split(/\s+/, localtime))[4]; } - my $epochDate = timelocal($sec, $min, $hour, $numDay, $numMonth, $year); - - return $epochDate; -} - -sub getEpochFromStr { - my $lexDate = shift; - - my ($lexMonth, $dateDay, $fullTime, $year) = split(/\s+/, $lexDate); - my ($hour, $min, $sec) = split(/\:/, $fullTime); - - if (!$year) { $year = (split(/\s+/, localtime))[4]; } - - my $numMonth = month2Num($lexMonth); - - my $epochDate = timelocal($sec, $min, $hour, $dateDay, $numMonth, $year); - - return $epochDate; -} - -# Replaces old files with new files -sub updateFiles { - my ($oldFile, $newFile) = @_; - - if (unlink("$oldFile")) { - if (!rename("$newFile", "$oldFile")) { - if (!system('/bin/mv', "$newFile", "$oldFile")) { - ycp::y2error(sprintf(gettext("Failed copying %s."), $oldFile)); - return 1; - } - } - } else { - system('/bin/rm', "$oldFile"); - system('/bin/mv', "$newFile", "$oldFile"); - } - - return 0; -} - -# This is a holder, that was originally part of exportLog() -# Used by /usr/bin/reportgen.pl -sub exportFormattedText { - my ($repName, $logFile, $db) = @_; - - my $date = localtime; - open(LOG, ">$logFile") || die "Couldn't open $logFile"; - - print LOG "$repName: Log generated by Novell AppArmor, $date\n\n"; - printf LOG "%-21s%-32s%-8s%-51s", "Host", "Date", "Program", "Profile", - "PID", "Severity", "Mode Deny", "Mode Request","Detail", "Access Type", - "Operation", "Attribute", "Additional Name", "Parent", "Active Hat", - "Net Family", "Net Protocol", "Net Socket Type"; - - print LOG "\n"; - - for (sort (@$db)) { - print LOG "$_->{'host'},$_->{'time'},$_->{'prog'},$_->{'profile'},"; - print LOG "$_->{'pid'},$_->{'severity'},$_->{'mode_deny'},$_->{'mode_req'},"; - print LOG "$_->{'resource'},$_->{'sdmode'},$_->{'op'},$_->{'attr'},"; - print LOG "$_->{'name_alt'},$_->{'parent'},$_->{'active_hat'},"; - print LOG "$_->{'net_family'},$_->{'net_proto'},$_->{'net_socktype'}\n"; - } - - close LOG; -} - -sub exportLog { - - my ($exportLog, $db, $header) = @_; - - return unless $db; - - if (open(LOG, ">$exportLog")) { - - my $date = localtime(); - - if ($exportLog =~ /csv/) { - - # $header comes from reportgen.pl (scheduled reports) - if ($header) { print LOG "$header\n\n"; } - - for (@$db) { - print LOG "$_->{'host'},$_->{'time'},$_->{'prog'},$_->{'profile'},"; - print LOG "$_->{'pid'},$_->{'severity'},$_->{'mode_deny'},$_->{'mode_req'},"; - print LOG "$_->{'resource'},$_->{'sdmode'},$_->{'op'},$_->{'attr'},"; - print LOG "$_->{'name_alt'},$_->{'parent'},$_->{'active_hat'},"; - print LOG "$_->{'net_family'},$_->{'net_proto'},$_->{'net_socktype'}\n"; - } - - } elsif ($exportLog =~ /html/) { - - print LOG "\n\n"; - print LOG "\n"; - - # $header comes from reportgen.pl (scheduled reports) - if ($header) { - print LOG "$header\n\n"; - } else { - print LOG "

$exportLog


\n

Log generated by Novell AppArmor, $date

\n\n"; - } - - print LOG "

\n"; - - print LOG "" . - "" . - "" . - "" . - "\n"; - - my $idx = 1; - - for (@$db) { - $idx++; - - my $logLine = - "" - . "" - . "" - . "" - . "" - . "" - . "" - . "" - . "" - . "" - . "" - . "" - . "" - . "" - . "" - . "" - . ""; - - my $plainCell = ""; - my $shadedCell = ""; - my $logLinePlain = $plainCell . $logLine; - my $logLineShaded = $shadedCell . $logLine; - - if ($idx % 2 == 0) { - print LOG "$logLinePlain\n"; - } else { - # Shade every other row - print LOG "$logLineShaded\n"; - } - } - - print LOG "
HostDateProgramProfilePIDSeverityMode DenyMode RequestDetailAccess TypeOperationAttributeAdditional NameParentActive HatNet FamilyNet ProtocolNet Socket Type
 $_->{'date'}  $_->{'prog'}  $_->{'profile'}  $_->{'pid'}  $_->{'severity'}  $_->{'mode_deny'}  $_->{'mode_req'}  $_->{'resource'}  $_->{'sdmode'}  $_->{'op'}  $_->{'attr'}  $_->{'name_alt'}  $_->{'parent'}  $_->{'active_hat'}  $_->{'net_family'}  $_->{'net_proto'}  $_->{'net_socktype'} 
 $_->{'host'}  $_->{'host'} 
\n\n"; - } - - close LOG; - } else { - ycp::y2error(sprintf(gettext("Export Log Error: Couldn't open %s"), $exportLog)); - } - -} - -# Pulls info on single report from apparmor xml file -sub getXmlReport { - my ($repName, $repConf) = @_; - - my $repFlag = 0; - my %rep = (); - - if (defined($repName) && ref($repName)) { - - if ($repName->{'base'}) { - $repName = $repName->{'base'}; - } elsif ($repName->{'name'}) { - $repName = $repName->{'name'}; - } - } - - if (!$repName) { - ycp::y2error(gettext("Fatal error. No report name given. Exiting.")); - } - - if (!$repConf || !-e $repConf) { - $repConf = '/etc/apparmor/reports.conf'; - if (!-e $repConf) { - ycp::y2error( - sprintf( - gettext( - "Unable to get configuration info for %s. - Unable to find %s." - ), - $repName, - $repConf - ) - ); - exit 1; - } - } - - if (open(XML, "<$repConf")) { - - while () { - - chomp; - - if (/\/) { - - /\(.+)\<\/name\>/; - my $name = $1; - if ($name eq $repName) { - $rep{'name'} = $name; - $repFlag = 1; - } - - } elsif (/\<\/report\>/) { - - $repFlag = 0; - - } elsif ($repFlag == 1) { - if (/\s*\<\w+\s+(.*)\/\>.*$/) { - my $attrs = $1; - chomp($attrs); - my @attrlist = split(/\s+/, $attrs); - for (@attrlist) { - - #Match attributes - if (/\s*(\S+)=\"(\S+)\"/) { - $rep{$1} = $2 unless $2 eq '-'; - } - } - } elsif (/\<(\w+)\>([\w+|\/].*)\<\//) { - - if ($1) { - $rep{"$1"} = $2 unless $2 eq '-'; - } else { - ycp::y2error(sprintf(gettext("Failed to parse: %s."), $_)); - } - } - } - } - - close XML; - - } else { - ycp::y2error(sprintf(gettext("Fatal Error. Couldn't open %s."), $repConf)); - exit 1; - } - - return \%rep; -} - -# Returns info on currently confined processes -sub getCfInfo { - - my $ref = (); - my @cfDb = (); - - my $cfApp = '/usr/sbin/unconfined'; - - if (open(CF, "$cfApp |")) { - - my $host = `hostname`; - chomp($host); - - my $date = localtime; - - while () { - - my $ref = (); - my $all = undef; - $ref->{'host'} = $host; - $ref->{'date'} = $date; - chomp; - - ($ref->{'pid'}, $ref->{'prog'}, $all) = split(/\s+/, $_, 3); - $all = /\s*((not)*\s*confined\s*(by)*)/; - $ref->{'state'} = $1; - $ref->{'state'} =~ s/\s*by//g; - $ref->{'state'} =~ s/not\s+/not-/g; - ($ref->{'prof'}, $ref->{'type'}) = split(/\s+/, $_); - - if ($ref->{'prog'} eq "") { $ref->{'prog'} = "-"; } - if ($ref->{'prof'} eq "") { $ref->{'prof'} = "-"; } - if ($ref->{'pid'} eq "") { $ref->{'pid'} = "-"; } - if ($ref->{'state'} eq "") { $ref->{'state'} = "-"; } - if ($ref->{'type'} eq "") { $ref->{'type'} = "-"; } - - push(@cfDb, $ref); - } - close CF; - - } else { - my $error = sprintf(gettext("Fatal Error. Can't run %s. Exiting."), $cfApp); - ycp::y2error($error); - return $error; - } - - return (\@cfDb); -} - -# generate stats for ESS reports -sub getEssStats { - my $args = shift; - - #my ($host, $targetDir, $startdate, $enddate) = @_; - - my @hostDb = (); - my @hostList = (); - my $targetDir = undef; - my $host = undef; - my $startdate = undef; - my $enddate = undef; - - if (!$args->{'targetDir'}) { - $targetDir = '/var/log/apparmor/'; - } - - if ($args->{'host'}) { $host = $args->{'host'}; } - - if ($args->{'startdate'}) { - $startdate = $args->{'startdate'}; - } else { - $startdate = '1104566401'; # Jan 1, 2005 - } - - if ($args->{'enddate'}) { - $enddate = $args->{'enddate'}; - } else { - $enddate = time; - } - - if (!-e $targetDir) { - ycp::y2error(sprintf(gettext("Fatal Error. No directory, %s, found. Exiting."), $targetDir)); - return; - } - - # Max Sev, Ave. Sev, Num. Rejects, Start Time, End Time - my $ctQuery = "SELECT count(*) FROM events WHERE time >= $startdate AND time <= $enddate"; - - my $query = "SELECT MAX(severity), AVG(severity), COUNT(id), MIN(time), " - . "MAX(time) FROM events WHERE sdmode='REJECTING' AND " - . "time >= $startdate AND time <= $enddate"; - - # Get list of hosts to scan - if (opendir(TDIR, $targetDir)) { - - @hostList = grep(/\.db/, readdir(TDIR)); - close TDIR; - - } else { - ycp::y2error(sprintf(gettext("Fatal Error. Couldn't open %s. Exiting"), $targetDir)); - return; - } - - # Cycle through for each host - for my $eventDb (@hostList) { - - $eventDb = "$targetDir/$eventDb"; - - my $ess = undef; - my $ret = undef; - my $count = undef; - - my $dbh = DBI->connect("dbi:SQLite:dbname=$eventDb", "", "", { RaiseError => 1, AutoCommit => 1 }); - - # get hostname - my $host = undef; - my $hostQuery = "SELECT * FROM info WHERE name='host'"; - - eval { - my $sth = $dbh->prepare($hostQuery); - $sth->execute; - $host = $sth->fetchrow_array(); - $sth->finish; - }; - - if ($@) { - ycp::y2error(sprintf(gettext("DBI Execution failed: %s."), $DBI::errstr)); - return; - } - - # Get number of events - eval { - my $sth = $dbh->prepare($ctQuery); - $sth->execute; - $count = $sth->fetchrow_array(); - $sth->finish; - }; - - if ($@) { - ycp::y2error(sprintf(gettext("DBI Execution failed: %s."), $DBI::errstr)); - return; - } - - # Get rest of stats - eval { $ret = $dbh->selectall_arrayref("$query"); }; - - if ($@) { - ycp::y2error(sprintf(gettext("DBI Execution failed: %s."), $DBI::errstr)); - return; - } - - $dbh->disconnect(); - - # hostIp, startDate, endDate, sevHi, sevMean, numRejects - if ($host) { - $ess->{'host'} = $host; - } else { - $ess->{'host'} = ''; - } - - $ess->{'sevHi'} = $$ret[0]->[0]; - - if (!$ess->{'sevHi'}) { - $ess->{'sevHi'} = 0; - } - - $ess->{'sevMean'} = $$ret[0]->[1]; - - if (!$ess->{'sevMean'} || $ess->{'sevHi'} == 0) { - $ess->{'sevMean'} = 0; - } else { - $ess->{'sevMean'} = round("$ess->{'sevMean'}"); - } - - $ess->{'numRejects'} = $$ret[0]->[2]; - $ess->{'startdate'} = $$ret[0]->[3]; - $ess->{'enddate'} = $$ret[0]->[4]; - $ess->{'numEvents'} = $count; - - # Convert dates - if ($ess->{'startdate'} && $ess->{'startdate'} !~ /:/) { - $ess->{'startdate'} = Immunix::Reports::getDate($ess->{'startdate'}); - } - if ($ess->{'enddate'} && $ess->{'enddate'} !~ /:/) { - $ess->{'enddate'} = Immunix::Reports::getDate($ess->{'enddate'}); - } - - push(@hostDb, $ess); - } - - return \@hostDb; -} - -# get ESS stats for archived reports (warning -- this can be slow for large files -# debug -- not fully functional yet -sub getArchEssStats { - my $args = shift; - - my $prevTime = '0'; - my $prevDate = '0'; - my $startDate = '1104566401'; # Jan 1, 2005 - my $endDate = time; - - if ($args->{'startdate'}) { $startDate = $args->{'startdate'}; } - if ($args->{'enddate'}) { $endDate = $args->{'enddate'}; } - - # hostIp, startDate, endDate, sevHi, sevMean, numRejects - my @eventDb = getEvents("$startDate", "$endDate"); - - my @hostIdx = (); # Simple index to all hosts for quick host matching - my @hostDb = (); # Host-keyed Data for doing REJECT stats - - # Outer Loop for Raw Event db - for (@eventDb) { - - if ($_->{'host'}) { - - my $ev = $_; # current event record - - # Create new host entry, or add to existing - if (grep(/$ev->{'host'}/, @hostIdx) == 1) { - - # Inner loop, but the number of hosts should be small - for (@hostDb) { - - if ($_->{'host'} eq $ev->{'host'}) { - - # Find earliest start date - if ($_->{'startdate'} > $ev->{'date'}) { - $_->{'startdate'} = $ev->{'date'}; - } - - # tally all events reported for host - $_->{'numEvents'}++; - - if ($ev->{'sdmode'}) { - if ($ev->{'sdmode'} =~ /PERMIT/) { - $_->{'numPermits'}++; - } - if ($ev->{'sdmode'} =~ /REJECT/) { - $_->{'numRejects'}++; - } - if ($ev->{'sdmode'} =~ /AUDIT/) { - $_->{'numAudits'}++; - } - } - - # Add stats to host entry - #if ( $ev->{'severity'} && $ev->{'severity'} =~ /\b\d+\b/ ) {} - if ($ev->{'severity'} && $ev->{'severity'} != -1) { - - $_->{'sevNum'}++; - $_->{'sevTotal'} = $_->{'sevTotal'} + $ev->{'severity'}; - - if ($ev->{'severity'} > $_->{'sevHi'}) { - $_->{'sevHi'} = $ev->{'severity'}; - } - } else { - $_->{'unknown'}++; - } - } - } - - } else { - - # New host - my $rec = undef; - push(@hostIdx, $ev->{'host'}); # Add host entry to index - - $rec->{'host'} = $ev->{'host'}; - $rec->{'startdate'} = $startDate; - - #$rec->{'startdate'} = $ev->{'date'}; - - if ($endDate) { - $rec->{'enddate'} = $endDate; - } else { - $rec->{'enddate'} = time; - } - - # Add stats to host entry - if ($ev->{'sev'} && $ev->{'sev'} ne "U") { - - $rec->{'sevHi'} = $ev->{'sev'}; - $rec->{'sevTotal'} = $ev->{'sev'}; - $rec->{'sevNum'} = 1; - $rec->{'unknown'} = 0; - - } else { - - $rec->{'sevHi'} = 0; - $rec->{'sevTotal'} = 0; - $rec->{'sevNum'} = 0; - $rec->{'unknown'} = 1; - - } - - # Start sdmode stats - $rec->{'numPermits'} = 0; - $rec->{'numRejects'} = 0; - $rec->{'numAudits'} = 0; - $rec->{'numEvents'} = 1; # tally all events reported for host - - if ($ev->{'sdmode'}) { - if ($ev->{'sdmode'} =~ /PERMIT/) { $rec->{'numPermits'}++; } - if ($ev->{'sdmode'} =~ /REJECT/) { $rec->{'numRejects'}++; } - if ($ev->{'sdmode'} =~ /AUDIT/) { $rec->{'numAudits'}++; } - } - - push(@hostDb, $rec); # Add new records to host data list - } - - } else { - next; # Missing host info -- big problem - } - } # END @eventDb loop - - # Process simple REJECT-related stats (for Executive Security Summaries) - for (@hostDb) { - -# In the end, we want this info: -# - Hostname, Startdate, Enddate, # Events, # Rejects, Ave. Severity, High Severity - - if ($_->{'sevTotal'} > 0 && $_->{'sevNum'} > 0) { - $_->{'sevMean'} = round($_->{'sevTotal'} / $_->{'sevNum'}); - } else { - $_->{'sevMean'} = 0; - } - - # Convert dates - if ($_->{'startdate'} !~ /:/) { - $_->{'startdate'} = getDate($startDate); - } - if ($_->{'enddate'} !~ /:/) { - $_->{'enddate'} = getDate($_->{'enddate'}); - } - - # Delete stuff that we may use in later versions (YaST is a silly, - # silly data handler) - delete($_->{'sevTotal'}); - delete($_->{'sevNum'}); - delete($_->{'numPermits'}); - delete($_->{'numAudits'}); - delete($_->{'unknown'}); - - } - - return (\@hostDb); -} - -# special version of getEvents() for /usr/bin/reportgen.pl -sub grabEvents { - my ($rep, $start, $end) = @_; - - my $db = undef; - my $prevDate = "0"; - my $prevTime = "0"; - - my $query = "SELECT * FROM events "; - - # Clear unnecessary filters - for my $filt (%$rep) { - next unless $filt && $rep->{$filt}; - $rep->{$filt} =~ s/\s+//g; # repname won't be in here, so no spaces - if ( $rep->{$filt} eq "-" || $rep->{$filt} eq 'All' || - $rep->{$filt} eq '*' ) - { - delete($rep->{$filt}); - } - } - - $rep = rewriteModes($rep); - - # Set Dates far enough apart to get all entries (ie. no date filter) - my $startDate = '1104566401'; # Jan 1, 2005 - my $endDate = time; - - if ($start && $start > 0) { $startDate = $start; } - - if (ref($rep)) { - my $midQuery = getQueryFilters($rep, $startDate, $endDate); - $query .= "$midQuery"; - } - - $db = getEvents($query, "$startDate", "$endDate"); - - return ($db); -} - -sub getQueryFilters { - my ($filts, $start, $end) = @_; - - my $query = undef; - my $wFlag = 0; - - if ($filts) { - - # Match any requested filters or drop record - ############################################################ - for my $key(keys(%$filts)) { - - # Special case for severity - if ( $key eq 'severity' ) { - - if ($filts->{$key} eq "-" || $filts->{$key} eq "All") { - delete($filts->{$key}); - } elsif ($filts->{$key} eq "-1" - || $filts->{$key} eq "U") - { - if ($wFlag == 1) { - $query .= "AND events.severity = '-1' "; - } else { - $query .= "WHERE events.severity = '-1' "; - } - $wFlag = 1; - } else { - if ($wFlag == 1) { - $query .= "AND events.severity >= \'$filts->{$key}\' "; - } else { - $query .= "WHERE events.severity >= \'$filts->{$key}\' "; - } - $wFlag = 1; - } - - # Special case for sdmode - } elsif ($filts->{'sdmode'}) { - - if ($filts->{'sdmode'} =~ /\|/) { - - my @sdmunge = split(/\|/, $filts->{'sdmode'}); - for (@sdmunge) { $_ = "\'\%" . "$_" . "\%\'"; } - - $filts->{'sdmode'} = join(" OR events.sdmode LIKE ", @sdmunge); - - } else { - $filts->{'sdmode'} = "\'\%" . "$filts->{'sdmode'}" . "\%\'"; - } - - if ($wFlag == 1) { - $query .= "AND events.sdmode LIKE $filts->{'sdmode'} "; - } else { - $query .= "WHERE events.sdmode LIKE $filts->{'sdmode'} "; - } - $wFlag = 1; - - # All other filters - } elsif ($wFlag == 0) { - $query .= "WHERE events.$key LIKE \'\%$filts->{$key}\%\' "; - $wFlag = 1; - } else { - $query .= "AND events.$key LIKE \'\%$filts->{$key}\%\' "; - } - } - } - - if ($start && $start =~ /\d+/ && $start > 0) { - if ($wFlag == 1) { - $query .= "AND events.time >= $start "; - } else { - $query .= "WHERE events.time >= $start "; - } - $wFlag = 1; - } - - if ($end && $end =~ /\d+/ && $end > $start) { - if ($wFlag == 1) { - $query .= "AND events.time <= $end "; - } else { - $query .= "WHERE events.time <= $end "; - } - } - - return $query; -} - -sub getQuery { - my ($filts, $page, $sortKey, $numEvents) = @_; - - if (!$page || $page < 1 || $page !~ /\d+/) { $page = 1; } - if (!$sortKey) { $sortKey = 'time'; } - if (!$numEvents) { $numEvents = '1000'; } - - my $limit = (($page * $numEvents) - $numEvents); - - my $query = "SELECT * FROM events "; - - if ($filts) { - my $midQuery = getQueryFilters($filts); - if ($midQuery) { $query .= "$midQuery"; } - } - - # Finish query - $query .= "Order by $sortKey LIMIT $limit,$numEvents"; - - return $query; -} - -# Creates single hashref for the various filters -sub setFormFilters { - my $args = shift; - - my $filts = undef; - - if ($args) { - - if ($args->{'prog'}) { $filts->{'prog'} = $args->{'prog'}; } - if ($args->{'profile'}) { $filts->{'profile'} = $args->{'profile'}; } - if ($args->{'pid'}) { $filts->{'pid'} = $args->{'pid'}; } - if ($args->{'resource'}) { $filts->{'resource'} = $args->{'resource'}; } - if ($args->{'severity'}) { $filts->{'severity'} = $args->{'severity'}; } - if ($args->{'sdmode'}) { $filts->{'sdmode'} = $args->{'sdmode'}; } - if ($args->{'mode'}) { $filts->{'mode_req'} = $args->{'mode'}; } - if ($args->{'mode_req'}) { $filts->{'mode_req'} = $args->{'mode_req'}; } - if ($args->{'mode_deny'}) { $filts->{'mode_deny'} = $args->{'mode_deny'}; } - - } - - return $filts; -} - -# helper for getSirFilters() -# Makes gui-centric filters querying-friendly -sub rewriteFilters { - my $filts = shift; - - # Clear unnecessary filters - for (keys(%$filts)) { - if ($filts->{$_} eq "All") { delete($filts->{$_}); } - } - - if ($filts->{'prog'} - && ($filts->{'prog'} eq "-" || $filts->{'prog'} eq "All")) - { - delete($filts->{'prog'}); - } - if ($filts->{'profile'} && ($filts->{'profile'} eq "-")) { - delete($filts->{'profile'}); - } - if ($filts->{'pid'} && ($filts->{'pid'} eq "-")) { - delete($filts->{'pid'}); - } - if ($filts->{'severity'} && ($filts->{'severity'} eq "-")) { - delete($filts->{'severity'}); - } - if ($filts->{'resource'} && ($filts->{'resource'} eq "-")) { - delete($filts->{'resource'}); - } - - if ($filts->{'mode_req'} - && ($filts->{'mode_req'} eq "-" || $filts->{'mode_req'} eq "All")) - { - delete($filts->{'mode_req'}); - } - - if ($filts->{'sdmode'} - && ($filts->{'sdmode'} eq "-" || $filts->{'sdmode'} eq "All")) - { - delete($filts->{'sdmode'}); - } - - $filts = rewriteModes($filts); - return $filts; -} - -# returns ref to active filters for the specific SIR report -sub getSirFilters { - my $args = shift; - - my $repName = undef; - - if ($args && $args->{'name'}) { - $repName = $args->{'name'}; - } else { - $repName = "Security.Incident.Report"; - } - - my $repConf = '/etc/apparmor/reports.conf'; - my $rec = undef; - - my $filts = getXmlReport($repName); - - # Clean hash of useless refs - for (sort keys(%$filts)) { - if ($filts->{$_} eq "-") { - delete($filts->{$_}); - } - } - - # remove non-filter info - if ($filts->{'name'}) { delete($filts->{'name'}); } - if ($filts->{'exportpath'}) { delete($filts->{'exportpath'}); } - if ($filts->{'exporttype'}) { delete($filts->{'exporttype'}); } - if ($filts->{'addr1'}) { delete($filts->{'addr1'}); } - if ($filts->{'addr2'}) { delete($filts->{'addr2'}); } - if ($filts->{'addr3'}) { delete($filts->{'addr3'}); } - if ($filts->{'time'}) { delete($filts->{'time'}); } - - if (!$args->{'gui'} || $args->{'gui'} ne "1") { - $filts = rewriteModes($filts); - $filts = rewriteFilters($filts); - } - - return $filts; -} - -# Main SIR report generator -sub getEvents { - my ($query, $start, $end, $dbFile) = @_; - - my @events = (); - my $prevTime = 0; - my $prevDate = '0'; - - if (!$query || $query !~ /^SELECT/) { $query = "SELECT * FROM events"; } - if ($dbFile && -f $dbFile) { $eventDb = $dbFile; } - - my $hostName = `/bin/hostname` || 'unknown'; - chomp $hostName unless $hostName eq 'unknown'; - - if (!$start) { $start = '1104566401'; } # Give default start of 1/1/2005 - if (!$end) { $end = time; } - - # make sure they don't give us a bad range - ($start, $end) = ($end, $start) if $start > $end; - - # Events Schema - # id, time, counter, op, pid, sdmode, type, mode_deny, mode_req, - # resource, target, profile, prog, name_alt, attr, parent, active_hat, - # net_family, net_proto, net_socktype, severity - - # Pull stuff from db - my $dbh = DBI->connect("dbi:SQLite:dbname=$eventDb", "", "", { RaiseError => 1, AutoCommit => 1 }); - my $all = undef; - eval { $all = $dbh->selectall_arrayref("$query"); }; - - if ($@) { - ycp::y2error(sprintf(gettext("DBI Execution failed: %s."), $DBI::errstr)); - return; - } - - $dbh->disconnect(); - - for my $row (@$all) { - my $rec = undef; - - ($rec->{'id'},$rec->{'time'},$rec->{'counter'},$rec->{'op'},$rec->{'pid'}, - $rec->{'sdmode'},$rec->{'type'},$rec->{'mode_deny'},$rec->{'mode_req'}, - $rec->{'resource'},$rec->{'target'},$rec->{'profile'}, $rec->{'prog'}, - $rec->{'name_alt'},$rec->{'attr'},$rec->{'parent'},$rec->{'active_hat'}, - $rec->{'net_family'},$rec->{'net_proto'},$rec->{'net_socktype'}, - $rec->{'severity'}) = @$row; - - # Give empty record values a default value - if (!$rec->{'host'}) { $rec->{'host'} = $hostName; } - for (keys(%$rec)) { - if (!$rec->{$_}) { $rec->{$_} = '-'; } - } - - # Change 'time' to date - if ($rec->{'time'} && $rec->{'time'} == $prevTime) { - $rec->{'date'} = $prevDate; - } elsif ($rec->{'time'}) { - my $newDate = getDate("$rec->{'time'}"); - $rec->{'date'} = $newDate; - $prevDate = $newDate; - $prevTime = $rec->{'time'}; - } else { - $rec->{'date'} = "0000-00-00-00:00:00"; - } - - if ($rec->{'severity'} && $rec->{'severity'} eq '-1') { - $rec->{'severity'} = 'U'; - } - - delete($rec->{'time'}); - delete($rec->{'counter'}); - - push(@events, $rec); - } - - return \@events; -} - -# Archived Reports Stuff -- Some of this would go away in an ideal world -################################################################################ -sub getArchReport { - my $args = shift; - my @rec = (); - my $eventRep = "/var/log/apparmor/reports/events.rpt"; - - if ($args->{'logFile'}) { - $eventRep = $args->{'logFile'}; - } - - if (open(REP, "<$eventRep")) { - - my $page = 1; - - if ($args->{'page'}) { $page = $args->{'page'}; } - - my $id = 1; - my $slurp = 0; - - my $prevTime = undef; - my $prevDate = undef; - - while () { - - my $db = (); - - # Why not get rid of page and just do divide by $i later? - if (/Page/) { - chomp; - if ($_ eq "Page $page") { - $slurp = 1; - } else { - $slurp = 0; - } - } elsif ($slurp == 1) { - - chomp; - - ($db->{'host'},$db->{'time'},$db->{'prog'},$db->{'profile'}, - $db->{'pid'},$db->{'severity'},$db->{'mode_deny'},$db->{'mode_req'}, - $db->{'resource'},$db->{'sdmode'},$db->{'op'},$db->{'attr'}, - $db->{'name_alt'},$db->{'parent'},$db->{'active_hat'}, - $db->{'net_family'},$db->{'net_proto'},$db->{'net_socktype'}) - = split(/\,/, $_); - - # Convert epoch time to date - if ($db->{'time'} == $prevTime) { - $db->{'date'} = $prevDate; - } else { - $prevTime = $db->{'time'}; - $prevDate = getDate("$db->{'time'}"); - $db->{'date'} = $prevDate; - } - - $id++; - $db->{'date'} = $db->{'time'}; - delete $db->{'time'}; - push(@rec, $db); - } - } - - close REP; - - } else { - ycp::y2error(sprintf(gettext("Fatal Error. getArchReport() couldn't open %s"), $eventRep)); - return ("Couldn't open $eventRep"); - } - - return (\@rec); -} - -sub writeEventReport { - - my ($db, $args) = @_; # Filters for date, && regexp - my $eventRep = "/var/log/apparmor/reports/events.rpt"; - - if (open(REP, ">$eventRep")) { - - my $i = 1; - my $page = 1; - my $skip = 0; - - # Title for scheduled reports - if ($args->{'title'}) { print REP "$args->{'title'}"; } - - print REP "Page $page\n"; - $page++; - - for (@$db) { - - print REP "$_->{'host'},$_->{'time'},$_->{'prog'},$_->{'profile'},"; - print REP "$_->{'pid'},$_->{'severity'},$_->{'mode_deny'},$_->{'mode_req'},"; - print REP "$_->{'resource'},$_->{'sdmode'},$_->{'op'},$_->{'attr'},"; - print REP "$_->{'name_alt'},$_->{'parent'},$_->{'active_hat'},"; - print REP "$_->{'net_family'},$_->{'net_proto'},$_->{'net_socktype'}\n"; - - if (($i % $numEvents) == 0 && $skip == 0) { - print REP "Page $page\n"; - $page++; - $skip = 1; - } else { - $i++; - $skip = 0; - } - - } - - close REP; - - } else { - return ("Couldn't open $eventRep"); - } - - return 0; -} - -sub prepSingleLog { - my $args = shift; - - my $dir = '/var/log/apparmor/reports-archived'; - my $error = "0"; - my @errors = (); # For non-fatal errors - my @repList = (); - my $readFile = ""; - my $eventRep = "/var/log/apparmor/reports/all-reports.rpt"; # write summary here - - if ($args->{'logFile'}) { $readFile = $args->{'logFile'}; } - if ($args->{'repPath'}) { $dir = $args->{'repPath'}; } - - my @rawDb = (); - my $numPages = 1; - my $numRecords = 1; - my $skip = 0; - - # Open record compilation file - if (open(RREP, "<$dir/$readFile")) { - - if (open(WREP, ">$eventRep")) { - - $numPages++; - - while () { - - next if (/Page/); - next if /^#/; - - print WREP "$_"; - - if (($numRecords % $numEvents) == 0 && $skip == 0) { - print WREP "Page $numPages\n"; - $numPages++; - $skip = 1; - } else { - $numRecords++; - $skip = 0; - } - - } - close WREP; - } else { - $error = "Problem in prepSingleLog() - couldn't open $eventRep."; - return $error; - } - - close RREP; - - } else { - $error = "Problem in prepSingleLog() - couldn't open -$dir/$readFile-."; - return $error; - } - - return $error; -} - -# Cats files in specified directory for easier parsing -sub prepArchivedLogs { - my $args = shift; - - my $dir = '/var/log/apparmor/reports-archived'; - my $error = "0"; - my @errors = (); # For non-fatal errors - my @repList = (); - my @db = (); - my $eventRep = "/var/log/apparmor/reports/all-reports.rpt"; - - my $useFilters = 0; - - if ($args->{'logFile'}) { - $eventRep = $args->{'logFile'}; - } - - if ($args->{'repPath'}) { - $dir = $args->{'repPath'}; - } - - # Check to see if we need to use filters - if ($args->{'mode_req'} - && ($args->{'mode_req'} =~ /All/ || $args->{'mode_req'} =~ /^\s*-\s*$/)) - { - delete($args->{'mode_req'}); - } - if ($args->{'mode_deny'} - && ($args->{'mode_deny'} =~ /All/ || $args->{'mode_deny'} =~ /^\s*-\s*$/)) - { - delete($args->{'mode_deny'}); - } - - if ($args->{'sdmode'} - && ($args->{'sdmode'} =~ /All/ || $args->{'sdmode'} =~ /^\s*-\s*$/)) - { - delete($args->{'sdmode'}); - } - if ($args->{'resource'} - && ($args->{'resource'} =~ /All/ || $args->{'resource'} =~ /^\s*-\s*$/)) - { - delete($args->{'resource'}); - } - if ($args->{'severity'} - && ($args->{'severity'} =~ /All/ || $args->{'severity'} =~ /^\s*-\s*$/)) - { - delete($args->{'severity'}); - } - - my $regExp = 'prog|profile|pid|resource|mode|severity|date|op|target|attr|net_|name_alt'; - - # get list of keys - my @keyList = keys(%$args); - - # find filters in @keyList - if ( grep(/$regExp/, @keyList) == 1 ) { - $useFilters = 1; - } - - ############################################################ - - # Get list of files in archived report directory - if (opendir(RDIR, $dir)) { - - my @firstPass = grep(/csv/, readdir(RDIR)); - @repList = - grep(!/Applications.Audit|Executive.Security.Summary/, @firstPass); - close RDIR; - - } else { - $error = "Failure in prepArchivedLogs() - couldn't open $dir."; - return ($error); # debug - exit instead? - } - - my @rawDb = (); - my $numPages = 1; - my $numRecords = 1; - - # Open record compilation file - if (open(AREP, ">$eventRep")) { - - for (@repList) { - - my $file = $_; - - # Cycle through each $file in $dir - if (open(RPT, "<$dir/$file")) { - push(@rawDb, ); - close RPT; - } else { - $error = "Problem in prepArchivedLogs() - couldn't open $dir/$file."; - push(@errors, $error); - } - } - - # sort & store cat'd files - if (@rawDb > 0) { - - # Run Filters - if ($useFilters == 1) { - - my @tmpDb = parseMultiDb($args, @rawDb); - @db = sort(@tmpDb); - - } else { - @db = sort(@rawDb); - } - - my $skip = 0; - print AREP "Page $numPages\n"; - $numPages++; - - for (@db) { - - next if /^Page/; - next if /^#/; - - print AREP "$_"; - - if (($numRecords % $numEvents) == 0 && $skip == 0) { - print AREP "Page $numPages\n"; - $numPages++; - $skip = 1; - } else { - $numRecords++; - $skip = 0; - } - } - - } else { - $error = "DB created from $dir is empty."; - } - - close AREP; - - } else { - $error = "Problem in prepArchivedLogs() - couldn't open $eventRep."; - push(@errors, $error); - } - - return $error; -} - -# Similar to parseLog(), but expects @db to be passed -sub parseMultiDb { - my ($args, @db) = @_; - - my @newDb = (); - - my $error = undef; - my $startDate = undef; - my $endDate = undef; - - # deref dates for speed - if ($args->{'startdate'} && $args->{'enddate'}) { - $startDate = getEpochFromNum("$args->{'startdate'}", 'start'); - $endDate = getEpochFromNum("$args->{'enddate'}", 'end'); - } - - $args = rewriteModes($args); - - for (@db) { - - my $rec = undef; - my $line = $_; - - next if /true|false/; # avoid horrible yast bug - next if /^Page/; - next if /^#/; - chomp; - next if (!$_ || $_ eq ""); - - # Lazy filters -- maybe these should be with the rest below - if ($args->{'prog'}) { next unless /$args->{'prog'}/; } - if ($args->{'profile'}) { next unless /$args->{'profile'}/; } - - # Need (epoch) 'time' element here, do we want to store 'date' instead? - ($rec->{'host'},$rec->{'time'},$rec->{'prog'},$rec->{'profile'}, - $rec->{'pid'},$rec->{'severity'},$rec->{'mode_deny'},$rec->{'mode_req'}, - $rec->{'resource'},$rec->{'sdmode'},$rec->{'op'},$rec->{'attr'}, - $rec->{'name_alt'},$rec->{'parent'},$rec->{'active_hat'}, - $rec->{'net_family'},$rec->{'net_proto'},$rec->{'net_socktype'}) - = split(/\,/, $_); - - - # Get the time/date ref. name right. If it's $args->"time", - # the arg will be converted to a human-friendly "date" ref in writeEventReport(). - if ($rec->{'time'} =~ /\:|\-/) { - $rec->{'date'} = $rec->{'time'}; - delete $rec->{'time'}; - } - - # Check filters - next if matchFailed($args,$rec); - - push(@newDb, $line); - - } - - return @newDb; -} - -# Grab & filter events from archived reports (.csv files) -sub parseLog { - my $args = shift; - - my @db = (); - my $eventRep = "/var/log/apparmor/reports/events.rpt"; - - if ($args->{'logFile'}) { - $eventRep = $args->{'logFile'}; - } - - my $error = undef; - my $startDate = undef; - my $endDate = undef; - - # deref dates for speed - if ($args->{'startdate'} && $args->{'enddate'}) { - $startDate = getEpochFromNum("$args->{'startdate'}", 'start'); - $endDate = getEpochFromNum("$args->{'enddate'}", 'end'); - } - - if ($args->{'mode_req'} - && ($args->{'mode_req'} =~ /All/ || $args->{'mode_req'} =~ /^\s*-\s*$/)) - { - delete($args->{'mode_req'}); - } - - if ($args->{'mode_deny'} - && ($args->{'mode_deny'} =~ /All/ || $args->{'mode_deny'} =~ /^\s*-\s*$/)) - { - delete($args->{'mode_deny'}); - } - - if ($args->{'sdmode'} - && ($args->{'sdmode'} =~ /All/ || $args->{'sdmode'} =~ /^\s*-\s*$/)) - { - delete($args->{'sdmode'}); - } - if ($args->{'resource'} - && ($args->{'resource'} =~ /All/ || $args->{'resource'} =~ /^\s*-\s*$/)) - { - delete($args->{'resource'}); - } - if ($args->{'severity'} - && ($args->{'severity'} =~ /All/ || $args->{'severity'} =~ /^\s*-\s*$/)) - { - delete($args->{'severity'}); - } - - $args = rewriteModes($args); - - if (open(LOG, "<$eventRep")) { - - # Log Parsing - while () { - - my $rec = undef; - - next if /true|false/; # avoid horrible yast bug - next if /Page/; - next if /^#/; - chomp; - next if (!$_ || $_ eq ""); - - # Lazy filters -- maybe these should be with the rest below - if ($args->{'prog'}) { next unless /$args->{'prog'}/; } - if ($args->{'profile'}) { next unless /$args->{'profile'}/; } - - ($rec->{'host'}, $rec->{'time'}, $rec->{'prog'}, $rec->{'profile'}, - $rec->{'pid'}, $rec->{'severity'}, $rec->{'mode_req'}, $rec->{'resource'}, - $rec->{'sdmode'}) = split(/\,/, $_); - - # Get the time/date ref. name right. If it's $args->{'time'}, the arg - # will be converted to a human-friendly date ref in writeEventReport(). - if ($rec->{'time'} =~ /\:|\-/) { - $rec->{'date'} = $rec->{'time'}; - delete $rec->{'time'}; - } - - # Check filters - next if matchFailed($args,$rec); - - push(@db, $rec); - - } - - close LOG; - - # Export results to file if requested - if ($args->{'exporttext'} || $args->{'exporthtml'}) { - - my $rawLog = undef; - my $expLog = undef; - - if ($args->{'exportPath'}) { - $rawLog = $args->{'exportPath'} . '/export-log'; - } else { - $rawLog = '/var/log/apparmor/reports-exported/export-log'; - } - - if ($args->{'exporttext'} && $args->{'exporttext'} eq 'true') { - $expLog = "$rawLog.csv"; - exportLog($expLog, \@db); # redo w/ @$db instead of %db? - } - - if ($args->{'exporthtml'} && $args->{'exporthtml'} eq 'true') { - $expLog = "$rawLog.html"; - exportLog($expLog, \@db); # redo w/ @$db instead of %db? - } - } - - $error = writeEventReport(\@db, $args); - - } else { - $error = "Couldn't open $eventRep."; - } - - return $error; -} - -1; - diff --git a/deprecated/utils/Immunix/Repository.pm b/deprecated/utils/Immunix/Repository.pm deleted file mode 100644 index 74b59f11c..000000000 --- a/deprecated/utils/Immunix/Repository.pm +++ /dev/null @@ -1,354 +0,0 @@ -# ---------------------------------------------------------------------- -# Copyright (c) 2008 Dominic Reynolds -# -# 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 as 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. -# -# -# ---------------------------------------------------------------------- - -package Immunix::Repository; - -use strict; -use warnings; - -use Carp; -use Cwd qw(cwd realpath); -use Data::Dumper; -use File::Basename; -use File::Temp qw/ tempfile tempdir /; -use Immunix::Config; -use Locale::gettext; -use POSIX; -use RPC::XML; -use RPC::XML::Client; - - -require Exporter; -our @ISA = qw(Exporter); -our @EXPORT = qw( - get_repo_client - did_result_succeed - get_result_error - user_login - user_register - upload_profile - fetch_profile_by_id - fetch_profiles_by_user - fetch_profiles_by_name - fetch_profiles_by_name_and_user - fetch_newer_profile - get_repo_config - set_repo_config -); - -our %clients; -our %uid2login; -our $DEBUGGING = 0; -our $repo_cfg; -our $aa_cfg; - -sub get_repo_client ($) { - my $repo_url = shift; - unless ( $clients{$repo_url} ) { - $clients{$repo_url} = new RPC::XML::Client $repo_url; - } - return $clients{$repo_url}; -} - -sub did_result_succeed { - my $result = shift; - - my $ref = ref $result; - return ($ref && $ref ne "RPC::XML::fault") ? 1 : 0; -} - -sub get_result_error { - my $result = shift; - - if (ref $result) { - if (ref $result eq "RPC::XML::fault") { - $result = $result->string; - } else { - $result = $$result; - } - } - return $result; -} - -sub user_login ($$$) { - my ($repo_url,$user,$pass) = @_; - my ($status,$detail); - my $repo_client = get_repo_client( $repo_url ); - if ( $repo_client ) { - my $res = $repo_client->send_request('LoginConfirm', $user, $pass); - if (did_result_succeed($res)) { - $status = 1; - $detail = ""; - } else { - $status = 0; - $detail = get_result_error($res); - } - } - return $status,$detail; -} - - -sub user_register ($$$$) { - my ($repo_url,$user,$pass,$email) = @_; - my $repo_client = get_repo_client( $repo_url ); - my ($status,$detail); - if ( $repo_client ) { - my $res = $repo_client->send_request('Signup', $user, $pass, $email); - if (did_result_succeed($res)) { - $status = 1; - $detail = ""; - } else { - $status = 0; - $detail = get_result_error($res); - } - } - return $status,$detail; -} - -sub upload_profile ($$$$$$$) { - my ($repo_url,$user,$pass,$distro,$pname,$profile,$changelog) = @_; - my ($status,$detail); - my $repo_client = get_repo_client( $repo_url ); - my $res = $repo_client->send_request( 'Create', $user, $pass, $distro, - $pname, $profile, $changelog); - if (did_result_succeed($res)) { - $detail = $res->value; - $status = 1; - } else { - $detail = get_result_error($res); - $status = 0; - } - return $status,$detail; -} - -sub fetch_profile_by_id ($$) { - my ($repo_url,$id) = @_; - my $repo_client = get_repo_client( $repo_url ); - my $repo_profile; - my ($status,$detail); - my $res = $repo_client->send_request('Show', $id); - if (did_result_succeed($res)) { - $status = 1; - $detail = $res->value(); - } else { - $status = 0; - $detail = get_result_error($res); - } - - return $status, $detail; -} - - -sub fetch_profiles ($$$$) { - my ($repo_url,$distro,$username,$fqdn) = @_; - my $p_hash = {}; - my ($status,$detail); - my $repo_client = get_repo_client( $repo_url ); - my $res = - $repo_client->send_request('FindProfiles', $distro, $fqdn, $username); - if (did_result_succeed($res)) { - $status = 1; - for my $p ( @$res ) { - my $p_repo = $p->{profile}->value(); - $p_repo =~ s/flags=\(complain\)// if ( $p_repo ); #strip complain flag - $p->{profile} = $p_repo; - $p->{user_id} = $p->{user_id}->value(); - $p->{id} = $p->{id}->value(); - $p->{name} = $p->{name}->value(); - $p->{created_at} = $p->{created_at}->value(); - $p->{downloaded_count} = $p->{downloaded_count}->value(); - } - $detail = $res; - } else { - $status = 0; - $detail = get_result_error($res); - } - return $status,$detail; -} - -sub fetch_profiles_by_user ($$$) { - my ($repo_url,$distro,$username) = @_; - my $p_hash = {}; - my ($status,$detail) = fetch_profiles( $repo_url, $distro, $username, "" ); - if ( $status ) { - for my $p ( @$detail ) { - my $p_repo = $p->{profile}; - if ($p_repo ne "") { - $p->{username} = $username; - $p_hash->{$p->{name}} = $p; - } - } - } else { - return ($status,$detail); - } - return($status,$p_hash); -} - - -sub fetch_profiles_by_name_and_user ($$$$) { - my ($repo_url,$distro,$fqdbin, $username) = @_; - my $p_hash = {}; - my ($status,$detail) = fetch_profiles( $repo_url, $distro, $username, $fqdbin ); - if ( $status ) { - for my $p ( @$detail ) { - my $p_repo = $p->{profile}?$p->{profile}:""; - $p_hash->{$p->{name}} = $p if ($p_repo ne ""); - } - } else { - return ($status,$detail); - } - return($status,$p_hash); -} - - -sub fetch_profiles_by_name ($$$) { - my ($repo_url,$distro,$fqdbin) = @_; - my ($status,$detail,$data); - $detail = {}; - ($status,$data) = fetch_profiles( $repo_url, $distro, "", $fqdbin); - if ($status) { - my @uids; - for my $p (@$data) { - push @uids, $p->{user_id}; - } - my ($status_unames,$unames) = fetch_usernames_from_uids($repo_url, @uids); - if ( $status_unames ) { - for my $p (@$data) { - if ( $unames->{$p->{user_id}} ) { - $p->{username} = $unames->{$p->{user_id}}; - } else { - $p->{username} = "unkown-" . $p->{user_id}; - } - } - - } else { - print STDOUT "ERROR UID\n"; - } - for my $p (@$data) { - $p->{profile_type} = "REPOSITORY"; - $detail->{$p->{username}} = $p; - } - } else { - $detail = $data; - } - return $status,$detail; -} - - -sub fetch_newer_profile ($$$$$) { - my ($repo_url,$distro,$user,$id,$profile) = @_; - my $repo_client = get_repo_client( $repo_url ); - my $p; - my ($status,$detail); - - if ($repo_client) { - my $res = - $repo_client->send_request('FindProfiles', $distro, $profile, $user); - if (did_result_succeed($res)) { - my @profiles; - my @profile_list = @{$res->value}; - $status = 1; - - if (@profile_list) { - if ($profile_list[0]->{id} > $id) { - $p = $profile_list[0]; - } - } - $detail = $p; - } else { - $status = 0; - $detail = get_result_error($res); - } - } - return $status,$detail; -} - -sub fetch_usernames_from_uids ($) { - my ($repo_url,@searchuids) = @_; - my ($status,$result) = (1,{}); - my @uids; - - for my $uid ( @searchuids ) { - if ( $uid2login{$uid} ) { - $result->{$uid} = $uid2login{$uid}; - } else { - push @uids, $uid; - } - } - if (@uids) { - my $repo_client = get_repo_client( $repo_url ); - #RPC::XML will serialize the array into XML with the is_utf8 flag set - #which causes, HTTP:Message to fail. Looping on the array elements - #stops this from happening, and since these are all numbers it - #will not cause problems. - for my $foo (@uids) { - Encode::_utf8_off($foo); - } - my $res = $repo_client->send_request('LoginNamesFromUserIds', [@uids]); - if (did_result_succeed($res)) { - my @usernames = @{ $res->value }; - for my $uid (@uids) { - my $username = shift @usernames; - $uid2login{$uid} = $username; - $result->{$uid} = $uid2login{$uid}; - } - } else { - $status = 0; - $result = get_result_error($res); - } - } - return $status,$result; -} - -sub get_repo_config { - unless ( $repo_cfg ) { - $repo_cfg = Immunix::Config::read_config("repository.conf"); - } - unless ( $aa_cfg ) { - $aa_cfg = Immunix::Config::read_config("logprof.conf"); - } - return { - "url" => $aa_cfg->{repository}{url}, - "distro" => $aa_cfg->{repository}{distro}, - "enabled" => $repo_cfg->{repository}{enabled}, - "upload" => $repo_cfg->{repository}{upload}, - "user" => $repo_cfg->{repository}{user}, - "password" => $repo_cfg->{repository}{pass}, - "email" => $repo_cfg->{repository}{email} - }; -} - -sub set_repo_config ($) { - my $cfg = shift; - my ($url,$distro,$enabled,$upload,$user,$pass); - unless ( $repo_cfg ) { - $repo_cfg = Immunix::Config::read_config("repository.conf"); - } - unless ( $aa_cfg ) { - $aa_cfg = Immunix::Config::read_config("logprof.conf"); - } - $repo_cfg->{repository}{enabled} = $cfg->{enabled} if ( $cfg->{enabled} ); - $repo_cfg->{repository}{upload} = $cfg->{upload} if ( $cfg->{upload} ); - $repo_cfg->{repository}{user} = $cfg->{user} if ( $cfg->{user} ); - $repo_cfg->{repository}{pass} = $cfg->{password}if ( $cfg->{password} ); - $repo_cfg->{repository}{email} = $cfg->{email} if ( $cfg->{email} ); - $aa_cfg->{repository}{distro} = $cfg->{distro} if ( $cfg->{distro} ); - $aa_cfg->{repository}{url} = $cfg->{url} if ( $cfg->{url} ); - write_config("repository.conf", $repo_cfg); - write_config("logprof.conf", $aa_cfg); -} - - -1; diff --git a/deprecated/utils/Immunix/Severity.pm b/deprecated/utils/Immunix/Severity.pm deleted file mode 100644 index 955fa5597..000000000 --- a/deprecated/utils/Immunix/Severity.pm +++ /dev/null @@ -1,221 +0,0 @@ -# ------------------------------------------------------------------ -# -# Copyright (C) 2005-2006 Novell/SUSE -# -# 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. -# -# ------------------------------------------------------------------ - -package Immunix::Severity; -use strict; -use Data::Dumper; - -my ($debug) = 0; - -sub debug { - print @_ if $debug; -} - -sub new { - my $self = {}; - $self->{DATABASENAME} = undef; - $self->{CAPABILITIES} = {}; - $self->{FILES} = {}; - $self->{REGEXPS} = {}; - $self->{DEFAULT_RANK} = 10; - bless($self); - shift; - $self->init(@_) if @_; - return $self; -} - -sub init ($;$) { - my ($self, $resource, $read, $write, $execute, $severity); - $self = shift; - $self->{DATABASENAME} = shift; - $self->{DEFAULT_RANK} = shift if defined $_[0]; - open(DATABASE, $self->{DATABASENAME}) - or die "Could not open severity db $self->{DATABASENAME}: $!\n"; - while () { - chomp(); - next if m/^\s*#/; - next if m/^\s*$/; - - # leading whitespace is fine; maybe it shouldn't be? - if (/^\s*\/(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) { - my ($path, $read, $write, $execute) = ($1, $2, $3, $4); - - if (index($path, "*") == -1) { - - $self->{FILES}{$path} = { - r => $read, - w => $write, - x => $execute - }; - - } else { - - my $ptr = $self->{REGEXPS}; - my @pieces = split(/\//, $path); - - while (my $piece = shift @pieces) { - if (index($piece, "*") != -1) { - my $path = join("/", $piece, @pieces); - my $regexp = convert_regexp($path); - $ptr->{$regexp}{SD_RANK} = { - r => $read, - w => $write, - x => $execute - }; - last; - } else { - $ptr->{$piece} = {} unless exists $ptr->{$piece}; - $ptr = $ptr->{$piece}; - } - } - } - } elsif (m|^\s*CAP|) { - ($resource, $severity) = split; - $self->{CAPABILITIES}{$resource} = $severity; - } else { - print "unexpected database line: $_\n"; - } - } - close(DATABASE); - debug Dumper($self); - return $self; -} - -#rank: -# handle capability -# handle file -# -# handle capability -# if the name is in the database, return it -# otherwise, send a diagnostic message to stderr and return the default -# -# handle file -# initialize the current return value to 0 -# loop over each entry in the database; -# find the max() value for each mode that matches and set a 'found' flag -# if the found flag has not been set, return the default; -# otherwise, return the maximum from the database - -sub handle_capability ($) { - my ($self, $resource) = @_; - - my $ret = $self->{CAPABILITIES}{$resource}; - if (!defined($ret)) { - return "unexpected capability rank input: $resource\n"; - } - return $ret; -} - -sub check_subtree { - my ($tree, $mode, $sev, $first, @rest) = @_; - - # reassemble the remaining path from this directory level - my $path = join("/", $first, @rest); - - # first check if we have a literal directory match to descend into - if ($tree->{$first}) { - $sev = check_subtree($tree->{$first}, $mode, $sev, @rest); - } - - # if we didn't get a severity already, check for matching globs - unless ($sev) { - - # check each glob at this directory level - for my $chunk (grep { index($_, "*") != -1 } keys %{$tree}) { - - # does it match the rest of our path? - if ($path =~ /^$chunk$/) { - - # if we've got a ranking, check if it's higher than - # current one, if any - if ($tree->{$chunk}->{SD_RANK}) { - for my $m (split(//, $mode)) { - if ((!defined $sev) - || $tree->{$chunk}->{SD_RANK}->{$m} > $sev) - { - $sev = $tree->{$chunk}->{SD_RANK}->{$m}; - } - } - } - } - } - } - - return $sev; -} - -sub handle_file ($$) { - my ($self, $resource, $mode) = @_; - - # strip off the initial / from the path we're checking - $resource = substr($resource, 1); - - # break the path into directory-level chunks - my @pieces = split(/\//, $resource); - - my $sev; - - # if there's a exact match for this path in the db, use that instead of - # checking the globs - if ($self->{FILES}{$resource}) { - - # check each piece of the passed mode against the db entry - for my $m (split(//, $mode)) { - if ((!defined $sev) || $self->{FILES}{$resource}{$m} > $sev) { - $sev = $self->{FILES}{$resource}{$m}; - } - } - - } else { - - # descend into the regexp tree looking for matches - $sev = check_subtree($self->{REGEXPS}, $mode, $sev, @pieces); - - } - - return (defined $sev) ? $sev : $self->{DEFAULT_RANK}; -} - -sub rank ($;$) { - my ($self, $resource, $mode) = @_; - - if (substr($resource, 0, 1) eq "/") { - return $self->handle_file($resource, $mode); - } elsif (substr($resource, 0, 3) eq "CAP") { - return $self->handle_capability($resource); - } else { - return "unexpected rank input: $resource\n"; - } -} - -sub convert_regexp ($) { - my ($input) = shift; - - # we need to convert subdomain regexps to perl regexps - my $regexp = $input; - - # escape + . [ and ] characters - $regexp =~ s/(\+|\.|\[|\])/\\$1/g; - - # convert ** globs to match anything - $regexp =~ s/\*\*/.SDPROF_INTERNAL_GLOB/g; - - # convert * globs to match anything at current path level - $regexp =~ s/\*/[^\/]SDPROF_INTERNAL_GLOB/g; - - # convert {foo,baz} to (foo|baz) - $regexp =~ y/\{\}\,/\(\)\|/ if $regexp =~ /\{.*\,.*\}/; - - # twiddle the escaped * chars back - $regexp =~ s/SDPROF_INTERNAL_GLOB/\*/g; - return $regexp; -} - -1; # so the require or use succeeds diff --git a/deprecated/utils/Makefile b/deprecated/utils/Makefile deleted file mode 100644 index 393d2d4a0..000000000 --- a/deprecated/utils/Makefile +++ /dev/null @@ -1,62 +0,0 @@ -# ---------------------------------------------------------------------- -# Copyright (c) 1999, 2004-2009 NOVELL (All rights reserved) -# Copyright (c) 2010-2011, 2014 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. -# ---------------------------------------------------------------------- - -# NOTE: this Makefile has been adjusted from the original to assist in -# the installation of the Immunix perl modules, if they're still needed -# by users. Because the utilities conflict with their replacments, make -# install *will* *not* install them. - -NAME = apparmor-utils -all: -COMMONDIR=../../common/ - -include $(COMMONDIR)/Make.rules - -MODDIR = Immunix -PERLTOOLS = aa-genprof aa-logprof aa-autodep aa-audit aa-complain aa-enforce \ - aa-unconfined aa-disable -MODULES = ${MODDIR}/AppArmor.pm ${MODDIR}/Repository.pm \ - ${MODDIR}/Config.pm ${MODDIR}/Severity.pm - -all: - -# need some better way of determining this -DESTDIR=/ -BINDIR=${DESTDIR}/usr/sbin -CONFDIR=${DESTDIR}/etc/apparmor -VENDOR_PERL=$(shell perl -e 'use Config; print $$Config{"vendorlib"};') -PERLDIR=${DESTDIR}${VENDOR_PERL}/${MODDIR} - -.PHONY: install -install: - install -d ${PERLDIR} - install -m 644 ${MODULES} ${PERLDIR} - -.PHONY: clean -ifndef VERBOSE -.SILENT: clean -endif -clean: pod_clean - rm -f core core.* *.o *.s *.a *~ - rm -rf staging/ build/ - -.PHONY: check -.SILENT: check -check: - for i in ${MODULES} ${PERLTOOLS} ; do \ - perl -c $$i || exit 1; \ - done diff --git a/deprecated/utils/aa-audit b/deprecated/utils/aa-audit deleted file mode 100755 index 8ddec1cac..000000000 --- a/deprecated/utils/aa-audit +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/perl -# ---------------------------------------------------------------------- -# Copyright (c) 2005 Novell, Inc. All Rights Reserved. -# Copyright (c) 2011 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 as 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. -# -# To contact Novell about this file by physical or electronic mail, -# you may find current contact information at www.novell.com. -# ---------------------------------------------------------------------- - -use strict; -use FindBin; -use Getopt::Long; - -use Immunix::AppArmor; - -use Data::Dumper; - -use Locale::gettext; -use POSIX; - -# initialize the local poo -setlocale(LC_MESSAGES, ""); -textdomain("apparmor-utils"); - -$UI_Mode = "text"; - -# options variables -my $help = ''; - -GetOptions( - 'dir|d=s' => \$profiledir, - 'help|h' => \$help, -); - -# tell 'em how to use it... -&usage && exit if $help; - -# let's convert it to full path... -$profiledir = get_full_path($profiledir); - -unless (-d $profiledir) { - UI_Important("Can't find AppArmor profiles in $profiledir."); - exit 1; -} - -# what are we profiling? -my @profiling = @ARGV; - -unless (@profiling) { - @profiling = (UI_GetString("Please enter the program to switch to audit mode: ", "")); -} - -for my $profiling (@profiling) { - - next unless $profiling; - - my $fqdbin; - if (-e $profiling) { - $fqdbin = get_full_path($profiling); - chomp($fqdbin); - } else { - if ($profiling !~ /\//) { - opendir(DIR,$profiledir); - my @tmp_fqdbin = grep ( /$profiling/, readdir(DIR)); - closedir(DIR); - if (scalar @tmp_fqdbin eq 1) { - $fqdbin = "$profiledir/$tmp_fqdbin[0]"; - } else { - my $which = which($profiling); - if ($which) { - $fqdbin = get_full_path($which); - } - } - } - } - - if (-e $fqdbin) { - - my $filename; - if ($fqdbin =~ /^$profiledir\//) { - $filename = $fqdbin; - } else { - $filename = getprofilefilename($fqdbin); - } - - # argh, skip directories - next unless -f $filename; - - # skip rpm backup files - next if isSkippableFile($filename); - - printf(gettext('Setting %s to audit mode.'), $fqdbin); - print "\n"; - setprofileflags($filename, "audit"); - - my $cmd_info = qx(cat $filename | $parser -I$profiledir -r 2>&1 1>/dev/null); - if ($? != 0) { - UI_Info($cmd_info); - exit $?; - } - -# if check_for_subdomain(); - } else { - if ($profiling =~ /^[^\/]+$/) { - UI_Info(sprintf(gettext('Can\'t find %s in the system path list. If the name of the application is correct, please run \'which %s\' as a user with the correct PATH environment set up in order to find the fully-qualified path.'), $profiling, $profiling)); - exit 1; - } else { - UI_Info(sprintf(gettext('%s does not exist, please double-check the path.'), $profiling)); - exit 1; - } - } -} - -exit 0; - -sub usage { - UI_Info(sprintf(gettext("usage: \%s [ -d /path/to/profiles ] [ program to switch to audit mode ]"), $0)); - exit 0; -} - diff --git a/deprecated/utils/aa-autodep b/deprecated/utils/aa-autodep deleted file mode 100755 index 3d28b642a..000000000 --- a/deprecated/utils/aa-autodep +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/perl -# ---------------------------------------------------------------------- -# Copyright (c) 2005 Novell, Inc. All Rights Reserved. -# Copyright (c) 2011 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 as 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. -# -# To contact Novell about this file by physical or electronic mail, -# you may find current contact information at www.novell.com. -# ---------------------------------------------------------------------- - -use strict; -use FindBin; -use Getopt::Long; - -use Immunix::AppArmor; - -use Data::Dumper; - -use Locale::gettext; -use POSIX; - -# force $PATH to be sane -$ENV{PATH} = "/bin:/sbin:/usr/bin:/usr/sbin"; - -# initialize the local poo -setlocale(LC_MESSAGES, ""); -textdomain("apparmor-utils"); - -$UI_Mode = "text"; - -# options variables -my $help = ''; -my $force = undef; - -GetOptions( - 'force' => \$force, - 'dir|d=s' => \$profiledir, - 'help|h' => \$help, -); - -# tell 'em how to use it... -&usage && exit if $help; - -my $sd_mountpoint = check_for_subdomain(); - -# let's convert it to full path... -$profiledir = get_full_path($profiledir); - -unless (-d $profiledir) { - UI_Important(sprintf(gettext('Can\'t find AppArmor profiles in %s.'), $profiledir)); - exit 1; -} - -# what are we profiling? -my @profiling = @ARGV; - -unless (@profiling) { - @profiling = (UI_GetString(gettext("Please enter the program to create a profile for: "), "")); -} - -for my $profiling (@profiling) { - - next unless $profiling; - - my $fqdbin; - if (-e $profiling) { - $fqdbin = get_full_path($profiling); - chomp($fqdbin); - } else { - if ($profiling !~ /\//) { - my $which = which($profiling); - if ($which) { - $fqdbin = get_full_path($which); - } - } - } - - # make sure that the app they're requesting to profile is not marked as - # not allowed to have it's own profile - if ($qualifiers{$fqdbin}) { - unless ($qualifiers{$fqdbin} =~ /p/) { - UI_Info(sprintf(gettext('%s is currently marked as a program that should not have it\'s own profile. Usually, programs are marked this way if creating a profile for them is likely to break the rest of the system. If you know what you\'re doing and are certain you want to create a profile for this program, edit the corresponding entry in the [qualifiers] section in /etc/apparmor/logprof.conf.'), $fqdbin)); - exit 1; - } - } - - if (-e $fqdbin) { - if (-e getprofilefilename($fqdbin) && !$force) { - UI_Info(sprintf(gettext('Profile for %s already exists - skipping.'), $fqdbin)); - } else { - autodep($fqdbin); - reload($fqdbin) if $sd_mountpoint; - } - } else { - if ($profiling =~ /^[^\/]+$/) { - UI_Info(sprintf(gettext('Can\'t find %s in the system path list. If the name of the application is correct, please run \'which %s\' as a user with the correct PATH environment set up in order to find the fully-qualified path.'), $profiling, $profiling)); - exit 1; - } else { - UI_Info(sprintf(gettext('%s does not exist, please double-check the path.'), $profiling)); - exit 1; - } - } -} - -exit 0; - -sub usage { - UI_Info("usage: $0 [ --force ] [ -d /path/to/profiles ]"); - exit 0; -} - diff --git a/deprecated/utils/aa-complain b/deprecated/utils/aa-complain deleted file mode 100755 index 5e497e05f..000000000 --- a/deprecated/utils/aa-complain +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/perl -# ---------------------------------------------------------------------- -# Copyright (c) 2005 Novell, Inc. 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 as 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. -# -# To contact Novell about this file by physical or electronic mail, -# you may find current contact information at www.novell.com. -# ---------------------------------------------------------------------- - -use strict; -use FindBin; -use Getopt::Long; - -use Immunix::AppArmor; - -use Data::Dumper; - -use Locale::gettext; -use POSIX; - -# initialize the local poo -setlocale(LC_MESSAGES, ""); -textdomain("apparmor-utils"); - -$UI_Mode = "text"; - -# options variables -my $help = ''; - -GetOptions( - 'dir|d=s' => \$profiledir, - 'help|h' => \$help, -); - -# tell 'em how to use it... -&usage && exit if $help; - -# let's convert it to full path... -$profiledir = get_full_path($profiledir); - -unless (-d $profiledir) { - UI_Important("Can't find AppArmor profiles in $profiledir."); - exit 1; -} - -# what are we profiling? -my @profiling = @ARGV; - -unless (@profiling) { - @profiling = (UI_GetString(gettext("Please enter the program to switch to complain mode: "), "")); -} - -for my $profiling (@profiling) { - - next unless $profiling; - - my $fqdbin; - if (-e $profiling) { - $fqdbin = get_full_path($profiling); - chomp($fqdbin); - } else { - if ($profiling !~ /\//) { - opendir(DIR,$profiledir); - my @tmp_fqdbin = grep ( /$profiling/, readdir(DIR)); - closedir(DIR); - if (scalar @tmp_fqdbin eq 1) { - $fqdbin = "$profiledir/$tmp_fqdbin[0]"; - } else { - my $which = which($profiling); - if ($which) { - $fqdbin = get_full_path($which); - } - } - } - } - - if (-e $fqdbin) { - - my $filename; - if ($fqdbin =~ /^$profiledir\//) { - $filename = $fqdbin; - } else { - $filename = getprofilefilename($fqdbin); - } - - # argh, skip directories - next unless -f $filename; - - # skip rpm backup files - next if isSkippableFile($filename); - - printf(gettext('Setting %s to complain mode.'), $fqdbin); - print "\n"; - setprofileflags($filename, "complain"); - - my $cmd_info = qx(cat $filename | $parser -I$profiledir -r 2>&1 1>/dev/null); - if ($? != 0) { - UI_Info($cmd_info); - exit $?; - } - -# if check_for_subdomain(); - } else { - if ($profiling =~ /^[^\/]+$/) { - UI_Info(sprintf(gettext('Can\'t find %s in the system path list. If the name of the application is correct, please run \'which %s\' as a user with the correct PATH environment set up in order to find the fully-qualified path.'), $profiling, $profiling)); - exit 1; - } else { - UI_Info(sprintf(gettext('%s does not exist, please double-check the path.'), $profiling)); - exit 1; - } - } -} - -exit 0; - -sub usage { - UI_Info(sprintf(gettext("usage: \%s [ -d /path/to/profiles ] [ program to switch to complain mode ]"), $0)); - exit 0; -} - diff --git a/deprecated/utils/aa-disable b/deprecated/utils/aa-disable deleted file mode 100755 index 2cc19a55a..000000000 --- a/deprecated/utils/aa-disable +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/perl -# ---------------------------------------------------------------------- -# Copyright (c) 2005-2010 Novell, Inc. All Rights Reserved. -# Copyright (c) 2011 Canonical, Inc. 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 as 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 Canonical, Inc. -# -# To contact Canonical about this file by physical or electronic mail, -# you may find current contact information at www.canonical.com. -# ---------------------------------------------------------------------- - -use strict; -use FindBin; -use Getopt::Long; - -use Immunix::AppArmor; - -use Data::Dumper; - -use Locale::gettext; -use POSIX; -use File::Basename; - -# initialize the local poo -setlocale(LC_MESSAGES, ""); -textdomain("apparmor-utils"); - -$UI_Mode = "text"; - -# options variables -my $help = ''; - -GetOptions( - 'dir|d=s' => \$profiledir, - 'help|h' => \$help, -); - -# tell 'em how to use it... -&usage && exit if $help; - -# let's convert it to full path... -$profiledir = get_full_path($profiledir); - -unless (-d $profiledir) { - UI_Important("Can't find AppArmor profiles in $profiledir."); - exit 1; -} - -my $disabledir = "$profiledir/disable"; -unless (-d $disabledir) { - UI_Important("Can't find AppArmor disable directory '$disabledir'."); - exit 1; -} - -# what are we profiling? -my @profiling = @ARGV; - -unless (@profiling) { - @profiling = (UI_GetString(gettext("Please enter the program whose profile should be disabled: "), "")); -} - -for my $profiling (@profiling) { - - next unless $profiling; - - my $fqdbin; - if (-e $profiling) { - $fqdbin = get_full_path($profiling); - chomp($fqdbin); - } else { - if ($profiling !~ /\//) { - opendir(DIR,$profiledir); - my @tmp_fqdbin = grep ( /$profiling/, readdir(DIR)); - closedir(DIR); - if (scalar @tmp_fqdbin eq 1) { - $fqdbin = "$profiledir/$tmp_fqdbin[0]"; - } else { - my $which = which($profiling); - if ($which) { - $fqdbin = get_full_path($which); - } - } - } - } - - if (-e $fqdbin) { - - my $filename; - if ($fqdbin =~ /^$profiledir\//) { - $filename = $fqdbin; - } else { - $filename = getprofilefilename($fqdbin); - } - - # argh, skip directories - next unless -f $filename; - - # skip package manager backup files - next if isSkippableFile($filename); - - my ($bname, $dname, $suffix) = File::Basename::fileparse($filename); - if ($bname eq "") { - UI_Info(sprintf(gettext('Could not find basename for %s.'), $filename)); - exit 1; - } - - printf(gettext('Disabling %s.'), $fqdbin); - print "\n"; - - my $link = "$disabledir/$bname"; - if (! -e $link) { - if (symlink($filename, $link) != 1) { - UI_Info(sprintf(gettext('Could not create %s symlink.'), $link)); - exit 1; - } - } - - my $cmd_info = qx(cat $filename | $parser -I$profiledir -R 2>&1 1>/dev/null); - if ($? != 0) { - UI_Info($cmd_info); - exit $?; - } - -# if check_for_subdomain(); - } else { - if ($profiling =~ /^[^\/]+$/) { - UI_Info(sprintf(gettext('Can\'t find %s in the system path list. If the name of the application is correct, please run \'which %s\' as a user with the correct PATH environment set up in order to find the fully-qualified path.'), $profiling, $profiling)); - exit 1; - } else { - UI_Info(sprintf(gettext('%s does not exist, please double-check the path.'), $profiling)); - exit 1; - } - } -} - -exit 0; - -sub usage { - UI_Info(sprintf(gettext("usage: \%s [ -d /path/to/profiles ] [ program to have profile disabled ]"), $0)); - exit 0; -} - diff --git a/deprecated/utils/aa-enforce b/deprecated/utils/aa-enforce deleted file mode 100755 index 06415ba5b..000000000 --- a/deprecated/utils/aa-enforce +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/perl -# ---------------------------------------------------------------------- -# Copyright (c) 2005 Novell, Inc. All Rights Reserved. -# Copyright (c) 2011 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 as 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. -# -# To contact Novell about this file by physical or electronic mail, -# you may find current contact information at www.novell.com. -# ---------------------------------------------------------------------- - -use strict; -use FindBin; -use Getopt::Long; - -use Immunix::AppArmor; - -use Data::Dumper; - -use Locale::gettext; -use POSIX; - -# initialize the local poo -setlocale(LC_MESSAGES, ""); -textdomain("apparmor-utils"); - -$UI_Mode = "text"; - -# options variables -my $help = ''; - -GetOptions( - 'dir|d=s' => \$profiledir, - 'help|h' => \$help, -); - -# tell 'em how to use it... -&usage && exit if $help; - -# let's convert it to full path... -$profiledir = get_full_path($profiledir); - -unless (-d $profiledir) { - UI_Important("Can't find AppArmor profiles in $profiledir."); - exit 1; -} - -# what are we profiling? -my @profiling = @ARGV; - -unless (@profiling) { - @profiling = (UI_GetString(gettext("Please enter the program to switch to enforce mode: "), "")); -} - -for my $profiling (@profiling) { - - next unless $profiling; - - my $fqdbin; - if (-e $profiling) { - $fqdbin = get_full_path($profiling); - chomp($fqdbin); - } else { - if ($profiling !~ /\//) { - opendir(DIR,$profiledir); - my @tmp_fqdbin = grep ( /$profiling/, readdir(DIR)); - closedir(DIR); - if (scalar @tmp_fqdbin eq 1) { - $fqdbin = "$profiledir/$tmp_fqdbin[0]"; - } else { - my $which = which($profiling); - if ($which) { - $fqdbin = get_full_path($which); - } - } - } - } - - if (-e $fqdbin) { - my $filename; - if ($fqdbin =~ /^$profiledir\//) { - $filename = $fqdbin; - } else { - $filename = getprofilefilename($fqdbin); - } - - # argh, skip directories - next unless -f $filename; - - # skip rpm backup files - next if isSkippableFile($filename); - - printf(gettext('Setting %s to enforce mode.'), $fqdbin); - print "\n"; - setprofileflags($filename, ""); - - # remove symlink in $profiledir/force-complain as well - my $complainlink = $filename; - $complainlink =~ s/^$profiledir/$profiledir\/force-complain/; - -e $complainlink and unlink($complainlink); - - # remove symlink in $profiledir/disable as well - my $disablelink = $filename; - $disablelink =~ s/^$profiledir/$profiledir\/disable/; - -e $disablelink and unlink($disablelink); - - my $cmd_info = qx(cat $filename | $parser -I$profiledir -r 2>&1 1>/dev/null); - if ($? != 0) { - UI_Info($cmd_info); - exit $?; - } - - -# if check_for_subdomain(); - } else { - if ($profiling =~ /^[^\/]+$/) { - UI_Info(sprintf(gettext('Can\'t find %s in the system path list. If the name of the application is correct, please run \'which %s\' as a user with the correct PATH environment set up in order to find the fully-qualified path.'), $profiling, $profiling)); - exit 1; - } else { - UI_Info(sprintf(gettext('%s does not exist, please double-check the path.'), $profiling)); - exit 1; - } - } -} - -exit 0; - -sub usage { - UI_Info(sprintf(gettext("usage: \%s [ -d /path/to/profiles ] [ program to switch to enforce mode ]"), $0)); - exit 0; -} - diff --git a/deprecated/utils/aa-eventd b/deprecated/utils/aa-eventd deleted file mode 100755 index 70fcf125b..000000000 --- a/deprecated/utils/aa-eventd +++ /dev/null @@ -1,940 +0,0 @@ -#!/usr/bin/perl -w - -# ---------------------------------------------------------------------- -# Copyright (c) 2005 Novell, Inc. 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 as 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. -# -# To contact Novell about this file by physical or electronic mail, -# you may find current contact information at www.novell.com. -# ---------------------------------------------------------------------- - -use strict; - -use Data::Dumper; -use DBI; -use Fcntl; -use File::Temp qw(tempfile); -use Getopt::Long; -use POSIX 'setsid'; -use Time::Local; -use File::Tail; - -use Immunix::Severity; -require LibAppArmor; - -########################################################################## -# locations - -my $productname = "apparmor"; - -my $cfgdir = "/etc/$productname"; -my $dbdir = "/var/log/$productname"; - -my $cfgfile = "$cfgdir/notify.cfg"; -my $errlog = "$dbdir/event-dispatch.log"; - -my $logfile = "/var/log/audit/audit.log"; -my $syslogfile = "/var/log/messages"; - -########################################################################## - -# options variables -my $pidfile = ''; - -GetOptions('pidfile|p=s' => \$pidfile); - -my $DEBUG = 0; - -my $config; - -my $verbose = { last_notify => 0 }; -my $summary = { last_notify => 0 }; -my $terse = { last_notify => 0 }; - -# we don't want to call str2time on every line and also batch up event dbs -# a month at a time, so we need to keep track of a few extra things -my $timestamp = 0; -my $lasttime = ""; -my $counter = 0; -my $thismonth = 0; -my $nextmonth = 0; - -# pop open a connection to the severity database -my $sevdb = new Immunix::Severity("$cfgdir/severity.db", -1); - -my $REdate = '\w{3}\s+\d+\s+\d{2}:\d{2}:\d{2}'; - -my $last_inserted_time; -my $last_inserted_counter; - -########################################################################## - -# commit how often? -my $timeout = 5; - -# keep track of when we commited last -my $last_flush_time = 0; - -# keep track of some statistics -my $max = 0; -my $inserts = 0; -my $total = 0; - -my @commit_buffer; -my @debug_buffer; - -my @verbose_buffer; -my @summary_buffer; -my @terse_buffer; - -my $date_module = "None"; - -my %templates = ( - "path" => "(time,counter,type,op,profile,sdmode,mode_req,mode_deny,resource,prog,pid,severity) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)", - "link" => "(time,counter,type,op,profile,sdmode,resource,target,prog,pid,severity) VALUES(?,?,?,?,?,?,?,?,?,?,?)", - "chattr" => "(time,counter,type,op,profile,sdmode,resource,mode_req,mode_deny,prog,pid,severity) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)", - "capability" => "(time,counter,type,op,profile,sdmode,resource,prog,pid,severity) VALUES(?,?,?,?,?,?,?,?,?,?)", - "capable" => "(time,counter,type,op,prog,pid,profile) VALUES(?,?,?,?,?,?,?)", - "unknown_hat" => "(time,counter,type,op,profile,sdmode,resource,pid) VALUES(?,?,?,?,?,?,?,?)", - "fork" => "(time,counter,type,op,profile,sdmode,pid,resource) VALUES(?,?,?,?,?,?,?,?)", - "changing_profile" => "(time,counter,type,op,profile,sdmode,pid) VALUES(?,?,?,?,?,?,?)", - "profile_replacement" => "(time,counter,type,op,profile,sdmode,prog,pid,severity) VALUES(?,?,?,?,?,?,?,?,?)", - "net" => "(time,counter,type,op,net_family,net_socktype,net_proto,pid,profile) VALUES(?,?,?,?,?,?,?,?,?)", - "removed" => "(time,counter,type,op,severity) VALUES(?,?,?,?,?)", - "initialized" => "(time,counter,type,op,resource,severity) VALUES(?,?,?,?,?,?)", - "ctrl_var" => "(time,counter,type,op,resource,mode_req,mode_deny,severity) VALUES(?,?,?,?,?,?,?,?)", - "profile_load" => "(time,counter,type,op,resource,prog,pid) VALUES(?,?,?,?,?,?,?)", -); - -########################################################################## -# generic functions - -sub errlog ($) { - my $mesg = shift; - my $localtime = localtime(time); - print ERRLOG "[$localtime] $mesg\n"; -} - -sub readconfig () { - my $cfg = {}; - - # record when we read the config file - $cfg->{load_time} = time; - - if (open(CFG, $cfgfile)) { - - # yank in the values we need - while () { - $cfg->{$1} = $2 if /^(\S+)\s+(.+)\s*$/; - } - close(CFG); - } - - return $cfg; -} - -sub daemonize { - chdir '/' or die "Can't chdir to /: $!"; - open STDIN, '/dev/null' or die "Can't read /dev/null: $!"; - open STDOUT, '>/dev/null' or die "Can't write to /dev/null: $!"; - defined(my $pid = fork) or die "Can't fork: $!"; - exit if $pid; - setsid or die "Can't start a new session: $!"; - open STDERR, '>&STDOUT' or die "Can't dup stdout: $!"; -} - -sub parsedate ($) { - my $time = shift; - my $timestamp = 0; - if ($date_module eq 'TimeDate') { - $timestamp = Date::Parse::str2time($time); - } elsif ($date_module eq 'DateManip') { - $timestamp = Date::Manip::UnixDate(Date::Manip::ParseDateString($time), '%s'); - } else { - errlog "No date module found, exiing"; - kill HUP => -$$; - } - - return $timestamp; -} - -########################################################################## -# database handling functions - -sub connect_database ($) { - my $dbdir = shift; - - my $dbh = DBI->connect("dbi:SQLite:dbname=$dbdir/events.db", "", "", {RaiseError=>1}); - - # we'll do the commits ourselves so performance doesn't suck - $dbh->{AutoCommit} = 0; - - # bump up our cache size a little - $dbh->do("PRAGMA cache_size = 20000;"); - - # figure out if the tables already exist or not - my %existing_tables; - my $sth = $dbh->prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;"); - $sth->execute; - while (my @row = $sth->fetchrow_array) { - $existing_tables{ $row[0] } = 1; - } - $sth->finish; - - # create the info table and fill in the appropriate values for this db - unless ($existing_tables{info}) { - - my $host = `hostname -f`; - chomp $host; - - $dbh->do("CREATE TABLE info (name,value)"); - $sth = $dbh->prepare("INSERT INTO info(name,value) VALUES(?,?)"); - $sth->execute("version", "0.2"); - $sth->execute("host", "$host"); - } - - # create the events table - unless ($existing_tables{events}) { - $dbh->do( - "CREATE TABLE events ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - time INTEGER NOT NULL, - counter INTEGER NOT NULL, - op, - pid, - sdmode, - type, - mode_deny, - mode_req, - resource, - target, - profile, - prog, - name_alt, - attr, - parent, - active_hat, - net_family, - net_proto, - net_socktype, - severity INTEGER - )" - ); - - # set up the indexes we want - #my @indexes = qw(time type sdmode mode resource profile prog severity); - my @indexes = qw(time type op sdmode mode_req mode_deny resource profile prog severity); - for my $index (@indexes) { - $dbh->do("CREATE INDEX " . $index . "_idx ON events($index)"); - } - } - # make sure our changes actually get saved - $dbh->commit || errlog "Error commiting changes: $!"; - - # mark the db as up to date as of now - $last_flush_time = time; - - return $dbh; -} - -########################################################################## - -sub verbose_notify_handler { - my ($email, $file, $last, $level, $unknown) = @_; - - $last = localtime($last); - - my $now = time; - - my $host = `hostname -f`; - chomp $host; - - my $subj = "Verbose Security Report for $host."; - my $mesg = "The following security events occured since $last:\n\n"; - - my @events; - if (open(V, $file)) { - while () { - chomp; - if (/^(\d+) (\d+) (.+)$/) { - my ($timestamp, $counter, $logmsg) = ($1, $2, $3); - push @events, [ $timestamp, $counter ]; - $mesg .= "$logmsg\n"; - } - } - close(V); - - if (@events) { - if ($DEBUG) { - my $count = scalar @events; - errlog "[$count events] sending verbose notification to $email."; - } - - # actually send out the notification... - open(MAIL, "| sendmail -F 'AppArmor Security Notification' $email"); - print MAIL "To: $email\n"; - print MAIL "Subject: $subj\n\n"; - print MAIL "$mesg\n"; - print MAIL ".\n"; - close(MAIL); - } - - # delete the verbose notification logfile once we've processed it - unlink($file); - } -} - -sub summary_notify_handler { - my ($email, $file, $last, $level, $unknown) = @_; - - $last = localtime($last); - - my $now = time; - - my $host = `hostname -f`; - chomp $host; - - my $subj = "Summary Security Report for $host."; - my $mesg = "The following security events occured since $last:\n\n"; - - my @events; - if (open(V, $file)) { - while () { - chomp; - if (/^(\d+) (\d+) (.+)$/) { - my ($timestamp, $counter, $logmsg) = ($1, $2, $3); - push @events, [ $timestamp, $counter ]; - $mesg .= "$logmsg\n"; - } - } - close(V); - - if (@events) { - if ($DEBUG) { - my $count = scalar @events; - errlog "[$count events] sending summary notification to $email."; - } - - # actually send out the notification... - open(MAIL, "| sendmail -F 'AppArmor Security Notification' $email"); - print MAIL "To: $email\n"; - print MAIL "Subject: $subj\n\n"; - print MAIL "$mesg\n"; - print MAIL ".\n"; - close(MAIL); - } - - # delete the verbose notification logfile once we've processed it - unlink($file); - } -} - -sub terse_notify_handler { - my ($email, $file, $last, $level, $unknown) = @_; - - $last = localtime($last); - - my $now = time; - - my $host = `hostname -f`; - chomp $host; - - my @events; - my $count = 0; - if (open(V, $file)) { - while () { - chomp; - if (/^(\d+) (\d+) (.+)$/) { - my ($timestamp, $counter, $logmsg) = ($1, $2, $3); - push @events, [ $timestamp, $counter ]; - $count++; - } - } - close(V); - - if ($count) { - if ($DEBUG) { - errlog "[$count events] sending terse notification to $email."; - } - my $subj = "Security Report for $host."; - my $mesg = "$host has had $count security events since $last."; - - # actually send out the notification... - open(MAIL, "| sendmail -F 'AppArmor Security Notification' $email"); - print MAIL "To: $email\n"; - print MAIL "Subject: $subj\n\n"; - print MAIL "$mesg\n"; - print MAIL ".\n"; - close(MAIL); - } - - # delete the terse notification logfile once we've processed it - unlink($file); - } -} - -sub fork_into_background { - my ($name, $func, @args) = @_; - - my $pid = fork; - - if (not defined $pid) { - - # something bad happened, just log it... - errlog "couldn't fork for \"$name\": $!" - - } elsif ($pid == 0) { - - # we're in the child process now... - - # set our process name - $0 = $name; - - # call our subroutine - my $ret = &$func(@args); - - exit($ret); - } - - return $pid; -} - -########################################################################## -# Parse event record into key-value pairs -sub parseEvent($) { - - my %ev = (); - my $msg = shift; - chomp($msg); - - my $event = LibAppArmor::parse_record($msg); - - # resource is an alternate term for 'name1' below - # mode is an alternate term for 'mode_deny' below - $ev{'time'} = LibAppArmor::aa_log_record::swig_epoch_get($event); - $ev{'op'} = LibAppArmor::aa_log_record::swig_operation_get($event); - $ev{'pid'} = LibAppArmor::aa_log_record::swig_pid_get($event); - $ev{'mode_deny'} = LibAppArmor::aa_log_record::swig_denied_mask_get($event); - $ev{'mode_req'} = LibAppArmor::aa_log_record::swig_requested_mask_get($event); - $ev{'profile'}= LibAppArmor::aa_log_record::swig_profile_get($event); - $ev{'prog'} = LibAppArmor::aa_log_record::swig_name_get($event); - $ev{'name2'} = LibAppArmor::aa_log_record::swig_name2_get($event); - $ev{'attr'} = LibAppArmor::aa_log_record::swig_attribute_get($event); - $ev{'parent'} = LibAppArmor::aa_log_record::swig_parent_get($event); - $ev{'magic_token'} = LibAppArmor::aa_log_record::swig_magic_token_get($event); - $ev{'resource'} = LibAppArmor::aa_log_record::swig_info_get($event); - $ev{'active_hat'} = LibAppArmor::aa_log_record::swig_active_hat_get($event); - $ev{'sdmode'} = LibAppArmor::aa_log_record::swig_event_get($event); - - # NetDomain - if ( $ev{'op'} && $ev{'op'} =~ /socket/ ) { - next if $ev{'op'} =~ /create/; - $ev{'net_family'} = LibAppArmor::aa_log_record::swig_net_family_get($event); - $ev{'net_proto'} = LibAppArmor::aa_log_record::swig_net_protocol_get($event); - $ev{'net_socktype'} = LibAppArmor::aa_log_record::swig_net_sock_type_get($event); - } - - LibAppArmor::free_record($event); - - if ( ! $ev{'time'} ) { $ev{'time'} = time; } - - # remove null responses - for (keys(%ev)) { - if ( ! $ev{$_} || $ev{$_} !~ /\w+/) {delete($ev{$_}); } - #errlog "EVENT: $_ is $ev{$_}"; - } - - if ( $ev{'sdmode'} ) { - #0 = invalid, 1 = error, 2 = AUDIT, 3 = ALLOW/PERMIT, - #4 = DENIED/REJECTED, 5 = HINT, 6 = STATUS/config change - if ( $ev{'sdmode'} == 2 ) { $ev{'sdmode'} = "AUDITING"; } - elsif ( $ev{'sdmode'} == 3 ) { $ev{'sdmode'} = "PERMITING"; } - elsif ( $ev{'sdmode'} == 4 ) { $ev{'sdmode'} = "REJECTING"; } - else { delete($ev{'sdmode'}); } - } - - return \%ev; -} - -sub process_event ($$) { - - my $dbh = shift; - my $logmsg = shift; - my $sth; - my $severity = ""; - my @eventList = (); - my $type = undef; - my $time = undef; - - return unless $logmsg && $logmsg =~ /APPARMOR/; - my $ev = parseEvent($logmsg); - - # skip logprof hints - if ( ! $ev->{'op'} || $ev->{'op'} eq 'clone') { return; } - - $time = time; # XXX - do we want current time or $ev->{'time'}? - - if ($time ne $lasttime) { - $counter = 0; - $timestamp = $time; - $lasttime = $time; - } - - $counter++; - - # some statistics... - $max = $counter if $counter > $max; - - # if we already have events in the db, make sure we don't try to re-enter - # duplicates if we start up again and parse the same logfile over again - if ($last_inserted_time) { - return if $timestamp < $last_inserted_time; - - if ($timestamp == $last_inserted_time) { - return if $counter <= $last_inserted_counter; - } - - $last_inserted_time = undef; - } - - if ( $ev->{'sdmode'} && $ev->{'sdmode'} eq "REJECTING") { - $severity = $sevdb->rank($ev->{'prog'}, $ev->{'mode_req'}); - if ( ! $severity ) { $severity = "-1"; } - - # we only do notification for enforce mode events - if ($config->{verbose_freq}) { - if ( ($severity >= $config->{verbose_level}) - || (($severity == -1) && $config->{verbose_unknown})) - { - push @verbose_buffer, [ $timestamp, $counter, $logmsg ]; - } - } - - if ($config->{summary_freq}) { - if ( ($severity >= $config->{summary_level}) - || (($severity == -1) && $config->{summary_unknown})) - { - push @summary_buffer, [ $timestamp, $counter, "path", - $ev->{'prog'}, $ev->{'mode_req'}, $ev->{'resource'} ]; - } - } - - if ($config->{terse_freq}) { - if ( ($severity >= $config->{terse_level}) - || (($severity == -1) && $config->{terse_unknown})) - { - push @terse_buffer, [ $timestamp, $counter, "dummy" ]; - } - } - - } - - unless ( $ev->{'op'} ) { - my $errmsg = "ERROR: No operation found: "; - for my $k (sort keys(%$ev)) { - $errmsg .= "$k is $ev->{$k}, "; - } - errlog("$errmsg\n"); - return; - } - - # Format the message to match the db template - if ($ev->{'op'} eq 'link' ) { - $type = 'link'; - push(@eventList, [$time,$counter,$type,$ev->{'profile'},$ev->{'sdmode'}, - $ev->{'resource'},$ev->{'target'},$ev->{'prog'},$ev->{'pid'},$severity]); - } elsif ($ev->{'op'} eq 'attribute') { - $type = 'chattr'; - push(@eventList, []); - push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'profile'},$ev->{'sdmode'}, - $ev->{'resource'},$ev->{'mode_req'},$ev->{'mode_deny'},$ev->{'prog'}, - $ev->{'pid'},$severity]); - } elsif ($ev->{'op'} eq 'capability') { - $type = 'capability'; - push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'profile'},$ev->{'sdmode'}, - $ev->{'resource'},$ev->{'prog'},$ev->{'pid'},$severity]); - } elsif ($ev->{'op'} eq 'capable') { - $type = 'capable'; - push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'prog'}, - $ev->{'profile'},$ev->{'pid'}]); - } elsif ($ev->{'op'} =~ /ontrol variable/ ) { - $type = 'ctrl_var'; - push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'resource'}, - $ev->{'mode_req'},$ev->{'mode_deny'},$severity]); - } elsif ($ev->{'op'} eq 'unknown_hat') { - $type = 'unknown_hat'; - push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'profile'},$ev->{'sdmode'}, - $ev->{'resource'},$ev->{'pid'},$severity]); - } elsif ($ev->{'op'} eq 'fork') { - $type = 'fork'; - push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'profile'},$ev->{'sdmode'}, - $ev->{'pid'},$ev->{'resource'}]); - } elsif ($ev->{'op'} eq 'changing_profile') { - $type = 'changing_profile'; - push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'profile'},$ev->{'sdmode'}, - $ev->{'pid'}]); - } elsif ($ev->{'op'} eq 'profile_load') { - $type = 'profile_load'; - push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'resource'}, - $ev->{'prog'},$ev->{'pid'}]); - } elsif ($ev->{'op'} eq 'profile_replace') { - $type = 'profile_replacement'; - push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'profile'},$ev->{'sdmode'}, - $ev->{'prog'},$ev->{'pid'},$severity]); - } elsif ($ev->{'op'} eq 'removed') { - $type = 'removed'; - push(@eventList, [$time,$counter,$type,$ev->{'op'},$severity]); - } elsif ($ev->{'op'} eq 'initialized') { - $type = 'initialized'; - push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'resource'},$severity]); - } elsif ( $ev->{'op'} =~ /socket/) { - $type = 'net'; - push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'net_family'}, - $ev->{'net_sock_type'},$ev->{'net_proto'},$ev->{'pid'},$ev->{'profile'}]); - } else { - $type = 'path'; - if ( ! $ev->{'prog'} ) { $ev->{'prog'} = "NIL"; } - push(@eventList, [$time,$counter,$type,$ev->{'op'},$ev->{'profile'}, - $ev->{'sdmode'},$ev->{'mode_req'},$ev->{'mode_deny'},$ev->{'resource'}, - $ev->{'prog'},$ev->{'pid'},$severity]); - } - - push(@commit_buffer, @eventList); - $inserts++; - -} - -sub dump_events { - my ($which, @events) = @_; - - if ($DEBUG) { - my $count = scalar @events; - errlog "dumping $count events to $which db."; - } - - if (open(F, ">>$dbdir/$which.db")) { - for my $event (@events) { - my @event = @$event; - print F "@event\n"; - } - close(F); - } else { - errlog "can't write to $dbdir/$which.db: $!"; - } -} - -sub check_timers ($) { - my $dbh = shift; - - # what time is it right... NOW - my $now = time; - - # make sure we commit periodically - if (($inserts > 10000) || ($now >= ($last_flush_time + $timeout))) { - - my $last_prepare = ""; - my $sth; - - for my $event (sort { $a->[0] cmp $b->[0] } @commit_buffer) { - my @event = @{$event}; - - #my $type = shift @event; - my $type = $event[2]; - - eval { - if ($type ne $last_prepare) { - $sth = $dbh->prepare("INSERT INTO events $templates{$type}"); - $last_prepare = $type; - } - - $sth->execute(@event); - }; - - if ($@) { - print ERRLOG "DBI Execution failed: $DBI::errstr\n"; - } - - #$sth->execute(@event); - } - - $dbh->commit || errlog "Error commiting changes: $!"; - - # need to get the time again to include how much time it takes to - # actually write all this crap to the db - $now = time; - - if ($DEBUG && $inserts) { - $total += $inserts; - my $delta = $now - $last_flush_time; - my $rate = int($inserts / $delta); - errlog "$rate/s $inserts in ${delta}s total=$total max=$max"; - } - - $last_flush_time = $now; - - @commit_buffer = (); - - $max = 0; - $inserts = 0; - - if (@verbose_buffer) { - - # if we've got verbose events, dump them - dump_events("verbose", @verbose_buffer); - - # and clear out our buffer - @verbose_buffer = (); - } - - if (@terse_buffer) { - - # if we've got terse events, dump them - dump_events("terse", @terse_buffer); - - # and clear out our buffer - @terse_buffer = (); - } - - # bail out if we don't have notification configured - return unless -f $cfgfile; - - # what time did we last read the config file? - my $load_time = $config->{load_time}; - - # check when the config file was last modified... - my $mtime = (stat($cfgfile))[9]; - - # if it's been changed since we last read the config file, we need to - # load the new settings - if ($load_time < $mtime) { - errlog "Reloading changed config file."; - $config = readconfig(); - } - - } - - # bail out if we don't have notification configured - return unless -f $cfgfile; - - if ($config->{terse_freq}) { - if (($terse->{last_notify} + $config->{terse_freq}) <= $now) { - if (-f "$dbdir/terse.db") { - $DEBUG && errlog "doing terse notification..."; - - # get a temporary filename... - my ($fh, $filename) = tempfile("terseXXXXXX", DIR => $dbdir); - - # overwrite the temp file we just created... - rename("$dbdir/terse.db", $filename); - - if ($DEBUG) { - errlog "terse file is $filename"; - } - - # do the actual notification in the background - fork_into_background("terse-notification", - \&terse_notify_handler, - $config->{terse_email}, - $filename, - $terse->{last_notify}, - $config->{terse_level}, - $config->{terse_unknown}); - - # ...keep track of when we last sent out a notify - $terse->{last_notify} = $now; - } - } - } - - if ($config->{summary_freq}) { - if (($summary->{last_notify} + $config->{summary_freq}) <= $now) { - if (-f "$dbdir/summary.db") { - $DEBUG && errlog "doing summary notification..."; - - # get a temporary filename... - my ($fh, $filename) = tempfile("summaryXXXXXX", DIR => $dbdir); - - # overwrite the temp file we just created... - rename("$dbdir/summary.db", $filename); - - # do the actual notification in the background - fork_into_background("summary-notification", - \&summary_notify_handler, - $config->{summary_email}, - $filename, - $summary->{last_notify}, - $config->{summary_level}, - $config->{summary_unknown}); - - # ...keep track of when we last sent out a notify - $summary->{last_notify} = $now; - } - } - } - - if ($config->{verbose_freq}) { - if (($verbose->{last_notify} + $config->{verbose_freq}) <= $now) { - if (-f "$dbdir/verbose.db") { - $DEBUG && errlog "doing verbose notification..."; - - # get a temporary filename... - my ($fh, $filename) = tempfile("verboseXXXXXX", DIR => $dbdir); - - # overwrite the temp file we just created... - rename("$dbdir/verbose.db", $filename); - - if ($DEBUG) { - errlog "verbose file is $filename"; - } - - # do the actual notification in the background - fork_into_background("verbose-notification", - \&verbose_notify_handler, - $config->{verbose_email}, - $filename, - $verbose->{last_notify}, - $config->{verbose_level}, - $config->{verbose_unknown}); - - # ...keep track of when we last sent out a notify - $verbose->{last_notify} = $now; - } - } - } - -} - -sub get_last_event { - my $dbh = shift; - - my ($time, $counter); - - # get the oldest timestamp... - my $sth = $dbh->prepare('SELECT MAX(time) FROM events'); - $sth->execute; - my @row = $sth->fetchrow_array || (0); - $time = $row[0]; - if ($time) { - - # get the highest counter for this timestamp... - $sth = $dbh->prepare("SELECT MAX(counter) FROM events WHERE time = $time"); - $sth->execute; - @row = $sth->fetchrow_array || (0); - $counter = $row[0]; - } - - return ($time, $counter); -} - -########################################################################## -# start the real magic... - -my $finished; - -# make sure we exit if someone sends us the right signal -sub sig_handler { - my $signame = shift; - - errlog("Caught signal '$signame'. Exiting..."); - $finished = 1; -} - -# set up our error log without buffering -open(ERRLOG, ">>$dbdir/event-dispatch.log"); -my $oldfd = select(ERRLOG); -$| = 1; -select($oldfd); - -$config = readconfig(); - -# fork off into the background. we need to do this before we connect to -# the db, otherwise, we'll get an ugly error about rolling back a -# connection that's being destroyed -daemonize; - -# automagically reap child processes -$SIG{INT} = \&sig_handler; -$SIG{TERM} = \&sig_handler; -$SIG{CHLD} = 'IGNORE'; - -# Sigh, portable dates in perl sucks -eval "use Date::Parse"; -if (!$@) { - $date_module = 'TimeDate'; -} else { - eval "use Date::Manip"; - if (!$@) { - $date_module = 'DateManip'; - } else { - errlog "Unable to load Date module; use either TimeDate or Date::Manip"; - $finished = 1; - } -} - -# if they want us to write a pid, do it -if ($pidfile) { - if (open(PIDFILE, ">$pidfile")) { - print PIDFILE "$$\n"; - close(PIDFILE); - } -} - -my $dbh = connect_database($dbdir); - -($last_inserted_time, $last_inserted_counter) = get_last_event($dbh); - -my $auditlog = File::Tail->new( - name => $logfile, - debug => 1, - tail => -1, - interval => 1, - maxinterval => 5, - adjustafter => 20, - errmode => "return", - ignore_noexistant => 1 -); -my $syslog = File::Tail->new( - name => $syslogfile, - debug => 1, - tail => -1, - interval => 1, - maxinterval => 5, - adjustafter => 20, - errmode => "return", - ignore_noexistant => 1 -); -my $line = ''; - -# process complete lines from the buffer... -while (not $finished) { - my ($nfound, $timeleft, @pending) = File::Tail::select(undef, undef, undef, $timeout, ($auditlog, $syslog)); - - foreach (@pending) { - process_event($dbh, $_->read); - } - - # see if we should flush pending entries to disk and/or do notification - check_timers($dbh); -} - -# make sure we don't exit with any pending events not written to the db -$dbh->commit || errlog "Error commiting changes: $!"; -$dbh->disconnect || errlog "Error disconnecting from db: $!"; - -# close our error/debugging log file -close(ERRLOG); - -unlink($pidfile) if $pidfile; - -exit 0; diff --git a/deprecated/utils/aa-genprof b/deprecated/utils/aa-genprof deleted file mode 100755 index a2225a7f7..000000000 --- a/deprecated/utils/aa-genprof +++ /dev/null @@ -1,216 +0,0 @@ -#!/usr/bin/perl -# ---------------------------------------------------------------------- -# Copyright (c) 2005 Novell, Inc. 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 as 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. -# -# To contact Novell about this file by physical or electronic mail, -# you may find current contact information at www.novell.com. -# ---------------------------------------------------------------------- - -use strict; -use Getopt::Long; - -use Immunix::AppArmor; - -use Data::Dumper; - -use Locale::gettext; -use POSIX; - -sub sysctl_read($) { - my $path = shift; - my $value = undef; - if (open(SYSCTL, "<$path")) { - $value = int(); - } - close(SYSCTL); - return $value; -} - -sub sysctl_write($$) { - my $path = shift; - my $value = shift; - return if (!defined($value)); - if (open(SYSCTL, ">$path")) { - print SYSCTL $value; - close(SYSCTl); - } -} - -# force $PATH to be sane -$ENV{PATH} = "/bin:/sbin:/usr/bin:/usr/sbin"; - -# initialize the local poo -setlocale(LC_MESSAGES, ""); -textdomain("apparmor-utils"); - -# options variables -my $help = ''; - -GetOptions( - 'file|f=s' => \$filename, - 'dir|d=s' => \$profiledir, - 'help|h' => \$help, -); - -# tell 'em how to use it... -&usage && exit if $help; - -my $sd_mountpoint = check_for_subdomain(); -unless ($sd_mountpoint) { - fatal_error(gettext("AppArmor does not appear to be started. Please enable AppArmor and try again.")); -} - -# let's convert it to full path... -$profiledir = get_full_path($profiledir); - -unless (-d $profiledir) { - fatal_error "Can't find AppArmor profiles in $profiledir."; -} - -# what are we profiling? -my $profiling = shift; - -unless ($profiling) { - $profiling = UI_GetString(gettext("Please enter the program to profile: "), "") - || exit 0; -} - -my $fqdbin; -if (-e $profiling) { - $fqdbin = get_full_path($profiling); - chomp($fqdbin); -} else { - if ($profiling !~ /\//) { - my $which = which($profiling); - if ($which) { - $fqdbin = get_full_path($which); - } - } -} - -unless ($fqdbin && -e $fqdbin) { - if ($profiling =~ /^[^\/]+$/) { - fatal_error(sprintf(gettext('Can\'t find %s in the system path list. If the name of the application is correct, please run \'which %s\' in the other window in order to find the fully-qualified path.'), $profiling, $profiling)); - } else { - fatal_error(sprintf(gettext('%s does not exist, please double-check the path.'), $profiling)); - } -} - - -# make sure that the app they're requesting to profile is not marked as -# not allowed to have it's own profile -check_qualifiers($fqdbin); - -# load all the include files -loadincludes(); - -my $profilefilename = getprofilefilename($fqdbin); -if (-e $profilefilename) { - $helpers{$fqdbin} = getprofileflags($profilefilename) || "enforce"; -} else { - autodep($fqdbin); - $helpers{$fqdbin} = "enforce"; -} - -if ($helpers{$fqdbin} eq "enforce") { - complain($fqdbin); - reload($fqdbin); -} - -# When reading from syslog, it is possible to hit the default kernel -# printk ratelimit. This will result in audit entries getting skipped, -# making profile generation inaccurate. When using genprof, disable -# the printk ratelimit, and restore it on exit. -my $ratelimit_sysctl = "/proc/sys/kernel/printk_ratelimit"; -my $ratelimit_saved = sysctl_read($ratelimit_sysctl); -END { sysctl_write($ratelimit_sysctl, $ratelimit_saved); } -sysctl_write($ratelimit_sysctl, 0); - -UI_Info(gettext("\nBefore you begin, you may wish to check if a\nprofile already exists for the application you\nwish to confine. See the following wiki page for\nmore information:\nhttps://gitlab.com/apparmor/apparmor/wikis/Profiles")); - -UI_Important(gettext("Please start the application to be profiled in \nanother window and exercise its functionality now.\n\nOnce completed, select the \"Scan\" button below in \norder to scan the system logs for AppArmor events. \n\nFor each AppArmor event, you will be given the \nopportunity to choose whether the access should be \nallowed or denied.")); - -my $syslog = 1; -my $logmark = ""; -my $done_profiling = 0; - -$syslog = 0 if (-e "/var/log/audit/audit.log"); - -while (not $done_profiling) { - if ($syslog) { - $logmark = `date | md5sum`; - chomp $logmark; - $logmark = $1 if $logmark =~ /^([0-9a-f]+)/; - system("$logger -p kern.warn 'GenProf: $logmark'"); - } else { - $logmark = last_audit_entry_time(); - } - eval { - - my $q = {}; - $q->{headers} = [ gettext("Profiling"), $fqdbin ]; - $q->{functions} = [ "CMD_SCAN", "CMD_FINISHED" ]; - $q->{default} = "CMD_SCAN"; - - my ($ans, $arg) = UI_PromptUser($q); - - if ($ans eq "CMD_SCAN") { - - my $lp_ret = do_logprof_pass($logmark); - - $done_profiling = 1 if $lp_ret eq "FINISHED"; - - } else { - - $done_profiling = 1; - - } - }; - if ($@) { - if ($@ =~ /FINISHING/) { - $done_profiling = 1; - } else { - die $@; - } - } -} - -for my $p (sort keys %helpers) { - if ($helpers{$p} eq "enforce") { - enforce($p); - reload($p); - } -} - -UI_Info(gettext("Reloaded AppArmor profiles in enforce mode.")); -UI_Info(gettext("\nPlease consider contributing your new profile! See\nthe following wiki page for more information:\nhttps://gitlab.com/apparmor/apparmor/wikis/Profiles\n")); -UI_Info(sprintf(gettext('Finished generating profile for %s.'), $fqdbin)); -exit 0; - -sub usage { - UI_Info(sprintf(gettext("usage: \%s [ -d /path/to/profiles ] [ -f /path/to/logfile ] [ program to profile ]"), $0)); - exit 0; -} - -sub last_audit_entry_time { - local $_ = `tail -1 /var/log/audit/audit.log`; - my $logmark; - if (/^*msg\=audit\((\d+\.\d+\:\d+).*\).*$/) { - $logmark = $1; - } else { - $logmark = ""; - } - return $logmark; -} diff --git a/deprecated/utils/aa-logprof b/deprecated/utils/aa-logprof deleted file mode 100755 index b4c34b993..000000000 --- a/deprecated/utils/aa-logprof +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/perl -# ---------------------------------------------------------------------- -# Copyright (c) 2005 Novell, Inc. 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 as 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. -# -# To contact Novell about this file by physical or electronic mail, -# you may find current contact information at www.novell.com. -# ---------------------------------------------------------------------- - -use strict; -use Data::Dumper; -use Getopt::Long; -use Locale::gettext; -use POSIX; - -use Immunix::AppArmor; - -# force $PATH to be sane -$ENV{PATH} = "/bin:/sbin:/usr/bin:/usr/sbin"; - -# initialize the local poo -setlocale(LC_MESSAGES, ""); -textdomain("apparmor-utils"); - -setup_yast(); - -# options variables -my $help = ''; -my $logmark; - -GetOptions( - 'file|f=s' => \$filename, - 'dir|d=s' => \$profiledir, - 'logmark|m=s' => \$logmark, - 'help|h' => \$help, -); - -# tell 'em how to use it... -&usage && exit if $help; - -# let's convert it to full path... -$profiledir = get_full_path($profiledir); - -unless (-d $profiledir) { - fatal_error "Can't find AppArmor profiles in $profiledir."; -} - -# load all the include files -loadincludes(); - -do_logprof_pass($logmark); - -shutdown_yast(); - -exit 0; - -sub usage { - UI_Info(sprintf(gettext("usage: \%s [ -d /path/to/profiles ] [ -f /path/to/logfile ] [ -m \"mark in log to start processing after\""), $0)); - exit 0; -} - diff --git a/deprecated/utils/aa-notify b/deprecated/utils/aa-notify deleted file mode 100755 index 32c0285e9..000000000 --- a/deprecated/utils/aa-notify +++ /dev/null @@ -1,698 +0,0 @@ -#!/usr/bin/perl -# ------------------------------------------------------------------ -# -# Copyright (C) 2009-2011 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. -# -# ------------------------------------------------------------------ -# -# /etc/apparmor/notify.conf: -# # set to 'yes' to enable AppArmor DENIED notifications -# show_notifications="yes" -# -# # only people in use_group can run this script -# use_group="admin" -# -# $HOME/.apparmor/notify.conf can have: -# # set to 'yes' to enable AppArmor DENIED notifications -# show_notifications="yes" -# - -use strict; -use warnings; -no warnings qw( once ); - -require LibAppArmor; -require POSIX; -require Time::Local; -require File::Basename; - -use Getopt::Long; - -my %prefs; -my $conf = "/etc/apparmor/notify.conf"; -my $user_conf = ""; -my $notify_exe = "/usr/bin/notify-send"; -my $notify_home = ""; -my $notify_display = ""; -my $last_exe = "/usr/bin/last"; -my $ps_exe = "/bin/ps"; -my $url = "https://wiki.ubuntu.com/DebuggingApparmor"; -my $nobody_user = "nobody"; -my $nobody_group = "nogroup"; - -sub readconf; -sub parse_message; -sub format_message; -sub format_stats; -sub kill_running_daemons; -sub do_notify; -sub show_since; -sub do_last; -sub do_show_messages; -sub _error; -sub _warn; -sub _debug; -sub exitscript; -sub usage; - -# -# Main script -# - -# Clean environment -$ENV{PATH} = "/bin:/usr/bin"; -$ENV{SHELL} = "/bin/sh"; -defined($ENV{IFS}) and $ENV{IFS} = ' \t\n'; - -my $prog = File::Basename::basename($0); - -if ($prog !~ /^[a-zA-Z0-9_\-]+$/) { - print STDERR "ERROR: bad programe name '$prog'\n"; - exitscript(1); -} - -$> == $< or die "Cannot be suid\n"; -$) == $( or die "Cannot be sgid\n"; - -my $login; -our $orig_euid = $>; - -my $opt_d = ''; -my $opt_display = ''; -my $opt_h = ''; -my $opt_l = ''; -my $opt_p = ''; -my $opt_v = ''; -my $opt_f = ''; -my $opt_s = 0; -my $opt_u = ''; -my $opt_w = 0; -GetOptions( - 'debug|d' => \$opt_d, - 'display=s' => \$opt_display, - 'help|h' => \$opt_h, - 'since-last|l' => \$opt_l, - 'poll|p' => \$opt_p, - 'verbose|v' => \$opt_v, - 'file|f=s' => \$opt_f, - 'since-days|s=n' => \$opt_s, - 'user|u=s' => \$opt_u, - 'wait|w=n' => \$opt_w, -); -if ($opt_h) { - usage; - exitscript(0); -} - -# monitor file specified with -f, else use audit.log if auditd is running, -# otherwise kern.log -our $logfile = "/var/log/kern.log"; -if ($opt_f) { - -f $opt_f or die "'$opt_f' does not exist. Aborting\n"; - $logfile = $opt_f; -} else { - -e "/var/run/auditd.pid" and $logfile = "/var/log/audit/audit.log"; -} - --r $logfile or die "Cannot read '$logfile'\n"; -our $logfile_inode = get_logfile_inode($logfile); -our $logfile_size = get_logfile_size($logfile); -open (LOGFILE, "<$logfile") or die "Could not open '$logfile'\n"; -# Drop priviliges, if running as root -if ($< == 0) { - $login = "root"; - if (defined($ENV{SUDO_UID}) and defined($ENV{SUDO_GID})) { - $) = "$ENV{SUDO_GID} $ENV{SUDO_GID}" or _error("Could not change egid"); - $( = $ENV{SUDO_GID} or _error("Could not change gid"); - $> = $ENV{SUDO_UID} or _error("Could not change euid"); - defined($ENV{SUDO_USER}) and $login = $ENV{SUDO_USER}; - } else { - my $drop_to = $nobody_user; - if ($opt_u) { - $drop_to = $opt_u; - } - # nobody/nogroup - my $nam = scalar(getgrnam($nobody_group)); - $) = "$nam $nam" or _error("Could not change egid"); - $( = $nam or _error("Could not change gid"); - $> = scalar(getpwnam($drop_to)) or _error("Could not change euid to '$drop_to'"); - } -} else { - $login = getlogin(); - defined $login or $login = $ENV{'USER'}; -} - -if (-s $conf) { - readconf($conf); - if (defined($prefs{use_group})) { - my ($name, $passwd, $gid, $members) = getgrnam($prefs{use_group}); - if (not defined($members) or not defined($login) or (not grep { $_ eq $login } split(/ /, $members) and $login ne "root")) { - _error("'$login' must be in '$prefs{use_group}' group. Aborting.\nAsk your admin to add you to this group or to change the group in\n$conf if you want to use aa-notify."); - } - } -} - -# find user's notify.conf -if (-e "$ENV{HOME}/.apparmor/notify.conf" ) { - # use legacy path if the conf file is there - $user_conf = "$ENV{HOME}/.apparmor/notify.conf"; -} elsif (defined $ENV{XDG_CONFIG_HOME}) { - # use XDG_CONFIG_HOME if it is defined - $user_conf = "$ENV{XDG_CONFIG_HOME}/apparmor/notify.conf"; -} else { - # fallback to the default value of XDG_CONFIG_HOME - $user_conf = "$ENV{HOME}/.config/apparmor/notify.conf"; -} - -if ($opt_p) { - # notify-send is packaged in libnotify-bin on Debian/Ubuntu, libnotify-tools on openSUSE - -x "$notify_exe" or _error("Could not find '$notify_exe'. Please install it (package libnotify-bin or libnotify-tools). Aborting"); - - # we need correct values for $HOME and $DISPLAY environment variables, - # otherwise $notify_exe won't be able to connect to DBUS to display the - # message. Do this here to avoid excessive lookups. - $notify_home = (getpwuid $>)[7]; # homedir of the user - - if ($opt_display ne '') { - $notify_display = $opt_display; - } elsif (defined($ENV{'DISPLAY'})) { - $notify_display = $ENV{'DISPLAY'}; - } - - if ($notify_display eq '') { - my $sudo_warn_msg = ''; - if (defined($ENV{'SUDO_USER'})) { - $sudo_warn_msg = ' (or reset by sudo)'; - } - _warn("Environment variable \$DISPLAY not set$sudo_warn_msg."); - _warn ('Desktop notifications will not work.'); - if ($sudo_warn_msg ne '') { - _warn ('Use sudo aa-notify -p --display "$DISPLAY" to set the environment variable.'); - } else { - _warn ('Use something like aa-notify -p --display :0 to set the environment variable.') - } - } -} elsif ($opt_l) { - -x "$last_exe" or _error("Could not find '$last_exe'. Aborting"); -} -if ($opt_s and not $opt_l) { - $opt_s =~ /^[0-9]+$/ or _error("-s requires a number"); -} - -if ($opt_w) { - $opt_w =~ /^[0-9]+$/ or _error("-w requires a number"); -} - -if ($opt_p or $opt_l) { - if (-s $user_conf) { - readconf($user_conf); - } - - if (defined($prefs{show_notifications}) and $prefs{show_notifications} ne "yes") { - _debug("'show_notifications' is disabled. Exiting"); - exitscript(0); - } -} - -my $now = time(); -if ($opt_p) { - do_notify(); -} elsif ($opt_l) { - do_last(); -} elsif ($opt_s and not $opt_p) { - do_show_messages($opt_s); -} else { - usage; - exitscript(1); -} - -exitscript(0); - -# -# Subroutines -# -sub readconf { - my $cfg = $_[0]; - -r $cfg or die "'$cfg' does not exist\n"; - - open (CFG, "<$cfg") or die "Could not open '$cfg'\n"; - while () { - chomp; - s/#.*//; # no comments - s/^\s+//; # no leading white - s/\s+$//; # no trailing white - next unless length; # anything left? - my ($var, $value) = split(/\s*=\s*/, $_, 2); - if ($var eq "show_notifications" or $var eq "use_group" or $var eq "message_body" or $var eq "message_title" or $var eq "message_footer") { - $value =~ s/^"(.*)"$/$1/g; - $prefs{$var} = $value; - } - } - close(CFG); -} - -sub parse_message { - my @params = @_; - my $msg = $params[0]; - - chomp($msg); - #_debug("processing: $msg"); - - my ($test) = LibAppArmorc::parse_record($msg); - - # Don't show logs before certain date - my $date = LibAppArmor::aa_log_record::swig_epoch_get($test); - my $since = 0; - if (defined($date) and $#params > 0 and $params[1] =~ /^[0-9]+$/) { - $since = int($params[1]); - int($date) >= $since or goto err; - } - - # ignore all but status and denied messages - my $type = LibAppArmor::aa_log_record::swig_event_get($test); - - if ($type != $LibAppArmor::AA_RECORD_DENIED and $type != $LibAppArmor::AA_RECORD_ALLOWED) { - goto err; - } - - my $profile = LibAppArmor::aa_log_record::swig_profile_get($test); - my $operation = LibAppArmor::aa_log_record::swig_operation_get($test); - my $name = LibAppArmor::aa_log_record::swig_name_get($test); - my $denied = LibAppArmor::aa_log_record::swig_denied_mask_get($test); - my $family = LibAppArmor::aa_log_record::swig_net_family_get($test); - my $sock_type = LibAppArmor::aa_log_record::swig_net_sock_type_get($test); - LibAppArmorc::free_record($test); - - return ($profile, $operation, $name, $denied, $family, $sock_type, $date); - -err: - LibAppArmorc::free_record($test); - return (); -} - -sub format_message { - my ($profile, $operation, $name, $denied, $family, $sock_type, $date) = @_; - - my $formatted = ""; - if (defined($prefs{message_body})) { - $formatted .= $prefs{message_body}; - } else { - defined($profile) and $formatted .= "Profile: $profile\n"; - defined($operation) and $formatted .= "Operation: $operation\n"; - defined($name) and $formatted .= "Name: $name\n"; - defined($denied) and $formatted .= "Denied: $denied\n"; - defined($family) and defined ($sock_type) and $formatted .= "Family: $family\nSocket type: $sock_type\n"; - $formatted .= "Logfile: $logfile\n"; - } - - return $formatted; -} - -sub format_stats { - my $num = $_[0]; - my $time = $_[1]; - if ($num > 0) { - print "AppArmor denial"; - $num > 1 and print "s"; - print ": $num (since " . scalar(localtime($time)) . ")\n"; - $opt_v and print "For more information, please see: $url\n"; - } -} - -sub kill_running_daemons { - # Look for other daemon instances of this script and kill them. This - # can happen on logout and back in (in which case $notify_exe fails - # anyway). 'ps xw' should output something like: - # 9987 ? Ss 0:01 /usr/bin/perl ./bin/aa-notify -p - # 10170 ? Ss 0:00 /usr/bin/perl ./bin/aa-notify -p - open(PS,"$ps_exe xw|") or die "Unable to run '$ps_exe':$!\n"; - while() { - chomp; - /$prog -[ps]/ or next; - s/^\s+//; - my @line = split(/\s+/, $_); - if ($line[5] =~ /$prog$/ and ($line[6] eq "-p" or $line[6] eq "-s")) { - if ($line[0] != $$) { - _warn("killing old daemon '$line[0]'"); - kill 15, ($line[0]); - } - } - } - close(PS); -} - -sub send_message { - my $msg = $_[0]; - - my $pid = fork(); - if ($pid == 0) { # child - # notify-send needs $< to be the unprivileged user - $< = $>; - - $notify_home ne "" and $ENV{'HOME'} = $notify_home; - $notify_display ne "" and $ENV{'DISPLAY'} = $notify_display; - if (not defined($ENV{'DBUS_SESSION_BUS_ADDRESS'})) { - $ENV{'DBUS_SESSION_BUS_ADDRESS'} = "unix:path=/run/user/$>/bus"; - } - - # 'system' uses execvp() so no shell metacharacters here. - # $notify_exe is an absolute path so execvp won't search PATH. - system "$notify_exe", "-i", "gtk-dialog-warning", "-u", "normal", "--", "AppArmor Message", "$msg"; - my $exit_code = $? >> 8; - exit($exit_code); - } - - # parent - waitpid($pid, 0); - return $?; -} - -sub do_notify { - my %seen; - my $seconds = 5; - our $time_to_die = 0; - - print "Starting aa-notify\n"; - kill_running_daemons(); - - # Daemonize, but not if in debug mode - if (not $opt_d) { - chdir('/') or die "Can't chdir to /: $!"; - umask 0; - open STDIN, '/dev/null' or die "Can't read /dev/null: $!"; - open STDOUT, '>/dev/null' or die "Can't write to /dev/null: $!"; - #open STDERR, '>/dev/null' or die "Can't write to /dev/null: $!"; - my $pid = fork(); - exit if $pid; - die "Couldn't fork: $!" unless defined($pid); - POSIX::setsid() or die "Can't start a new session: $!"; - } - - sub signal_handler { - $time_to_die = 1; - } - $SIG{INT} = $SIG{TERM} = $SIG{HUP} = \&signal_handler; - $SIG{'PIPE'} = 'IGNORE'; - - if ($opt_w) { - sleep($opt_w); - } - - my $count = 0; - my $footer = exists $prefs{message_footer} ? $prefs{message_footer} : "For more information, please see:\n$url"; - my $first_run = 1; - my $since = $now; - if ($opt_s and int($opt_s) > 0) { - $since = $since - (int($opt_s) * 60 * 60 * 24); - } - for (my $i=0; $time_to_die == 0; $i++) { - if ($logfile_inode != get_logfile_inode($logfile)) { - _warn("$logfile changed inodes, reopening"); - reopen_logfile(); - } elsif (get_logfile_size($logfile) < $logfile_size) { - _warn("$logfile is smaller, reopening"); - reopen_logfile(); - } - while(my $msg = ) { - my @attrib; - if ($first_run == 1) { - if ($since != $now) { - @attrib = parse_message($msg, $since); - } - } else { - @attrib = parse_message($msg); - } - $#attrib > 0 or next; - if ($first_run == 1) { - $count++; - next; - } - - my ($profile, $operation, $name, $denied, $family, $sock_type, $date) = @attrib; - - # Rate limit messages by creating a hash whose keys are: - # - for files: $profile|$name|$denied| - # - for everything else: $profile|$operation|$name|$denied|$family|$sock_type| (as available) - # The value for the key is a timestamp (epoch) and we won't show - # messages whose key has a timestamp from less than 5 seconds afo - my $k = ""; - defined($profile) and $k .= "$profile|"; - if (defined($name) and defined($denied)) { - $k .= "$name|$denied|"; # for file access, don't worry about operation - } else { - defined($operation) and $k .= "$operation|"; - defined($name) and $k .= "$name|"; - defined($denied) and $k .= "$denied|"; - defined($family) and defined ($sock_type) and $k .= "$family|$sock_type|"; - } - - # don't display same message if seen in last 5 seconds - if (not defined($seen{$k})) { - $seen{$k} = time(); - } else { - my $now = time(); - $now - $seen{$k} < $seconds and next; - $seen{$k} = $now; - } - - my $m = format_message(@attrib); - $m ne "" or next; - - $m .= $footer; - - my $rc = send_message($m); - if ($rc != 0) { - _warn("'$notify_exe' exited with error '$rc'"); - $time_to_die = 1; - last; - } - } - # from seek() in Programming Perl - seek(LOGFILE, 0, 1); - sleep(1); - - if ($first_run) { - if ($count > 0) { - my $m = "$logfile contains $count denied message"; - $count > 1 and $m .= "s"; - if ($opt_s) { - $m .= " in the last "; - if ($opt_s > 1) { - $m .= "$opt_s days"; - } else { - $m .= "day"; - } - } - $m .= ". "; - $m .= $footer; - send_message($m); - } - $first_run = 0; - } - - # clean out the %seen database every 30 seconds - if ($i > 30) { - foreach my $k (keys %seen) { - my $now = time(); - $now - $seen{$k} > $seconds and delete $seen{$k} and _debug("deleted $k"); - } - $i = 0; - _debug("done purging"); - foreach my $k (keys %seen) { - _debug("remaining key: $k: $seen{$k}"); - } - } - } - print STDERR "Stopping aa-notify\n"; -} - -sub show_since { - my %msg_hash; - my %last_date; - my @msg_list; - my $count = 0; - while(my $msg = ) { - my @attrib = parse_message($msg, $_[0]); - $#attrib > 0 or next; - - my $m = format_message(@attrib); - $m ne "" or next; - my $date = $attrib[6]; - if ($opt_v) { - if (exists($msg_hash{$m})) { - $msg_hash{$m}++; - defined($date) and $last_date{$m} = scalar(localtime($date)); - } else { - $msg_hash{$m} = 1; - push(@msg_list, $m); - } - } - $count++; - } - if ($opt_v) { - foreach my $m (@msg_list) { - print "$m"; - if ($msg_hash{$m} gt 1) { - print "($msg_hash{$m} found"; - if (exists($last_date{$m})) { - print ", most recent from '$last_date{$m}'"; - } - print ")\n"; - } - print "\n"; - } - } - return $count; -} - -sub do_last { - open(LAST,"$last_exe -F -a $login|") or die "Unable to run $last_exe:$!\n"; - my $time = 0; - while(my $line = ) { - _debug("Checking '$line'"); - $line =~ /^$login/ or next; - $line !~ /^$login\s+pts.*\s+:[0-9]+\.[0-9]+$/ or next; # ignore xterm and friends - my @entry = split(/\s+/, $line); - my ($hour, $min, $sec) = (split(/:/, $entry[5]))[0,1,2]; - $time = Time::Local::timelocal($sec, $min, $hour, $entry[4], $entry[3], $entry[6]); - last; - } - close(LAST); - $time > 0 or _error("Couldn't find last login"); - - format_stats(show_since($time), $time); -} - -sub do_show_messages { - my $since = $now - (int($_[0]) * 60 * 60 * 24); - format_stats(show_since($since), $since); -} - -sub _warn { - my $msg = $_[0]; - print STDERR "aa-notify: WARN: $msg\n"; -} -sub _error { - my $msg = $_[0]; - print STDERR "aa-notify: ERROR: $msg\n"; - exitscript(1); -} - -sub _debug { - $opt_d or return; - my $msg = $_[0]; - print STDERR "aa-notify: DEBUG: $msg\n"; -} - -sub exitscript { - my $rc = $_[0]; - close(LOGFILE); - exit $rc; -} - -sub usage { - my $s = <<'EOF'; -USAGE: aa-notify [OPTIONS] - -Display AppArmor notifications or messages for DENIED entries. - -OPTIONS: - -p, --poll poll AppArmor logs and display notifications - --display $DISPLAY set the DISPLAY environment variable to $DISPLAY - (might be needed if sudo resets $DISPLAY) - -f FILE, --file=FILE search FILE for AppArmor messages - -l, --since-last display stats since last login - -s NUM, --since-days=NUM show stats for last NUM days (can be used alone - or with -p) - -v, --verbose show messages with stats - -h, --help display this help - -u USER, --user=USER user to drop privileges to when not using sudo - -w NUM, --wait=NUM wait NUM seconds before displaying - notifications (with -p) -EOF - print $s; -} - -sub raise_privileges { - my $old_euid = -1; - - if ($> != $<) { - _debug("raising privileges to '$orig_euid'"); - $old_euid = $>; - $> = $orig_euid; - $> == $orig_euid or die "Could not raise privileges\n"; - } - - return $old_euid; -} - -sub drop_privileges { - my $old_euid = $_[0]; - - # Just exit if we didn't raise privileges - $old_euid == -1 and return; - - _debug("dropping privileges to '$old_euid'"); - $> = $old_euid; - $> == $old_euid or die "Could not drop privileges\n"; -} - -sub reopen_logfile { - # reopen the logfile, temporarily switching back to starting euid for - # file permissions. - close(LOGFILE); - - my $old_euid = raise_privileges(); - - $logfile_inode = get_logfile_inode($logfile); - $logfile_size = get_logfile_size($logfile); - open (LOGFILE, "<$logfile") or die "Could not open '$logfile'\n"; - - drop_privileges($old_euid); -} - -sub get_logfile_size { - my $fn = $_[0]; - my $size; - my $dir = File::Basename::dirname($fn); - - # If we can't access the file, then raise privs. This can happen when - # using auditd and /var/log/audit/ is 700. - my $old_euid = -1; - if (! -x $dir) { - $old_euid = raise_privileges(); - } - - defined(($size = (stat($fn))[7])) or (sleep(10) and defined(($size = (stat($fn))[7])) or die "'$fn' disappeared. Aborting\n"); - - drop_privileges($old_euid); - - return $size; -} - -sub get_logfile_inode { - my $fn = $_[0]; - my $inode; - my $dir = File::Basename::dirname($fn); - - # If we can't access the file, then raise privs. This can happen when - # using auditd and /var/log/audit/ is 700. - my $old_euid = -1; - if (! -x $dir) { - $old_euid = raise_privileges(); - } - - defined(($inode = (stat($fn))[1])) or (sleep(10) and defined(($inode = (stat($fn))[1])) or die "'$fn' disappeared. Aborting\n"); - - drop_privileges($old_euid); - - return $inode; -} - -# -# end Subroutines -# diff --git a/deprecated/utils/aa-status b/deprecated/utils/aa-status deleted file mode 100644 index 7192912b1..000000000 --- a/deprecated/utils/aa-status +++ /dev/null @@ -1,218 +0,0 @@ -#!/usr/bin/perl -w -# ------------------------------------------------------------------ -# -# Copyright (C) 2005-2006 Novell/SUSE -# -# 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. -# -# ------------------------------------------------------------------ - - -use strict; -use Getopt::Long; -use Cwd 'abs_path'; - -my $confdir = "/etc/apparmor"; -my $sd_mountpoint; -my $check_enabled = 0; -my $count_enforced = 0; -my $count_profiled = 0; -my $count_complain = 0; -my $verbose = 0; -my $help; - -GetOptions( - 'complaining' => \$count_complain, - 'enabled' => \$check_enabled, - 'enforced' => \$count_enforced, - 'profiled' => \$count_profiled, - 'verbose|v' => \$verbose, - 'help|h' => \$help, -) or usage(); - -sub usage { - print "Usage: $0 [OPTIONS]\n"; - print "Displays various information about the currently loaded AppArmor policy.\n"; - print "OPTIONS (one only):\n"; - print " --enabled returns error code if subdomain not enabled\n"; - print " --profiled prints the number of loaded policies\n"; - print " --enforced prints the number of loaded enforcing policies\n"; - print " --complaining prints the number of loaded non-enforcing policies\n"; - print " --verbose (default) displays multiple data points about loaded policy set\n"; - print " --help this message\n"; - exit; -} - -$verbose = 1 if ($count_complain + $check_enabled + $count_enforced + $count_profiled == 0); -usage() if $help or ($count_complain + $check_enabled + $count_enforced + $count_profiled + $verbose > 1); - -sub is_subdomain_loaded() { - return 1 if (-d "/sys/module/apparmor"); - if(open(MODULES, "/proc/modules")) { - while() { - return 1 if m/^(subdomain|apparmor)\s+/; - } - } - - return 0; -} - -sub find_subdomainfs() { - - my $sd_mountpoint; - if(open(MOUNTS, "/proc/mounts")) { - while() { - $sd_mountpoint = "$1/apparmor" if m/^\S+\s+(\S+)\s+securityfs\s/ && -e "$1/apparmor"; - $sd_mountpoint = "$1/subdomain" if m/^\S+\s+(\S+)\s+securityfs\s/ && -e "$1/subdomain"; - $sd_mountpoint = $1 if m/^\S+\s+(\S+)\s+subdomainfs\s/ && -e "$1"; - } - close(MOUNTS); - } - - return $sd_mountpoint; -} - -sub get_profiles { - my $mountpoint = shift; - my %profiles = (); - - if (open(PROFILES, "$mountpoint/profiles")) { - while() { - $profiles{$1} = $2 if m/^([^\(]+)\s+\((\w+)\)$/; - } - close(PROFILES); - } - return (%profiles); -} - -sub get_processes { - my %profiles = @_; - my %processes = (); - if (opendir(PROC, "/proc")) { - my $file; - while (defined($file = readdir(PROC))) { - if ($file =~ m/^\d+/) { - if (open(CURRENT, "/proc/$file/attr/current")) { - while () { - if (m/^([^\(]+)\s+\((\w+)\)$/) { - $processes{$file}{'profile'} = $1; - $processes{$file}{'mode'} = $2; - } elsif (grep(abs_path("/proc/$file/exe") eq $_ , keys(%profiles))) { - # keep only unconfined processes that have a profile defined - $processes{$file}{'profile'} = abs_path("/proc/$file/exe"); - $processes{$file}{'mode'} = 'unconfined'; - } - } - close(CURRENT); - } - } - } - closedir(PROC); - } - return (%processes); -} - -my $is_loaded = is_subdomain_loaded(); - -if (!$is_loaded) { - print STDERR "apparmor module is not loaded.\n" if $verbose; - exit 1; -} - -print "apparmor module is loaded.\n" if $verbose; - -$sd_mountpoint = find_subdomainfs(); -if (!$sd_mountpoint) { - print STDERR "apparmor filesystem is not mounted.\n" if $verbose; - exit 3; -} - -if (! -r "$sd_mountpoint/profiles") { - print STDERR "You do not have enough privilege to read the profile set.\n" if $verbose; - exit 4; -} - -#print "subdomainfs is at $sd_mountpoint.\n" if $verbose; - -# processes is a hash table : -# * keys : processes pid -# * values : hash containing information about the running process: -# * 'profile' : name of the profile applied to the running process -# * 'mode' : mode of the profile applied to the running process -my %processes = (); -my %enforced_processes = (); -my %complain_processes = (); -my %unconfined_processes = (); - -# profiles is a hash table : -# * keys : profile name -# * value : profile mode -my %profiles; -my @enforced_profiles = (); -my @complain_profiles = (); - -%profiles = get_profiles($sd_mountpoint); -@enforced_profiles = grep { $profiles{$_} eq 'enforce' } keys %profiles; -@complain_profiles = grep { $profiles{$_} eq 'complain' } keys %profiles; - -# we consider the case where no profiles are loaded to be "disabled" as well -my $rc = (keys(%profiles) == 0) ? 2 : 0; - -if ($check_enabled) { - exit $rc; -} - -if ($count_profiled) { - print scalar(keys(%profiles)). "\n"; - exit $rc; -} - -if ($count_enforced) { - print $#enforced_profiles + 1 . "\n"; - exit $rc; -} - -if ($count_complain) { - print $#complain_profiles + 1 . "\n"; - exit $rc; -} - - -if ($verbose) { - print keys(%profiles) . " profiles are loaded.\n"; - print $#enforced_profiles + 1 . " profiles are in enforce mode.\n"; - for (sort(@enforced_profiles)) { - print " " . $_ . "\n"; - } - print $#complain_profiles + 1 . " profiles are in complain mode.\n"; - for (sort(@complain_profiles)) { - print " " . $_ . "\n"; - } -} - -%processes = get_processes(%profiles); -if ($verbose) { - for (keys(%processes)) { - $enforced_processes{$_} = $processes{$_} if $processes{$_}{'mode'} eq 'enforce'; - $complain_processes{$_} = $processes{$_} if $processes{$_}{'mode'} eq 'complain'; - # some early code uses unconfined instead of unconfined. - $unconfined_processes{$_} = $processes{$_} if $processes{$_}{'mode'} =~ /uncon(fi|strai)ned/; - } - print keys(%processes) . " processes have profiles defined.\n"; - print keys(%enforced_processes) . " processes are in enforce mode :\n"; - for (sort { $enforced_processes{$a}{'profile'} cmp $enforced_processes{$b}{'profile'} } keys(%enforced_processes)) { - print " " . $enforced_processes{$_}{'profile'} . " ($_) \n"; - } - print keys(%complain_processes) . " processes are in complain mode.\n"; - for (sort { $complain_processes{$a}{'profile'} cmp $complain_processes{$b}{'profile'} } keys(%complain_processes)) { - print " " . $complain_processes{$_}{'profile'} . " ($_) \n"; - } - print keys(%unconfined_processes) . " processes are unconfined but have a profile defined.\n"; - for (sort { $unconfined_processes{$a}{'profile'} cmp $unconfined_processes{$b}{'profile'} } keys(%unconfined_processes)) { - print " " . $unconfined_processes{$_}{'profile'} . " ($_) \n"; - } -} - -exit $rc; diff --git a/deprecated/utils/aa-unconfined b/deprecated/utils/aa-unconfined deleted file mode 100755 index a5dac3e25..000000000 --- a/deprecated/utils/aa-unconfined +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/perl -w -# ---------------------------------------------------------------------- -# Copyright (c) 2005 Novell, Inc. 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 as 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. -# -# To contact Novell about this file by physical or electronic mail, -# you may find current contact information at www.novell.com. -# ---------------------------------------------------------------------- -# -# unconfined - -# audit local system for processes listening on network connections -# that are not currently running with a profile. - -use strict; -use Getopt::Long; - -use Immunix::AppArmor; -use Locale::gettext; -use POSIX; - -setlocale(LC_MESSAGES, ""); -textdomain("apparmor-utils"); - -# options variables -my $paranoid = ''; -my $help = ''; - -GetOptions( - 'paranoid' => \$paranoid, - 'help|h' => \$help, -); - -# tell 'em how to use it... -&usage && exit if $help; - -sub usage { - printf(gettext("Usage: %s [ --paranoid ]\n"), $0); - exit 0; -} - -my $subdomainfs = check_for_subdomain(); - -die gettext("AppArmor does not appear to be started. Please enable AppArmor and try again.") . "\n" - unless $subdomainfs; - -my @pids; -if ($paranoid) { - opendir(PROC, "/proc") or die gettext("Can't read /proc\n"); - @pids = grep { /^\d+$/ } readdir(PROC); - closedir(PROC); -} else { - if (open(NETSTAT, "LANG=C /bin/netstat -nlp |")) { - while () { - chomp; - push @pids, $5 - if /^(tcp|udp)\s+\d+\s+\d+\s+\S+\:(\d+)\s+\S+\:(\*|\d+)\s+(LISTEN|\s+)\s+(\d+)\/(\S+)/; - } - close(NETSTAT); - } -} - -for my $pid (sort { $a <=> $b } @pids) { - my $prog = readlink "/proc/$pid/exe" or next; - my $attr; - if (open(CURRENT, "/proc/$pid/attr/current")) { - while () { - chomp; - $attr = $_ if (/^\// || /^null/); - } - close(CURRENT); - } - my $cmdline = `cat /proc/$pid/cmdline`; - my $pname = (split(/\0/, $cmdline))[0]; - if ($pname =~ /\// && !($pname eq $prog)) { - $pname = "($pname) "; - } else { - $pname = ""; - } - if (not $attr) { - if ($prog =~ m/^(\/usr\/bin\/python|\/usr\/bin\/perl|\/bin\/bash)$/) { - - #my $scriptname = (split(/\0/, `cat /proc/$pid/cmdline`))[1]; - $cmdline =~ s/\0/ /g; - $cmdline =~ s/\s+$//; - chomp $cmdline; - print "$pid $prog ($cmdline) " . gettext("not confined\n"); - } else { - print "$pid $prog $pname" . gettext("not confined\n"); - } - } else { - if ($prog =~ m/^(\/usr\/bin\/python|\/usr\/bin\/perl|\/bin\/bash)$/) { - - #my $scriptname = (split(/\0/, `cat /proc/$pid/cmdline`))[1]; - $cmdline =~ s/\0/ /g; - $cmdline =~ s/\s+$//; - chomp $cmdline; - print "$pid $prog ($cmdline) " . gettext("confined by") . " '$attr'\n"; - } else { - print "$pid $prog $pname" . gettext("confined by") . " '$attr'\n"; - } - } -} diff --git a/deprecated/utils/repair_obsolete_profiles b/deprecated/utils/repair_obsolete_profiles deleted file mode 100755 index ea2994544..000000000 --- a/deprecated/utils/repair_obsolete_profiles +++ /dev/null @@ -1,201 +0,0 @@ -#!/usr/bin/perl -wi -# automatically repair apparmor profiles that have had their supporting -# infrastructure refactored out from underneath them - -# note -i in shebang line -- this program will modify in-place -# profiles or #include chunks specified on the command line without -# backups. Please make some yourself and inspect the changes made by -# this tool to ensure they look correct. - -# It'll try to fix up #include files (supplied by SUSE/Immunix) that have -# moved; it will also inspect many #include files that exist solely -# for netdomain rule separation, and either remove the #include line -# from profiles/includes or suck in the contents of the specific file, -# depending if there was any non-netdomain content. - -# If you haven't modified any of the files listed in the @useless array, -# you probably don't have to concern yourself with the complicated part -# of the previous paragraph. If you did modify any of those files, this -# tool will inspect those for changes, try to update any lines in those -# files for correctness, and insert those lines directly into the -# referencing profiles. - -our %count_cache; - -# count the number of 'interesting' lines in the file -sub numlines ($) { - my $name = $_[0]; - - return $count_cache{$name} if $count_cache{$name}; - - open FH, $name or return 1; # can't tell -> not empty - - my $linecount=0; - while() { - if (m/^[^#]*#include/) { - $linecount++; - } elsif (m/^\s*#/) { - # just a comment, skip it - } elsif (m/\s*tcp_/) { - # netdomain rules are unenforced, skip it - } elsif (m/\s*udp_/) { - # netdomain rules are unenforced, skip it - } elsif (m/\S+/) { - $linecount++; - } - } - close FH; - - $count_cache{$name} = $linecount; - - return $linecount; -} - -# given a single line from a profile, perform some search/replace -# operations to reflect new locations for old files. -# -# change #include lines that reference files in the @useless array: -# don't print the #include any more, and either suck in the contents of -# the referenced file (calling itself recursively to fix up _those_ -# files) or just leave well enough alone, if the file had no -# 'interesting' lines as defined above. - -%transforms = ( - # renamed around SuSE 9.3 - "abstractions/kde3" => "abstractions/kde", - "abstractions/user-GTK" => "abstractions/gnome", - "abstractions/user-Xauthority" => "abstractions/X", - - # user-custom -> program-chunks around SHASS 1.1, but these changed dirs - "user-custom/fonts" => "abstractions/fonts", - "user-custom/kde3" => "abstractions/kde", - "user-custom/user-GTK" => "abstractions/gnome", - "user-custom/user-mail" => "abstractions/user-mail", - "user-custom/user-manpages" => "abstractions/user-manpages", - "user-custom/user-Xauthority" => "abstractions/X", - "user-custom/user-tmp" => "abstractions/user-tmp", - - # try to forget the -files - "program-chunks/base-files" => "abstractions/base", - "program-chunks/nameservice-files" => "abstractions/nameservice", - "immunix-standard/base-files" => "abstractions/base", - "immunix-standard/nameservice-files" => "abstractions/nameservice", - - # immunix-standard -> program-chunks - "immunix-standard/postfix-bounce" => "program-chunks/postfix-bounce", - "immunix-standard/postfix-cleanup" => "program-chunks/postfix-cleanup", - "immunix-standard/postfix-common" => "program-chunks/postfix-common", - "immunix-standard/postfix-flush" => "program-chunks/postfix-flush", - "immunix-standard/postfix-local" => "program-chunks/postfix-local", - "immunix-standard/postfix-master" => "program-chunks/postfix-master", - "immunix-standard/postfix-nqmgr" => "program-chunks/postfix-nqmgr", - "immunix-standard/postfix-pickup" => "program-chunks/postfix-pickup", - "immunix-standard/postfix-proxymap" => "program-chunks/postfix-proxymap", - "immunix-standard/postfix-qmgr" => "program-chunks/postfix-qmgr", - "immunix-standard/postfix-showq" => "program-chunks/postfix-showq", - "immunix-standard/postfix-smtp" => "program-chunks/postfix-smtp", - "immunix-standard/postfix-smtpd" => "program-chunks/postfix-smtpd", - "immunix-standard/postfix-trivial-rewrite" => "program-chunks/postfix-trivial-rewrite", - "immunix-standard/apache-default-uri" => "program-chunks/apache-default-uri", - "immunix-standard/at" => "program-chunks/at", -); - -# chunks that immunix tools never populated -- lets remove the ones that -# don't have any useful information -my @useless = qw{ - program-chunks/base-nd - program-chunks/portmap-nd - program-chunks/postfix-local-nd - program-chunks/postfix-master-nd - program-chunks/postfix-proxymap-nd - program-chunks/postfix-smtpd-nd - program-chunks/postfix-smtp-nd - user-custom/base-nd - user-custom/portmap-nd - user-custom/postfix-local-nd - user-custom/postfix-master-nd - user-custom/postfix-proxymap-nd - user-custom/postfix-smtpd-nd - user-custom/postfix-smtp-nd - immunix-standard/base-nd - immunix-standard/portmap-nd - immunix-standard/postfix-local-nd - immunix-standard/postfix-master-nd - immunix-standard/postfix-proxymap-nd - immunix-standard/postfix-smtpd-nd - immunix-standard/postfix-smtp-nd - program-chunks/at - program-chunks/fam - program-chunks/httpd - program-chunks/identd - program-chunks/imapd - program-chunks/ipop2d - program-chunks/ipop3d - program-chunks/lpd - program-chunks/mutt - program-chunks/named - program-chunks/nmbd - program-chunks/ntalkd - program-chunks/ntpd - program-chunks/postgres - program-chunks/rpc.lockd - program-chunks/rpc.nfsd - program-chunks/rpc.statd - program-chunks/samba - program-chunks/sendmail.sendmail - program-chunks/shells - program-chunks/slocate - program-chunks/snmpd - program-chunks/spamc - program-chunks/sshd - program-chunks/swat - program-chunks/syslogd - program-chunks/talk - program-chunks/xfs -}; - -# create an alternation to speed up the regexp below -my $useless = join('|', @useless); - -sub fixup ($) { - $line = $_[0]; - - $line =~ s/#include\s+<([^>]+)>/$i = (exists $transforms{$1}) ? $transforms{$1} : "$1"; "#include <$i>"/e; - - if ($line =~ m/\s*#include\s+<($useless)>/) { - my $file = $1; - if (numlines("/etc/subdomain.d/$file") > 0) { - my $succ = open INC, "/etc/subdomain.d/$file"; - if (not $succ) { - print STDERR "Error opening /etc/subdomain.d/$file\n"; - } else { - while(my $included_line = ) { - print fixup_loop($included_line); - } - close INC; - } - } - $line = ""; # this line has been handled by the file - } - - return $line; -} - -# call fixup on a single entry repeatedly -- this way, we can encode -# 'small' changes in the fixup routine when they are made, rather than -# encoding all possible starting points and which specific end point -# they should go to. -sub fixup_loop ($) { - my $line = $_[0]; - my $saved; - do { - $saved = $line; - $line = fixup($saved); - } until ($line eq $saved); - return $line; -} - -# main entry point; fix each line in every file in argv. -while(<>) { - print fixup_loop($_); -}