2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-09-03 15:55:46 +00:00

Re-implement aa-notify in Python (Closes: #16)

- Code layout based on aa-genprof example
- Extend Python dependencies to cover new need by aa-notify
- Update documentation after aa-notify is no longer in Perl
This commit is contained in:
Otto Kekäläinen
2019-01-09 23:59:40 +01:00
parent 3a1eec49d4
commit a74d7cf51c
10 changed files with 1209 additions and 635 deletions

View File

@@ -1,7 +1,7 @@
--- ---
image: ubuntu:latest image: ubuntu:latest
before_script: before_script:
- export DEBIAN_FRONTEND=noninteractive && apt-get update -qq && apt-get install --no-install-recommends -y build-essential apache2-dev autoconf automake bison dejagnu flex libpam-dev libtool perl liblocale-gettext-perl pkg-config python-all-dev python3-all-dev pyflakes3 ruby-dev swig lsb-release - export DEBIAN_FRONTEND=noninteractive && apt-get update -qq && apt-get install --no-install-recommends -y build-essential apache2-dev autoconf automake bison dejagnu flex libpam-dev libtool perl liblocale-gettext-perl pkg-config python-all-dev python3-all-dev pyflakes3 ruby-dev swig lsb-release python3-notify2 python3-psutil
- lsb_release -a - lsb_release -a
- uname -a - uname -a

View File

@@ -317,7 +317,13 @@ incomplete) list of known version dependencies:
The Python utilities require a minimum of Python 2.7 (deprecated) or Python 3.3. The Python utilities require a minimum of Python 2.7 (deprecated) or Python 3.3.
Python 3.x is recommended. Python 2.x support is deprecated since AppArmor 2.11. Python 3.x is recommended. Python 2.x support is deprecated since AppArmor 2.11.
Some utilities (aa-exec, aa-notify and aa-decode) require Perl 5.10.1 or newer. The aa-notify tool's Python dependencies can be satisfied by installing the
following packages (Debian package names, other distros may vary):
* python3-notify2
* python3-psutil
Perl is no longer needed since none of the utilities shipped to end users depend
on it anymore.
Most shell scripts are written for POSIX-compatible sh. aa-decode expects Most shell scripts are written for POSIX-compatible sh. aa-decode expects
bash, probably version 3.2 and higher. bash, probably version 3.2 and higher.

698
deprecated/utils/aa-notify Executable file
View File

