2006-04-12 19:31:08 +00:00
# $Id$
2006-04-11 21:52:54 +00:00
#
# ----------------------------------------------------------------------
# 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::SubDomain ;
2007-03-18 19:44:57 +00:00
use strict ;
2007-03-21 16:49:39 +00:00
use warnings ;
2006-04-11 21:52:54 +00:00
use Carp ;
use Cwd qw( cwd realpath ) ;
2007-03-12 14:12:40 +00:00
use File::Basename ;
2007-04-26 02:48:24 +00:00
use File::Temp qw/ tempfile tempdir / ;
2007-03-18 19:44:57 +00:00
use Data::Dumper ;
2006-04-11 21:52:54 +00:00
use Locale::gettext ;
use POSIX ;
2007-04-26 02:48:24 +00:00
use Storable qw( dclone ) ;
2007-04-26 02:46:23 +00:00
use Term::ReadKey ;
2006-04-11 21:52:54 +00:00
use Immunix::Severity ;
2008-02-26 12:00:37 +00:00
use Immunix::Repository ;
use Immunix::Config ;
2008-02-26 12:01:10 +00:00
use LibAppArmor ;
2006-04-11 21:52:54 +00:00
require Exporter ;
our @ ISA = qw( Exporter ) ;
2007-03-20 21:58:38 +00:00
our @ EXPORT = qw(
% sd
% qualifiers
% include
% helpers
$ filename
$ profiledir
$ parser
2007-08-14 22:07:40 +00:00
$ logger
2007-03-20 21:58:38 +00:00
$ UI_Mode
$ running_under_genprof
which
getprofilefilename
get_full_path
fatal_error
2008-02-26 12:02:00 +00:00
get_pager
2007-03-20 21:58:38 +00:00
getprofileflags
setprofileflags
complain
enforce
autodep
reload
UI_GetString
UI_GetFile
UI_YesNo
2007-04-26 02:56:54 +00:00
UI_ShortMessage
UI_LongMessage
2007-03-20 21:58:38 +00:00
UI_Important
UI_Info
UI_PromptUser
2008-02-26 12:00:37 +00:00
display_changes
2007-03-20 21:58:38 +00:00
getkey
do_logprof_pass
loadincludes
readprofile
readprofiles
writeprofile
2007-07-13 17:53:12 +00:00
serialize_profile
2008-02-26 12:00:37 +00:00
attach_profile_data
parse_repo_profile
activate_repo_profiles
2007-03-20 21:58:38 +00:00
check_for_subdomain
setup_yast
shutdown_yast
GetDataFromYast
SendDataToYast
checkProfileSyntax
checkIncludeSyntax
2007-04-25 21:06:52 +00:00
check_qualifiers
2007-03-23 18:52:22 +00:00
isSkippableFile
2007-03-20 21:58:38 +00:00
) ;
2007-03-18 19:44:57 +00:00
2006-04-11 21:52:54 +00:00
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 {
2007-03-20 21:58:38 +00:00
require Term::ReadLine ;
import Term:: ReadLine ;
$ term = new Term:: ReadLine 'AppArmor' ;
2006-04-11 21:52:54 +00:00
} ;
# initialize the local poo
2008-04-10 08:25:23 +00:00
setlocale ( LC_MESSAGES , "" )
unless defined ( LC_MESSAGES ) ;
2006-04-11 21:52:54 +00:00
textdomain ( "apparmor-utils" ) ;
# where do we get our log messages from?
our $ filename ;
2008-02-26 12:00:37 +00:00
our $ cfg ;
our $ repo_cfg ;
2007-04-25 21:06:52 +00:00
our $ parser ;
our $ ldd ;
2007-08-14 22:07:40 +00:00
our $ logger ;
2007-04-25 21:06:52 +00:00
our $ profiledir ;
2007-04-26 02:59:17 +00:00
our $ extraprofiledir ;
2006-04-11 21:52:54 +00:00
# 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 ;
2007-03-20 21:58:38 +00:00
our % sd ; # we keep track of the original profiles in %sd
2007-04-26 02:48:24 +00:00
our % original_sd ;
2007-04-26 02:59:17 +00:00
our % extras ; # inactive profiles from extras
2006-04-11 21:52:54 +00:00
2007-03-21 16:49:39 +00:00
my @ log ;
my % pid ;
2006-04-11 21:52:54 +00:00
my % seen ;
my % profilechanges ;
my % prelog ;
my % log ;
my % changed ;
2007-04-26 02:56:54 +00:00
my @ created ;
2006-04-11 21:52:54 +00:00
my % skip ;
2007-03-20 21:58:38 +00:00
our % helpers ; # we want to preserve this one between passes
2006-04-11 21:52:54 +00:00
2008-04-18 21:02:47 +00:00
### THESE VARIABLES ARE USED WITHIN LOGPROF
2008-04-18 21:06:24 +00:00
my % filelist ; # file level stuff including variables in config files
2006-04-11 21:52:54 +00:00
2008-04-18 21:02:47 +00:00
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 ;
2008-04-18 21:04:16 +00:00
my $ AA_EXEC_CHILD = 2048 ;
my $ AA_EXEC_NT = 4096 ;
2008-04-18 21:09:05 +00:00
my $ AA_LINK_SUBSET = 8192 ;
2008-04-18 21:02:47 +00:00
2008-04-18 21:16:15 +00:00
my $ AA_OTHER_SHIFT = 14 ;
my $ AA_USER_MASK = 16384 - 1 ;
2008-04-18 21:02:47 +00:00
my $ AA_EXEC_TYPE = $ AA_MAY_EXEC | $ AA_EXEC_UNSAFE | $ AA_EXEC_INHERIT |
2008-04-24 16:05:33 +00:00
$ AA_EXEC_UNCONFINED | $ AA_EXEC_PROFILE | $ AA_EXEC_CHILD | $ AA_EXEC_NT ;
2008-04-18 21:02:47 +00:00
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 ,
2008-04-18 21:04:16 +00:00
c = > $ AA_EXEC_CHILD + $ AA_EXEC_UNSAFE ,
C = > $ AA_EXEC_CHILD ,
n = > $ AA_EXEC_NT + $ AA_EXEC_UNSAFE ,
N = > $ AA_EXEC_NT ,
2008-04-18 21:02:47 +00:00
) ;
2006-04-11 21:52:54 +00:00
sub debug ($) {
2007-03-20 21:58:38 +00:00
my $ message = shift ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
print DEBUG "$message\n" if $ DEBUGGING ;
2006-04-11 21:52:54 +00:00
}
2007-04-26 02:46:23 +00:00
my % arrows = ( A = > "UP" , B = > "DOWN" , C = > "RIGHT" , D = > "LEFT" ) ;
2006-04-11 21:52:54 +00:00
2007-04-26 02:46:23 +00:00
sub getkey {
2007-05-22 20:49:51 +00:00
# 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 } ;
}
}
2007-03-20 21:58:38 +00:00
}
2006-04-11 21:52:54 +00:00
2007-05-22 20:49:51 +00:00
# return to cooked mode
ReadMode ( 0 ) ;
return $ key ;
2007-04-26 02:46:23 +00:00
}
2006-04-11 21:52:54 +00:00
2007-04-26 02:46:23 +00:00
BEGIN {
2007-03-20 21:58:38 +00:00
# set things up to log extra info if they want...
if ( $ ENV { LOGPROF_DEBUG } ) {
$ DEBUGGING = 1 ;
open ( DEBUG , ">/tmp/logprof_debug_$$.log" ) ;
my $ oldfd = select ( DEBUG ) ;
$| = 1 ;
select ( $ oldfd ) ;
} else {
$ DEBUGGING = 0 ;
}
2006-04-11 21:52:54 +00:00
}
END {
2007-03-20 21:58:38 +00:00
$ DEBUGGING && debug "Exiting..." ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# close the debug log if necessary
close ( DEBUG ) if $ DEBUGGING ;
2006-04-11 21:52:54 +00:00
}
2006-08-04 16:38:22 +00:00
# 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 ($) {
2007-03-20 21:58:38 +00:00
my $ file = shift ;
2006-08-04 16:38:22 +00:00
2007-03-20 21:58:38 +00:00
return undef unless - f $ file ;
2006-08-04 16:38:22 +00:00
2007-03-20 21:58:38 +00:00
# limit our checking to programs/scripts under 10k to speed things up a bit
my $ size = - s $ file ;
return undef unless ( $ size && $ size < 10000 ) ;
2006-08-04 16:38:22 +00:00
2007-03-20 21:58:38 +00:00
my $ found = undef ;
if ( open ( F , $ file ) ) {
while ( <F> ) {
$ found = 1 if /LD_(PRELOAD|LIBRARY_PATH)/ ;
}
close ( F ) ;
2006-08-04 16:38:22 +00:00
}
2007-03-20 21:58:38 +00:00
return $ found ;
2006-08-04 16:38:22 +00:00
}
2006-04-11 21:52:54 +00:00
sub fatal_error ($) {
2007-03-20 21:58:38 +00:00
my $ message = shift ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my $ details = "$message\n" ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
if ( $ DEBUGGING ) {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# we'll include the stack backtrace if we're debugging...
$ details = Carp:: longmess ( $ message ) ;
# write the error to the log
print DEBUG $ details ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# 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 ] ;
2008-04-18 20:49:00 +00:00
exit 1 if defined ( $ caller ) && $ caller =~ /::(Send|Get)Data(To|From)Yast$/ ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# tell the user what the hell happened
UI_Important ( $ details ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# make sure the frontend exits cleanly...
shutdown_yast ( ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# die a horrible flaming death
exit 1 ;
2006-04-11 21:52:54 +00:00
}
sub setup_yast {
2007-03-20 21:58:38 +00:00
# set up the yast connection if we're running under yast...
if ( $ ENV { YAST_IS_RUNNING } ) {
2007-03-18 19:44:57 +00:00
2007-03-20 21:58:38 +00:00
# load the yast module if available.
eval { require ycp ; } ;
unless ( $@ ) {
import ycp ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
$ UI_Mode = "yast" ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# let the frontend know that we're starting
SendDataToYast ( {
type = > "initial_handshake" ,
status = > "backend_starting"
} ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# 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" ) )
{
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# something's broken, die a horrible, painful death
fatal_error "Yast frontend is out of sync from backend agent." ;
}
2007-05-22 20:49:51 +00:00
$ DEBUGGING && debug "Initial handshake ok" ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# the yast connection seems to be working okay
return 1 ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
}
# couldn't init yast
return 0 ;
2006-04-11 21:52:54 +00:00
}
sub shutdown_yast {
2007-03-20 21:58:38 +00:00
if ( $ UI_Mode eq "yast" ) {
SendDataToYast ( { type = > "final_shutdown" } ) ;
my ( $ ypath , $ yarg ) = GetDataFromYast ( ) ;
}
2006-04-11 21:52:54 +00:00
}
sub check_for_subdomain () {
2007-03-20 21:58:38 +00:00
my ( $ support_subdomainfs , $ support_securityfs ) ;
if ( open ( MOUNTS , "/proc/filesystems" ) ) {
while ( <MOUNTS> ) {
$ support_subdomainfs = 1 if m/subdomainfs/ ;
$ support_securityfs = 1 if m/securityfs/ ;
}
close ( MOUNTS ) ;
}
2007-03-21 16:49:39 +00:00
my $ sd_mountpoint = "" ;
2007-03-20 21:58:38 +00:00
if ( open ( MOUNTS , "/proc/mounts" ) ) {
while ( <MOUNTS> ) {
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" ;
}
}
}
2006-04-11 21:52:54 +00:00
}
2007-03-20 21:58:38 +00:00
close ( MOUNTS ) ;
2006-04-11 21:52:54 +00:00
}
2007-03-20 21:58:38 +00:00
# make sure that subdomain is actually mounted there
$ sd_mountpoint = undef unless - f "$sd_mountpoint/profiles" ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
return $ sd_mountpoint ;
2006-04-11 21:52:54 +00:00
}
sub which ($) {
2007-03-20 21:58:38 +00:00
my $ file = shift ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
foreach my $ dir ( split ( /:/ , $ ENV { PATH } ) ) {
return "$dir/$file" if - x "$dir/$file" ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
return undef ;
2006-04-11 21:52:54 +00:00
}
# we need to convert subdomain regexps to perl regexps
sub convert_regexp ($) {
2007-03-20 21:58:38 +00:00
my $ regexp = shift ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# escape regexp-special characters we don't support
2007-07-29 02:23:31 +00:00
$ regexp =~ s/(?<!\\)(\.|\+|\$)/\\$1/g ;
# * and ** globs can't collapse to match an empty string when they're
# the only part of the glob at a specific directory level, which
# complicates things a little.
# ** globs match multiple directory levels
$ regexp =~ s{(?<!\\)\*\*+} {
my ( $ pre , $ post ) = ( $` , $' ) ;
if ( ( $ pre =~ /\/$/ ) && ( ! $ post || $ post =~ /^\// ) ) {
'SD_INTERNAL_MULTI_REQUIRED' ;
} else {
'SD_INTERNAL_MULTI_OPTIONAL' ;
}
} gex ;
# convert * globs to match anything at the current path level
$ regexp =~ s{(?<!\\)\*} {
my ( $ pre , $ post ) = ( $` , $' ) ;
if ( ( $ pre =~ /\/$/ ) && ( ! $ post || $ post =~ /^\// ) ) {
'SD_INTERNAL_SINGLE_REQUIRED' ;
} else {
'SD_INTERNAL_SINGLE_OPTIONAL' ;
}
} gex ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# convert ? globs to match a single character at current path level
$ regexp =~ s/(?<!\\)\?/[^\/]/g ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# convert {foo,baz} to (foo|baz)
$ regexp =~ y /\{\}\,/ \ ( \ ) \ | / if $regexp =~ / \ { . * \ , . * \ } / ;
2006-04-11 21:52:54 +00:00
2007-07-29 02:23:31 +00:00
# convert internal markers to their appropriate regexp equivalents
$ regexp =~ s/SD_INTERNAL_SINGLE_OPTIONAL/[^\/]*/g ;
$ regexp =~ s/SD_INTERNAL_SINGLE_REQUIRED/[^\/]+/g ;
$ regexp =~ s/SD_INTERNAL_MULTI_OPTIONAL/.*/g ;
$ regexp =~ s/SD_INTERNAL_MULTI_REQUIRED/[^\/].*/g ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
return $ regexp ;
2006-04-11 21:52:54 +00:00
}
sub get_full_path ($) {
2007-03-20 21:58:38 +00:00
my $ originalpath = shift ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my $ path = $ originalpath ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# keep track so we can break out of loops
my $ linkcount = 0 ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# if we don't have any directory foo, look in the current dir
$ path = cwd ( ) . "/$path" if $ path !~ m/\// ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# beat symlinks into submission
while ( - l $ path ) {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
if ( $ linkcount + + > 64 ) {
fatal_error "Followed too many symlinks resolving $originalpath" ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# split out the directory/file components
if ( $ path =~ m/^(.*)\/(.+)$/ ) {
my ( $ dir , $ file ) = ( $ 1 , $ 2 ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# 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" ;
}
}
2006-04-11 21:52:54 +00:00
}
2007-03-20 21:58:38 +00:00
if ( - f $ path ) {
my ( $ dir , $ file ) = $ path =~ m/^(.*)\/(.+)$/ ;
$ path = realpath ( $ dir ) . "/$file" ;
} else {
$ path = realpath ( $ path ) ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
return $ path ;
2006-04-11 21:52:54 +00:00
}
sub findexecutable ($) {
2007-03-20 21:58:38 +00:00
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 ) ;
}
}
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
unless ( $ fqdbin && - e $ fqdbin ) {
return undef ;
}
return $ fqdbin ;
2006-04-11 21:52:54 +00:00
}
2008-04-24 16:05:33 +00:00
sub name_to_prof_filename ($) {
2007-03-20 21:58:38 +00:00
my $ bin = shift ;
2008-04-24 16:05:33 +00:00
my $ filename ;
2006-04-11 21:52:54 +00:00
2008-04-24 16:05:33 +00:00
unless ( $ bin =~ /^($profiledir|profile_)/ ) {
my $ fqdbin = findexecutable ( $ bin ) ;
if ( $ fqdbin ) {
$ filename = getprofilefilename ( $ fqdbin ) ;
return ( $ filename , $ fqdbin ) if - f $ filename ;
}
}
2006-04-11 21:52:54 +00:00
2008-04-24 16:05:33 +00:00
if ( $ bin =~ /^$profiledir(.*)/ ) {
my $ profile = $ 1 ;
return ( $ bin , $ profile ) if - f $ bin ;
} elsif ( $ bin =~ /^\// ) {
$ filename = getprofilefilename ( $ bin ) ;
return ( $ filename , $ bin ) if - f $ filename ;
} else {
# not an absolute path try it as a profile_
$ bin = $ 1 if ( $ bin !~ /^profile_(.*)/ ) ;
$ filename = getprofilefilename ( $ bin ) ;
return ( $ filename , "profile_${bin}" ) if - f $ filename ;
}
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 ) ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
setprofileflags ( $ filename , "complain" ) ;
2006-04-11 21:52:54 +00:00
}
sub enforce ($) {
2007-03-20 21:58:38 +00:00
my $ bin = shift ;
2006-04-11 21:52:54 +00:00
2008-04-24 16:05:33 +00:00
return if ( ! $ bin ) ;
2006-04-11 21:52:54 +00:00
2008-04-24 16:05:33 +00:00
my ( $ filename , $ name ) = name_to_prof_filename ( $ bin )
or fatal_error ( sprintf ( gettext ( 'Can\'t find %s.' ) , $ bin ) ) ;
2006-04-11 21:52:54 +00:00
2008-04-24 16:05:33 +00:00
UI_Info ( sprintf ( gettext ( 'Setting %s to enforce mode.' ) , $ name ) ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
setprofileflags ( $ filename , "" ) ;
2006-04-11 21:52:54 +00:00
}
sub head ($) {
2007-03-20 21:58:38 +00:00
my $ file = shift ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my $ first = "" ;
if ( open ( FILE , $ file ) ) {
$ first = <FILE> ;
close ( FILE ) ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
return $ first ;
2006-04-11 21:52:54 +00:00
}
sub get_output (@) {
2007-03-20 21:58:38 +00:00
my ( $ program , @ args ) = @ _ ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my $ ret = - 1 ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my $ pid ;
my @ output ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
if ( - x $ program ) {
$ pid = open ( KID_TO_READ , "-|" ) ;
unless ( defined $ pid ) {
fatal_error "can't fork: $!" ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
if ( $ pid ) {
while ( <KID_TO_READ> ) {
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
}
2006-04-11 21:52:54 +00:00
}
2007-03-20 21:58:38 +00:00
return ( $ ret , @ output ) ;
2006-04-11 21:52:54 +00:00
}
sub get_reqs ($) {
2007-03-20 21:58:38 +00:00
my $ file = shift ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my @ reqs ;
my ( $ ret , @ ldd ) = get_output ( $ ldd , $ file ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
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/ ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# avoid new kernel 2.6 poo
next if $ line =~ /linux-(gate|vdso(32|64)).so/ ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
if ( $ line =~ /^\s*\S+ => (\/\S+)/ ) {
push @ reqs , $ 1 ;
} elsif ( $ line =~ /^\s*(\/\S+)/ ) {
push @ reqs , $ 1 ;
}
}
2006-04-11 21:52:54 +00:00
}
2007-03-20 21:58:38 +00:00
return @ reqs ;
2006-04-11 21:52:54 +00:00
}
sub handle_binfmt ($$) {
2007-03-20 21:58:38 +00:00
my ( $ profile , $ fqdbin ) = @ _ ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my % reqs ;
my @ reqs = get_reqs ( $ fqdbin ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
while ( my $ library = shift @ reqs ) {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
$ library = get_full_path ( $ library ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
push @ reqs , get_reqs ( $ library ) unless $ reqs { $ library } + + ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# does path match anything pulled in by includes in original profile?
2008-04-18 21:08:05 +00:00
my $ combinedmode = match_prof_incs_to_path ( $ profile , 'allow' , $ library ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# if we found any matching entries, do the modes match?
next if $ combinedmode ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
$ library = globcommon ( $ library ) ;
chomp $ library ;
next unless $ library ;
2006-04-11 21:52:54 +00:00
2008-04-18 21:07:16 +00:00
$ profile - > { allow } { path } - > { $ library } { mode } = str_to_mode ( "mr" ) ;
2008-04-18 21:10:25 +00:00
$ profile - > { allow } { path } - > { $ library } { audit } = 0 ;
2007-03-20 21:58:38 +00:00
}
2006-04-11 21:52:54 +00:00
}
2007-04-26 02:59:17 +00:00
sub get_inactive_profile {
my $ fqdbin = shift ;
if ( $ extras { $ fqdbin } ) {
return { $ fqdbin = > $ extras { $ fqdbin } } ;
}
}
2007-04-26 02:48:24 +00:00
2007-09-10 19:42:18 +00:00
2008-02-26 12:00:37 +00:00
sub create_new_profile {
my $ fqdbin = shift ;
2008-04-24 16:05:33 +00:00
my $ profile ;
if ( $ fqdbin =~ /^\// ) {
$ profile = {
$ fqdbin = > {
flags = > "complain" ,
include = > { "abstractions/base" = > 1 } ,
path = > { $ fqdbin = > { mode = > str_to_mode ( "mr" ) } } ,
}
} ;
} else {
$ profile = {
$ fqdbin = > {
flags = > "complain" ,
include = > { "abstractions/base" = > 1 } ,
}
} ;
}
2008-02-26 12:00:37 +00:00
# if the executable exists on this system, pull in extra dependencies
if ( - f $ fqdbin ) {
my $ hashbang = head ( $ fqdbin ) ;
if ( $ hashbang =~ /^#!\s*(\S+)/ ) {
my $ interpreter = get_full_path ( $ 1 ) ;
2008-04-18 21:07:16 +00:00
$ profile - > { $ fqdbin } { allow } { path } - > { $ interpreter } { mode } = str_to_mode ( "ix" ) ;
2008-04-18 21:10:25 +00:00
$ profile - > { $ fqdbin } { allow } { path } - > { $ interpreter } { audit } = 0 ;
2008-02-26 12:00:37 +00:00
if ( $ interpreter =~ /perl/ ) {
$ profile - > { $ fqdbin } { include } - > { "abstractions/perl" } = 1 ;
} elsif ( $ interpreter =~ m/\/bin\/(bash|sh)/ ) {
$ profile - > { $ fqdbin } { include } - > { "abstractions/bash" } = 1 ;
}
handle_binfmt ( $ profile - > { $ fqdbin } , $ interpreter ) ;
} else {
handle_binfmt ( $ profile - > { $ fqdbin } , $ fqdbin ) ;
}
}
# create required infrastructure hats if it's a known change_hat app
2008-04-24 16:05:33 +00:00
for my $ hatglob ( keys % { $ cfg - > { required_hats } } ) {
2008-02-26 12:00:37 +00:00
if ( $ fqdbin =~ /$hatglob/ ) {
for my $ hat ( sort split ( /\s+/ , $ cfg - > { required_hats } { $ hatglob } ) ) {
$ profile - > { $ hat } = { flags = > "complain" } ;
}
}
}
push @ created , $ fqdbin ;
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 } ;
}
}
2007-04-26 02:59:17 +00:00
sub get_profile {
my $ fqdbin = shift ;
my $ profile_data ;
2007-05-22 20:49:51 +00:00
my $ distro = $ cfg - > { repository } { distro } ;
2008-02-26 12:00:37 +00:00
my $ repo_url = $ cfg - > { repository } { url } ;
2007-04-26 02:59:17 +00:00
my @ profiles ;
2008-02-26 12:00:37 +00:00
my % profile_hash ;
2007-04-26 02:48:24 +00:00
2008-02-26 12:00:37 +00:00
if ( repo_is_enabled ( ) ) {
my $ results ;
UI_BusyStart ( gettext ( "Connecting to repository....." ) ) ;
2008-04-24 16:05:33 +00:00
2008-02-26 12:00:37 +00:00
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 ) ;
}
2007-04-26 02:59:17 +00:00
}
2007-04-26 02:48:24 +00:00
2007-04-26 02:59:17 +00:00
my $ inactive_profile = get_inactive_profile ( $ fqdbin ) ;
if ( defined $ inactive_profile && $ inactive_profile ne "" ) {
2007-07-16 13:19:02 +00:00
# set the profile to complain mode
2008-02-26 12:00:37 +00:00
my $ uname = gettext ( "Inactive local profile for " ) . $ fqdbin ;
2007-07-16 13:19:02 +00:00
$ inactive_profile - > { $ fqdbin } { $ fqdbin } { flags } = "complain" ;
2008-02-26 12:00:37 +00:00
$ profile_hash { $ uname } =
2007-04-26 02:59:17 +00:00
{
2008-02-26 12:00:37 +00:00
"username" = > $ uname ,
2007-04-26 02:59:17 +00:00
"profile_type" = > "INACTIVE_LOCAL" ,
"profile" = > serialize_profile (
$ { %$ inactive_profile } { $ fqdbin } ,
$ fqdbin
) ,
"profile_data" = > $ inactive_profile ,
} ;
}
2007-04-26 02:48:24 +00:00
2008-02-26 12:00:37 +00:00
return undef if ( keys % profile_hash == 0 ) ; # No repo profiles, no inactive
2007-04-26 02:59:17 +00:00
# profile
2007-05-22 20:49:51 +00:00
my @ options ;
my @ tmp_list ;
my $ preferred_present = 0 ;
my $ preferred_user = $ cfg - > { repository } { preferred_user } || "NOVELL" ;
2008-02-26 12:00:37 +00:00
foreach my $ p ( keys % profile_hash ) {
if ( $ profile_hash { $ p } - > { username } eq $ preferred_user ) {
2007-05-22 20:49:51 +00:00
$ preferred_present = 1 ;
} else {
2008-02-26 12:00:37 +00:00
push @ tmp_list , $ profile_hash { $ p } - > { username } ;
2007-05-22 20:49:51 +00:00
}
}
if ( $ preferred_present ) {
push @ options , $ preferred_user ;
}
push @ options , @ tmp_list ;
2007-04-26 02:48:24 +00:00
2007-04-26 02:59:17 +00:00
my $ q = { } ;
$ q - > { headers } = [] ;
push @ { $ q - > { headers } } , gettext ( "Profile" ) , $ fqdbin ;
2007-04-26 02:48:24 +00:00
2007-04-26 02:59:17 +00:00
$ q - > { functions } = [ "CMD_VIEW_PROFILE" , "CMD_USE_PROFILE" ,
"CMD_CREATE_PROFILE" , "CMD_ABORT" , "CMD_FINISHED" ] ;
2007-04-26 02:48:24 +00:00
2007-04-26 02:59:17 +00:00
$ q - > { default } = "CMD_VIEW_PROFILE" ;
2007-04-26 02:48:24 +00:00
2007-04-26 02:59:17 +00:00
$ q - > { options } = [ @ options ] ;
$ q - > { selected } = 0 ;
2007-04-26 02:48:24 +00:00
2007-04-26 02:59:17 +00:00
my ( $ p , $ ans , $ arg ) ;
do {
( $ ans , $ arg ) = UI_PromptUser ( $ q ) ;
2008-02-26 12:00:37 +00:00
$ p = $ profile_hash { $ options [ $ arg ] } ;
2007-05-22 20:49:51 +00:00
for ( my $ i = 0 ; $ i < scalar ( @ options ) ; $ i + + ) {
if ( $ options [ $ i ] eq $ options [ $ arg ] ) {
2007-04-26 02:59:17 +00:00
$ q - > { selected } = $ i ;
2007-04-26 02:48:24 +00:00
}
2007-04-26 02:59:17 +00:00
}
2007-04-26 02:48:24 +00:00
2007-04-26 02:59:17 +00:00
if ( $ ans eq "CMD_VIEW_PROFILE" ) {
2007-04-26 02:48:24 +00:00
if ( $ UI_Mode eq "yast" ) {
2007-04-26 02:59:17 +00:00
SendDataToYast (
{
type = > "dialog-view-profile" ,
user = > $ options [ $ arg ] ,
profile = > $ p - > { profile } ,
profile_type = > $ p - > { profile_type }
}
) ;
my ( $ ypath , $ yarg ) = GetDataFromYast ( ) ;
2007-04-26 02:48:24 +00:00
} else {
2007-05-22 20:49:51 +00:00
my $ pager = get_pager ( ) ;
open ( PAGER , "| $pager" ) ;
2007-04-26 02:59:17 +00:00
print PAGER gettext ( "Profile submitted by" ) .
2008-02-26 12:00:37 +00:00
" $options[$arg]:\n\n" . $ p - > { profile } . "\n\n" ;
2007-04-26 02:59:17 +00:00
close ( PAGER ) ;
2007-04-26 02:48:24 +00:00
}
2007-04-26 02:59:17 +00:00
} 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 =
2008-02-26 12:00:37 +00:00
parse_repo_profile ( $ fqdbin , $ repo_url , $ p ) ;
2007-04-26 02:59:17 +00:00
}
}
} until ( $ ans =~ /^CMD_(USE_PROFILE|CREATE_PROFILE)$/ ) ;
2007-04-26 02:48:24 +00:00
2007-04-26 02:59:17 +00:00
return $ profile_data ;
2007-04-26 02:48:24 +00:00
}
2008-02-26 12:00:37 +00:00
sub activate_repo_profiles ($$$) {
my ( $ url , $ profiles , $ complain ) = @ _ ;
2007-04-26 02:48:24 +00:00
2008-02-26 12:00:37 +00:00
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 ) ) ;
2007-03-20 21:58:38 +00:00
}
}
2008-02-26 12:00:37 +00:00
} ;
# if there were errors....
if ( $@ ) {
$@ =~ s/\n$// ;
print STDERR sprintf ( gettext ( "Error activating profiles: %s\n" ) , $@ ) ;
2006-04-11 21:52:54 +00:00
}
2007-04-25 21:05:07 +00:00
}
2008-04-24 16:05:33 +00:00
sub autodep_base ($$) {
my ( $ bin , $ pname ) = @ _ ;
2007-04-26 02:59:17 +00:00
% extras = ( ) ;
2007-04-25 21:05:07 +00:00
2008-04-24 16:05:33 +00:00
$ bin = $ pname if ( ! $ bin ) && ( $ pname =~ /^\// ) ;
2007-08-17 21:05:28 +00:00
unless ( $ repo_cfg || not defined $ cfg - > { repository } { url } ) {
2007-04-26 02:59:17 +00:00
$ repo_cfg = read_config ( "repository.conf" ) ;
2007-07-13 17:53:12 +00:00
if ( ( not defined $ repo_cfg - > { repository } ) ||
( $ repo_cfg - > { repository } { enabled } eq "later" ) ) {
2008-02-26 12:00:37 +00:00
UI_ask_to_enable_repo ( ) ;
2007-04-26 02:59:17 +00:00
}
2007-04-26 02:48:24 +00:00
}
2008-04-24 16:05:33 +00:00
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 ;
2007-04-25 21:05:07 +00:00
2008-04-24 16:05:33 +00:00
# try to make sure we have a full path in case findexecutable failed
return unless $ fqdbin =~ /^\// ;
2007-04-25 21:05:07 +00:00
2008-04-24 16:05:33 +00:00
# ignore directories
return if - d $ fqdbin ;
}
$ pname = $ fqdbin if $ fqdbin ;
2007-04-25 21:05:07 +00:00
2007-04-26 02:59:17 +00:00
my $ profile_data ;
2008-02-26 12:00:37 +00:00
2007-04-26 02:59:17 +00:00
readinactiveprofiles ( ) ; # need to read the profiles to see if an
# inactive local profile is present
2008-04-24 16:05:33 +00:00
$ profile_data = eval { get_profile ( $ pname ) } ;
2007-04-26 02:59:17 +00:00
unless ( $ profile_data ) {
2008-04-24 16:05:33 +00:00
$ profile_data = create_new_profile ( $ pname ) ;
2007-04-26 02:59:17 +00:00
}
2007-04-25 21:05:07 +00:00
2008-04-24 16:05:33 +00:00
my $ file = getprofilefilename ( $ pname ) ;
2007-04-25 21:05:07 +00:00
# stick the profile into our data structure.
attach_profile_data ( \ % sd , $ profile_data ) ;
2007-04-26 02:59:17 +00:00
# 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 ) ;
2007-04-25 21:05:07 +00:00
2007-03-20 21:58:38 +00:00
if ( - f "$profiledir/tunables/global" ) {
2008-04-18 21:06:24 +00:00
unless ( exists $ filelist { $ file } ) {
$ filelist { $ file } = { } ;
2007-03-20 21:58:38 +00:00
}
2008-04-24 16:05:33 +00:00
$ filelist { $ file } { include } { 'tunables/global' } = 1 ; # sorry
2007-03-20 21:58:38 +00:00
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# write out the profile...
2008-04-24 16:05:33 +00:00
writeprofile_ui_feedback ( $ pname ) ;
}
sub autodep ($) {
my $ bin = shift ;
return autodep_base ( $ bin , "" ) ;
2006-04-11 21:52:54 +00:00
}
sub getprofilefilename ($) {
2007-03-20 21:58:38 +00:00
my $ profile = shift ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my $ filename = $ profile ;
2008-04-18 21:01:10 +00:00
if ( $ filename =~ /^\// ) {
$ filename =~ s/^\/// ; # strip leading /
} else {
$ filename = "profile_$filename" ;
}
2007-03-20 21:58:38 +00:00
$ filename =~ s/\//./g ; # convert /'s to .'s
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
return "$profiledir/$filename" ;
2006-04-11 21:52:54 +00:00
}
sub setprofileflags ($$) {
2007-03-20 21:58:38 +00:00
my $ filename = shift ;
my $ newflags = shift ;
if ( open ( PROFILE , "$filename" ) ) {
if ( open ( NEWPROFILE , ">$filename.new" ) ) {
while ( <PROFILE> ) {
if ( m/^\s*("??\/.+?"??)\s+(flags=\(.+\)\s+)*\{\s*$/ ) {
my ( $ binary , $ flags ) = ( $ 1 , $ 2 ) ;
2008-02-26 12:00:37 +00:00
if ( $ newflags ) {
$ _ = "$binary flags=($newflags) {\n" ;
} else {
$ _ = "$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 } = [ "Repository" , $ url , ] ;
$ q - > { explanation } = $ explanation ;
$ q - > { functions } = [ "CMD_UPLOAD_CHANGES" ,
"CMD_VIEW_CHANGES" ,
2008-04-24 16:05:33 +00:00
"CMD_ASK_LATER" ,
2008-02-26 12:00:37 +00:00
"CMD_ASK_NEVER" ,
"CMD_ABORT" , ] ;
$ q - > { default } = "CMD_VIEW_CHANGES" ;
$ q - > { options } = [ map { $ _ - > [ 0 ] } @ profiles ] ;
$ q - > { selected } = 0 ;
2007-03-20 21:58:38 +00:00
2008-02-26 12:00:37 +00:00
my ( $ ans , $ arg ) ;
do {
( $ ans , $ arg ) = UI_PromptUser ( $ q ) ;
if ( $ ans eq "CMD_VIEW_CHANGES" ) {
display_changes ( $ profiles [ $ arg ] - > [ 2 ] , $ profiles [ $ arg ] - > [ 1 ] ) ;
}
2008-04-24 16:05:33 +00:00
} until $ ans =~ /^CMD_(UPLOAD_CHANGES|ASK_NEVER|ASK_LATER)/ ;
2008-02-26 12:00:37 +00:00
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 ) ;
2007-03-20 21:58:38 +00:00
}
}
2008-02-26 12:00:37 +00:00
} 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" ) ) ;
2006-04-11 21:52:54 +00:00
}
}
}
2008-02-26 12:00:37 +00:00
#
# 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 ) ;
2007-03-20 21:58:38 +00:00
}
2006-04-11 21:52:54 +00:00
}
##########################################################################
# Here are the console/yast interface functions
sub UI_Info ($) {
2007-03-20 21:58:38 +00:00
my $ text = shift ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
$ DEBUGGING && debug "UI_Info: $UI_Mode: $text" ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
if ( $ UI_Mode eq "text" ) {
print "$text\n" ;
} else {
ycp:: y2milestone ( $ text ) ;
}
2006-04-11 21:52:54 +00:00
}
sub UI_Important ($) {
2007-03-20 21:58:38 +00:00
my $ text = shift ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
$ DEBUGGING && debug "UI_Important: $UI_Mode: $text" ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
if ( $ UI_Mode eq "text" ) {
print "\n$text\n" ;
} else {
SendDataToYast ( { type = > "dialog-error" , message = > $ text } ) ;
my ( $ path , $ yarg ) = GetDataFromYast ( ) ;
}
2006-04-11 21:52:54 +00:00
}
sub UI_YesNo ($$) {
2007-03-20 21:58:38 +00:00
my $ text = shift ;
my $ default = shift ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
$ DEBUGGING && debug "UI_YesNo: $UI_Mode: $text $default" ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my $ ans ;
if ( $ UI_Mode eq "text" ) {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my $ yes = gettext ( "(Y)es" ) ;
my $ no = gettext ( "(N)o" ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# 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 ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
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 ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# convert back from a localized answer to english y or n
$ ans = ( lc ( $ ans ) eq $ yeskey ) ? "y" : "n" ;
} else {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
SendDataToYast ( { type = > "dialog-yesno" , question = > $ text } ) ;
my ( $ ypath , $ yarg ) = GetDataFromYast ( ) ;
$ ans = $ yarg - > { answer } || $ default ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
return $ ans ;
2006-04-11 21:52:54 +00:00
}
2006-08-04 16:38:22 +00:00
sub UI_YesNoCancel ($$) {
2007-03-20 21:58:38 +00:00
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 ;
}
2006-08-04 16:38:22 +00:00
}
2007-03-20 21:58:38 +00:00
} else {
2006-08-04 16:38:22 +00:00
2007-03-20 21:58:38 +00:00
SendDataToYast ( { type = > "dialog-yesnocancel" , question = > $ text } ) ;
my ( $ ypath , $ yarg ) = GetDataFromYast ( ) ;
$ ans = $ yarg - > { answer } || $ default ;
2006-08-04 16:38:22 +00:00
2007-03-20 21:58:38 +00:00
}
2006-08-04 16:38:22 +00:00
2007-03-20 21:58:38 +00:00
return $ ans ;
2006-08-04 16:38:22 +00:00
}
2006-04-11 21:52:54 +00:00
sub UI_GetString ($$) {
2007-03-20 21:58:38 +00:00
my $ text = shift ;
my $ default = shift ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
$ DEBUGGING && debug "UI_GetString: $UI_Mode: $text $default" ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my $ string ;
if ( $ UI_Mode eq "text" ) {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
if ( $ term ) {
$ string = $ term - > readline ( $ text , $ default ) ;
} else {
local $| = 1 ;
print "$text" ;
$ string = <STDIN> ;
chomp ( $ string ) ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
} else {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
SendDataToYast ( {
type = > "dialog-getstring" ,
label = > $ text ,
default = > $ default
} ) ;
my ( $ ypath , $ yarg ) = GetDataFromYast ( ) ;
$ string = $ yarg - > { string } ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
}
return $ string ;
2006-04-11 21:52:54 +00:00
}
sub UI_GetFile ($) {
2007-03-20 21:58:38 +00:00
my $ f = shift ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
$ DEBUGGING && debug "UI_GetFile: $UI_Mode" ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my $ filename ;
if ( $ UI_Mode eq "text" ) {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
local $| = 1 ;
print "$f->{description}\n" ;
$ filename = <STDIN> ;
chomp ( $ filename ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
} else {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
$ f - > { type } = "dialog-getfile" ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
SendDataToYast ( $ f ) ;
my ( $ ypath , $ yarg ) = GetDataFromYast ( ) ;
if ( $ yarg - > { answer } eq "okay" ) {
$ filename = $ yarg - > { filename } ;
}
2006-04-11 21:52:54 +00:00
}
2007-03-20 21:58:38 +00:00
return $ filename ;
2006-04-11 21:52:54 +00:00
}
2007-09-10 19:42:18 +00:00
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 ( ) ;
}
}
2006-04-11 21:52:54 +00:00
my % CMDS = (
2007-03-20 21:58:38 +00:00
CMD_ALLOW = > "(A)llow" ,
2008-04-18 21:10:25 +00:00
CMD_OTHER = > "(M)ore" ,
2008-04-24 16:05:33 +00:00
CMD_AUDIT_NEW = > "Audi(t)" ,
CMD_AUDIT_OFF = > "Audi(t) off" ,
CMD_AUDIT_FULL = > "Audit (A)ll" ,
2008-04-18 21:10:25 +00:00
CMD_OTHER = > "(O)pts" ,
2008-04-24 16:05:33 +00:00
CMD_USER_ON = > "(O)wner permissions on" ,
CMD_USER_OFF = > "(O)wner permissions off" ,
2007-03-20 21:58:38 +00:00
CMD_DENY = > "(D)eny" ,
CMD_ABORT = > "Abo(r)t" ,
CMD_FINISHED = > "(F)inish" ,
2008-04-24 16:05:33 +00:00
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" ,
2007-04-26 02:56:54 +00:00
CMD_SAVE = > "(S)ave Changes" ,
CMD_CONTINUE = > "(C)ontinue Profiling" ,
2007-03-20 21:58:38 +00:00
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 SubDomain events" ,
CMD_HELP = > "(H)elp" ,
2007-04-26 02:59:17 +00:00
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" ,
2008-02-26 12:02:00 +00:00
CMD_VIEW = > "(V)iew" ,
2007-04-26 02:59:17 +00:00
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" ,
2007-07-13 17:53:12 +00:00
CMD_YES = > "(Y)es" ,
CMD_NO = > "(N)o" ,
2007-07-29 02:06:00 +00:00
CMD_ALL_NET = > "Allow All (N)etwork" ,
CMD_NET_FAMILY = > "Allow Network Fa(m)ily" ,
2008-02-26 12:00:37 +00:00
CMD_OVERWRITE = > "(O)verwrite Profile" ,
CMD_KEEP = > "(K)eep Profile" ,
CMD_CONTINUE = > "(C)ontinue" ,
2006-04-11 21:52:54 +00:00
) ;
sub UI_PromptUser ($) {
2007-03-20 21:58:38 +00:00
my $ q = shift ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my ( $ cmd , $ arg ) ;
if ( $ UI_Mode eq "text" ) {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
( $ cmd , $ arg ) = Text_PromptUser ( $ q ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
} else {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
$ q - > { type } = "wizard" ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
SendDataToYast ( $ q ) ;
my ( $ ypath , $ yarg ) = GetDataFromYast ( ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
$ cmd = $ yarg - > { selection } || "CMD_ABORT" ;
$ arg = $ yarg - > { selected } ;
}
2006-04-11 21:52:54 +00:00
2007-04-26 02:46:23 +00:00
if ( $ cmd eq "CMD_ABORT" ) {
confirm_and_abort ( ) ;
$ cmd = "XXXINVALIDXXX" ;
} elsif ( $ cmd eq "CMD_FINISHED" ) {
confirm_and_finish ( ) ;
$ cmd = "XXXINVALIDXXX" ;
}
2007-04-25 20:47:13 +00:00
2007-04-26 02:46:23 +00:00
if ( wantarray ) {
return ( $ cmd , $ arg ) ;
} else {
return $ cmd ;
}
2006-04-11 21:52:54 +00:00
}
2007-04-26 02:56:54 +00:00
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 ( ) ;
}
2006-04-11 21:52:54 +00:00
##########################################################################
# 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 {
2007-03-20 21:58:38 +00:00
my $ data = shift ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
$ DEBUGGING && debug "SendDataToYast: Waiting for YCP command" ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
while ( <STDIN> ) {
$ DEBUGGING && debug "SendDataToYast: YCP: $_" ;
my ( $ ycommand , $ ypath , $ yargument ) = ycp:: ParseCommand ( $ _ ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
if ( $ ycommand && $ ycommand eq "Read" ) {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
if ( $ DEBUGGING ) {
my $ debugmsg = Data::Dumper - > Dump ( [ $ data ] , [ qw( *data ) ] ) ;
debug "SendDataToYast: Sending--\n$debugmsg" ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
ycp:: Return ( $ data ) ;
return 1 ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
} else {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
$ DEBUGGING && debug "SendDataToYast: Expected 'Read' but got-- $_" ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
}
2006-04-11 21:52:54 +00:00
}
2007-03-20 21:58:38 +00:00
# if we ever break out here, something's horribly wrong.
fatal_error "SendDataToYast: didn't receive YCP command before connection died" ;
2006-04-11 21:52:54 +00:00
}
# this is super ugly, but waits for the next ycp Write command and grabs
# whatever the ycp front end gives us
sub GetDataFromYast {
2007-03-20 21:58:38 +00:00
$ DEBUGGING && debug "GetDataFromYast: Waiting for YCP command" ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
while ( <STDIN> ) {
$ DEBUGGING && debug "GetDataFromYast: YCP: $_" ;
my ( $ ycmd , $ ypath , $ yarg ) = ycp:: ParseCommand ( $ _ ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
if ( $ DEBUGGING ) {
my $ debugmsg = Data::Dumper - > Dump ( [ $ yarg ] , [ qw( *data ) ] ) ;
debug "GetDataFromYast: Received--\n$debugmsg" ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
if ( $ ycmd && $ ycmd eq "Write" ) {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
ycp:: Return ( "true" ) ;
return ( $ ypath , $ yarg ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
} else {
$ DEBUGGING && debug "GetDataFromYast: Expected 'Write' but got-- $_" ;
}
2006-04-11 21:52:54 +00:00
}
2007-03-20 21:58:38 +00:00
# if we ever break out here, something's horribly wrong.
fatal_error "GetDataFromYast: didn't receive YCP command before connection died" ;
2006-04-11 21:52:54 +00:00
}
2007-04-25 20:47:13 +00:00
sub confirm_and_abort {
2007-04-26 02:59:17 +00:00
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 ( ) ;
exit 0 ;
}
2007-04-25 20:47:13 +00:00
}
sub confirm_and_finish {
2007-09-17 01:56:14 +00:00
die "FINISHING\n" ;
2007-04-25 20:47:13 +00:00
}
2008-04-24 16:05:33 +00:00
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 ;
}
2006-04-11 21:52:54 +00:00
##########################################################################
# this is the hideously ugly function that descends down the flow/event
# trees that we've generated by parsing the logfile
sub handlechildren {
2007-03-20 21:58:38 +00:00
my $ profile = shift ;
my $ hat = shift ;
my $ root = shift ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my @ entries = @$ root ;
for my $ entry ( @ entries ) {
fatal_error "$entry is not a ref" if not ref ( $ entry ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
if ( ref ( $ entry - > [ 0 ] ) ) {
handlechildren ( $ profile , $ hat , $ entry ) ;
} else {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my @ entry = @$ entry ;
my $ type = shift @ entry ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
if ( $ type eq "fork" ) {
my ( $ pid , $ p , $ h ) = @ entry ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
if ( ( $ p !~ /null(-complain)*-profile/ )
&& ( $ h !~ /null(-complain)*-profile/ ) )
{
$ profile = $ p ;
$ hat = $ h ;
}
2006-04-11 21:52:54 +00:00
2008-04-24 16:05:33 +00:00
if ( $ hat ) {
$ profilechanges { $ pid } = $ profile . "//" . $ hat ;
} else {
$ profilechanges { $ pid } = $ profile ;
}
2007-03-20 21:58:38 +00:00
} elsif ( $ type eq "unknown_hat" ) {
my ( $ pid , $ p , $ h , $ sdmode , $ uhat ) = @ entry ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
if ( $ p !~ /null(-complain)*-profile/ ) {
$ profile = $ p ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
if ( $ sd { $ profile } { $ uhat } ) {
$ hat = $ uhat ;
next ;
}
2008-02-26 12:00:37 +00:00
2008-04-18 21:03:28 +00:00
my $ new_p = update_repo_profile ( $ sd { $ profile } { $ profile } ) ;
2008-02-26 12:00:37 +00:00
if ( $ new_p and
UI_SelectUpdatedRepoProfile ( $ profile , $ new_p ) and
2007-11-06 16:46:57 +00:00
$ sd { $ profile } { $ uhat } ) {
$ hat = $ uhat ;
next ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# figure out what our default hat for this application is.
my $ defaulthat ;
2007-04-25 21:06:52 +00:00
for my $ hatglob ( keys % { $ cfg - > { defaulthat } } ) {
$ defaulthat = $ cfg - > { defaulthat } { $ hatglob }
2007-03-20 21:58:38 +00:00
if $ profile =~ /$hatglob/ ;
}
# keep track of previous answers for this run...
my $ context = $ profile ;
$ context . = " -> ^$uhat" ;
2007-04-25 20:48:51 +00:00
my $ ans = $ transitions { $ context } || "XXXINVALIDXXX" ;
2007-03-20 21:58:38 +00:00
2007-04-25 20:48:51 +00:00
while ( $ ans !~ /^CMD_(ADDHAT|USEDEFAULT|DENY)$/ ) {
2007-03-20 21:58:38 +00:00
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 ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
$ q - > { functions } = [] ;
push @ { $ q - > { functions } } , "CMD_ADDHAT" ;
push @ { $ q - > { functions } } , "CMD_USEDEFAULT" if $ defaulthat ;
2007-04-25 20:47:13 +00:00
push @ { $ q - > { functions } } , "CMD_DENY" , "CMD_ABORT" ,
"CMD_FINISHED" ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
$ q - > { default } = ( $ sdmode eq "PERMITTING" ) ? "CMD_ADDHAT" : "CMD_DENY" ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
$ seenevents + + ;
2006-04-11 21:52:54 +00:00
2007-04-26 02:46:23 +00:00
$ ans = UI_PromptUser ( $ q ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
}
2007-04-25 20:48:51 +00:00
$ transitions { $ context } = $ ans ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
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 ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
} elsif ( $ type eq "capability" ) {
my ( $ pid , $ p , $ h , $ prog , $ sdmode , $ capability ) = @ entry ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
if ( ( $ p !~ /null(-complain)*-profile/ )
&& ( $ h !~ /null(-complain)*-profile/ ) )
{
$ profile = $ p ;
$ hat = $ h ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# print "$pid $profile $hat $prog $sdmode capability $capability\n";
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
next unless $ profile && $ hat ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
$ prelog { $ sdmode } { $ profile } { $ hat } { capability } { $ capability } = 1 ;
} elsif ( ( $ type eq "path" ) || ( $ type eq "exec" ) ) {
2008-04-24 16:05:33 +00:00
my ( $ pid , $ p , $ h , $ prog , $ sdmode , $ mode , $ detail , $ to_name ) = @ entry ;
2006-08-04 16:38:22 +00:00
2008-04-18 21:02:47 +00:00
$ mode = 0 unless ( $ mode ) ;
2007-03-20 21:58:38 +00:00
if ( ( $ p !~ /null(-complain)*-profile/ )
&& ( $ h !~ /null(-complain)*-profile/ ) )
{
$ profile = $ p ;
$ hat = $ h ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
next unless $ profile && $ hat ;
my $ domainchange = ( $ type eq "exec" ) ? "change" : "nochange" ;
2008-04-24 16:05:33 +00:00
2007-03-20 21:58:38 +00:00
# escape special characters that show up in literal paths
$ detail =~ s/(\[|\]|\+|\*|\{|\})/\\$1/g ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# 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 ;
2007-07-16 13:19:02 +00:00
2008-04-24 16:05:33 +00:00
if ( $ mode & str_to_mode ( "x" ) ) {
2007-03-20 21:58:38 +00:00
if ( - d $ exec_target ) {
2008-04-24 16:05:33 +00:00
$ mode & = ( ~ $ ALL_AA_EXEC_TYPE ) ;
2008-04-18 21:02:47 +00:00
$ mode |= str_to_mode ( "ix" ) ;
2007-03-20 21:58:38 +00:00
} else {
$ do_execute = 1 ;
}
}
2006-04-11 21:52:54 +00:00
2008-04-18 21:02:47 +00:00
if ( $ mode & $ AA_MAY_LINK ) {
2007-03-20 21:58:38 +00:00
if ( $ detail =~ m/^from (.+) to (.+)$/ ) {
my ( $ path , $ target ) = ( $ 1 , $ 2 ) ;
2008-04-18 21:02:47 +00:00
my $ frommode = str_to_mode ( "lr" ) ;
2007-03-20 21:58:38 +00:00
if ( defined $ prelog { $ sdmode } { $ profile } { $ hat } { path } { $ path } ) {
2008-04-18 21:02:47 +00:00
$ frommode |= $ prelog { $ sdmode } { $ profile } { $ hat } { path } { $ path } ;
2007-03-20 21:58:38 +00:00
}
$ prelog { $ sdmode } { $ profile } { $ hat } { path } { $ path } = $ frommode ;
2008-04-18 21:02:47 +00:00
my $ tomode = str_to_mode ( "lr" ) ;
2007-03-20 21:58:38 +00:00
if ( defined $ prelog { $ sdmode } { $ profile } { $ hat } { path } { $ target } ) {
2008-04-18 21:02:47 +00:00
$ tomode |= $ prelog { $ sdmode } { $ profile } { $ hat } { path } { $ target } ;
2007-03-20 21:58:38 +00:00
}
$ 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 ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
if ( defined $ prelog { $ sdmode } { $ profile } { $ hat } { path } { $ path } ) {
2008-04-18 21:02:47 +00:00
$ mode |= $ prelog { $ sdmode } { $ profile } { $ hat } { path } { $ path } ;
2007-03-20 21:58:38 +00:00
}
$ prelog { $ sdmode } { $ profile } { $ hat } { path } { $ path } = $ mode ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# print "$pid $profile $hat $prog $sdmode $mode $path\n";
2006-08-04 16:38:22 +00:00
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
if ( $ do_execute ) {
2008-04-18 21:08:05 +00:00
next if ( profile_known_exec ( $ sd { $ profile } { $ hat } ,
"exec" , $ exec_target ) ) ;
2008-04-18 21:03:28 +00:00
my $ p = update_repo_profile ( $ sd { $ profile } { $ profile } ) ;
2008-04-24 16:05:33 +00:00
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 ) ) ;
}
2007-03-20 21:58:38 +00:00
my $ context = $ profile ;
$ context . = "^$hat" if $ profile ne $ hat ;
$ context . = " -> $exec_target" ;
my $ ans = $ transitions { $ context } || "" ;
2008-04-18 21:10:25 +00:00
my ( $ combinedmode , $ combinedaudit , $ cm , $ am , @ m ) ;
2008-04-18 21:02:47 +00:00
$ combinedmode = 0 ;
2008-04-18 21:10:25 +00:00
$ combinedaudit = 0 ;
2008-04-18 21:02:47 +00:00
2007-03-20 21:58:38 +00:00
# does path match any regexps in original profile?
2008-04-18 21:10:25 +00:00
( $ cm , $ am , @ m ) = rematchfrag ( $ sd { $ profile } { $ hat } , 'allow' , $ exec_target ) ;
2008-04-18 21:02:47 +00:00
$ combinedmode |= $ cm if $ cm ;
2008-04-18 21:10:25 +00:00
$ combinedaudit |= $ am if $ am ;
2007-03-20 21:58:38 +00:00
2008-04-24 16:05:33 +00:00
# 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 ;
}
}
2007-03-20 21:58:38 +00:00
# does path match anything pulled in by includes in
# original profile?
2008-04-18 21:10:25 +00:00
( $ cm , $ am , @ m ) = match_prof_incs_to_path ( $ sd { $ profile } { $ hat } , 'allow' , $ exec_target ) ;
2008-04-18 21:02:47 +00:00
$ combinedmode |= $ cm if $ cm ;
2008-04-18 21:10:25 +00:00
$ combinedaudit |= $ am if $ am ;
2008-04-24 16:05:33 +00:00
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 ;
}
}
2007-03-20 21:58:38 +00:00
2008-04-24 16:05:33 +00:00
#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
2008-04-18 21:02:47 +00:00
my $ exec_mode = 0 ;
2008-04-24 16:05:33 +00:00
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" ;
2008-04-18 21:02:47 +00:00
$ exec_mode = str_to_mode ( "ixr" ) ;
2007-03-20 21:58:38 +00:00
} elsif ( contains ( $ combinedmode , "px" ) ) {
2008-04-24 16:05:33 +00:00
if ( $ to_name ) {
$ ans = "CMD_nx" ;
} else {
$ ans = "CMD_px" ;
}
2008-04-18 21:02:47 +00:00
$ exec_mode = str_to_mode ( "px" ) ;
2008-04-24 16:05:33 +00:00
} elsif ( contains ( $ combinedmode , "cx" ) ) {
if ( $ to_name ) {
$ ans = "CMD_nx" ;
} else {
$ ans = "CMD_cx" ;
}
$ exec_mode = str_to_mode ( "cx" ) ;
2007-03-20 21:58:38 +00:00
} elsif ( contains ( $ combinedmode , "ux" ) ) {
2008-04-24 16:05:33 +00:00
$ ans = "CMD_ux" ;
2008-04-18 21:02:47 +00:00
$ exec_mode = str_to_mode ( "ux" ) ;
2007-03-20 21:58:38 +00:00
} elsif ( contains ( $ combinedmode , "Px" ) ) {
2008-04-24 16:05:33 +00:00
if ( $ to_name ) {
$ ans = "CMD_nx_safe" ;
} else {
$ ans = "CMD_px_safe" ;
}
2008-04-18 21:02:47 +00:00
$ exec_mode = str_to_mode ( "Px" ) ;
2008-04-24 16:05:33 +00:00
} elsif ( contains ( $ combinedmode , "Cx" ) ) {
if ( $ to_name ) {
$ ans = "CMD_nx_safe" ;
} else {
$ ans = "CMD_cx_safe" ;
}
$ exec_mode = str_to_mode ( "Cx" ) ;
2007-03-20 21:58:38 +00:00
} elsif ( contains ( $ combinedmode , "Ux" ) ) {
2008-04-24 16:05:33 +00:00
$ ans = "CMD_ux_safe" ;
2008-04-18 21:02:47 +00:00
$ exec_mode = str_to_mode ( "Ux" ) ;
2007-03-20 21:58:38 +00:00
} else {
2008-04-24 16:05:33 +00:00
my $ options = $ cfg - > { qualifiers } { $ exec_target } || "ipcnu" ;
fatal_error "$entry has transition name but not transition mode" if $ to_name ;
2007-03-20 21:58:38 +00:00
# force "ix" as the only option when the profiled
# program executes itself
$ options = "i" if $ exec_target eq $ profile ;
2008-04-24 16:05:33 +00:00
# for now don't allow hats to cx
$ options =~ s/c// if $ hat and $ hat ne $ profile ;
2007-03-20 21:58:38 +00:00
# we always need deny...
$ options . = "d" ;
# figure out what our default option should be...
my $ default ;
if ( $ options =~ /p/
&& - e getprofilefilename ( $ exec_target ) )
{
2008-04-24 16:05:33 +00:00
$ default = "CMD_px" ;
2007-03-20 21:58:38 +00:00
} elsif ( $ options =~ /i/ ) {
2008-04-24 16:05:33 +00:00
$ default = "CMD_ix" ;
} elsif ( $ options =~ /c/ ) {
$ default = "CMD_cx" ;
} elsif ( $ options =~ /n/ ) {
$ default = "CMD_nx" ;
2007-03-20 21:58:38 +00:00
} 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 ;
}
2008-04-24 16:05:33 +00:00
# $to_name should NOT exist here other wise we know what
# mode we are supposed to be transitioning to
# which is handled above.
2007-03-20 21:58:38 +00:00
push @ { $ q - > { headers } } , gettext ( "Execute" ) , $ exec_target ;
push @ { $ q - > { headers } } , gettext ( "Severity" ) , $ severity ;
$ q - > { functions } = [] ;
my $ prompt = "\n$context\n" ;
2008-04-24 16:05:33 +00:00
my $ exec_toggle = 0 ;
push @ { $ q - > { functions } } , build_x_functions ( $ default , $ options , $ exec_toggle ) ;
2007-03-20 21:58:38 +00:00
$ options = join ( "|" , split ( // , $ options ) ) ;
$ seenevents + + ;
2008-04-24 16:05:33 +00:00
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" ) ;
}
2007-03-20 21:58:38 +00:00
2008-04-24 16:05:33 +00:00
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 ) ;
2007-03-20 21:58:38 +00:00
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 ) ;
2008-04-24 16:05:33 +00:00
$ ans = "CMD_$match" ;
2007-03-20 21:58:38 +00:00
if ( $ ynans eq "y" ) {
2008-04-24 16:05:33 +00:00
$ exec_mode & = ~ $ AA_EXEC_UNSAFE ;
2007-03-20 21:58:38 +00:00
}
2008-04-24 16:05:33 +00:00
} elsif ( $ ans eq "CMD_ux" ) {
$ exec_mode = str_to_mode ( "ux" ) ;
2007-03-20 21:58:38 +00:00
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" ) {
2008-04-24 16:05:33 +00:00
$ exec_mode & = ~ ( $ AA_EXEC_UNSAFE | ( $ AA_EXEC_UNSAFE << $ AA_OTHER_SHIFT ) ) ;
2007-03-20 21:58:38 +00:00
}
} else {
$ ans = "INVALID" ;
}
}
}
$ transitions { $ context } = $ ans ;
2008-04-24 16:05:33 +00:00
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 {
2008-04-18 21:08:05 +00:00
if ( $ ans eq "CMD_DENY" ) {
2008-04-24 16:05:33 +00:00
$ sd { $ profile } { $ hat } { deny } { path } { $ exec_target } { mode } |= str_to_mode ( "x" ) ;
2008-04-18 21:10:25 +00:00
$ sd { $ profile } { $ hat } { deny } { path } { $ exec_target } { audit } |= 0 ;
2008-04-18 21:08:05 +00:00
$ changed { $ profile } = 1 ;
}
2007-03-20 21:58:38 +00:00
# skip all remaining events if they say to deny
# the exec
return if $ domainchange eq "change" ;
}
2008-04-24 16:05:33 +00:00
unless ( $ ans eq "CMD_DENY" ) {
# ???? if its defined in the prelog we shouldn't have asked
2007-03-20 21:58:38 +00:00
if ( defined $ prelog { PERMITTING } { $ profile } { $ hat } { path } { $ exec_target } ) {
2008-04-18 21:08:05 +00:00
# $exec_mode = $prelog{PERMITTING}{$profile}{$hat}{path}{$exec_target};
2007-03-20 21:58:38 +00:00
}
2008-04-18 21:02:47 +00:00
$ prelog { PERMITTING } { $ profile } { $ hat } { path } { $ exec_target } |= $ exec_mode ;
2007-03-20 21:58:38 +00:00
$ log { PERMITTING } { $ profile } = { } ;
2008-04-18 21:07:16 +00:00
$ sd { $ profile } { $ hat } { allow } { path } { $ exec_target } { mode } = $ exec_mode ;
2008-04-18 21:10:25 +00:00
$ sd { $ profile } { $ hat } { allow } { path } { $ exec_target } { audit } |= 0 ;
2008-04-24 16:05:33 +00:00
$ sd { $ profile } { $ hat } { allow } { path } { $ exec_target } { to } = $ to_name if ( $ to_name ) ;
2007-03-20 21:58:38 +00:00
# mark this profile as changed
$ changed { $ profile } = 1 ;
2008-04-24 16:05:33 +00:00
if ( $ exec_mode & str_to_mode ( "i" ) ) {
2007-03-20 21:58:38 +00:00
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 ) ;
2008-04-18 21:02:47 +00:00
$ sd { $ profile } { $ hat } { path } - > { $ interpreter } { mode } = str_to_mode ( "ix" ) ;
2008-04-18 21:10:25 +00:00
$ sd { $ profile } { $ hat } { path } - > { $ interpreter } { audit } |= 0 ;
2007-03-20 21:58:38 +00:00
if ( $ interpreter =~ /perl/ ) {
$ sd { $ profile } { $ hat } { include } { "abstractions/perl" } = 1 ;
} elsif ( $ interpreter =~ m/\/bin\/(bash|sh)/ ) {
$ sd { $ profile } { $ hat } { include } { "abstractions/bash" } = 1 ;
}
}
}
}
2008-04-24 16:05:33 +00:00
}
2007-03-20 21:58:38 +00:00
# print "$pid $profile $hat EXEC $exec_target $ans $exec_mode\n";
# update our tracking info based on what kind of change
# this is...
2008-04-24 16:05:33 +00:00
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 ) ;
2007-03-20 21:58:38 +00:00
if ( $ sdmode eq "PERMITTING" ) {
if ( $ domainchange eq "change" ) {
$ profile = $ exec_target ;
$ hat = $ exec_target ;
$ profilechanges { $ pid } = $ profile ;
}
}
2007-07-29 02:25:25 +00:00
# if they want to use px, make sure a profile
# exists for the target.
unless ( - e getprofilefilename ( $ exec_target ) ) {
2008-04-24 16:05:33 +00:00
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 ;
$ sd { $ profile } { $ hat } { allow } { path } = { } ;
$ sd { $ profile } { $ hat } { allow } { netdomain } = { } ;
my $ file = $ sd { $ profile } { $ profile } { filename } ;
$ filelist { $ file } { profiles } { $ profile } { $ hat } = 1 ;
}
2007-07-29 02:25:25 +00:00
}
2008-04-24 16:05:33 +00:00
} elsif ( $ ans =~ /^CMD_ux/ ) {
2007-03-20 21:58:38 +00:00
$ profilechanges { $ pid } = "unconstrained" ;
return if $ domainchange eq "change" ;
}
2006-04-11 21:52:54 +00:00
}
2007-07-29 02:06:00 +00:00
} 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 ) ;
2006-04-11 21:52:54 +00:00
}
}
}
}
2007-03-21 16:49:39 +00:00
sub add_to_tree ($@) {
my ( $ pid , $ type , @ event ) = @ _ ;
2007-07-29 02:06:00 +00:00
if ( $ DEBUGGING ) {
my $ eventmsg = Data::Dumper - > Dump ( [ @ event ] , [ qw( *event ) ] ) ;
$ eventmsg =~ s/\n/ /g ;
debug " add_to_tree: pid [$pid] type [$type] event [ $eventmsg ]" ;
}
2007-03-21 16:49:39 +00:00
unless ( exists $ pid { $ pid } ) {
my $ arrayref = [] ;
push @ log , $ arrayref ;
$ pid { $ pid } = $ arrayref ;
}
push @ { $ pid { $ pid } } , [ $ type , $ pid , @ event ] ;
}
2007-09-10 19:44:07 +00:00
#
# variables used in the logparsing routines
#
our $ LOG ;
our $ next_log_entry ;
our $ logmark ;
our $ seenmark ;
my $ RE_LOG_v2_0_syslog = qr/SubDomain/ ;
2007-11-06 16:37:52 +00:00
my $ RE_LOG_v2_1_syslog = qr/kernel:\s+(\[[\d\.\s]+\]\s+)?audit\([\d\.\:]+\):\s+type=150[1-6]/ ;
2007-09-10 19:44:07 +00:00
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))/ ;
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> ;
} 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 |
$ 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 ) = @ _ ;
2008-02-26 12:01:10 +00:00
$ DEBUGGING && debug "parse_log_record_v_2_0: $record" ;
2007-09-10 19:44:07 +00:00
# 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 , "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 , "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 ) ;
2008-03-13 10:49:44 +00:00
if ( $ mode eq "link" ) {
$ mode = "l" ;
}
2007-09-10 19:44:07 +00:00
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 , "exec" , $ profile , $ hat , $ prog ,
2008-04-18 21:02:47 +00:00
$ sdmode , str_to_mode ( $ mode ) , $ detail ) ;
2007-09-10 19:44:07 +00:00
} else {
add_to_tree ( $ pid , "path" , $ profile , $ hat , $ prog ,
2008-04-18 21:02:47 +00:00
$ sdmode , str_to_mode ( $ mode ) , $ detail ) ;
2007-09-10 19:44:07 +00:00
}
} 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 , "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 , "path" , $ profile , $ hat , $ prog , $ sdmode ,
2008-04-18 21:02:47 +00:00
str_to_mode ( $ xattrmode ) , $ path ) ;
2007-09-10 19:44:07 +00:00
}
} 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 , "path" , $ profile , $ hat , $ prog , $ sdmode ,
2008-04-18 21:02:47 +00:00
str_to_mode ( "w" ) , $ path ) ;
2007-09-10 19:44:07 +00:00
} 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 , "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 ;
}
2007-07-16 13:19:02 +00:00
2008-02-26 12:01:10 +00:00
sub parse_log_record ($) {
my $ record = shift ;
$ DEBUGGING && debug "parse_log_record: $record" ;
my $ e = parse_event ( $ record ) ;
2007-07-29 02:20:24 +00:00
2007-07-16 13:19:02 +00:00
return $ e ;
}
2008-02-26 12:01:10 +00:00
2007-09-10 19:44:07 +00:00
sub add_event_to_tree ($) {
my $ e = shift ;
2007-07-16 13:19:02 +00:00
2008-02-26 12:01:10 +00:00
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" ;
}
2007-07-16 13:19:02 +00:00
}
2008-04-24 16:05:33 +00:00
return if ( $ sdmode =~ /UNKNOWN|AUDIT|STATUS|ERROR/ ) ;
2007-07-16 13:19:02 +00:00
my ( $ profile , $ hat ) ;
( $ profile , $ hat ) = split /\/\// , $ e - > { profile } ;
if ( $ e - > { operation } eq "change_hat" ) {
2007-09-10 19:44:07 +00:00
( $ profile , $ hat ) = split /\/\// , $ e - > { name } ;
2007-07-16 13:19:02 +00:00
}
$ hat = $ profile if ( ! $ hat ) ;
2007-07-29 02:05:06 +00:00
# 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" ;
2007-07-16 13:19:02 +00:00
2007-07-29 02:30:39 +00:00
return if ( $ profile ne 'null-complain-profile' && ! profile_exists ( $ profile ) ) ;
2007-07-29 02:18:29 +00:00
2007-07-16 13:19:02 +00:00
if ( $ e - > { operation } eq "exec" ) {
2007-07-29 02:25:25 +00:00
if ( defined $ e - > { info } && $ e - > { info } eq "mandatory profile missing" ) {
add_to_tree ( $ e - > { pid } ,
"exec" ,
$ profile ,
$ hat ,
$ sdmode ,
"PERMITTING" ,
$ e - > { denied_mask } ,
2008-04-24 16:05:33 +00:00
$ e - > { name } ,
$ e - > { name2 }
2007-07-29 02:25:25 +00:00
) ;
2007-08-16 21:51:08 +00:00
}
2007-07-29 02:23:31 +00:00
} elsif ( $ e - > { operation } =~ m/file_/ ) {
2007-07-16 13:19:02 +00:00
add_to_tree ( $ e - > { pid } ,
"path" ,
$ profile ,
$ hat ,
$ prog ,
$ sdmode ,
$ e - > { denied_mask } ,
$ e - > { name } ,
2008-04-24 16:05:33 +00:00
"" ,
2007-07-16 13:19:02 +00:00
) ;
} elsif ( $ e - > { operation } eq "capable" ) {
add_to_tree ( $ e - > { pid } ,
"capability" ,
$ profile ,
$ hat ,
$ prog ,
$ sdmode ,
$ e - > { name }
) ;
2007-07-29 02:23:31 +00:00
} elsif ( $ e - > { operation } =~ m/xattr/ ||
2007-07-16 13:19:02 +00:00
$ e - > { operation } eq "setattr" ) {
add_to_tree ( $ e - > { pid } ,
"path" ,
$ profile ,
$ hat ,
$ prog ,
$ sdmode ,
$ e - > { denied_mask } ,
2008-04-24 16:05:33 +00:00
$ e - > { name } ,
""
2007-07-16 13:19:02 +00:00
) ;
2007-07-29 02:23:31 +00:00
} elsif ( $ e - > { operation } =~ m/inode_/ ) {
2007-09-10 19:44:07 +00:00
my $ is_domain_change = 0 ;
if ( $ e - > { operation } eq "inode_permission" &&
2008-04-18 21:02:47 +00:00
$ e - > { denied_mask } & $ AA_MAY_EXEC &&
2007-09-10 19:44:07 +00:00
$ sdmode eq "PERMITTING" ) {
my $ following = peek_at_next_log_entry ( ) ;
if ( $ following ) {
2008-02-26 12:01:10 +00:00
my $ entry = parse_log_record ( $ following ) ;
2007-09-10 19:44:07 +00:00
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 } ,
"exec" ,
$ profile ,
$ hat ,
$ prog ,
$ sdmode ,
$ e - > { denied_mask } ,
2008-04-24 16:05:33 +00:00
$ e - > { name } ,
$ e - > { name2 }
2007-09-10 19:44:07 +00:00
) ;
2007-07-16 13:19:02 +00:00
} else {
add_to_tree ( $ e - > { pid } ,
"path" ,
$ profile ,
$ hat ,
$ prog ,
$ sdmode ,
$ e - > { denied_mask } ,
2008-04-24 16:05:33 +00:00
$ e - > { name } ,
""
2007-07-16 13:19:02 +00:00
) ;
}
} elsif ( $ e - > { operation } eq "sysctl" ) {
add_to_tree ( $ e - > { pid } ,
"path" ,
$ profile ,
$ hat ,
$ prog ,
$ sdmode ,
$ e - > { denied_mask } ,
2008-04-24 16:05:33 +00:00
$ e - > { name } ,
""
2007-07-16 13:19:02 +00:00
) ;
2007-07-29 02:05:06 +00:00
} 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 { $ e - > { pid } } ) {
push @ { $ pid { $ parent } } , $ arrayref ;
} else {
push @ log , $ arrayref ;
}
$ pid { $ child } = $ arrayref ;
push @ { $ arrayref } , [ "fork" , $ child , $ profile , $ hat ] ;
2007-07-29 02:23:31 +00:00
} elsif ( $ e - > { operation } =~ m/socket_/ ) {
2007-07-29 02:06:00 +00:00
add_to_tree ( $ e - > { pid } ,
"netdomain" ,
$ profile ,
$ hat ,
$ prog ,
$ sdmode ,
$ e - > { family } ,
$ e - > { sock_type } ,
$ e - > { protocol } ,
) ;
2007-07-16 13:19:02 +00:00
} elsif ( $ e - > { operation } eq "change_hat" ) {
add_to_tree ( $ e - > { pid } , "unknown_hat" , $ profile , $ hat , $ sdmode , $ hat ) ;
} else {
2007-07-29 02:06:00 +00:00
if ( $ DEBUGGING ) {
my $ msg = Data::Dumper - > Dump ( [ $ e ] , [ qw( *event ) ] ) ;
debug "UNHANDLED: $msg" ;
}
2007-07-16 13:19:02 +00:00
}
}
2007-04-25 20:47:13 +00:00
sub read_log {
2007-09-10 19:44:07 +00:00
$ logmark = shift ;
$ seenmark = $ logmark ? 0 : 1 ;
2007-03-20 21:58:38 +00:00
my $ last ;
2007-07-16 13:19:02 +00:00
my $ event_type ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# okay, done loading the previous profiles, get on to the good stuff...
2007-09-10 19:44:07 +00:00
open ( $ LOG , $ filename )
2007-03-20 21:58:38 +00:00
or fatal_error "Can't read AppArmor logfile $filename: $!" ;
2007-09-10 19:44:07 +00:00
while ( $ _ = get_next_log_entry ( ) ) {
2007-03-20 21:58:38 +00:00
chomp ;
2006-04-11 21:52:54 +00:00
2008-02-26 12:00:37 +00:00
$ seenmark = 1 if /$logmark/ ;
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 {
2008-02-26 12:01:10 +00:00
my $ event = parse_log_record ( $ _ ) ;
add_event_to_tree ( $ event ) if ( $ event ) ;
2008-02-26 12:00:37 +00:00
}
}
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 =
2008-04-18 21:09:53 +00:00
parse_profile_data ( $ p - > { profile } , "repository profile" , 0 ) ;
2008-02-26 12:00:37 +00:00
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 } = [
"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 } = [
"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 {
2008-04-18 21:09:53 +00:00
parse_profile_data ( $ profile - > { profile } , "repository profile" , 0 ) ;
2008-02-26 12:00:37 +00:00
} ;
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 ;
}
2006-04-11 21:52:54 +00:00
2008-02-26 12:00:37 +00:00
sub set_repo_info {
my ( $ profile_data , $ repo_url , $ username , $ id ) = @ _ ;
2006-04-11 21:52:54 +00:00
2008-02-26 12:00:37 +00:00
# save repository metadata
$ profile_data - > { repo } { url } = $ repo_url ;
$ profile_data - > { repo } { user } = $ username ;
$ profile_data - > { repo } { id } = $ id ;
2007-04-25 20:47:13 +00:00
}
2006-04-11 21:52:54 +00:00
2007-09-10 19:42:18 +00:00
2008-02-26 12:00:37 +00:00
sub is_repo_profile {
my $ profile_data = shift ;
2007-07-13 17:53:12 +00:00
2008-02-26 12:00:37 +00:00
return $ profile_data - > { repo } { url } &&
$ profile_data - > { repo } { user } &&
$ profile_data - > { repo } { id } ;
2007-07-13 17:53:12 +00:00
}
2007-05-22 20:49:51 +00:00
2007-04-26 02:48:24 +00:00
2008-02-26 12:00:37 +00:00
sub get_repo_user_pass {
my ( $ user , $ pass ) ;
if ( $ repo_cfg ) {
$ user = $ repo_cfg - > { repository } { user } ;
$ pass = $ repo_cfg - > { repository } { pass } ;
2007-04-26 02:48:24 +00:00
}
2007-11-06 16:46:57 +00:00
2008-02-26 12:00:37 +00:00
unless ( $ user && $ pass ) {
( $ user , $ pass ) = UI_repo_signup ( ) ;
}
2007-11-06 16:46:57 +00:00
2008-02-26 12:00:37 +00:00
return ( $ user , $ pass ) ;
}
2007-04-26 02:48:24 +00:00
2007-04-26 14:42:56 +00:00
2008-02-26 12:00:37 +00:00
sub get_preferred_user ($) {
my $ repo_url = shift ;
return $ cfg - > { repository } { preferred_user } || "NOVELL" ;
}
2007-04-26 02:48:24 +00:00
2007-04-26 14:42:56 +00:00
2008-02-26 12:00:37 +00:00
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 ;
}
2007-04-26 02:48:24 +00:00
2008-02-26 12:00:37 +00:00
sub update_repo_profile {
my $ profile = shift ;
2007-04-26 02:48:24 +00:00
2008-04-18 21:03:28 +00:00
return undef if ( not is_repo_profile ( $ profile ) ) ;
2008-02-26 12:00:37 +00:00
my $ distro = $ cfg - > { repository } { distro } ;
2008-04-18 21:03:28 +00:00
my $ url = $ profile - > { repo } { url } ;
my $ user = $ profile - > { repo } { user } ;
my $ id = $ profile - > { repo } { id } ;
2008-02-26 12:00:37 +00:00
UI_BusyStart ( gettext ( "Connecting to repository....." ) ) ;
my ( $ status_ok , $ res ) = fetch_newer_profile ( $ url ,
$ distro ,
$ user ,
$ id ,
2008-04-18 21:03:28 +00:00
$ profile - > { name }
2008-02-26 12:00:37 +00:00
) ;
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 ;
2007-04-26 02:48:24 +00:00
}
2008-02-26 12:00:37 +00:00
return ( $ res ) ;
2007-04-26 02:48:24 +00:00
}
2008-04-18 21:16:15 +00:00
sub UI_ask_mode_toggles ($$$) {
my ( $ audit_toggle , $ owner_toggle , $ oldmode ) = @ _ ;
2008-04-18 21:10:25 +00:00
my $ q = { } ;
$ q - > { headers } = [ ] ;
# "Repository", $cfg->{repository}{url},
# ];
$ q - > { explanation } = gettext ( "Change mode modifiers" ) ;
2008-04-24 16:05:33 +00:00
if ( $ audit_toggle ) {
$ q - > { functions } = [ "CMD_AUDIT_OFF" ] ;
2008-04-18 21:10:25 +00:00
} else {
2008-04-24 16:05:33 +00:00
$ q - > { functions } = [ "CMD_AUDIT_NEW" ] ;
push @ { $ q - > { functions } } , "CMD_AUDIT_FULL" if ( $ oldmode ) ;
2008-04-18 21:10:25 +00:00
}
2008-04-24 16:05:33 +00:00
if ( $ owner_toggle ) {
push @ { $ q - > { functions } } , "CMD_USER_OFF" ;
} else {
push @ { $ q - > { functions } } , "CMD_USER_ON" ;
}
push @ { $ q - > { functions } } , "CMD_CONTINUE" ;
2008-04-18 21:10:25 +00:00
my $ cmd ;
do {
$ cmd = UI_PromptUser ( $ q ) ;
2008-04-24 16:05:33 +00:00
} until $ cmd =~ /^CMD_(AUDIT_OFF|AUDIT_NEW|AUDIT_FULL|USER_ON|USER_OFF|CONTINUE)/ ;
2008-04-18 21:10:25 +00:00
2008-04-24 16:05:33 +00:00
if ( $ cmd eq "CMD_AUDIT_OFF" ) {
2008-04-18 21:10:25 +00:00
$ audit_toggle = 0 ;
} elsif ( $ cmd eq "CMD_AUDIT_NEW" ) {
$ audit_toggle = 1 ;
} elsif ( $ cmd eq "CMD_AUDIT_FULL" ) {
$ audit_toggle = 2 ;
2008-04-24 16:05:33 +00:00
} 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);
2008-04-18 21:10:25 +00:00
}
2008-04-18 21:16:15 +00:00
return ( $ audit_toggle , $ owner_toggle ) ;
2008-04-18 21:10:25 +00:00
}
2008-02-26 12:00:37 +00:00
2007-04-25 20:47:13 +00:00
sub ask_the_questions {
2007-07-13 17:53:12 +00:00
my $ found ; # do the magic foo-foo
2007-03-20 21:58:38 +00:00
for my $ sdmode ( sort keys % log ) {
2006-08-04 16:38:22 +00:00
2007-03-20 21:58:38 +00:00
# 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 {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# 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 ) ) ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
for my $ profile ( sort keys % { $ log { $ sdmode } } ) {
2008-04-18 21:03:28 +00:00
my $ p = update_repo_profile ( $ sd { $ profile } { $ profile } ) ;
2007-11-06 16:46:57 +00:00
UI_SelectUpdatedRepoProfile ( $ profile , $ p ) if ( $ p ) ;
2007-04-26 02:48:24 +00:00
2007-03-20 21:58:38 +00:00
$ 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
2008-04-18 21:08:05 +00:00
next if profile_known_capability ( $ sd { $ profile } { $ hat } ,
$ capability ) ;
2007-03-20 21:58:38 +00:00
my $ severity = $ sevdb - > rank ( uc ( "cap_$capability" ) ) ;
2007-08-15 16:17:50 +00:00
my $ defaultoption = 1 ;
my @ options = ( ) ;
my @ newincludes ;
2008-04-18 21:03:28 +00:00
@ newincludes = matchcapincludes ( $ sd { $ profile } { $ hat } ,
2007-08-15 16:17:50 +00:00
$ capability ) ;
2007-03-20 21:58:38 +00:00
my $ q = { } ;
2007-08-15 16:17:50 +00:00
if ( @ newincludes ) {
push @ options ,
map { "#include <$_>" } sort ( uniq ( @ newincludes ) ) ;
}
if ( @ options ) {
push @ options , "capability $capability" ;
$ q - > { options } = [ @ options ] ;
$ q - > { selected } = $ defaultoption - 1 ;
}
2007-03-20 21:58:38 +00:00
$ q - > { headers } = [] ;
push @ { $ q - > { headers } } , gettext ( "Profile" ) , combine_name ( $ profile , $ hat ) ;
push @ { $ q - > { headers } } , gettext ( "Capability" ) , $ capability ;
push @ { $ q - > { headers } } , gettext ( "Severity" ) , $ severity ;
2008-04-24 16:05:33 +00:00
my $ audit_toggle = 0 ;
$ q - > { functions } = [
"CMD_ALLOW" , "CMD_DENY" , "CMD_AUDIT_NEW" , "CMD_ABORT" , "CMD_FINISHED"
] ;
2007-03-20 21:58:38 +00:00
# complain-mode events default to allow - enforce defaults
# to deny
$ q - > { default } = ( $ sdmode eq "PERMITTING" ) ? "CMD_ALLOW" : "CMD_DENY" ;
$ seenevents + + ;
2007-08-15 16:17:50 +00:00
my $ done = 0 ;
while ( not $ done ) {
# what did the grand exalted master tell us to do?
my ( $ ans , $ selected ) = UI_PromptUser ( $ q ) ;
2007-03-20 21:58:38 +00:00
2008-04-24 16:05:33 +00:00
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" ) {
2007-03-20 21:58:38 +00:00
2007-08-15 16:17:50 +00:00
# they picked (a)llow, so...
2007-03-20 21:58:38 +00:00
2007-08-15 16:17:50 +00:00
my $ selection = $ options [ $ selected ] ;
$ done = 1 ;
if ( $ selection &&
$ selection =~ m/^#include <(.+)>$/ ) {
my $ deleted = 0 ;
my $ inc = $ 1 ;
2008-04-18 21:03:28 +00:00
$ deleted = delete_duplicates ( $ sd { $ profile } { $ hat } ,
2007-08-15 16:17:50 +00:00
$ 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
2008-04-18 21:08:34 +00:00
$ sd { $ profile } { $ hat } { allow } { capability } { $ capability } { set } = 1 ;
2008-04-24 16:05:33 +00:00
$ sd { $ profile } { $ hat } { allow } { capability } { $ capability } { audit } = $ audit_toggle ;
2007-03-20 21:58:38 +00:00
2007-08-15 16:17:50 +00:00
# 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" ) {
2008-04-18 21:08:34 +00:00
$ sd { $ profile } { $ hat } { deny } { capability } { $ capability } { set } = 1 ;
2008-04-18 21:08:05 +00:00
# mark this profile as changed
$ changed { $ profile } = 1 ;
2007-08-15 16:17:50 +00:00
UI_Info ( sprintf ( gettext ( 'Denying capability %s to profile.' ) , $ capability ) ) ;
$ done = 1 ;
} else {
redo ;
}
2007-03-20 21:58:38 +00:00
}
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# 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 } ;
2008-04-18 21:08:05 +00:00
# do original profile lookup once.
my $ allow_mode = 0 ;
2008-04-18 21:10:25 +00:00
my $ allow_audit = 0 ;
2008-04-18 21:08:05 +00:00
my $ deny_mode = 0 ;
2008-04-18 21:10:25 +00:00
my $ deny_audit = 0 ;
2008-04-18 21:08:05 +00:00
2008-04-18 21:10:25 +00:00
my ( $ fmode , $ famode , $ imode , $ iamode , @ fm , @ im , $ cm , $ am , $ cam , @ m ) ;
( $ fmode , $ famode , @ fm ) = rematchfrag ( $ sd { $ profile } { $ hat } , 'allow' , $ path ) ;
2008-04-18 21:08:05 +00:00
$ allow_mode |= $ fmode if $ fmode ;
2008-04-18 21:10:25 +00:00
$ allow_audit |= $ famode if $ famode ;
( $ imode , $ iamode , @ im ) = match_prof_incs_to_path ( $ sd { $ profile } { $ hat } , 'allow' , $ path ) ;
2008-04-18 21:08:05 +00:00
$ allow_mode |= $ imode if $ imode ;
2008-04-18 21:10:25 +00:00
$ allow_audit |= $ iamode if $ iamode ;
2008-04-18 21:08:05 +00:00
2008-04-18 21:10:25 +00:00
( $ cm , $ cam , @ m ) = rematchfrag ( $ sd { $ profile } { $ hat } , 'deny' , $ path ) ;
2008-04-18 21:08:05 +00:00
$ deny_mode |= $ cm if $ cm ;
2008-04-18 21:10:25 +00:00
$ deny_audit |= $ cam if $ cam ;
( $ cm , $ cam , @ m ) = match_prof_incs_to_path ( $ sd { $ profile } { $ hat } , 'deny' , $ path ) ;
2008-04-18 21:08:05 +00:00
$ deny_mode |= $ cm if $ cm ;
2008-04-18 21:10:25 +00:00
$ deny_audit |= $ cam if $ cam ;
2008-04-18 21:08:05 +00:00
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 ;
2007-03-20 21:58:38 +00:00
# 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.
2008-04-18 21:02:47 +00:00
if ( $ mode & $ AA_MAY_EXEC ) {
2007-03-20 21:58:38 +00:00
# get rid of the access() markers.
2008-04-18 21:02:47 +00:00
$ mode & = ( ~ $ ALL_AA_EXEC_TYPE ) ;
2007-03-20 21:58:38 +00:00
2008-04-18 21:08:05 +00:00
unless ( $ allow_mode & $ allow_mode & $ AA_MAY_EXEC ) {
2008-04-18 21:02:47 +00:00
$ mode |= str_to_mode ( "ix" ) ;
2007-03-20 21:58:38 +00:00
}
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# if we had an mmap(PROT_EXEC) request, first check if we
# already have added an ix rule to the profile
2008-04-18 21:02:47 +00:00
if ( $ mode & $ AA_EXEC_MMAP ) {
2007-03-20 21:58:38 +00:00
# ix implies m. don't ask if they want to add an "m"
# rule when we already have a matching ix rule.
2008-04-18 21:08:05 +00:00
if ( $ allow_mode && contains ( $ allow_mode , "ix" ) ) {
2008-04-18 21:02:47 +00:00
$ mode & = ( ~ $ AA_EXEC_MMAP ) ;
2007-03-20 21:58:38 +00:00
}
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
next unless $ mode ;
2006-04-11 21:52:54 +00:00
2008-04-18 21:08:05 +00:00
my @ matches ;
2006-04-11 21:52:54 +00:00
2008-04-18 21:08:05 +00:00
if ( $ fmode ) {
push @ matches , @ fm ;
2007-03-20 21:58:38 +00:00
}
2008-04-18 21:08:05 +00:00
if ( $ imode ) {
push @ matches , @ im ;
2007-03-20 21:58:38 +00:00
}
2008-04-18 21:08:05 +00:00
unless ( $ allow_mode && mode_contains ( $ allow_mode , $ mode ) ) {
2007-03-20 21:58:38 +00:00
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
2008-04-24 16:05:33 +00:00
if ( $ cfg - > { settings } { custom_includes } ) {
2007-04-25 21:06:52 +00:00
for my $ incm ( split ( /\s+/ ,
$ cfg - > { settings } { custom_includes } )
) {
$ includevalid = 1 if $ incname =~ /$incm/ ;
2007-03-20 21:58:38 +00:00
}
2008-04-24 16:05:33 +00:00
}
2007-03-20 21:58:38 +00:00
$ includevalid = 1 if $ incname =~ /abstractions/ ;
next if ( $ includevalid == 0 ) ;
2008-04-18 21:10:25 +00:00
( $ cm , $ am , @ m ) = match_include_to_path ( $ incname , 'allow' , $ path ) ;
2008-04-18 21:02:47 +00:00
if ( $ cm && mode_contains ( $ cm , $ mode ) ) {
2008-04-18 21:08:05 +00:00
#make sure it doesn't deny $mode
my $ dm = match_include_to_path ( $ incname , 'deny' , $ path ) ;
unless ( ( $ mode & $ dm ) || ( grep { $ _ eq "/**" } @ m ) ) {
2007-03-20 21:58:38 +00:00
push @ newincludes , $ incname ;
}
}
}
2008-04-18 21:08:05 +00:00
2007-03-20 21:58:38 +00:00
# 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 ;
2008-04-18 21:02:47 +00:00
my $ severity = $ sevdb - > rank ( $ path , mode_to_str ( $ mode ) ) ;
2007-03-20 21:58:38 +00:00
2008-04-18 21:10:25 +00:00
my $ audit_toggle = 0 ;
2008-04-18 21:16:15 +00:00
my $ owner_toggle = $ cfg - > { settings } { default_owner_prompt } ;
2007-03-20 21:58:38 +00:00
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
2008-04-18 21:08:05 +00:00
if ( $ allow_mode ) {
2008-04-18 21:10:25 +00:00
my $ str ;
2008-04-18 21:16:15 +00:00
#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)" ) ;
}
2008-04-18 21:10:25 +00:00
if ( $ audit_toggle == 1 ) {
2008-04-18 21:16:15 +00:00
$ str = mode_to_str_user ( $ allow_mode ) ;
$ str . = ", " if ( $ allow_mode ) ;
$ str . = "audit " . mode_to_str_user ( $ prompt_mode & ~ $ allow_mode ) . $ tail ;
2008-04-18 21:10:25 +00:00
} elsif ( $ audit_toggle == 2 ) {
2008-04-18 21:16:15 +00:00
$ str = "audit " . mode_to_str_user ( $ prompt_mode ) . $ tail ;
2008-04-18 21:10:25 +00:00
} else {
2008-04-18 21:16:15 +00:00
$ str = mode_to_str_user ( $ prompt_mode ) . $ tail ;
2008-04-18 21:10:25 +00:00
}
2008-04-18 21:16:15 +00:00
push @ { $ q - > { headers } } , gettext ( "Old Mode" ) , mode_to_str_user ( $ allow_mode ) ;
2008-04-18 21:10:25 +00:00
push @ { $ q - > { headers } } , gettext ( "New Mode" ) , $ str ;
2007-03-20 21:58:38 +00:00
} else {
2008-04-18 21:10:25 +00:00
my $ str = "" ;
if ( $ audit_toggle ) {
$ str = "audit " ;
}
2008-04-18 21:16:15 +00:00
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 ;
2008-04-18 21:10:25 +00:00
push @ { $ q - > { headers } } , gettext ( "Mode" ) , $ str ;
2007-03-20 21:58:38 +00:00
}
push @ { $ q - > { headers } } , gettext ( "Severity" ) , $ severity ;
$ q - > { options } = [ @ options ] ;
$ q - > { selected } = $ defaultoption - 1 ;
2007-04-25 20:47:13 +00:00
$ q - > { functions } = [
"CMD_ALLOW" , "CMD_DENY" , "CMD_GLOB" , "CMD_GLOBEXT" , "CMD_NEW" ,
2008-04-18 21:10:25 +00:00
"CMD_ABORT" , "CMD_FINISHED" , "CMD_OTHER"
2007-04-25 20:47:13 +00:00
] ;
2007-03-20 21:58:38 +00:00
$ 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 ) ;
2008-04-18 21:10:25 +00:00
if ( $ ans eq "CMD_OTHER" ) {
2008-04-18 21:16:15 +00:00
( $ 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 ) ;
2008-04-18 21:10:25 +00:00
} elsif ( $ ans eq "CMD_ALLOW" ) {
2007-04-26 02:59:17 +00:00
$ path = $ options [ $ selected ] ;
2007-03-20 21:58:38 +00:00
$ done = 1 ;
if ( $ path =~ m/^#include <(.+)>$/ ) {
my $ inc = $ 1 ;
my $ deleted = 0 ;
2008-04-18 21:03:28 +00:00
$ deleted = delete_duplicates ( $ sd { $ profile } { $ hat } ,
2007-08-15 16:17:50 +00:00
$ inc ) ;
2007-03-20 21:58:38 +00:00
# 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 {
2008-04-18 21:07:16 +00:00
if ( $ sd { $ profile } { $ hat } { allow } { path } { $ path } { mode } ) {
$ mode = $ mode | $ sd { $ profile } { $ hat } { allow } { path } { $ path } { mode } ;
2007-03-20 21:58:38 +00:00
}
my $ deleted = 0 ;
2008-04-18 21:07:16 +00:00
for my $ entry ( keys % { $ sd { $ profile } { $ hat } { allow } { path } } ) {
2007-03-20 21:58:38 +00:00
next if $ path eq $ entry ;
if ( matchregexp ( $ path , $ entry ) ) {
# regexp matches, add it's mode to
# the list to check against
2008-04-18 21:02:47 +00:00
if ( mode_contains ( $ mode ,
2008-04-18 21:07:16 +00:00
$ sd { $ profile } { $ hat } { allow } { path } { $ entry } { mode } ) ) {
delete $ sd { $ profile } { $ hat } { allow } { path } { $ entry } ;
2007-03-20 21:58:38 +00:00
$ deleted + + ;
}
}
}
# record the new entry
2008-04-18 21:16:15 +00:00
if ( $ owner_toggle == 0 ) {
2008-04-24 16:05:33 +00:00
$ mode = flatten_mode ( $ mode ) ;
2008-04-18 21:16:15 +00:00
} 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 ) ;
}
2008-04-18 21:07:16 +00:00
$ sd { $ profile } { $ hat } { allow } { path } { $ path } { mode } = $ mode ;
2008-04-18 21:10:25 +00:00
my $ tmpmode = ( $ audit_toggle == 1 ) ? $ mode & ~ $ allow_mode : 0 ;
$ tmpmode = ( $ audit_toggle == 2 ) ? $ mode : $ tmpmode ;
$ sd { $ profile } { $ hat } { allow } { path } { $ path } { audit } |= $ tmpmode ;
2007-03-20 21:58:38 +00:00
$ changed { $ profile } = 1 ;
2008-04-18 21:16:15 +00:00
UI_Info ( sprintf ( gettext ( 'Adding %s %s to profile.' ) , $ path , mode_to_str_user ( $ mode ) ) ) ;
2007-03-20 21:58:38 +00:00
UI_Info ( sprintf ( gettext ( 'Deleted %s previous matching profile entries.' ) , $ deleted ) ) if $ deleted ;
}
} elsif ( $ ans eq "CMD_DENY" ) {
2008-04-18 21:08:05 +00:00
# record the new entry
$ sd { $ profile } { $ hat } { deny } { path } { $ path } { mode } |= $ mode & ~ $ allow_mode ;
2008-04-18 21:10:25 +00:00
$ sd { $ profile } { $ hat } { deny } { path } { $ path } { audit } |= 0 ;
2008-04-18 21:08:05 +00:00
$ changed { $ profile } = 1 ;
2007-03-20 21:58:38 +00:00
# go on to the next entry without saving this
# one
$ done = 1 ;
} elsif ( $ ans eq "CMD_NEW" ) {
2007-04-26 02:59:17 +00:00
my $ arg = $ options [ $ selected ] ;
if ( $ arg !~ /^#include/ ) {
$ ans = UI_GetString ( gettext ( "Enter new path: " ) , $ arg ) ;
2007-03-20 21:58:38 +00:00
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
2007-04-26 02:46:23 +00:00
my $ newpath = $ options [ $ selected ] ;
2007-07-16 13:19:02 +00:00
chomp $ newpath ;
2007-04-26 02:46:23 +00:00
unless ( $ newpath =~ /^#include/ ) {
2007-07-16 13:19:02 +00:00
# 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/\/[^\/]+\/$/\/\*\// ;
}
2007-03-20 21:58:38 +00:00
} else {
2007-07-16 13:19:02 +00:00
# do we collapse to /* or /**?
if ( $ newpath =~ m/\/\*{1,2}$/ ) {
$ newpath =~ s/\/[^\/]+\/\*{1,2}$/\/\*\*/ ;
} else {
$ newpath =~ s/\/[^\/]+$/\/\*/ ;
}
2007-03-20 21:58:38 +00:00
}
if ( $ newpath ne $ selected ) {
push @ options , $ newpath ;
$ defaultoption = $# options + 1 ;
}
}
} elsif ( $ ans eq "CMD_GLOBEXT" ) {
# do globbing if they don't have an include
# selected
2007-04-26 02:46:23 +00:00
my $ newpath = $ options [ $ selected ] ;
unless ( $ newpath =~ /^#include/ ) {
2007-03-20 21:58:38 +00:00
# do we collapse to /*.ext or /**.ext?
if ( $ newpath =~ m/\/\*{1,2}\.[^\/]+$/ ) {
$ newpath =~ s/\/[^\/]+\/\*{1,2}(\.[^\/]+)$/\/\*\*$1/ ;
} else {
$ newpath =~ s/\/[^\/]+(\.[^\/]+)$/\/\*$1/ ;
}
if ( $ newpath ne $ selected ) {
push @ options , $ newpath ;
$ defaultoption = $# options + 1 ;
}
}
} elsif ( $ ans =~ /\d/ ) {
$ defaultoption = $ ans ;
}
}
}
2006-04-11 21:52:54 +00:00
}
2007-07-29 02:06:00 +00:00
# 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
2008-04-18 21:08:05 +00:00
next if ( profile_known_network ( $ sd { $ profile } { $ hat } ,
$ family ,
$ sock_type ) ) ;
2007-08-15 16:17:50 +00:00
my $ defaultoption = 1 ;
my @ options = ( ) ;
my @ newincludes ;
2008-04-18 21:03:28 +00:00
@ newincludes = matchnetincludes ( $ sd { $ profile } { $ hat } ,
2007-08-15 16:17:50 +00:00
$ family ,
$ sock_type ) ;
2007-07-29 02:06:00 +00:00
my $ q = { } ;
2007-08-15 16:17:50 +00:00
if ( @ newincludes ) {
push @ options ,
map { "#include <$_>" } sort ( uniq ( @ newincludes ) ) ;
}
if ( @ options ) {
push @ options , "network $family $sock_type" ;
$ q - > { options } = [ @ options ] ;
$ q - > { selected } = $ defaultoption - 1 ;
}
2007-07-29 02:06:00 +00:00
$ 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 ;
2008-04-24 16:05:33 +00:00
my $ audit_toggle = 0 ;
2007-07-29 02:06:00 +00:00
$ q - > { functions } = [
"CMD_ALLOW" ,
"CMD_DENY" ,
2008-04-24 16:05:33 +00:00
"CMD_AUDIT_NEW" ,
2007-07-29 02:06:00 +00:00
"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?
2007-08-15 16:17:50 +00:00
my $ done = 0 ;
while ( not $ done ) {
my ( $ ans , $ selected ) = UI_PromptUser ( $ q ) ;
2008-04-24 16:05:33 +00:00
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" ) {
2007-08-15 16:17:50 +00:00
my $ selection = $ options [ $ selected ] ;
$ done = 1 ;
if ( $ selection &&
$ selection =~ m/^#include <(.+)>$/ ) {
my $ inc = $ 1 ;
my $ deleted = 0 ;
2008-04-18 21:03:28 +00:00
$ deleted = delete_duplicates ( $ sd { $ profile } { $ hat } ,
2007-08-15 16:17:50 +00:00
$ inc
) ;
# record the new entry
$ sd { $ profile } { $ hat } { include } { $ inc } = 1 ;
2007-07-29 02:06:00 +00:00
2007-08-15 16:17:50 +00:00
$ 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 {
2007-07-29 02:06:00 +00:00
2007-08-15 16:17:50 +00:00
# stick the whole rule into the profile
2008-04-24 16:05:33 +00:00
$ sd { $ profile }
{ $ hat }
{ allow }
{ netdomain }
{ audit }
{ $ family }
{ $ sock_type } = $ audit_toggle ;
2007-08-15 16:17:50 +00:00
$ sd { $ profile }
{ $ hat }
2008-04-18 21:07:16 +00:00
{ allow }
2007-08-15 16:17:50 +00:00
{ netdomain }
2008-04-18 21:09:05 +00:00
{ rule }
2007-08-15 16:17:50 +00:00
{ $ family }
{ $ sock_type } = 1 ;
2007-07-29 02:06:00 +00:00
2007-08-15 16:17:50 +00:00
# mark this profile as changed
$ changed { $ profile } = 1 ;
2007-07-29 02:06:00 +00:00
2007-08-15 16:17:50 +00:00
# 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" ) {
2008-04-24 16:05:33 +00:00
$ done = 1 ;
2008-04-18 21:08:05 +00:00
# record the new entry
$ sd { $ profile }
{ $ hat }
{ deny }
{ netdomain }
2008-04-18 21:09:05 +00:00
{ rule }
2008-04-18 21:08:05 +00:00
{ $ family }
{ $ sock_type } = 1 ;
$ changed { $ profile } = 1 ;
2007-08-15 16:17:50 +00:00
UI_Info ( sprintf (
gettext ( 'Denying network access %s %s to profile.' ) ,
$ family ,
$ sock_type
)
) ;
} else {
redo ;
}
2007-07-29 02:06:00 +00:00
}
}
}
2006-04-11 21:52:54 +00:00
}
2007-03-20 21:58:38 +00:00
}
}
2007-04-25 20:47:13 +00:00
}
2006-04-11 21:52:54 +00:00
2008-04-18 21:08:05 +00:00
sub delete_net_duplicates {
my ( $ netrules , $ incnetrules ) = @ _ ;
2007-08-15 16:17:50 +00:00
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 ) {
2008-04-18 21:09:05 +00:00
if ( $ incnetglob || ( ref ( $ incnetrules - > { rule } { $ fam } ) ne "HASH" &&
$ incnetrules - > { rule } { $ fam } == 1 ) ) { # include allows
2007-08-15 16:17:50 +00:00
# all net or
# all fam
2008-04-18 21:09:05 +00:00
if ( ref ( $ netrules - > { rule } { $ fam } ) eq "HASH" ) {
$ deleted += ( keys % { $ netrules - > { rule } { $ fam } } ) ;
2007-08-15 16:17:50 +00:00
} else {
$ deleted + + ;
}
2008-04-18 21:09:05 +00:00
delete $ netrules - > { rule } { $ fam } ;
} elsif ( ref ( $ netrules - > { rule } { $ fam } ) ne "HASH" &&
$ netrules - > { rule } { $ fam } == 1 ) {
2007-08-15 16:17:50 +00:00
next ; # profile has all family
} else {
2008-04-18 21:09:05 +00:00
for my $ socket_type ( keys % { $ netrules - > { rule } { $ fam } } ) {
2007-08-15 16:17:50 +00:00
if ( defined $ incnetrules - > { $ fam } { $ socket_type } ) {
delete $ netrules - > { $ fam } { $ socket_type } ;
$ deleted + + ;
}
}
}
}
}
2008-04-18 21:08:05 +00:00
return $ deleted ;
}
2007-08-15 16:17:50 +00:00
2008-04-18 21:08:05 +00:00
sub delete_cap_duplicates ($$) {
my ( $ profilecaps , $ inccaps ) = @ _ ;
my $ deleted = 0 ;
2007-08-15 16:17:50 +00:00
if ( $ profilecaps && $ inccaps ) {
for my $ capname ( keys %$ profilecaps ) {
2008-04-18 21:09:05 +00:00
if ( defined $ inccaps - > { $ capname } { set } && $ inccaps - > { $ capname } { set } == 1 ) {
2007-08-15 16:17:50 +00:00
delete $ profilecaps - > { $ capname } ;
$ deleted + + ;
}
}
}
2008-04-18 21:08:05 +00:00
return $ deleted ;
}
sub delete_path_duplicates ($$$) {
my ( $ profile , $ incname , $ allow ) = @ _ ;
my $ deleted = 0 ;
2007-08-15 16:17:50 +00:00
2008-04-18 21:08:05 +00:00
for my $ entry ( keys % { $ profile - > { $ allow } { path } } ) {
2007-08-15 16:17:50 +00:00
next if $ entry eq "#include <$incname>" ;
2008-04-18 21:10:25 +00:00
my ( $ cm , $ am , @ m ) = match_include_to_path ( $ incname , $ allow , $ entry ) ;
2007-08-15 16:17:50 +00:00
if ( $ cm
2008-04-18 21:10:25 +00:00
&& mode_contains ( $ cm , $ profile - > { $ allow } { path } { $ entry } { mode } )
&& mode_contains ( $ am , $ profile - > { $ allow } { path } { $ entry } { audit } ) )
2007-08-15 16:17:50 +00:00
{
2008-04-18 21:08:05 +00:00
delete $ profile - > { $ allow } { path } { $ entry } ;
2007-08-15 16:17:50 +00:00
$ deleted + + ;
}
}
return $ deleted ;
}
2008-04-18 21:08:05 +00:00
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
2008-04-18 21:09:53 +00:00
$ deleted += delete_net_duplicates ( $ profile - > { allow } { netdomain } , $ include { $ incname } { $ incname } { allow } { netdomain } ) ;
$ deleted += delete_net_duplicates ( $ profile - > { deny } { netdomain } , $ include { $ incname } { $ incname } { deny } { netdomain } ) ;
2008-04-18 21:08:05 +00:00
## capabilities
$ deleted += delete_cap_duplicates ( $ profile - > { allow } { capability } ,
2008-04-18 21:09:53 +00:00
$ include { $ incname } { $ incname } { allow } { capability } ) ;
2008-04-18 21:08:05 +00:00
$ deleted += delete_cap_duplicates ( $ profile - > { deny } { capability } ,
2008-04-18 21:09:53 +00:00
$ include { $ incname } { $ incname } { deny } { capability } ) ;
2008-04-18 21:08:05 +00:00
## paths
$ deleted += delete_path_duplicates ( $ profile , $ incname , 'allow' ) ;
$ deleted += delete_path_duplicates ( $ profile , $ incname , 'deny' ) ;
return $ deleted ;
}
2007-08-15 16:17:50 +00:00
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
2008-04-18 21:09:53 +00:00
if netrules_access_check ( $ include { $ name } { $ name } { allow } { netdomain } , $ family , $ type ) ;
2007-08-15 16:17:50 +00:00
# if this fragment includes others, check them too
2008-04-18 21:09:53 +00:00
if ( keys % { $ include { $ name } { $ name } { include } } &&
2007-08-15 16:17:50 +00:00
( grep ( $ name , @ checked ) == 0 ) ) {
2008-04-18 21:09:53 +00:00
push @ includelist , keys % { $ include { $ name } { $ name } { include } } ;
2007-08-15 16:17:50 +00:00
}
}
return 0 ;
}
2008-04-18 21:03:28 +00:00
sub matchcapincludes (\%$) {
2008-04-18 21:08:05 +00:00
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
2008-04-24 16:05:33 +00:00
if ( $ cfg - > { settings } { custom_includes } ) {
for my $ incm ( split ( /\s+/ ,
$ cfg - > { settings } { custom_includes } ) ) {
$ includevalid = 1 if $ incname =~ /$incm/ ;
}
2008-04-18 21:08:05 +00:00
}
$ includevalid = 1 if $ incname =~ /abstractions/ ;
next if ( $ includevalid == 0 ) ;
2007-08-15 16:17:50 +00:00
2008-04-18 21:08:05 +00:00
push @ newincludes , $ incname
2008-04-18 21:09:53 +00:00
if ( defined $ include { $ incname } { $ incname } { allow } { capability } { $ cap } { set } &&
$ include { $ incname } { $ incname } { allow } { capability } { $ cap } { set } == 1 ) ;
2008-04-18 21:08:05 +00:00
}
return @ newincludes ;
2007-08-15 16:17:50 +00:00
}
2008-04-18 21:03:28 +00:00
sub matchnetincludes (\%$$) {
2008-04-18 21:08:05 +00:00
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
2008-04-24 16:05:33 +00:00
if ( $ cfg - > { settings } { custom_includes } ) {
for my $ incm ( split ( /\s+/ , $ cfg - > { settings } { custom_includes } ) ) {
$ includevalid = 1 if $ incname =~ /$incm/ ;
}
2008-04-18 21:08:05 +00:00
}
$ includevalid = 1 if $ incname =~ /abstractions/ ;
next if ( $ includevalid == 0 ) ;
2007-08-15 16:17:50 +00:00
2008-04-18 21:08:05 +00:00
push @ newincludes , $ incname
if matchnetinclude ( $ incname , $ family , $ type ) ;
}
return @ newincludes ;
2007-08-15 16:17:50 +00:00
}
2007-04-26 02:48:24 +00:00
2007-04-25 20:47:13 +00:00
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 = ( ) ;
2008-04-18 21:06:24 +00:00
% filelist = ( ) ;
2007-04-25 20:47:13 +00:00
UI_Info ( sprintf ( gettext ( 'Reading log entries from %s.' ) , $ filename ) ) ;
UI_Info ( sprintf ( gettext ( 'Updating AppArmor profiles in %s.' ) , $ profiledir ) ) ;
readprofiles ( ) ;
unless ( $ sevdb ) {
2008-04-18 21:08:05 +00:00
$ sevdb = new Immunix:: Severity ( "$confdir/severity.db" , gettext ( " unknown
" ) ) ;
2007-04-25 20:47:13 +00:00
}
# 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 {
2007-08-17 21:05:28 +00:00
unless ( $ repo_cfg || not defined $ cfg - > { repository } { url } ) {
2007-04-26 14:42:56 +00:00
$ repo_cfg = read_config ( "repository.conf" ) ;
2008-02-26 11:58:40 +00:00
unless ( $ repo_cfg - > { repository } { enabled } eq "yes" ||
$ repo_cfg - > { repository } { enabled } eq "no" ) {
2008-02-26 12:00:37 +00:00
UI_ask_to_enable_repo ( ) ;
2007-04-26 14:42:56 +00:00
}
}
2007-04-26 02:48:24 +00:00
2007-04-25 20:47:13 +00:00
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 ( ) ;
}
2006-04-11 21:52:54 +00:00
}
2007-03-20 21:58:38 +00:00
}
2007-04-25 20:47:13 +00:00
} ;
my $ finishing = 0 ;
if ( $@ ) {
if ( $@ =~ /FINISHING/ ) {
$ finishing = 1 ;
} else {
die $@ ;
}
2007-03-20 21:58:38 +00:00
}
2006-04-11 21:52:54 +00:00
2007-04-25 20:47:13 +00:00
save_profiles ( ) ;
2006-04-11 21:52:54 +00:00
2007-05-22 20:49:51 +00:00
if ( repo_is_enabled ( ) ) {
2007-07-13 17:53:12 +00:00
if ( ( not defined $ repo_cfg - > { repository } { upload } ) ||
( $ repo_cfg - > { repository } { upload } eq "later" ) ) {
2008-04-18 21:08:05 +00:00
UI_ask_to_upload_profiles ( ) ;
2007-07-13 17:53:12 +00:00
}
if ( $ repo_cfg - > { repository } { upload } eq "yes" ) {
2008-02-26 12:00:37 +00:00
sync_profiles ( ) ;
2007-05-22 20:49:51 +00:00
}
2007-04-26 02:56:54 +00:00
@ created = ( ) ;
2007-05-22 20:49:51 +00:00
}
2007-04-26 02:48:24 +00:00
2007-04-25 20:47:13 +00:00
# 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 {
2007-03-20 21:58:38 +00:00
# make sure the profile changes we've made are saved to disk...
2007-04-26 02:48:24 +00:00
my @ changed = sort keys % changed ;
2007-07-16 13:19:02 +00:00
#
# 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 ;
2007-04-26 02:48:24 +00:00
if ( @ changed ) {
2007-04-26 02:56:54 +00:00
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 } ;
2007-07-30 01:56:21 +00:00
for my $ profile ( @$ selected_profiles_ref ) {
2008-02-26 12:00:37 +00:00
writeprofile_ui_feedback ( $ profile ) ;
2008-04-24 16:05:33 +00:00
reload_base ( $ profile ) ;
2007-07-30 01:56:21 +00:00
}
2007-04-26 02:56:54 +00:00
}
} else {
my $ q = { } ;
$ q - > { title } = "Changed Local Profiles" ;
$ q - > { headers } = [] ;
2007-04-26 02:48:24 +00:00
2007-04-26 02:56:54 +00:00
$ q - > { explanation } =
2007-04-26 16:31:08 +00:00
gettext ( "The following local profiles were changed. Would you like to save them?" ) ;
2007-04-26 02:48:24 +00:00
2007-04-26 02:56:54 +00:00
$ q - > { functions } = [ "CMD_SAVE_CHANGES" ,
"CMD_VIEW_CHANGES" ,
"CMD_ABORT" , ] ;
2007-04-26 02:48:24 +00:00
2007-04-26 02:56:54 +00:00
$ q - > { default } = "CMD_VIEW_CHANGES" ;
2007-04-26 02:48:24 +00:00
2007-04-26 02:56:54 +00:00
$ q - > { options } = [ @ changed ] ;
$ q - > { selected } = 0 ;
2007-04-26 02:48:24 +00:00
2007-04-26 02:56:54 +00:00
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 ) ;
}
2007-04-26 02:48:24 +00:00
2007-04-26 02:56:54 +00:00
} until $ ans =~ /^CMD_SAVE_CHANGES/ ;
2007-04-26 02:48:24 +00:00
2007-04-26 02:56:54 +00:00
for my $ profile ( sort keys % changed ) {
2008-02-26 12:00:37 +00:00
writeprofile_ui_feedback ( $ profile ) ;
2008-04-24 16:05:33 +00:00
reload_base ( $ profile ) ;
2007-04-26 02:56:54 +00:00
}
2007-04-26 02:48:24 +00:00
}
}
}
2007-05-22 20:49:51 +00:00
sub get_pager {
if ( $ ENV { PAGER } and ( - x "/usr/bin/$ENV{PAGER}" ||
- x "/usr/sbin/$ENV{PAGER}" )
) {
return $ ENV { PAGER } ;
} else {
return "less"
}
}
2007-04-26 02:48:24 +00:00
sub display_text {
my ( $ header , $ body ) = @ _ ;
2007-05-22 20:49:51 +00:00
my $ pager = get_pager ( ) ;
if ( open ( PAGER , "| $pager" ) ) {
2007-04-26 02:48:24 +00:00
print PAGER "$header\n\n$body" ;
close ( PAGER ) ;
}
}
2007-04-26 02:56:54 +00:00
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 ;
2008-02-26 12:00:37 +00:00
system ( "diff -u $oldtmp $newtmp > $difftmp" ) ;
2007-04-26 02:56:54 +00:00
while ( <$difftmp> ) {
push ( @ diff , $ _ ) unless ( ( $ _ =~ /^(---|\+\+\+)/ ) ||
( $ _ =~ /^\@\@.*\@\@$/ ) ) ;
}
unlink ( $ difftmp ) ;
unlink ( $ oldtmp ) ;
unlink ( $ newtmp ) ;
return join ( "" , @ diff ) ;
}
2007-04-26 02:48:24 +00:00
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 ) ;
2007-04-26 02:56:54 +00:00
my $ difftmp = new File:: Temp ( UNLINK = > 0 ) ;
my @ diff ;
2008-02-26 12:00:37 +00:00
system ( "diff -u $oldtmp $newtmp > $difftmp" ) ;
2007-04-26 02:56:54 +00:00
if ( $ UI_Mode eq "yast" ) {
while ( <$difftmp> ) {
push ( @ diff , $ _ ) unless ( ( $ _ =~ /^(---|\+\+\+)/ ) ||
( $ _ =~ /^\@\@.*\@\@$/ ) ) ;
}
UI_LongMessage ( gettext ( "Profile Changes" ) , join ( "" , @ diff ) ) ;
} else {
system ( "less $difftmp" ) ;
}
2007-04-26 02:48:24 +00:00
2007-04-26 02:56:54 +00:00
unlink ( $ difftmp ) ;
2007-04-26 02:48:24 +00:00
unlink ( $ oldtmp ) ;
unlink ( $ newtmp ) ;
}
2007-04-25 20:47:13 +00:00
2007-03-20 21:58:38 +00:00
sub setprocess ($$) {
my ( $ pid , $ profile ) = @ _ ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# don't do anything if the process exited already...
return unless - e "/proc/$pid/attr/current" ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
return unless open ( CURR , "/proc/$pid/attr/current" ) ;
my $ current = <CURR> ;
2008-02-26 12:00:37 +00:00
return unless $ current ;
2007-03-20 21:58:38 +00:00
chomp $ current ;
close ( CURR ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# only change null profiles
return unless $ current =~ /null(-complain)*-profile/ ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
return unless open ( STAT , "/proc/$pid/stat" ) ;
my $ stat = <STAT> ;
chomp $ stat ;
close ( STAT ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
return unless $ stat =~ /^\d+ \((\S+)\) / ;
my $ currprog = $ 1 ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
open ( CURR , ">/proc/$pid/attr/current" ) or return ;
print CURR "setprofile $profile" ;
close ( CURR ) ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
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 } } ) {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my $ mode = $ prelog { $ sdmode } { $ profile } { $ hat } { path } { $ path } ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# we want to ignore anything from the log that's already
# in the profile
2008-04-18 21:02:47 +00:00
my $ combinedmode = 0 ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# is it in the original profile?
2008-04-18 21:07:16 +00:00
if ( $ sd { $ profile } { $ hat } { allow } { path } { $ path } ) {
$ combinedmode |= $ sd { $ profile } { $ hat } { allow } { path } { $ path } { mode } ;
2006-04-11 21:52:54 +00:00
}
2007-03-20 21:58:38 +00:00
# does path match any regexps in original profile?
2008-04-18 21:08:05 +00:00
$ combinedmode |= rematchfrag ( $ sd { $ profile } { $ hat } , 'allow' , $ path ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# does path match anything pulled in by includes in
# original profile?
2008-04-18 21:08:05 +00:00
$ combinedmode |= match_prof_incs_to_path ( $ sd { $ profile } { $ hat } , 'allow' , $ path ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# if we found any matching entries, do the modes match?
2008-04-18 21:02:47 +00:00
unless ( $ combinedmode && mode_contains ( $ combinedmode , $ mode ) ) {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# merge in any previous modes from this run
2008-04-18 21:07:16 +00:00
if ( $ log { $ sdmode } { $ profile } { $ hat } { $ path } ) {
$ mode |= $ log { $ sdmode } { $ profile } { $ hat } { path } { $ path } ;
2007-03-20 21:58:38 +00:00
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# record the new entry
2008-04-18 21:02:47 +00:00
$ log { $ sdmode } { $ profile } { $ hat } { path } { $ path } = $ mode ;
2006-04-11 21:52:54 +00:00
}
}
2007-03-20 21:58:38 +00:00
for my $ capability ( keys % { $ prelog { $ sdmode } { $ profile } { $ hat } { capability } } ) {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# if we don't already have this capability in the profile,
# add it
2008-04-18 21:08:34 +00:00
unless ( $ sd { $ profile } { $ hat } { allow } { capability } { $ capability } { set } ) {
2007-03-20 21:58:38 +00:00
$ log { $ sdmode } { $ profile } { $ hat } { capability } { $ capability } = 1 ;
}
2007-03-18 19:44:57 +00:00
}
2007-07-29 02:06:00 +00:00
# Network toggle handling
2008-04-24 16:05:33 +00:00
my $ ndref = $ prelog { $ sdmode } { $ profile } { $ hat } { netdomain } ;
2007-07-29 02:06:00 +00:00
for my $ family ( keys % { $ ndref } ) {
for my $ sock_type ( keys % { $ ndref - > { $ family } } ) {
2008-04-18 21:08:05 +00:00
unless ( profile_known_network ( $ sd { $ profile } { $ hat } ,
$ family , $ sock_type ) ) {
2007-07-29 02:06:00 +00:00
$ log { $ sdmode }
{ $ profile }
{ $ hat }
{ netdomain }
{ $ family }
{ $ sock_type } = 1 ;
}
}
}
2006-04-11 21:52:54 +00:00
}
}
}
}
sub profilemode ($) {
2007-03-20 21:58:38 +00:00
my $ mode = shift ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my $ modifier = ( $ mode =~ m/[iupUP]/ ) [ 0 ] ;
if ( $ modifier ) {
$ mode =~ s/[iupUPx]//g ;
$ mode . = $ modifier . "x" ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
return $ mode ;
2006-04-11 21:52:54 +00:00
}
# kinky.
2007-03-20 21:58:38 +00:00
sub commonprefix (@) { ( join ( "\0" , @ _ ) =~ m/^([^\0]*)[^\0]*(\0\1[^\0]*)*$/ ) [ 0 ] }
sub commonsuffix (@) { reverse ( ( ( reverse join ( "\0" , @ _ ) ) =~ m/^([^\0]*)[^\0]*(\0\1[^\0]*)*$/ ) [ 0 ] ) ; }
2006-04-11 21:52:54 +00:00
sub uniq (@) {
2007-03-20 21:58:38 +00:00
my % seen ;
my @ result = sort grep { ! $ seen { $ _ } + + } @ _ ;
return @ result ;
2006-04-11 21:52:54 +00:00
}
2008-04-18 21:04:16 +00:00
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|Cx|Nx|Pix|Cix" ;
our $ PROFILE_MODE_RE = "r|w|l|m|k|a|ix|ux|px|cx|pix|cix|Ux|Px|Cx|Pix|Cix" ;
2008-04-24 16:05:33 +00:00
our $ PROFILE_MODE_NT_RE = "r|w|l|m|k|a|x|ix|ux|px|cx|pix|cix|Ux|Px|Cx|Pix|Cix" ;
2008-04-18 21:08:05 +00:00
our $ PROFILE_MODE_DENY_RE = "r|w|l|m|k|a|x" ;
2007-07-29 02:20:24 +00:00
2008-04-18 21:16:15 +00:00
sub split_log_mode ($) {
2008-04-18 20:50:18 +00:00
my $ mode = shift ;
2008-04-18 21:16:15 +00:00
my $ user = "" ;
my $ other = "" ;
if ( $ mode =~ /(.*?)::(.*)/ ) {
$ user = $ 1 if ( $ 1 ) ;
$ other = $ 2 if ( $ 2 ) ;
} else {
$ user = $ mode ;
$ other = $ mode ;
}
return ( $ user , $ other ) ;
}
2008-04-18 20:50:18 +00:00
2008-04-18 21:16:15 +00:00
sub map_log_mode ($) {
my $ mode = shift ;
2008-04-18 20:50:18 +00:00
return $ mode ;
2008-04-18 21:16:15 +00:00
# $mode =~ s/(.*l.*)::.*/$1/ge;
# $mode =~ s/.*::(.*l.*)/$1/ge;
# $mode =~ s/:://;
# return $mode;
# return $1;
2008-04-18 20:50:18 +00:00
}
2008-04-18 21:16:15 +00:00
sub hide_log_mode ($) {
my $ mode = shift ;
$ mode =~ s/::// ;
return $ mode ;
}
2008-04-18 21:02:47 +00:00
2007-07-30 01:53:25 +00:00
sub validate_log_mode ($) {
2007-07-29 02:20:24 +00:00
my $ mode = shift ;
2007-07-30 01:53:25 +00:00
return ( $ mode =~ /^($LOG_MODE_RE)+$/ ) ? 1 : 0 ;
}
2008-04-24 16:05:33 +00:00
sub validate_profile_mode ($$$) {
my ( $ mode , $ allow , $ nt_name ) = @ _ ;
2008-04-18 21:08:05 +00:00
if ( $ allow eq 'deny' ) {
return ( $ mode =~ /^($PROFILE_MODE_DENY_RE)+$/ ) ? 1 : 0 ;
2008-04-24 16:05:33 +00:00
} elsif ( $ nt_name ) {
return ( $ mode =~ /^($PROFILE_MODE_NT_RE)+$/ ) ? 1 : 0 ;
2008-04-18 21:08:05 +00:00
}
2007-07-30 01:53:25 +00:00
return ( $ mode =~ /^($PROFILE_MODE_RE)+$/ ) ? 1 : 0 ;
2007-07-29 02:20:24 +00:00
}
2008-04-18 21:02:47 +00:00
# modes internally are stored as a bit Mask
2008-04-18 21:16:15 +00:00
sub sub_str_to_mode ($) {
2008-04-18 21:02:47 +00:00
my $ str = shift ;
my $ mode = 0 ;
2006-04-11 21:52:54 +00:00
2008-04-18 21:02:47 +00:00
return 0 if ( not $ str ) ;
2007-07-29 02:20:24 +00:00
2008-04-18 21:02:47 +00:00
while ( $ str =~ s/(${MODE_MAP_RE})// ) {
my $ tmp = $ 1 ;
#print "found mode $1\n";
2007-07-29 02:20:24 +00:00
2008-04-18 21:02:47 +00:00
if ( $ tmp && $ MODE_HASH { $ tmp } ) {
$ mode |= $ MODE_HASH { $ tmp } ;
} else {
2008-04-18 21:16:15 +00:00
#print "found mode $tmp\n";
2008-04-18 21:02:47 +00:00
}
}
#my $tmp = mode_to_str($mode);
#print "parsed_mode $mode\n";
return $ mode ;
2006-04-11 21:52:54 +00:00
}
2008-04-18 21:16:15 +00:00
sub print_mode ($) {
my $ mode = shift ;
my ( $ user , $ other ) = split_mode ( $ mode ) ;
my $ str = sub_str_to_mode ( $ user ) . "::" . sub_str_to_mode ( $ 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 ;
}
2008-04-24 16:05:33 +00:00
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 ) ;
}
2008-04-18 21:16:15 +00:00
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 ($) {
2008-04-18 21:02:47 +00:00
my $ mode = shift ;
my $ str = "" ;
2006-04-11 21:52:54 +00:00
2008-04-18 21:02:47 +00:00
# "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 ) ;
if ( $ mode & $ AA_EXEC_UNCONFINED ) {
if ( $ mode & $ AA_EXEC_UNSAFE ) {
$ str . = "u" ;
} else {
$ str . = "U" ;
}
}
if ( $ mode & ( $ AA_EXEC_PROFILE | $ AA_EXEC_NT ) ) {
if ( $ mode & $ AA_EXEC_UNSAFE ) {
$ str . = "p" ;
} else {
$ str . = "P" ;
}
}
2008-04-24 16:05:33 +00:00
if ( $ mode & $ AA_EXEC_CHILD ) {
if ( $ mode & $ AA_EXEC_UNSAFE ) {
$ str . = "c" ;
} else {
$ str . = "C" ;
}
}
2008-04-18 21:02:47 +00:00
$ str . = "i" if ( $ mode & $ AA_EXEC_INHERIT ) ;
$ str . = "x" if ( $ mode & $ AA_MAY_EXEC ) ;
2006-04-11 21:52:54 +00:00
2008-04-18 21:02:47 +00:00
return $ str ;
}
2007-07-29 02:20:24 +00:00
2008-04-18 21:16:15 +00:00
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 ;
}
2008-04-18 21:02:47 +00:00
sub mode_contains ($$) {
my ( $ mode , $ subset ) = @ _ ;
2006-04-11 21:52:54 +00:00
2008-04-18 21:02:47 +00:00
# "w" implies "a"
if ( $ mode & $ AA_MAY_WRITE ) {
$ mode |= $ AA_MAY_APPEND ;
2007-03-20 21:58:38 +00:00
}
2008-04-18 21:16:15 +00:00
if ( $ mode & ( $ AA_MAY_WRITE << $ AA_OTHER_SHIFT ) ) {
$ mode |= ( $ AA_MAY_APPEND << $ AA_OTHER_SHIFT ) ;
}
2008-04-18 21:02:47 +00:00
# "?ix" implies "m"
if ( $ mode & $ AA_EXEC_INHERIT ) {
2008-04-18 21:16:15 +00:00
$ mode |= $ AA_EXEC_MMAP ;
}
if ( $ mode & ( $ AA_EXEC_INHERIT << $ AA_OTHER_SHIFT ) ) {
$ mode |= ( $ AA_EXEC_MMAP << $ AA_OTHER_SHIFT ) ;
2008-04-18 21:02:47 +00:00
}
return ( ( $ mode & $ subset ) == $ subset ) ;
}
sub contains ($$) {
my ( $ mode , $ str ) = @ _ ;
2006-04-11 21:52:54 +00:00
2008-04-18 21:02:47 +00:00
return mode_contains ( $ mode , str_to_mode ( $ str ) ) ;
2006-04-11 21:52:54 +00:00
}
2007-03-23 18:52:22 +00:00
# isSkippableFile - return true if filename matches something that
# should be skipped (rpm backup files, dotfiles, emacs backup files
2007-08-14 19:19:59 +00:00
# Annoyingly, this needs to be kept in sync with the skipped files
# in the apparmor initscript.
2007-03-23 18:52:22 +00:00
sub isSkippableFile ($) {
my $ path = shift ;
return ( $ path =~ /(^|\/)\.[^\/]*$/
|| $ path =~ /\.rpm(save|new)$/
2007-08-14 19:19:59 +00:00
|| $ path =~ /\.dpkg-(old|new)$/
2008-04-10 08:40:52 +00:00
|| $ path =~ /\.swp$/
2007-03-23 18:52:22 +00:00
|| $ path =~ /\~$/ ) ;
}
2006-10-05 21:29:22 +00:00
sub checkIncludeSyntax ($) {
2007-03-20 21:58:38 +00:00
my $ errors = shift ;
if ( opendir ( SDDIR , $ profiledir ) ) {
my @ incdirs = grep { ( ! /^\./ ) && ( - d "$profiledir/$_" ) } readdir ( SDDIR ) ;
close ( SDDIR ) ;
while ( my $ id = shift @ incdirs ) {
if ( opendir ( SDDIR , "$profiledir/$id" ) ) {
for my $ path ( grep { ! /^\./ } readdir ( SDDIR ) ) {
chomp ( $ path ) ;
2007-03-23 18:52:22 +00:00
next if isSkippableFile ( $ path ) ;
2007-03-20 21:58:38 +00:00
if ( - f "$profiledir/$id/$path" ) {
my $ file = "$id/$path" ;
$ file =~ s/$profiledir\/// ;
2007-04-25 21:04:28 +00:00
eval { loadinclude ( $ file ) ; } ;
if ( defined $@ && $@ ne "" ) {
push @$ errors , $@ ;
2007-03-20 21:58:38 +00:00
}
} elsif ( - d "$id/$path" ) {
push @ incdirs , "$id/$path" ;
}
}
closedir ( SDDIR ) ;
}
}
}
return $ errors ;
2006-10-05 21:29:22 +00:00
}
sub checkProfileSyntax ($) {
2007-03-20 21:58:38 +00:00
my $ errors = shift ;
2006-10-05 21:29:22 +00:00
2007-03-20 21:58:38 +00:00
# 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 ) ) {
2007-03-23 18:52:22 +00:00
next if isSkippableFile ( $ file ) ;
2007-07-29 02:17:17 +00:00
my $ err = readprofile ( "$profiledir/$file" , \ & printMessageErrorHandler , 1 ) ;
2007-04-25 21:04:28 +00:00
if ( defined $ err and $ err ne "" ) {
2007-03-20 21:58:38 +00:00
push @$ errors , $ err ;
}
2006-10-05 21:29:22 +00:00
}
2007-03-20 21:58:38 +00:00
closedir ( SDDIR ) ;
return $ errors ;
2006-10-05 21:29:22 +00:00
}
sub printMessageErrorHandler ($) {
2007-03-20 21:58:38 +00:00
my $ message = shift ;
return $ message ;
2006-10-05 21:29:22 +00:00
}
2006-04-11 21:52:54 +00:00
sub readprofiles () {
2007-03-20 21:58:38 +00:00
opendir ( SDDIR , $ profiledir )
or fatal_error "Can't read AppArmor profiles in $profiledir." ;
for my $ file ( grep { - f "$profiledir/$_" } readdir ( SDDIR ) ) {
2007-03-23 18:52:22 +00:00
next if isSkippableFile ( $ file ) ;
2007-04-26 02:59:17 +00:00
readprofile ( "$profiledir/$file" , \ & fatal_error , 1 ) ;
2007-03-20 21:58:38 +00:00
}
closedir ( SDDIR ) ;
2006-04-11 21:52:54 +00:00
}
2007-04-26 02:59:17 +00:00
sub readinactiveprofiles () {
2007-09-28 15:39:42 +00:00
return if ( ! - e $ extraprofiledir ) ;
2007-04-26 02:59:17 +00:00
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 ($$$) {
2007-03-20 21:58:38 +00:00
my $ file = shift ;
my $ error_handler = shift ;
2007-04-26 02:59:17 +00:00
my $ active_profile = shift ;
2007-03-20 21:58:38 +00:00
if ( open ( SDPROF , "$file" ) ) {
2007-04-25 21:04:28 +00:00
local $/ ;
my $ data = <SDPROF> ;
close ( SDPROF ) ;
2007-03-20 21:58:38 +00:00
2007-04-25 21:04:28 +00:00
eval {
2008-04-18 21:09:53 +00:00
my $ profile_data = parse_profile_data ( $ data , $ file , 0 ) ;
2007-04-26 02:59:17 +00:00
if ( $ profile_data && $ active_profile ) {
2007-04-25 21:04:28 +00:00
attach_profile_data ( \ % sd , $ profile_data ) ;
2007-04-26 02:59:17 +00:00
attach_profile_data ( \ % original_sd , $ profile_data ) ;
} elsif ( $ profile_data ) {
attach_profile_data ( \ % extras , $ profile_data ) ;
2007-04-25 21:04:28 +00:00
}
} ;
2007-03-20 21:58:38 +00:00
2007-04-25 21:04:28 +00:00
# if there were errors loading the profile, call the error handler
if ( $@ ) {
$@ =~ s/\n$// ;
return & $ error_handler ( $@ ) ;
}
2007-07-13 17:53:12 +00:00
} else {
$ DEBUGGING && debug "readprofile: can't read $file - skipping" ;
}
2007-04-25 21:04:28 +00:00
}
2007-03-20 21:58:38 +00:00
2007-04-25 21:04:28 +00:00
sub attach_profile_data {
my ( $ profiles , $ profile_data ) = @ _ ;
2006-04-11 21:52:54 +00:00
2007-04-26 14:42:56 +00:00
# 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
2007-04-25 21:04:28 +00:00
for my $ p ( keys %$ profile_data ) {
2007-04-26 14:42:56 +00:00
$ profiles - > { $ p } = dclone ( $ profile_data - > { $ p } ) ;
2007-04-25 21:04:28 +00:00
}
}
2006-04-11 21:52:54 +00:00
2007-04-25 21:04:28 +00:00
sub parse_profile_data {
2008-04-18 21:09:53 +00:00
my ( $ data , $ file , $ do_include ) = @ _ ;
2006-04-11 21:52:54 +00:00
2007-07-16 13:19:02 +00:00
my ( $ profile_data , $ profile , $ hat , $ in_contained_hat , $ repo_data ,
@ parsed_profiles ) ;
2007-04-25 21:04:28 +00:00
my $ initial_comment = "" ;
2008-04-18 21:09:53 +00:00
if ( $ do_include ) {
$ profile = $ file ;
$ hat = $ file ;
}
2007-04-25 21:04:28 +00:00
for ( split ( /\n/ , $ data ) ) {
chomp ;
2006-04-11 21:52:54 +00:00
2007-04-25 21:04:28 +00:00
# we don't care about blank lines
next if /^\s*$/ ;
2006-04-11 21:52:54 +00:00
2007-04-25 21:04:28 +00:00
# start of a profile...
2008-04-18 20:57:33 +00:00
if ( m/^\s*(("??\/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$/ ) {
2008-04-18 21:09:53 +00:00
if ( $ do_include ) {
die "include <$file> contains syntax errors.\n" ;
}
2006-04-11 21:52:54 +00:00
2007-04-25 21:04:28 +00:00
# if we run into the start of a profile while we're already in a
# profile, something's wrong...
if ( $ profile ) {
2008-04-24 16:05:33 +00:00
unless ( ( $ profile eq $ hat ) and $ 4 ) {
die "$profile profile in $file contains syntax errors.\n" ;
}
}
2006-04-11 21:52:54 +00:00
2007-04-25 21:04:28 +00:00
# we hit the start of a profile, keep track of it...
2008-04-24 16:05:33 +00:00
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 ;
}
2006-04-11 21:52:54 +00:00
2008-04-24 16:05:33 +00:00
$ hat || = $ profile ;
}
my $ flags = $ 7 ;
2006-04-11 21:52:54 +00:00
2007-04-25 21:04:28 +00:00
# deal with whitespace in profile and hat names.
2008-04-18 21:03:28 +00:00
$ profile = strip_quotes ( $ profile ) ;
$ hat = strip_quotes ( $ hat ) if $ hat ;
2006-04-11 21:52:54 +00:00
2008-04-18 21:06:24 +00:00
# save off the name and filename
2008-04-18 21:03:28 +00:00
$ profile_data - > { $ profile } { $ hat } { name } = $ profile ;
2008-04-18 21:06:24 +00:00
$ profile_data - > { $ profile } { $ hat } { filename } = $ file ;
$ filelist { $ file } { profiles } { $ profile } { $ hat } = 1 ;
2008-04-18 21:03:28 +00:00
2007-04-25 21:04:28 +00:00
# keep track of profile flags
2008-04-18 20:57:33 +00:00
$ profile_data - > { $ profile } { $ hat } { flags } = $ flags ;
2006-04-11 21:52:54 +00:00
2008-04-18 21:07:16 +00:00
$ profile_data - > { $ profile } { $ hat } { allow } { netdomain } = { } ;
$ profile_data - > { $ profile } { $ hat } { allow } { path } = { } ;
2006-04-11 21:52:54 +00:00
2007-04-25 21:04:28 +00:00
# store off initial comment if they have one
2007-04-26 03:00:22 +00:00
$ profile_data - > { $ profile } { $ hat } { initial_comment } = $ initial_comment
if $ initial_comment ;
2007-04-25 21:04:28 +00:00
$ initial_comment = "" ;
2006-04-11 21:52:54 +00:00
2007-04-26 03:00:22 +00:00
if ( $ repo_data ) {
2007-04-26 14:42:56 +00:00
$ 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 ;
2007-04-26 03:00:22 +00:00
}
2007-04-26 02:48:24 +00:00
2007-07-29 02:06:41 +00:00
} elsif ( m/^\s*\}\s*(#.*)?$/ ) { # end of a profile...
2006-04-11 21:52:54 +00:00
2007-04-25 21:04:28 +00:00
# if we hit the end of a profile when we're not in one, something's
# wrong...
2008-04-18 21:09:53 +00:00
if ( $ do_include ) {
die "include <$file> contains syntax errors." ;
}
2007-04-25 21:04:28 +00:00
if ( not $ profile ) {
die sprintf ( gettext ( '%s contains syntax errors.' ) , $ file ) . "\n" ;
}
2006-04-11 21:52:54 +00:00
2007-04-25 21:04:28 +00:00
if ( $ in_contained_hat ) {
$ hat = $ profile ;
$ in_contained_hat = 0 ;
} else {
2007-07-16 13:19:02 +00:00
push @ parsed_profiles , $ profile ;
2007-04-25 21:04:28 +00:00
# mark that we're outside of a profile now...
$ profile = undef ;
}
2006-04-11 21:52:54 +00:00
2007-07-16 13:19:02 +00:00
$ initial_comment = "" ;
2008-04-18 21:09:05 +00:00
} elsif ( m/^\s*(audit\s+)?(deny\s+)?capability\s+(\S+)\s*,\s*(#.*)?$/ ) { # capability entry
2007-04-25 21:04:28 +00:00
if ( not $ profile ) {
die sprintf ( gettext ( '%s contains syntax errors.' ) , $ file ) . "\n" ;
}
2006-04-11 21:52:54 +00:00
2008-04-18 21:09:05 +00:00
my $ audit = $ 1 ? 1 : 0 ;
my $ allow = $ 2 ? 'deny' : 'allow' ;
$ allow = 'deny' if ( $ 2 ) ;
my $ capability = $ 3 ;
2008-04-18 21:08:34 +00:00
$ profile_data - > { $ profile } { $ hat } { $ allow } { capability } { $ capability } { set } = 1 ;
2008-04-18 21:09:05 +00:00
$ profile_data - > { $ profile } { $ hat } { $ allow } { capability } { $ capability } { audit } = $ audit ;
2008-04-18 20:57:01 +00:00
} 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 ;
2007-04-25 21:04:28 +00:00
2008-04-18 21:09:05 +00:00
} elsif ( m/^\s*(audit\s+)?(deny\s+)?link\s+(((subset)|(<=))\s+)?([\"\@\/].*?"??)\s+->\s*([\"\@\/].*?"??)\s*,\s*(#.*)?$/ ) { # for now just keep link
2008-04-18 20:55:43 +00:00
if ( not $ profile ) {
die sprintf ( gettext ( '%s contains syntax errors.' ) , $ file ) . "\n" ;
}
2008-04-18 21:09:05 +00:00
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 ;
2008-04-18 20:59:00 +00:00
if ( $ subset ) {
2008-04-18 21:09:05 +00:00
$ profile_data - > { $ profile } { $ hat } { $ allow } { link } { $ link } { mode } = $ AA_LINK_SUBSET ;
}
if ( $ audit ) {
$ profile_data - > { $ profile } { $ hat } { $ allow } { link } { $ link } { audit } = $ AA_LINK_SUBSET ;
2008-04-18 20:59:00 +00:00
} else {
2008-04-18 21:09:05 +00:00
$ profile_data - > { $ profile } { $ hat } { $ allow } { link } { $ link } { audit } = 0 ;
2008-04-18 20:59:00 +00:00
}
2008-04-18 21:09:05 +00:00
2008-04-18 20:59:00 +00:00
} elsif ( m/^\s*change_profile\s+->\s*("??.+?"??),(#.*)?$/ ) { # for now just keep change_profile
2008-04-18 20:56:08 +00:00
if ( not $ profile ) {
die sprintf ( gettext ( '%s contains syntax errors.' ) , $ file ) . "\n" ;
}
2008-04-18 20:59:00 +00:00
my $ cp = strip_quotes ( $ 1 ) ;
2008-04-18 20:56:08 +00:00
$ profile_data - > { $ profile } { $ hat } { change_profile } { $ cp } = 1 ;
2008-04-18 20:59:00 +00:00
} 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 ) ;
2008-04-18 20:56:26 +00:00
2008-04-18 21:00:35 +00:00
if ( $ profile ) {
$ profile_data - > { $ profile } { $ hat } { alias } { $ from } = $ to ;
} else {
2008-04-18 21:06:24 +00:00
unless ( exists $ filelist { $ file } ) {
$ filelist { $ file } = { } ;
2008-04-18 21:00:35 +00:00
}
2008-04-18 21:06:24 +00:00
$ filelist { $ file } { alias } { $ from } = $ to ;
2008-04-18 21:00:35 +00:00
}
2008-04-18 20:56:41 +00:00
} 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 ;
2008-04-18 20:56:08 +00:00
2008-04-18 20:58:07 +00:00
} 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
2008-04-18 20:59:00 +00:00
my $ list_var = strip_quotes ( $ 1 ) ;
my $ value = strip_quotes ( $ 2 ) ;
2008-04-18 20:58:07 +00:00
2008-04-18 21:00:35 +00:00
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 ) ;
2008-04-18 21:09:53 +00:00
} else {
2008-04-18 21:06:24 +00:00
unless ( exists $ filelist { $ file } { lvar } ) {
2008-04-18 21:00:35 +00:00
# create lval hash by sticking an empty list into list_var
my @ empty = ( ) ;
2008-04-18 21:06:24 +00:00
$ filelist { $ file } { lvar } { $ list_var } = \ @ empty ;
2008-04-18 21:00:35 +00:00
}
2008-04-18 21:06:24 +00:00
store_list_var ( $ filelist { $ file } { lvar } , $ list_var , $ value ) ;
2008-04-18 20:58:07 +00:00
}
2007-07-29 02:06:41 +00:00
} 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
2008-04-24 16:05:33 +00:00
} elsif ( m/^\s*(audit\s+)?(deny\s+)?(owner\s+)?([\"\@\/].*?)\s+(\S+)(\s+->\s*(.*?))?\s*,\s*(#.*)?$/ ) { # path entry
2007-04-25 21:04:28 +00:00
if ( not $ profile ) {
die sprintf ( gettext ( '%s contains syntax errors.' ) , $ file ) . "\n" ;
}
2006-04-11 21:52:54 +00:00
2008-04-24 16:05:33 +00:00
my $ audit = $ 1 ? 1 : 0 ;
my $ allow = $ 2 ? 'deny' : 'allow' ;
my $ user = $ 3 ? 1 : 0 ;
my ( $ path , $ mode , $ nt_name ) = ( $ 4 , $ 5 , $ 7 ) ;
2006-04-11 21:52:54 +00:00
2007-04-25 21:04:28 +00:00
# strip off any trailing spaces.
$ path =~ s/\s+$// ;
2008-04-24 16:05:33 +00:00
$ nt_name =~ s/\s+$// if $ nt_name ;
2006-04-11 21:52:54 +00:00
2008-04-18 21:02:07 +00:00
$ path = strip_quotes ( $ path ) ;
2008-04-24 16:05:33 +00:00
$ nt_name = strip_quotes ( $ nt_name ) if $ nt_name ;
2006-04-11 21:52:54 +00:00
2007-04-25 21:04:28 +00:00
# make sure they don't have broken regexps in the profile
my $ p_re = convert_regexp ( $ path ) ;
eval { "foo" =~ m/^$p_re$/ ; } ;
if ( $@ ) {
2007-04-26 03:00:22 +00:00
die sprintf ( gettext ( 'Profile %s contains invalid regexp %s.' ) ,
$ file , $ path ) . "\n" ;
2007-04-25 21:04:28 +00:00
}
2006-04-11 21:52:54 +00:00
2008-04-24 16:05:33 +00:00
if ( ! validate_profile_mode ( $ mode , $ allow , $ nt_name ) ) {
2007-07-30 01:53:25 +00:00
fatal_error ( sprintf ( gettext ( 'Profile %s contains invalid mode %s.' ) , $ file , $ mode ) ) ;
2007-07-29 02:20:24 +00:00
}
2008-04-24 16:05:33 +00:00
my $ tmpmode ;
if ( $ user ) {
$ tmpmode = str_to_mode ( "${mode}::" ) ;
} else {
$ tmpmode = str_to_mode ( $ mode ) ;
}
2008-04-18 21:16:15 +00:00
$ profile_data - > { $ profile } { $ hat } { $ allow } { path } { $ path } { mode } = $ tmpmode ;
2008-04-24 16:05:33 +00:00
$ profile_data - > { $ profile } { $ hat } { $ allow } { path } { $ path } { to } = $ nt_name if $ nt_name ;
2008-04-18 21:10:25 +00:00
if ( $ audit ) {
2008-04-18 21:16:15 +00:00
$ profile_data - > { $ profile } { $ hat } { $ allow } { path } { $ path } { audit } = $ tmpmode ;
2008-04-18 21:10:25 +00:00
} else {
$ profile_data - > { $ profile } { $ hat } { $ allow } { path } { $ path } { audit } = 0 ;
}
2007-04-25 21:04:28 +00:00
} elsif ( m/^\s*#include <(.+)>\s*$/ ) { # include stuff
my $ include = $ 1 ;
2006-04-11 21:52:54 +00:00
2007-04-25 21:04:28 +00:00
if ( $ profile ) {
$ profile_data - > { $ profile } { $ hat } { include } { $ include } = 1 ;
} else {
2008-04-18 21:06:24 +00:00
unless ( exists $ filelist { $ file } ) {
$ filelist { $ file } = { } ;
2007-03-20 21:58:38 +00:00
}
2008-04-18 21:06:24 +00:00
$ filelist { $ file } { include } { $ include } = 1 ;
2007-04-25 21:04:28 +00:00
}
2007-03-20 21:58:38 +00:00
2007-04-25 21:04:28 +00:00
# try to load the include...
my $ ret = eval { loadinclude ( $ include ) ; } ;
# propagate errors up the chain
if ( $@ ) { die $@ ; }
2007-03-20 21:58:38 +00:00
2007-04-25 21:04:28 +00:00
return $ ret if ( $ ret != 0 ) ;
2006-04-11 21:52:54 +00:00
2008-04-18 21:09:05 +00:00
} elsif ( /^\s*(audit\s+)?(deny\s+)?network(.*)/ ) {
2007-07-29 02:06:00 +00:00
if ( not $ profile ) {
die sprintf ( gettext ( '%s contains syntax errors.' ) , $ file ) . "\n" ;
}
2008-04-18 21:09:05 +00:00
my $ audit = $ 1 ? 1 : 0 ;
my $ allow = $ 2 ? 'deny' : 'allow' ;
my $ network = $ 3 ;
2007-07-29 02:06:00 +00:00
2008-04-18 21:09:05 +00:00
unless ( $ profile_data - > { $ profile } { $ hat } { $ allow } { netdomain } { rule } ) {
$ profile_data - > { $ profile } { $ hat } { $ allow } { netdomain } { rule } = { } ;
2007-07-29 02:06:00 +00:00
}
2008-04-18 21:09:05 +00:00
if ( $ 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 ;
} elsif ( $ 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 ;
2007-07-29 02:06:00 +00:00
} else {
2008-04-18 21:09:05 +00:00
$ profile_data - > { $ profile } { $ hat } { $ allow } { netdomain } { rule } { all } = 1 ;
$ profile_data - > { $ profile } { $ hat } { $ allow } { netdomain } { audit } { all } = 1 ;
2007-07-29 02:06:00 +00:00
}
2007-04-25 21:04:28 +00:00
} elsif ( /^\s*(tcp_connect|tcp_accept|udp_send|udp_receive)/ ) {
2008-04-24 16:05:33 +00:00
# just ignore and drop old style network
# die sprintf(gettext('%s contains old style network rules.'), $file) . "\n";
2006-04-11 21:52:54 +00:00
2008-04-18 20:57:51 +00:00
} elsif ( m/^\s*\^(\"??.+?\"??)\s*,\s*(#.*)?$/ ) {
2008-04-18 21:09:53 +00:00
if ( not $ profile ) {
die "$file contains syntax errors." ;
}
2008-04-18 20:57:51 +00:00
# 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*(#.*)?$/ ) {
2008-04-18 21:09:53 +00:00
if ( $ do_include ) {
die "include <$file> contains syntax errors." ;
}
2008-04-18 21:00:35 +00:00
# start of embedded hat syntax hat definition
2007-07-16 13:19:02 +00:00
# read in and mark as changed so that will be written out in the new
# format
2007-04-25 21:04:28 +00:00
# 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" ;
}
2007-03-20 21:58:38 +00:00
2007-04-25 21:04:28 +00:00
$ in_contained_hat = 1 ;
2007-03-20 21:58:38 +00:00
2007-04-25 21:04:28 +00:00
# we hit the start of a hat inside the current profile
$ hat = $ 1 ;
2008-04-18 20:57:51 +00:00
my $ flags = $ 3 ;
2006-04-11 21:52:54 +00:00
2008-04-18 20:57:51 +00:00
# strip quotes.
2007-04-26 14:42:56 +00:00
$ hat = $ 1 if $ hat =~ /^"(.+)"$/ ;
2007-04-25 21:04:28 +00:00
# keep track of profile flags
2008-04-18 20:57:51 +00:00
$ profile_data - > { $ profile } { $ hat } { flags } = $ flags ;
2006-04-11 21:52:54 +00:00
2008-04-18 20:57:51 +00:00
# we have seen more than a declaration so clear it
$ profile_data - > { $ profile } { $ hat } { 'declared' } = 0 ;
2008-04-18 21:07:16 +00:00
$ profile_data - > { $ profile } { $ hat } { allow } { path } = { } ;
$ profile_data - > { $ profile } { $ hat } { allow } { netdomain } = { } ;
2007-04-25 21:04:28 +00:00
# store off initial comment if they have one
2007-04-26 03:00:22 +00:00
$ profile_data - > { $ profile } { $ hat } { initial_comment } = $ initial_comment
if $ initial_comment ;
2007-04-25 21:04:28 +00:00
$ initial_comment = "" ;
2008-04-18 21:04:54 +00:00
#don't mark profile as changed just because it has an embedded
#hat.
#$changed{$profile} = 1;
2007-07-16 13:19:02 +00:00
2008-04-18 21:06:24 +00:00
$ filelist { $ file } { profiles } { $ profile } { $ hat } = 1 ;
2007-04-25 21:04:28 +00:00
} 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:/ ;
2007-04-26 02:58:10 +00:00
if ( /^\s*\# REPOSITORY: (\S+) (\S+) (\S+)$/ ) {
$ repo_data = { url = > $ 1 , user = > $ 2 , id = > $ 3 } ;
2007-04-26 02:56:54 +00:00
} elsif ( /^\s*\# REPOSITORY: NEVERSUBMIT$/ ) {
$ repo_data = { neversubmit = > 1 } ;
2007-04-26 02:58:10 +00:00
} else {
$ initial_comment . = "$_\n" ;
}
2007-04-25 21:04:28 +00:00
}
} else {
2008-04-18 21:09:53 +00:00
# we hit something we don't understand in a profile...
die sprintf ( gettext ( '%s contains syntax errors. Line [%s]' ) , $ file , $ _ ) . "\n" ;
2007-07-16 13:19:02 +00:00
}
}
#
# Cleanup : add required hats if not present in the
# parsed profiles
#
2008-04-18 21:09:53 +00:00
if ( not $ do_include ) {
2007-07-16 13:19:02 +00:00
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 } = { } ;
}
}
}
2006-04-11 21:52:54 +00:00
}
2007-04-25 21:04:28 +00:00
}
2006-04-11 21:52:54 +00:00
2008-04-18 21:09:53 +00:00
} # if we're still in a profile when we hit the end of the file, it's bad
if ( $ profile and not $ do_include ) {
2007-04-25 21:04:28 +00:00
die "Reached the end of $file while we were still inside the $profile profile.\n" ;
2006-04-11 21:52:54 +00:00
}
2007-04-25 21:04:28 +00:00
return $ profile_data ;
2007-03-20 21:58:38 +00:00
}
2006-04-11 21:52:54 +00:00
2008-04-18 20:58:07 +00:00
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 ;
}
2007-07-16 13:19:02 +00:00
sub is_active_profile ($) {
my $ pname = shift ;
if ( $ sd { $ pname } ) {
return 1 ;
} else {
return 0 ;
}
}
2008-04-18 20:58:07 +00:00
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 ;
}
2008-04-18 20:55:11 +00:00
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 ;
}
2007-03-20 21:58:38 +00:00
sub escape ($) {
my $ dangerous = shift ;
2006-04-11 21:52:54 +00:00
2008-04-18 20:55:11 +00:00
$ dangerous = strip_quotes ( $ dangerous ) ;
2007-03-20 21:58:38 +00:00
$ dangerous =~ s/((?<!\\))"/$1\\"/g ;
if ( $ dangerous =~ m/(\s|^$|")/ ) {
$ dangerous = "\"$dangerous\"" ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
return $ dangerous ;
2006-04-11 21:52:54 +00:00
}
2008-04-18 21:09:05 +00:00
sub writeheader ($$$$$) {
my ( $ profile_data , $ depth , $ name , $ embedded_hat , $ write_flags ) = @ _ ;
2006-04-11 21:52:54 +00:00
2008-04-18 21:09:05 +00:00
my $ pre = ' ' x $ depth ;
2007-04-25 21:04:28 +00:00
my @ data ;
2007-03-20 21:58:38 +00:00
# deal with whitespace in profile names...
2008-04-18 20:55:11 +00:00
$ name = quote_if_needed ( $ name ) ;
2008-04-18 20:57:33 +00:00
2008-04-24 16:05:33 +00:00
$ name = "profile $name" if ( ( ! $ embedded_hat && $ name =~ /^[^\/]|^"[^\/]/ )
|| ( $ embedded_hat && $ name =~ /^[^^]/ ) ) ;
2008-04-18 21:00:35 +00:00
#push @data, "#include <tunables/global>" unless ( $is_hat );
2007-11-06 18:06:18 +00:00
if ( $ write_flags and $ profile_data - > { flags } ) {
2008-04-18 21:09:05 +00:00
push @ data , "${pre}$name flags=($profile_data->{flags}) {" ;
2007-03-20 21:58:38 +00:00
} else {
2008-04-18 21:09:05 +00:00
push @ data , "${pre}$name {" ;
2007-03-20 21:58:38 +00:00
}
2007-04-25 21:04:28 +00:00
return @ data ;
2006-04-11 21:52:54 +00:00
}
2008-04-18 20:58:07 +00:00
sub qin_trans ($) {
my $ value = shift ;
return quote_if_needed ( $ value ) ;
}
2008-04-18 21:09:05 +00:00
sub write_single ($$$$$$) {
my ( $ profile_data , $ depth , $ allow , $ name , $ prefix , $ tail ) = @ _ ;
2008-04-18 21:07:16 +00:00
my $ ref ;
2007-04-25 21:04:28 +00:00
my @ data ;
2008-04-18 21:07:16 +00:00
if ( $ allow ) {
$ ref = $ profile_data - > { $ allow } ;
2008-04-18 21:08:05 +00:00
if ( $ allow eq 'deny' ) {
$ allow . = " " ;
} else {
$ allow = "" ;
}
2008-04-18 21:07:16 +00:00
} else {
$ ref = $ profile_data ;
2008-04-18 21:08:05 +00:00
$ allow = "" ;
2008-04-18 21:07:16 +00:00
}
2008-04-18 21:09:05 +00:00
my $ pre = " " x $ depth ;
2008-04-18 20:55:11 +00:00
# dump out the data
2008-04-18 21:07:16 +00:00
if ( exists $ ref - > { $ name } ) {
for my $ key ( sort keys % { $ ref - > { $ name } } ) {
2008-04-18 20:55:11 +00:00
my $ qkey = quote_if_needed ( $ key ) ;
2008-04-18 21:09:05 +00:00
push @ data , "${pre}${allow}${prefix}${qkey}${tail}" ;
2007-03-20 21:58:38 +00:00
}
2008-04-18 21:07:16 +00:00
push @ data , "" if keys % { $ ref - > { $ name } } ;
2006-04-11 21:52:54 +00:00
}
2007-04-25 21:04:28 +00:00
return @ data ;
2006-04-11 21:52:54 +00:00
}
2008-04-18 21:09:05 +00:00
sub write_pair ($$$$$$$$) {
my ( $ profile_data , $ depth , $ allow , $ name , $ prefix , $ sep , $ tail , $ fn ) = @ _ ;
2008-04-18 21:07:16 +00:00
my $ ref ;
2007-04-25 21:04:28 +00:00
my @ data ;
2008-04-18 21:07:16 +00:00
if ( $ allow ) {
$ ref = $ profile_data - > { $ allow } ;
2008-04-18 21:08:05 +00:00
if ( $ allow eq 'deny' ) {
$ allow . = " " ;
} else {
$ allow = "" ;
}
2008-04-18 21:07:16 +00:00
} else {
$ ref = $ profile_data ;
2008-04-18 21:08:05 +00:00
$ allow = "" ;
2008-04-18 21:07:16 +00:00
}
2008-04-18 21:09:05 +00:00
my $ pre = " " x $ depth ;
2008-04-18 20:55:11 +00:00
# dump out the data
2008-04-18 21:07:16 +00:00
if ( exists $ ref - > { $ name } ) {
for my $ key ( sort keys % { $ ref - > { $ name } } ) {
my $ value = & { $ fn } ( $ ref - > { $ name } { $ key } ) ;
2008-04-18 21:09:05 +00:00
push @ data , "${pre}${allow}${prefix}${key}${sep}${value}${tail}" ;
2007-03-20 21:58:38 +00:00
}
2008-04-18 21:07:16 +00:00
push @ data , "" if keys % { $ ref - > { $ name } } ;
2006-04-11 21:52:54 +00:00
}
2007-04-25 21:04:28 +00:00
return @ data ;
2006-04-11 21:52:54 +00:00
}
2008-04-18 21:09:05 +00:00
sub writeincludes ($$) {
my ( $ prof_data , $ depth ) = @ _ ;
2008-04-18 20:55:11 +00:00
2008-04-18 21:09:05 +00:00
return write_single ( $ prof_data , $ depth , '' , 'include' , "#include <" , ">" ) ;
2008-04-18 20:55:11 +00:00
}
2008-04-18 21:09:05 +00:00
sub writechange_profile ($$) {
my ( $ prof_data , $ depth ) = @ _ ;
2008-04-18 20:55:43 +00:00
2008-04-18 21:09:05 +00:00
return write_single ( $ prof_data , $ depth , '' , 'change_profile' , "change_profile -> " , "," ) ;
2008-04-18 20:55:43 +00:00
}
2008-04-18 21:09:05 +00:00
sub writealiases ($$) {
my ( $ prof_data , $ depth ) = @ _ ;
2008-04-18 20:56:26 +00:00
2008-04-18 21:09:05 +00:00
return write_pair ( $ prof_data , $ depth , '' , 'alias' , "alias " , " -> " , "," , \ & qin_trans ) ;
2008-04-18 20:56:26 +00:00
}
2008-04-18 21:09:05 +00:00
sub writerlimits ($$) {
my ( $ prof_data , $ depth ) = @ _ ;
2008-04-18 20:56:41 +00:00
2008-04-18 21:09:05 +00:00
return write_pair ( $ prof_data , $ depth , '' , 'rlimit' , "set rlimit " , " <= " , "," , \ & qin_trans ) ;
2008-04-18 20:58:07 +00:00
}
# 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 ;
}
2008-04-18 21:09:05 +00:00
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 } ) {
for my $ cap ( sort keys % { $ profile_data - > { $ allow } { capability } } ) {
my $ audit = ( $ profile_data - > { $ allow } { capability } { $ cap } { audit } ) ? 'audit ' : '' ;
2008-04-24 16:05:33 +00:00
if ( $ profile_data - > { $ allow } { capability } { $ cap } { set } ) {
push @ data , "${pre}${audit}${allowstr}capability ${cap}," ;
}
2008-04-18 21:09:05 +00:00
}
push @ data , "" ;
}
return @ data ;
}
2008-04-18 20:58:07 +00:00
2008-04-18 21:09:05 +00:00
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 ;
2008-04-18 20:56:41 +00:00
}
2008-04-18 21:09:05 +00:00
sub writenet_rules ($$$) {
my ( $ profile_data , $ depth , $ allow ) = @ _ ;
2008-04-18 21:08:05 +00:00
my $ allowstr = $ allow eq 'deny' ? 'deny ' : '' ;
2006-04-11 21:52:54 +00:00
2008-04-18 21:09:05 +00:00
my $ pre = " " x $ depth ;
my $ audit = "" ;
2007-04-25 21:04:28 +00:00
my @ data ;
2007-03-20 21:58:38 +00:00
# dump out the netdomain entries...
2008-04-18 21:08:05 +00:00
if ( exists $ profile_data - > { $ allow } { netdomain } ) {
2008-04-18 21:09:05 +00:00
if ( $ profile_data - > { $ allow } { netdomain } { rule } &&
$ profile_data - > { $ allow } { netdomain } { rule } eq 'all' ) {
$ audit = "audit " if $ profile_data - > { $ allow } { netdomain } { audit } { all } ;
push @ data , "${pre}${audit}network," ;
2007-07-29 02:06:00 +00:00
} else {
2008-04-18 21:09:05 +00:00
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," ;
2007-07-29 02:06:00 +00:00
} else {
2008-04-18 21:09:05 +00:00
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," ;
2007-07-29 02:06:00 +00:00
}
}
}
2007-03-20 21:58:38 +00:00
}
2008-04-18 21:08:05 +00:00
push @ data , "" if % { $ profile_data - > { $ allow } { netdomain } } ;
2006-04-11 21:52:54 +00:00
}
2007-04-25 21:04:28 +00:00
return @ data ;
2008-04-18 21:08:05 +00:00
2006-04-11 21:52:54 +00:00
}
2008-04-18 21:09:05 +00:00
sub writenetdomain ($$) {
my ( $ prof_data , $ depth ) = @ _ ;
2008-04-18 21:08:05 +00:00
my @ data ;
2008-04-18 21:09:05 +00:00
push @ data , writenet_rules ( $ prof_data , $ depth , 'deny' ) ;
push @ data , writenet_rules ( $ prof_data , $ depth , 'allow' ) ;
2008-04-18 21:08:05 +00:00
return @ data ;
}
2008-04-18 21:09:05 +00:00
sub writelink_rules ($$$) {
my ( $ profile_data , $ depth , $ allow ) = @ _ ;
2008-04-18 21:08:05 +00:00
my $ allowstr = $ allow eq 'deny' ? 'deny ' : '' ;
2008-04-18 21:09:05 +00:00
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 ;
2006-04-11 21:52:54 +00:00
2007-04-25 21:04:28 +00:00
my @ data ;
2008-04-18 21:08:05 +00:00
if ( exists $ profile_data - > { $ allow } { path } ) {
for my $ path ( sort keys % { $ profile_data - > { $ allow } { path } } ) {
2008-04-18 21:10:25 +00:00
my $ mode = $ profile_data - > { $ allow } { path } { $ path } { mode } ;
my $ audit = $ profile_data - > { $ allow } { path } { $ path } { audit } ;
2008-04-24 16:05:33 +00:00
my $ tail = "" ;
$ tail = " -> " . $ profile_data - > { $ allow } { path } { $ path } { to } if ( $ profile_data - > { $ allow } { path } { $ path } { to } ) ;
2008-04-18 21:16:15 +00:00
my ( $ user , $ other ) = split_mode ( $ mode ) ;
if ( $ user & ~ $ other ) {
$ user = $ user & ~ $ other ;
$ mode = $ other ;
if ( $ user & $ audit ) {
my $ amode = $ user & $ audit ;
my $ modestr = mode_to_str_user ( $ amode ) ;
my $ str = $ allowstr ;
$ str . = "owner " if $ modestr =~ s/owner // ;
if ( $ path =~ /\s/ ) {
2008-04-24 16:05:33 +00:00
push @ data , "${pre}audit ${str}\"$path\" ${modestr}${tail}," ;
2008-04-18 21:16:15 +00:00
} else {
2008-04-24 16:05:33 +00:00
push @ data , "${pre}audit ${str}$path ${modestr}${tail}," ;
2008-04-18 21:16:15 +00:00
}
# mask off the bits we have already written
$ user & = ~ $ audit ;
2008-04-18 21:10:25 +00:00
}
2008-04-18 21:16:15 +00:00
if ( $ user ) {
my $ modestr = mode_to_str_user ( $ user & ~ $ audit ) ;
my $ str = $ allowstr ;
$ str . = "owner " if $ modestr =~ s/owner // ;
# deal with whitespace in path names
if ( $ path =~ /\s/ ) {
2008-04-24 16:05:33 +00:00
push @ data , "${pre}${str}\"$path\" ${modestr}${tail}," ;
2008-04-18 21:16:15 +00:00
} else {
2008-04-24 16:05:33 +00:00
push @ data , "${pre}${str}$path ${modestr}${tail}," ;
2008-04-18 21:16:15 +00:00
}
}
if ( $ mode & $ audit ) {
my $ amode = $ mode & $ audit ;
my $ modestr = mode_to_str_user ( $ amode ) ;
my $ str = $ allowstr ;
$ str . = "owner " if $ modestr =~ s/owner // ;
if ( $ path =~ /\s/ ) {
2008-04-24 16:05:33 +00:00
push @ data , "${pre}audit ${str}\"$path\" ${modestr}${tail}," ;
2008-04-18 21:16:15 +00:00
} else {
2008-04-24 16:05:33 +00:00
push @ data , "${pre}audit ${str}$path ${modestr}${tail}," ;
2008-04-18 21:16:15 +00:00
}
# mask off the bits we have already written
$ mode & = ~ $ audit ;
}
if ( $ mode ) {
my $ modestr = mode_to_str_user ( $ mode & ~ $ audit ) ;
my $ str = $ allowstr ;
$ str . = "owner " if $ modestr =~ s/owner // ;
# deal with whitespace in path names
if ( $ path =~ /\s/ ) {
2008-04-24 16:05:33 +00:00
push @ data , "${pre}${str}\"$path\" ${modestr}${tail}," ;
2008-04-18 21:16:15 +00:00
} else {
2008-04-24 16:05:33 +00:00
push @ data , "${pre}${str}$path ${modestr}${tail}," ;
2008-04-18 21:16:15 +00:00
}
}
} else {
if ( $ mode & $ audit ) {
my $ amode = $ mode & $ audit ;
my $ modestr = mode_to_str_user ( $ amode ) ;
my $ str = $ allowstr ;
$ str . = "owner " if $ modestr =~ s/owner // ;
if ( $ path =~ /\s/ ) {
2008-04-24 16:05:33 +00:00
push @ data , "${pre}audit ${str}\"$path\" ${modestr}${tail}," ;
2008-04-18 21:16:15 +00:00
} else {
2008-04-24 16:05:33 +00:00
push @ data , "${pre}audit ${str}$path ${modestr}${tail}," ;
2008-04-18 21:16:15 +00:00
}
# mask off the bits we have already written
$ mode & = ~ $ audit ;
}
if ( $ mode ) {
my $ modestr = mode_to_str_user ( $ mode & ~ $ audit ) ;
my $ str = $ allowstr ;
$ str . = "owner " if $ modestr =~ s/owner // ;
# deal with whitespace in path names
if ( $ path =~ /\s/ ) {
2008-04-24 16:05:33 +00:00
push @ data , "${pre}${str}\"$path\" ${modestr}${tail}," ;
2008-04-18 21:16:15 +00:00
} else {
2008-04-24 16:05:33 +00:00
push @ data , "${pre}${str}$path ${modestr}${tail}," ;
2008-04-18 21:16:15 +00:00
}
2008-04-18 21:10:25 +00:00
}
}
2007-03-20 21:58:38 +00:00
}
2008-04-18 21:00:35 +00:00
push @ data , "" ;
2006-04-11 21:52:54 +00:00
}
2007-04-25 21:04:28 +00:00
return @ data ;
2006-04-11 21:52:54 +00:00
}
2008-04-18 21:09:05 +00:00
sub writepaths ($$) {
my ( $ prof_data , $ depth ) = @ _ ;
2008-04-18 21:08:05 +00:00
my @ data ;
2008-04-18 21:09:05 +00:00
push @ data , writepath_rules ( $ prof_data , $ depth , 'deny' ) ;
push @ data , writepath_rules ( $ prof_data , $ depth , 'allow' ) ;
2008-04-18 21:08:05 +00:00
return @ data ;
}
2008-04-18 21:09:05 +00:00
sub write_rules ($$) {
my ( $ prof_data , $ depth ) = @ _ ;
2006-04-11 21:52:54 +00:00
2007-04-25 21:04:28 +00:00
my @ data ;
2008-04-18 21:09:05 +00:00
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 , writelinks ( $ prof_data , $ depth ) ;
push @ data , writepaths ( $ prof_data , $ depth ) ;
push @ data , writechange_profile ( $ prof_data , $ depth ) ;
2006-04-11 21:52:54 +00:00
2008-04-18 21:09:05 +00:00
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 ) ;
2008-04-18 21:00:35 +00:00
# write external hat declarations
2007-04-25 21:04:28 +00:00
for my $ hat ( grep { $ _ ne $ name } sort keys % { $ profile_data } ) {
2008-04-18 21:00:35 +00:00
if ( $ profile_data - > { $ hat } { declared } ) {
2008-04-18 21:09:05 +00:00
push @ data , "${pre2}^$hat," ;
2008-04-18 21:00:35 +00:00
}
2007-04-25 21:04:28 +00:00
}
2006-04-11 21:52:54 +00:00
2008-04-18 21:09:05 +00:00
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 , "" ;
2008-04-24 16:05:33 +00:00
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 ) ;
}
2008-04-18 21:09:05 +00:00
push @ data , map { "$_" } write_rules ( $ profile_data - > { $ hat } ,
$ depth + 2 ) ;
push @ data , "${pre2}}" ;
}
2008-04-18 21:00:35 +00:00
}
2008-04-18 21:09:05 +00:00
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 , "" ;
2008-04-24 16:05:33 +00:00
push @ data , map { " $_" } writepiece ( $ profile_data , $ depth - 1 ,
2008-04-18 21:09:05 +00:00
$ name , $ hat , $ write_flags ) ;
push @ data , " }" ;
}
2008-04-18 21:00:35 +00:00
}
}
2007-04-25 21:04:28 +00:00
return @ data ;
}
sub serialize_profile {
2007-11-06 18:06:18 +00:00
my ( $ profile_data , $ name , $ options ) = @ _ ;
2006-04-11 21:52:54 +00:00
2007-04-25 21:04:28 +00:00
my $ string = "" ;
2007-11-06 18:06:18 +00:00
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 } ) ;
}
2007-03-20 21:58:38 +00:00
2007-04-25 21:04:28 +00:00
if ( $ include_metadata ) {
# keep track of when the file was last updated
$ string . = "# Last Modified: " . localtime ( time ) . "\n" ;
2007-04-26 02:48:24 +00:00
2007-04-26 02:58:10 +00:00
# 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" ;
2007-04-26 02:56:54 +00:00
} elsif ( $ profile_data - > { $ name } { repo } { neversubmit } ) {
$ string . = "# REPOSITORY: NEVERSUBMIT\n" ;
2007-04-26 02:58:10 +00:00
}
2007-03-20 21:58:38 +00:00
}
2006-04-11 21:52:54 +00:00
2007-04-25 21:04:28 +00:00
# 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" ;
}
2008-04-18 21:00:35 +00:00
#bleah this is stupid the data structure needs to be reworked
my $ filename = getprofilefilename ( $ name ) ;
my @ data ;
2008-04-18 21:06:24 +00:00
if ( $ filelist { $ filename } ) {
2008-04-18 21:09:05 +00:00
push @ data , writealiases ( $ filelist { $ filename } , 0 ) ;
push @ data , writelistvars ( $ filelist { $ filename } , 0 ) ;
push @ data , writeincludes ( $ filelist { $ filename } , 0 ) ;
2008-04-18 21:00:35 +00:00
}
2007-07-13 17:53:12 +00:00
# XXX - FIXME
2007-04-25 21:04:28 +00:00
#
# # 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";
# }
# }
2008-04-18 21:09:05 +00:00
push @ data , writepiece ( $ profile_data , 0 , $ name , $ name , $ include_flags ) ;
2008-04-18 21:00:35 +00:00
$ string . = join ( "\n" , @ data ) ;
2007-04-25 21:04:28 +00:00
return "$string\n" ;
2006-04-11 21:52:54 +00:00
}
2008-02-26 12:00:37 +00:00
sub writeprofile_ui_feedback ($) {
2007-03-20 21:58:38 +00:00
my $ profile = shift ;
UI_Info ( sprintf ( gettext ( 'Writing updated profile for %s.' ) , $ profile ) ) ;
2008-02-26 12:00:37 +00:00
writeprofile ( $ profile ) ;
}
sub writeprofile ($) {
my ( $ profile ) = shift ;
2006-04-11 21:52:54 +00:00
2008-04-18 21:06:24 +00:00
my $ filename = $ sd { $ profile } { $ profile } { filename } || getprofilefilename ( $ profile ) ;
2006-04-11 21:52:54 +00:00
2007-04-26 14:42:56 +00:00
open ( SDPROF , ">$filename" ) or
fatal_error "Can't write new AppArmor profile $filename: $!" ;
2007-11-06 18:06:18 +00:00
my $ serialize_opts = { } ;
$ serialize_opts - > { METADATA } = 1 ;
2008-04-18 21:06:24 +00:00
#make sure to write out all the profiles in the file
2007-11-06 18:06:18 +00:00
my $ profile_string = serialize_profile ( $ sd { $ profile } , $ profile , $ serialize_opts ) ;
2007-04-25 21:04:28 +00:00
print SDPROF $ profile_string ;
2007-03-20 21:58:38 +00:00
close ( SDPROF ) ;
2007-04-25 21:04:28 +00:00
# mark the profile as up-to-date
delete $ changed { $ profile } ;
2007-04-26 14:42:56 +00:00
$ original_sd { $ profile } = dclone ( $ sd { $ profile } ) ;
2006-04-11 21:52:54 +00:00
}
sub getprofileflags {
2007-03-20 21:58:38 +00:00
my $ filename = shift ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my $ flags = "enforce" ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
if ( open ( PROFILE , "$filename" ) ) {
while ( <PROFILE> ) {
2008-03-13 15:12:30 +00:00
if ( m/^\s*\/\S+\s+flags=\((.+)\)\s+{\s*$/ ) {
2007-03-20 21:58:38 +00:00
$ flags = $ 1 ;
close ( PROFILE ) ;
return $ flags ;
}
}
2006-04-11 21:52:54 +00:00
close ( PROFILE ) ;
}
2007-03-20 21:58:38 +00:00
return $ flags ;
2006-04-11 21:52:54 +00:00
}
2007-04-25 21:04:28 +00:00
2006-04-11 21:52:54 +00:00
sub matchliteral {
2007-03-20 21:58:38 +00:00
my ( $ sd_regexp , $ literal ) = @ _ ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my $ p_regexp = convert_regexp ( $ sd_regexp ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# check the log entry against our converted regexp...
my $ matches = eval { $ literal =~ /^$p_regexp$/ ; } ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# doesn't match if we've got a broken regexp
return undef if $@ ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
return $ matches ;
2006-04-11 21:52:54 +00:00
}
2008-04-18 21:08:05 +00:00
# test if profile has exec rule for $exec_target
sub profile_known_exec (\%$$) {
2008-04-18 21:03:28 +00:00
my ( $ profile , $ type , $ exec_target ) = @ _ ;
2007-11-06 16:46:57 +00:00
if ( $ type eq "exec" ) {
2008-04-18 21:10:25 +00:00
my ( $ cm , $ am , @ m ) ;
2008-04-18 21:08:05 +00:00
# test denies first
2008-04-18 21:10:25 +00:00
( $ cm , $ am , @ m ) = rematchfrag ( $ profile , 'deny' , $ exec_target ) ;
2008-04-18 21:08:05 +00:00
if ( $ cm & $ AA_MAY_EXEC ) {
return - 1 ;
}
2008-04-18 21:10:25 +00:00
( $ cm , $ am , @ m ) = match_prof_incs_to_path ( $ profile , 'deny' , $ exec_target ) ;
2008-04-18 21:08:05 +00:00
if ( $ cm & $ AA_MAY_EXEC ) {
return - 1 ;
}
# now test the generally longer allow lists
2008-04-18 21:10:25 +00:00
( $ cm , $ am , @ m ) = rematchfrag ( $ profile , 'allow' , $ exec_target ) ;
2008-04-18 21:08:05 +00:00
if ( $ cm & $ AA_MAY_EXEC ) {
return 1 ;
}
2008-04-18 21:10:25 +00:00
( $ cm , $ am , @ m ) = match_prof_incs_to_path ( $ profile , 'allow' , $ exec_target ) ;
2008-04-18 21:08:05 +00:00
if ( $ cm & $ AA_MAY_EXEC ) {
return 1 ;
}
2007-11-06 16:46:57 +00:00
}
return 0 ;
}
2008-04-18 21:08:05 +00:00
sub profile_known_capability (\%$) {
2008-04-18 21:03:28 +00:00
my ( $ profile , $ capname ) = @ _ ;
2008-04-18 21:08:05 +00:00
2008-04-18 21:08:34 +00:00
return - 1 if $ profile - > { deny } { capability } { $ capname } { set } ;
return 1 if $ profile - > { allow } { capability } { $ capname } { set } ;
2008-04-18 21:03:28 +00:00
for my $ incname ( keys % { $ profile - > { include } } ) {
2008-04-18 21:09:53 +00:00
return - 1 if $ include { $ incname } { $ incname } { deny } { capability } { $ capname } { set } ;
return 1 if $ include { $ incname } { $ incname } { allow } { capability } { $ capname } { set } ;
2007-08-15 16:17:50 +00:00
}
return 0 ;
}
2008-04-18 21:08:05 +00:00
sub profile_known_network (\%$$) {
2008-04-18 21:03:28 +00:00
my ( $ profile , $ family , $ sock_type ) = @ _ ;
2007-08-15 16:17:50 +00:00
2008-04-18 21:08:05 +00:00
return - 1 if netrules_access_check ( $ profile - > { deny } { netdomain } ,
$ family , $ sock_type ) ;
return 1 if netrules_access_check ( $ profile - > { allow } { netdomain } ,
$ family , $ sock_type ) ;
2008-04-18 21:03:28 +00:00
for my $ incname ( keys % { $ profile - > { include } } ) {
2008-04-18 21:09:53 +00:00
return - 1 if netrules_access_check ( $ include { $ incname } { $ incname } { deny } { netdomain } ,
2008-04-18 21:08:05 +00:00
$ family , $ sock_type ) ;
2008-04-24 16:05:33 +00:00
return 1 if netrules_access_check ( $ include { $ incname } { $ incname } { allow } { netdomain } ,
2008-04-18 21:08:05 +00:00
$ family , $ sock_type ) ;
2007-08-15 16:17:50 +00:00
}
2008-04-18 21:08:05 +00:00
2007-08-15 16:17:50 +00:00
return 0 ;
}
sub netrules_access_check ($$$) {
my ( $ netrules , $ family , $ sock_type ) = @ _ ;
return 0 if ( not defined $ netrules ) ;
my % netrules = %$ netrules ; ;
2008-04-18 21:09:05 +00:00
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 } ;
2007-07-29 02:06:00 +00:00
if ( $ all_net || $ all_net_family || $ net_family_sock ) {
return 1 ;
} else {
return 0 ;
}
}
2008-04-24 16:05:33 +00:00
sub reload_base ($) {
2007-03-20 21:58:38 +00:00
my $ bin = shift ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# don't try to reload profile if AppArmor is not running
return unless check_for_subdomain ( ) ;
2006-04-11 21:52:54 +00:00
2008-04-24 16:05:33 +00:00
my $ filename = getprofilefilename ( $ bin ) ;
system ( "/bin/cat '$filename' | $parser -I$profiledir -r >/dev/null 2>&1" ) ;
}
sub reload ($) {
my $ bin = shift ;
2007-03-20 21:58:38 +00:00
# don't reload the profile if the corresponding executable doesn't exist
my $ fqdbin = findexecutable ( $ bin ) or return ;
2006-04-11 21:52:54 +00:00
2008-04-24 16:05:33 +00:00
return reload_base ( $ fqdbin ) ;
2006-04-11 21:52:54 +00:00
}
2007-04-25 21:04:28 +00:00
sub read_include_from_file {
my $ which = shift ;
my $ data ;
if ( open ( INCLUDE , "$profiledir/$which" ) ) {
local $/ ;
$ data = <INCLUDE> ;
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 ;
}
2006-04-11 21:52:54 +00:00
sub loadinclude {
2007-04-25 21:04:28 +00:00
my $ which = shift ;
2007-03-20 21:58:38 +00:00
# don't bother loading it again if we already have
2008-04-18 21:09:53 +00:00
return 0 if $ include { $ which } { $ which } ;
2007-03-20 21:58:38 +00:00
my @ loadincludes = ( $ which ) ;
while ( my $ incfile = shift @ loadincludes ) {
2007-04-25 21:04:28 +00:00
my $ data = get_include_data ( $ incfile ) ;
2008-04-18 21:09:53 +00:00
my $ incdata = parse_profile_data ( $ data , $ incfile , 1 ) ;
if ( $ incdata ) {
attach_profile_data ( \ % include , $ incdata ) ;
}
2006-04-11 21:52:54 +00:00
}
2007-03-20 21:58:38 +00:00
return 0 ;
2006-04-11 21:52:54 +00:00
}
2008-04-18 21:08:05 +00:00
sub rematchfrag ($$$) {
my ( $ frag , $ allow , $ path ) = @ _ ;
2006-04-11 21:52:54 +00:00
2008-04-18 21:02:47 +00:00
my $ combinedmode = 0 ;
2008-04-18 21:10:25 +00:00
my $ combinedaudit = 0 ;
2007-03-20 21:58:38 +00:00
my @ matches ;
2006-04-11 21:52:54 +00:00
2008-04-18 21:08:05 +00:00
for my $ entry ( keys % { $ frag - > { $ allow } { path } } ) {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my $ regexp = convert_regexp ( $ entry ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# check the log entry against our converted regexp...
if ( $ path =~ /^$regexp$/ ) {
# regexp matches, add it's mode to the list to check against
2008-04-18 21:08:05 +00:00
$ combinedmode |= $ frag - > { $ allow } { path } { $ entry } { mode } ;
2008-04-18 21:10:25 +00:00
$ combinedaudit |= $ frag - > { $ allow } { path } { $ entry } { audit } ;
2007-03-20 21:58:38 +00:00
push @ matches , $ entry ;
}
2006-04-11 21:52:54 +00:00
}
2008-04-18 21:10:25 +00:00
return wantarray ? ( $ combinedmode , $ combinedaudit , @ matches ) : $ combinedmode ;
2006-04-11 21:52:54 +00:00
}
2008-04-18 21:08:05 +00:00
sub match_include_to_path ($$$) {
my ( $ incname , $ allow , $ path ) = @ _ ;
2006-04-11 21:52:54 +00:00
2008-04-18 21:02:47 +00:00
my $ combinedmode = 0 ;
2008-04-18 21:10:25 +00:00
my $ combinedaudit = 0 ;
2007-03-20 21:58:38 +00:00
my @ matches ;
2006-04-11 21:52:54 +00:00
2008-04-18 21:08:05 +00:00
my @ includelist = ( $ incname ) ;
while ( my $ incfile = shift @ includelist ) {
my $ ret = eval { loadinclude ( $ incfile ) ; } ;
2007-08-15 16:17:50 +00:00
if ( $@ ) { fatal_error $@ ; }
2008-04-18 21:10:25 +00:00
my ( $ cm , $ am , @ m ) = rematchfrag ( $ include { $ incfile } { $ incfile } , $ allow , $ path ) ;
2007-03-20 21:58:38 +00:00
if ( $ cm ) {
2008-04-18 21:02:47 +00:00
$ combinedmode |= $ cm ;
2008-04-18 21:10:25 +00:00
$ combinedaudit |= $ am ;
2007-03-20 21:58:38 +00:00
push @ matches , @ m ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# check if a literal version is in the current include fragment
2008-04-18 21:09:53 +00:00
if ( $ include { $ incfile } { $ incfile } { $ allow } { path } { $ path } ) {
$ combinedmode |= $ include { $ incfile } { $ incfile } { $ allow } { path } { $ path } { mode } ;
2008-04-18 21:10:25 +00:00
$ combinedaudit |= $ include { $ incfile } { $ incfile } { $ allow } { path } { $ path } { audit } ;
2007-03-20 21:58:38 +00:00
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# if this fragment includes others, check them too
2008-04-18 21:09:53 +00:00
if ( keys % { $ include { $ incfile } { $ incfile } { include } } ) {
push @ includelist , keys % { $ include { $ incfile } { $ incfile } { include } } ;
2007-03-20 21:58:38 +00:00
}
2006-04-11 21:52:54 +00:00
}
2008-04-18 21:10:25 +00:00
return wantarray ? ( $ combinedmode , $ combinedaudit , @ matches ) : $ combinedmode ;
2006-04-11 21:52:54 +00:00
}
2008-04-18 21:08:05 +00:00
sub match_prof_incs_to_path ($$$) {
my ( $ frag , $ allow , $ path ) = @ _ ;
2006-04-11 21:52:54 +00:00
2008-04-18 21:02:47 +00:00
my $ combinedmode = 0 ;
2008-04-18 21:10:25 +00:00
my $ combinedaudit = 0 ;
2007-03-20 21:58:38 +00:00
my @ matches ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# scan the include fragments for this profile looking for matches
2008-04-18 21:08:05 +00:00
my @ includelist = keys % { $ frag - > { include } } ;
while ( my $ include = shift @ includelist ) {
2008-04-18 21:10:25 +00:00
my ( $ cm , $ am , @ m ) = match_include_to_path ( $ include , $ allow , $ path ) ;
2008-04-18 21:08:05 +00:00
if ( $ cm ) {
$ combinedmode |= $ cm ;
2008-04-18 21:10:25 +00:00
$ combinedaudit |= $ am ;
2008-04-18 21:08:05 +00:00
push @ matches , @ m ;
}
}
2008-04-18 21:10:25 +00:00
return wantarray ? ( $ combinedmode , $ combinedaudit , @ matches ) : $ combinedmode ;
2008-04-18 21:08:05 +00:00
}
#find includes that match the path to suggest
sub suggest_incs_for_path {
my ( $ incname , $ path , $ allow ) = @ _ ;
my $ combinedmode = 0 ;
2008-04-18 21:10:25 +00:00
my $ combinedaudit = 0 ;
2008-04-18 21:08:05 +00:00
my @ matches ;
# scan the include fragments looking for matches
2007-03-20 21:58:38 +00:00
my @ includelist = ( $ incname ) ;
while ( my $ include = shift @ includelist ) {
2008-04-18 21:10:25 +00:00
my ( $ cm , $ am , @ m ) = rematchfrag ( $ include { $ include } { $ include } , 'allow' , $ path ) ;
2007-03-20 21:58:38 +00:00
if ( $ cm ) {
2008-04-18 21:02:47 +00:00
$ combinedmode |= $ cm ;
2008-04-18 21:10:25 +00:00
$ combinedaudit |= $ am ;
2007-03-20 21:58:38 +00:00
push @ matches , @ m ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# check if a literal version is in the current include fragment
2008-04-18 21:09:53 +00:00
if ( $ include { $ include } { $ include } { allow } { path } { $ path } ) {
$ combinedmode |= $ include { $ include } { $ include } { allow } { path } { $ path } { mode } ;
2008-04-18 21:10:25 +00:00
$ combinedaudit |= $ include { $ include } { $ include } { allow } { path } { $ path } { audit } ;
2007-03-20 21:58:38 +00:00
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# if this fragment includes others, check them too
2008-04-18 21:09:53 +00:00
if ( keys % { $ include { $ include } { $ include } { include } } ) {
push @ includelist , keys % { $ include { $ include } { $ include } { include } } ;
2007-03-20 21:58:38 +00:00
}
2006-04-11 21:52:54 +00:00
}
2007-03-20 21:58:38 +00:00
if ( $ combinedmode ) {
2008-04-18 21:10:25 +00:00
return wantarray ? ( $ combinedmode , $ combinedaudit , @ matches ) : $ combinedmode ;
2007-03-20 21:58:38 +00:00
} else {
return ;
}
2006-04-11 21:52:54 +00:00
}
2007-04-25 21:06:52 +00:00
sub check_qualifiers {
2007-04-26 14:42:56 +00:00
my $ program = shift ;
2006-04-11 21:52:54 +00:00
2007-04-26 14:42:56 +00:00
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 ) ) ;
}
2007-04-25 21:06:52 +00:00
}
}
2006-04-11 21:52:54 +00:00
sub loadincludes {
2007-03-20 21:58:38 +00:00
if ( opendir ( SDDIR , $ profiledir ) ) {
my @ incdirs = grep { ( ! /^\./ ) && ( - d "$profiledir/$_" ) } readdir ( SDDIR ) ;
close ( SDDIR ) ;
while ( my $ id = shift @ incdirs ) {
if ( opendir ( SDDIR , "$profiledir/$id" ) ) {
2007-03-23 18:52:22 +00:00
for my $ path ( readdir ( SDDIR ) ) {
2007-03-20 21:58:38 +00:00
chomp ( $ path ) ;
2007-03-23 18:52:22 +00:00
next if isSkippableFile ( $ path ) ;
2007-03-20 21:58:38 +00:00
if ( - f "$profiledir/$id/$path" ) {
my $ file = "$id/$path" ;
$ file =~ s/$profiledir\/// ;
2007-04-26 14:42:56 +00:00
my $ ret = eval { loadinclude ( $ file ) ; } ;
if ( $@ ) { fatal_error $@ ; }
2007-03-20 21:58:38 +00:00
} elsif ( - d "$id/$path" ) {
push @ incdirs , "$id/$path" ;
}
}
closedir ( SDDIR ) ;
}
2006-04-11 21:52:54 +00:00
}
}
}
sub globcommon ($) {
2007-03-20 21:58:38 +00:00
my $ path = shift ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my @ globs ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# 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 ;
}
2006-04-11 21:52:54 +00:00
2007-04-26 14:42:56 +00:00
for my $ glob ( keys % { $ cfg - > { globs } } ) {
2007-03-20 21:58:38 +00:00
if ( $ path =~ /$glob/ ) {
my $ globbedpath = $ path ;
2007-04-26 14:42:56 +00:00
$ globbedpath =~ s/$glob/$cfg->{globs}{$glob}/g ;
2007-03-20 21:58:38 +00:00
push @ globs , $ globbedpath if $ globbedpath ne $ path ;
}
2006-04-11 21:52:54 +00:00
}
2007-03-20 21:58:38 +00:00
if ( wantarray ) {
return sort { length ( $ b ) <=> length ( $ a ) } uniq ( @ globs ) ;
} else {
my @ list = sort { length ( $ b ) <=> length ( $ a ) } uniq ( @ globs ) ;
return $ list [ $# list ] ;
}
2006-04-11 21:52:54 +00:00
}
# this is an ugly, nasty function that attempts to see if one regexp
# is a subset of another regexp
sub matchregexp ($$) {
2007-03-20 21:58:38 +00:00
my ( $ new , $ old ) = @ _ ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# bail out if old pattern has {foo,bar,baz} stuff in it
return undef if $ old =~ /\{.*(\,.*)*\}/ ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# are there any regexps at all in the old pattern?
if ( $ old =~ /\[.+\]/ or $ old =~ /\*/ or $ old =~ /\?/ ) {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# convert {foo,baz} to (foo|baz)
$ new =~ y /\{\}\,/ \ ( \ ) \ | / if $new =~ / \ { . * \ , . * \ } / ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# \001 == SD_GLOB_RECURSIVE
# \002 == SD_GLOB_SIBLING
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
$ new =~ s/\*\*/\001/g ;
$ new =~ s/\*/\002/g ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
$ old =~ s/\*\*/\001/g ;
$ old =~ s/\*/\002/g ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# strip common prefix
my $ prefix = commonprefix ( $ new , $ old ) ;
if ( $ prefix ) {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# make sure we don't accidentally gobble up a trailing * or **
$ prefix =~ s/(\001|\002)$// ;
$ new =~ s/^$prefix// ;
$ old =~ s/^$prefix// ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# strip common suffix
my $ suffix = commonsuffix ( $ new , $ old ) ;
if ( $ suffix ) {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# make sure we don't accidentally gobble up a leading * or **
$ suffix =~ s/^(\001|\002)// ;
$ new =~ s/$suffix$// ;
$ old =~ s/$suffix$// ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# 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" ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# 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 =~ /^[^\/]+$/ ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# we'll bail out if we have more globs in the old version
return undef if $ old =~ /\001|\002/ ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# see if we can match * globs in new against literal elements in old
$ new =~ s/\002/[^\/]*/g ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
return 1 if $ old =~ /^$new$/ ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
} else {
my $ new_regexp = convert_regexp ( $ new ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# check the log entry against our converted regexp...
return 1 if $ old =~ /^$new_regexp$/ ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
}
return undef ;
2006-04-11 21:52:54 +00:00
}
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 ($) {
2007-03-20 21:58:38 +00:00
my $ question = shift ;
2006-04-11 21:52:54 +00:00
2007-04-26 02:46:23 +00:00
my $ title = $ question - > { title } ;
my $ explanation = $ question - > { explanation } ;
2007-03-20 21:58:38 +00:00
my @ headers = ( @ { $ question - > { headers } } ) ;
my @ functions = ( @ { $ question - > { functions } } ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my $ default = $ question - > { default } ;
my $ options = $ question - > { options } ;
2007-04-26 02:46:23 +00:00
my $ selected = $ question - > { selected } || 0 ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my $ helptext = $ question - > { helptext } ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
push @ functions , "CMD_HELP" if $ helptext ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my % keys ;
my @ menu_items ;
for my $ cmd ( @ functions ) {
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# make sure we know about this particular command
my $ cmdmsg = "PromptUser: " . gettext ( "Unknown command" ) . " $cmd" ;
fatal_error $ cmdmsg unless $ CMDS { $ cmd } ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# grab the localized text to use for the menu for this command
my $ menutext = gettext ( $ CMDS { $ cmd } ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# figure out what the hotkey for this menu item is
2007-04-26 14:42:56 +00:00
my $ menumsg = "PromptUser: " .
gettext ( "Invalid hotkey in" ) .
" '$menutext'" ;
2007-03-20 21:58:38 +00:00
$ menutext =~ /\((\S)\)/ or fatal_error $ menumsg ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# we want case insensitive comparisons so we'll force things to
# lowercase
my $ key = lc ( $ 1 ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# check if we're already using this hotkey for this prompt
2007-04-26 14:42:56 +00:00
my $ hotkeymsg = "PromptUser: " .
gettext ( "Duplicate hotkey for" ) .
" $cmd: $menutext" ;
2007-03-20 21:58:38 +00:00
fatal_error $ hotkeymsg if $ keys { $ key } ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# keep track of which command they're picking if they hit this hotkey
$ keys { $ key } = $ cmd ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
if ( $ default && $ default eq $ cmd ) {
$ menutext = "[$menutext]" ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
push @ menu_items , $ menutext ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# figure out the key for the default option
my $ default_key ;
if ( $ default && $ CMDS { $ default } ) {
my $ defaulttext = gettext ( $ CMDS { $ default } ) ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# figure out what the hotkey for this menu item is
2007-04-26 14:42:56 +00:00
my $ defmsg = "PromptUser: " .
gettext ( "Invalid hotkey in default item" ) .
" '$defaulttext'" ;
2007-03-20 21:58:38 +00:00
$ defaulttext =~ /\((\S)\)/ or fatal_error $ defmsg ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
# we want case insensitive comparisons so we'll force things to
# lowercase
$ default_key = lc ( $ 1 ) ;
2006-04-11 21:52:54 +00:00
2007-04-26 14:42:56 +00:00
my $ defkeymsg = "PromptUser: " .
gettext ( "Invalid default" ) .
" $default" ;
2007-03-20 21:58:38 +00:00
fatal_error $ defkeymsg unless $ keys { $ default_key } ;
}
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my $ widest = 0 ;
my @ poo = @ headers ;
while ( my $ header = shift @ poo ) {
my $ value = shift @ poo ;
$ widest = length ( $ header ) if length ( $ header ) > $ widest ;
}
$ widest + + ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my $ format = '%-' . $ widest . "s \%s\n" ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my $ function_regexp = '^(' ;
$ function_regexp . = join ( "|" , keys % keys ) ;
$ function_regexp . = '|\d' if $ options ;
$ function_regexp . = ')$' ;
2006-04-11 21:52:54 +00:00
2007-03-20 21:58:38 +00:00
my $ ans = "XXXINVALIDXXX" ;
while ( $ ans !~ /$function_regexp/i ) {
# build up the prompt...
my $ prompt = "\n" ;
2007-04-26 02:46:23 +00:00
$ prompt . = "= $title =\n\n" if $ title ;
if ( @ headers ) {
2007-04-26 14:42:56 +00:00
my @ poo = @ headers ;
while ( my $ header = shift @ poo ) {
my $ value = shift @ poo ;
$ prompt . = sprintf ( $ format , "$header:" , $ value ) ;
}
$ prompt . = "\n" ;
2007-04-26 02:46:23 +00:00
}
if ( $ explanation ) {
$ prompt . = "$explanation\n\n" ;
2007-03-20 21:58:38 +00:00
}
2007-04-26 02:46:23 +00:00
2007-03-20 21:58:38 +00:00
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 ] ) ;
2006-04-11 21:52:54 +00:00
}
2007-03-20 21:58:38 +00:00
$ prompt . = "\n" ;
}
$ prompt . = join ( " / " , @ menu_items ) ;
print "$prompt\n" ;
# get their input...
2007-04-26 02:46:23 +00:00
$ 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 - - ;
2007-03-20 21:58:38 +00:00
}
2007-04-26 02:46:23 +00:00
$ ans = "XXXINVALIDXXX" ;
2006-04-11 21:52:54 +00:00
2007-04-26 02:46:23 +00:00
} 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" ;
2007-03-20 21:58:38 +00:00
}
2007-03-30 16:04:04 +00:00
}
if ( $ keys { $ ans } && $ keys { $ ans } eq "CMD_HELP" ) {
print "\n$helptext\n" ;
$ ans = "again" ;
2007-03-20 21:58:38 +00:00
}
2006-04-11 21:52:54 +00:00
}
2007-03-20 21:58:38 +00:00
# pull our command back from our hotkey map
$ ans = $ keys { $ ans } if $ keys { $ ans } ;
2007-04-26 02:46:23 +00:00
return ( $ ans , $ selected ) ;
2006-04-11 21:52:54 +00:00
}
2008-02-26 12:01:10 +00:00
# Parse event record into key-value pairs
sub parse_event ($) {
my % ev = ( ) ;
my $ msg = shift ;
chomp ( $ msg ) ;
my $ event = LibAppArmor:: parse_record ( $ msg ) ;
2008-04-18 21:02:47 +00:00
my ( $ rmask , $ dmask ) ;
2008-02-26 12:01:10 +00:00
$ 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 ) ;
2008-04-18 20:49:48 +00:00
$ ev { 'task' } = LibAppArmor::aa_log_record:: swig_task_get ( $ event ) ;
2008-04-24 16:05:33 +00:00
$ ev { 'info' } = LibAppArmor::aa_log_record:: swig_info_get ( $ event ) ;
2008-04-18 21:02:47 +00:00
$ dmask = LibAppArmor::aa_log_record:: swig_denied_mask_get ( $ event ) ;
$ rmask = LibAppArmor::aa_log_record:: swig_requested_mask_get ( $ event ) ;
2008-02-26 12:01:10 +00:00
$ ev { 'magic_token' } =
LibAppArmor::aa_log_record:: swig_magic_token_get ( $ event ) ;
# NetDomain
if ( $ ev { 'operation' } && $ ev { 'operation' } =~ /socket/ ) {
$ 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 ) ;
2008-04-18 21:16:15 +00:00
if ( $ rmask && ! validate_log_mode ( hide_log_mode ( $ rmask ) ) ) {
2008-04-18 21:02:47 +00:00
fatal_error ( sprintf ( gettext ( 'Log contains unknown mode %s.' ) ,
$ rmask ) ) ;
}
2008-04-18 21:16:15 +00:00
if ( $ dmask && ! validate_log_mode ( hide_log_mode ( $ dmask ) ) ) {
2008-04-18 21:02:47 +00:00
fatal_error ( sprintf ( gettext ( 'Log contains unknown mode %s.' ) ,
$ dmask ) ) ;
}
2008-04-18 21:16:15 +00:00
#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);
2008-04-24 16:05:33 +00:00
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 ;
2008-04-18 21:02:47 +00:00
2008-02-26 12:01:10 +00:00
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 ) ;
}
}
2007-04-25 21:06:52 +00:00
###############################################################################
# required initialization
2006-04-11 21:52:54 +00:00
2007-04-25 21:06:52 +00:00
$ cfg = read_config ( "logprof.conf" ) ;
$ profiledir = find_first_dir ( $ cfg - > { settings } { profiledir } ) || "/etc/apparmor.d" ;
unless ( - d $ profiledir ) { fatal_error "Can't find AppArmor profiles." ; }
2007-04-26 02:59:17 +00:00
$ extraprofiledir = find_first_dir ( $ cfg - > { settings } { inactive_profiledir } ) ||
"/etc/apparmor/profiles/extras/" ;
2007-04-25 21:06:52 +00:00
$ 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/messages" ;
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." ; }
2006-04-11 21:52:54 +00:00
2007-08-14 22:07:40 +00:00
$ logger = find_first_file ( $ cfg - > { settings } { logger } ) || "/bin/logger" ;
unless ( - x $ logger ) { fatal_error "Can't find logger." ; }
2006-04-11 21:52:54 +00:00
1 ;