@@ -0,0 +1,698 @@
#!/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 (<CFG>) {
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(<PS>) {
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 = <LOGFILE>) {
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 = <LOGFILE>) {
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 = <LAST>) {
_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
#

View File

@@ -1,3 +1,3 @@
What little documentation exists is in src/aalogparse.h. What little documentation exists is in include/aalogparse.h.
Please file bugs using https://bugs.launchpad.net/apparmor/+filebug Please file bugs using https://bugs.launchpad.net/apparmor/+filebug

View File

@@ -20,11 +20,10 @@ COMMONDIR=../common/
include $(COMMONDIR)/Make.rules include $(COMMONDIR)/Make.rules
PERLTOOLS = aa-notify
PYTOOLS = aa-easyprof aa-genprof aa-logprof aa-cleanprof aa-mergeprof \ PYTOOLS = aa-easyprof aa-genprof aa-logprof aa-cleanprof aa-mergeprof \
aa-autodep aa-audit aa-complain aa-enforce aa-disable \ aa-autodep aa-audit aa-complain aa-enforce aa-disable \
aa-status aa-unconfined aa-notify aa-status aa-unconfined
TOOLS = ${PERLTOOLS} ${PYTOOLS} aa-decode aa-remove-unknown TOOLS = ${PYTOOLS} aa-decode aa-remove-unknown
PYSETUP = python-tools-setup.py PYSETUP = python-tools-setup.py
PYMODULES = $(wildcard apparmor/*.py apparmor/rule/*.py) PYMODULES = $(wildcard apparmor/*.py apparmor/rule/*.py)
@@ -92,9 +91,6 @@ check_severity_db: /usr/include/linux/capability.h severity.db
.PHONY: check .PHONY: check
.SILENT: check .SILENT: check
check: check_severity_db check_pod_files check: check_severity_db check_pod_files
for i in ${PERLTOOLS} ; do \
perl -c $$i || exit 1; \
done
for i in ${PYTOOLS} apparmor test/*.py; do \ for i in ${PYTOOLS} apparmor test/*.py; do \
echo Checking $$i; \ echo Checking $$i; \
$(PYFLAKES) $$i || exit 1; \ $(PYFLAKES) $$i || exit 1; \

File diff suppressed because it is too large Load Diff

View File

@@ -181,9 +181,13 @@ def open_file_write(path):
return open_file_anymode('w', path, 'UTF-8') return open_file_anymode('w', path, 'UTF-8')
def open_file_anymode(mode, path, encoding='UTF-8'): def open_file_anymode(mode, path, encoding='UTF-8'):
'''Open specified file in specified mode''' '''Crash-resistant wrapper to open a specified file in specified mode'''
# This avoids a crash when reading a logfile with special characters that
# are not utf8-encoded (for example a latin1 "ö"), and also avoids crashes
# at several other places we don't know yet ;-)
errorhandling = 'surrogateescape' errorhandling = 'surrogateescape'
if sys.version_info[0] < 3: if sys.version_info[0] < 3:
errorhandling = 'replace' errorhandling = 'replace'

View File

@@ -133,6 +133,7 @@ class Config(object):
def read_shell(self, filepath): def read_shell(self, filepath):
"""Reads the shell type conf files and returns config[''][option]=value""" """Reads the shell type conf files and returns config[''][option]=value"""
# @TODO: Use standard ConfigParser when https://bugs.python.org/issue22253 is fixed
config = {'': dict()} config = {'': dict()}
with open_file_read(filepath) as conf_file: with open_file_read(filepath) as conf_file:
for line in conf_file: for line in conf_file:

View File

@@ -30,7 +30,6 @@ else
# PYTHON_DIST_BUILD_PATH based on libapparmor/swig/python/test/Makefile.am # PYTHON_DIST_BUILD_PATH based on libapparmor/swig/python/test/Makefile.am
PYTHON_DIST_BUILD_PATH = ../../libraries/libapparmor/swig/python/build/$$($(PYTHON) -c "import distutils.util; import platform; print(\"lib.%s-%s\" %(distutils.util.get_platform(), platform.python_version()[:3]))") PYTHON_DIST_BUILD_PATH = ../../libraries/libapparmor/swig/python/build/$$($(PYTHON) -c "import distutils.util; import platform; print(\"lib.%s-%s\" %(distutils.util.get_platform(), platform.python_version()[:3]))")
LD_LIBRARY_PATH=../../libraries/libapparmor/src/.libs/ LD_LIBRARY_PATH=../../libraries/libapparmor/src/.libs/
PERL5LIB=../../libraries/libapparmor/swig/perl/blib/lib/:../../libraries/libapparmor/swig/perl/:../../libraries/libapparmor/swig/perl/blib/arch/auto/:../../libraries/libapparmor/swig/perl/blib/arch/auto/LibAppArmor/
PYTHONPATH=..:$(PYTHON_DIST_BUILD_PATH) PYTHONPATH=..:$(PYTHON_DIST_BUILD_PATH)
CONFDIR=$(CURDIR) CONFDIR=$(CURDIR)
BASEDIR=../../profiles/apparmor.d BASEDIR=../../profiles/apparmor.d
@@ -79,7 +78,7 @@ clean:
rm -rf __pycache__/ .coverage htmlcov rm -rf __pycache__/ .coverage htmlcov
check: __libapparmor __parser check: __libapparmor __parser
export PYTHONPATH=$(PYTHONPATH) PERL5LIB=$(PERL5LIB) LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) LC_ALL=C __AA_CONFDIR=$(CONFDIR) __AA_BASEDIR=$(BASEDIR) __AA_PARSER=$(PARSER) ; $(foreach test, $(wildcard test-*.py), echo ; echo === $(test) === ; $(call pyalldo, $(test))) export PYTHONPATH=$(PYTHONPATH) LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) LC_ALL=C __AA_CONFDIR=$(CONFDIR) __AA_BASEDIR=$(BASEDIR) __AA_PARSER=$(PARSER) ; $(foreach test, $(wildcard test-*.py), echo ; echo === $(test) === ; $(call pyalldo, $(test)))
.coverage: $(wildcard ../aa-* ../apparmor/*.py test-*.py) __libapparmor __parser .coverage: $(wildcard ../aa-* ../apparmor/*.py test-*.py) __libapparmor __parser
export PYTHONPATH=$(PYTHONPATH) LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) LC_ALL=C __AA_CONFDIR=$(CONFDIR) __AA_BASEDIR=$(BASEDIR) __AA_PARSER=$(PARSER) ; $(COVERAGE_IGNORE_FAILURES_CMD) ; $(foreach test, $(wildcard test-*.py), echo ; echo === $(test) === ; $(PYTHON) -m coverage run --branch -p $(test); ) export PYTHONPATH=$(PYTHONPATH) LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) LC_ALL=C __AA_CONFDIR=$(CONFDIR) __AA_BASEDIR=$(BASEDIR) __AA_PARSER=$(PARSER) ; $(COVERAGE_IGNORE_FAILURES_CMD) ; $(foreach test, $(wildcard test-*.py), echo ; echo === $(test) === ; $(PYTHON) -m coverage run --branch -p $(test); )

View File

@@ -126,14 +126,14 @@ Feb 4 13:40:38 XPS-13-9370 kernel: [128552.880347] audit: type=1400 audit({epoc
if self.test_logfile and os.path.exists(self.test_logfile): if self.test_logfile and os.path.exists(self.test_logfile):
os.remove(self.test_logfile) os.remove(self.test_logfile)
# The Perl aa-notify script is written so, that it will check for kern.log # The Perl aa-notify script was written so, that it will checked for kern.log
# before printing help when invoked without arguments (sic!). # before printing help when invoked without arguments (sic!).
@unittest.skipUnless(os.path.isfile('/var/log/kern.log'), 'Requires kern.log on system') @unittest.skipUnless(os.path.isfile('/var/log/kern.log'), 'Requires kern.log on system')
def test_no_arguments(self): def test_no_arguments(self):
'''Test using no arguments at all''' '''Test using no arguments at all'''
expected_return_code = 1 expected_return_code = 0
expected_output_has = 'USAGE: aa-notify' expected_output_has = 'usage: aa-notify'
return_code, output = cmd([aanotify_bin]) return_code, output = cmd([aanotify_bin])
result = 'Got return code %d, expected %d\n' % (return_code, expected_return_code) result = 'Got return code %d, expected %d\n' % (return_code, expected_return_code)
@@ -146,23 +146,26 @@ Feb 4 13:40:38 XPS-13-9370 kernel: [128552.880347] audit: type=1400 audit({epoc
expected_return_code = 0 expected_return_code = 0
expected_output_is = \ expected_output_is = \
'''USAGE: aa-notify [OPTIONS] '''usage: aa-notify [-h] [-p] [--display DISPLAY] [-f FILE] [-l] [-s NUM] [-v]
[-u USER] [-w NUM] [--debug]
Display AppArmor notifications or messages for DENIED entries. Display AppArmor notifications or messages for DENIED entries.
OPTIONS: optional arguments:
-p, --poll poll AppArmor logs and display notifications -h, --help show this help message and exit
--display $DISPLAY set the DISPLAY environment variable to $DISPLAY -p, --poll poll AppArmor logs and display notifications
(might be needed if sudo resets $DISPLAY) --display DISPLAY set the DISPLAY environment variable (might be needed
-f FILE, --file=FILE search FILE for AppArmor messages if sudo resets $DISPLAY)
-l, --since-last display stats since last login -f FILE, --file FILE search FILE for AppArmor messages
-s NUM, --since-days=NUM show stats for last NUM days (can be used alone -l, --since-last display stats since last login
or with -p) -s NUM, --since-days NUM
-v, --verbose show messages with stats show stats for last NUM days (can be used alone or
-h, --help display this help with -p)
-u USER, --user=USER user to drop privileges to when not using sudo -v, --verbose show messages with stats
-w NUM, --wait=NUM wait NUM seconds before displaying -u USER, --user USER user to drop privileges to when not using sudo
notifications (with -p) -w NUM, --wait NUM wait NUM seconds before displaying notifications (with
-p)
--debug debug mode
''' '''
return_code, output = cmd([aanotify_bin, '--help']) return_code, output = cmd([aanotify_bin, '--help'])
@@ -190,7 +193,7 @@ OPTIONS:
expected_output_has = 'AppArmor denials: 10 (since' expected_output_has = 'AppArmor denials: 10 (since'
return_code, output = cmd([aanotify_bin, '-f', self.test_logfile, '-l']) return_code, output = cmd([aanotify_bin, '-f', self.test_logfile, '-l'])
if output == "aa-notify: ERROR: Couldn't find last login\n": if "ERROR: Could not find last login" in output:
self.skipTest('Could not find last login') self.skipTest('Could not find last login')
result = 'Got return code %d, expected %d\n' % (return_code, expected_return_code) result = 'Got return code %d, expected %d\n' % (return_code, expected_return_code)
self.assertEqual(expected_return_code, return_code, result + output) self.assertEqual(expected_return_code, return_code, result + output)
@@ -266,7 +269,7 @@ Logfile: {logfile}
AppArmor denials: 10 (since'''.format(logfile=self.test_logfile) AppArmor denials: 10 (since'''.format(logfile=self.test_logfile)
return_code, output = cmd([aanotify_bin, '-f', self.test_logfile, '-l', '-v']) return_code, output = cmd([aanotify_bin, '-f', self.test_logfile, '-l', '-v'])
if output == "aa-notify: ERROR: Couldn't find last login\n": if "ERROR: Could not find last login" in output:
self.skipTest('Could not find last login') self.skipTest('Could not find last login')
result = 'Got return code %d, expected %d\n' % (return_code, expected_return_code) result = 'Got return code %d, expected %d\n' % (return_code, expected_return_code)
self.assertEqual(expected_return_code, return_code, result + output) self.assertEqual(expected_return_code, return_code, result + output)