### general note: ### "sorted()" marks places that need changes to keep the profile order when writing === modified file 'apparmor/aa.py' --- apparmor/aa.py 2013-07-30 14:43:08 +0000 +++ apparmor/aa.py 2013-07-31 14:26:33 +0000 @ -3495,7 +3496,7 @@ if prof_data[allow]['capability'][cap].get('audit', False): audit = 'audit' if prof_data[allow]['capability'][cap].get('set', False): - data.append(pre + audit + allowstr + 'capability,') + data.append('%s%s%scapability %s,' %(pre, audit, allowstr)) ### looks more correct than the previous version in r30 ;-) ### but it doesn't handle the general "capability," rule @@ -3506,3 +3507,546 @@ +def write_net_rules(prof_data, depth, allow): + pre = ' ' * depth + data = [] + allowstr = set_allow_str(allow) + + if prof_data[allow].get('netdomain', False): + if prof_data[allow]['netdomain'].get('rule', False) == 'all': + if prof_data[allow]['netdomain']['audit'].get('all', False): + audit = 'audit ' + data.append('%s%snetwork,' %(pre, audit)) + else: + for fam in sorted(prof_data[allow]['netdomain']['rule'].keys()): ### sorted() + if prof_data[allow]['netdomain']['rule'][fam] == True: + if prof_data[allow]['netdomain']['audit'][fam]: + audit = 'audit' + data.append('%s%s%snetwork %s' % (pre, audit, allowstr, fam)) + else: + for typ in sorted(prof_data[allow]['netdomain']['rule'][fam].keys()): ### sorted() +def write_path_rules(prof_data, depth, allow): + pre = ' ' * depth + data = [] + allowstr = set_allow_str(allow) + + if prof_data[allow].get('path', False): + for path in sorted(prof_data[allow]['path'].keys()): ### sorted() ... + user, other = split_mode(mode) + user_audit, other_audit = split_mode(audit) ### would it make sense to split 'mode' into 'ownermode' and 'othermode'? ### that would avoid the need for split_mode later ### (this also implies large code changes, so think about it for some days before deciding if you want to do it ;-) +def write_piece(profile_data, depth, name, nhat, write_flags): + pre = ' ' * depth + data = [] + wname = None + inhat = False + if name == nhat: + wname = name + else: + wname = name + '//' + nhat + name = nhat + inhat = True + + data += write_header(profile_data[name], depth, wname, False, write_flags) + data += write_rules(profile_data[name], depth+1) + + pre2 = ' ' * (depth+1) + # External hat declarations + for hat in filter(lambda x: x != name, sorted(profile_data.keys())): ### sorted() + if profile_data[hat].get('declared', False): + data.append('%s^%s,' %(pre2, hat)) + + if not inhat: + # Embedded hats + for hat in filter(lambda x: x != name, sorted(profile_data.keys())): ### sorted() + if not profile_data[hat]['external'] and not profile_data[hat]['declared']: + data.append('') + if profile_data[hat]['profile']: + data += map(str, write_header(profile_data[hat], depth+1, hat, True, write_flags)) + else: + data += map(str, write_header(profile_data[hat], depth+1, '^'+hat, True, write_flags)) + + data += map(str, write_rules(profile_data[hat], depth+2)) + + data.append('%s}' %pre2) + + data.append('%s}' %pre) + + # External hats + for hat in filter(lambda x: x != name, sorted(profile_data.keys())): ### sorted() + if name == nhat and profile_data[hat].get('external', False): + data.append('') + data += map(lambda x: ' %s' %x, write_piece(profile_data, depth-1, name, nhat, write_flags)) + data.append(' }') + + return data + +def write_profile(profile): + filename = None + if aa[profile][profile].get('filename', False): + filename = aa[profile][profile]['filename'] + else: + filename = get_profile_filename(profile) + + newprof = tempfile.NamedTemporaryFile('rw', suffix='~' ,delete=False) + if os.path.exists(filename): + shutil.copymode(filename, newprof.name) + else: + #permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write + #os.chmod(newprof.name, permission_600) + pass + + serialize_options = {} + serialize_options['METADATA'] = True + + profile_string = serialize_profile(aa[profile], profile, serialize_options) + newprof.write(profile_string) ### check for errors? + newprof.close() + os.rename(newprof.name, filename) ### check for errors? +def netrules_access_check(netrules, family, sock_type): + if not netrules: + return 0 + all_net = False + all_net_family = False + net_family_sock = False + if netrules['rule'].get('all', False): + all_net = True + if netrules['rule'].get(family, False) == True: + all_net_family = True + if (netrules['rule'].get(family, False) and + type(netrules['rule'][family]) == dict and + netrules['rule'][family][sock_type]): + net_family_sock = True + + if all_net or all_net_family or net_family_sock: + return True + else: + return False ### instead of using some variables, you could just "return True" on first match ### and "return False" at the end of the function ### similar to profile_known_network() does it +def reload_base(bin_path): + if not check_for_apparmor(): + return None + + filename = get_profile_filename(bin_path) ### ------ additional note for get_profile_filename() ------ ### get_profile_filename() basically converts /bin/ping to bin.ping ### but the profile for /bin/ping could also be in /etc/apparmor.d/i.hate.default.filenames ;-) ### which means the profile will not be reloaded in those cases. ### ### get_profile_filename should check _all_ files in /etc/apparmor.d/ for the /bin/ping profile and ### - if it finds it somewhere, return that filename ("i.hate.default.filenames") ### - if it doesn't find it, return the default filename ("bin.ping") ### maybe easier solution: ### profile_data[profile][hat]['filename'] should already have the current filename, so check if you can take it from there ### ------ END additional note for get_profile_filename() ------ + subprocess.call("cat '%s' | %s -I%s -r >/dev/null 2>&1" %(filename, parser ,profile_dir), shell=True) ### useless use of cat - apparmor_parser can read the file itsself ;-) ### besides that - what happens with crazy filenames like /etc/apparmor.d/bin.pi'ng ? ;-) ### hint: create-apparmor.vim.py uses ### (rc, output) = cmd(['make', '-s', '--no-print-directory', 'list_capabilities']) ### if rc != 0: ### sys.stderr.write("make list_capabilities failed: " + output) ### exit(rc) ### which should solve all problems with funny filenames ;-) +def reload(bin_path): + bin_path = find_executable(bin_path) + if not bin: + return None + + return reload_base(bin_path) ### does this mean the profile only gets reloaded if the binary exists? ### doesn't sound like the best idea... +def loadincludes(): + incdirs = get_subdirectories(profile_dir) + + for idir in incdirs: + if is_skippable_dir(idir): + continue + for dirpath, dirname, files in os.walk(profile_dir + '/' + idir): + if is_skippable_dir(dirpath): + continue + for fi in files: + if is_skippable_file(fi): + continue + else: + load_include(dirpath + '/' + fi) ### I remember some checks that only allow abstractions/ - and now we check nearly every dir for possible includes? ### strange[tm] +def glob_common(path): + globs = [] + + if re.search('[\d\.]+\.so$', path) or re.search('\.so\.[\d\.]+$', path): ### [\d\.] shoud probably be [\d.] + libpath = path + libpath = re.sub('[\d\.]+\.so$', '*.so', libpath) + libpath = re.sub('\.so\.[\d\.]+$', '.so.*', libpath) + if libpath != path: + globs.append(libpath) + + for glob in cfg['globs']: + if re.search(glob, path): + globbedpath = path + globbedpath = re.sub(glob, cfg['globs'][glob]) + if globbedpath != path: + globs.append(globbedpath) + + return sorted(set(globs)) ### does sorted() make sense? +def matchregexp(new, old): + if re.search('\{.*(\,.*)*\}', old): ### this regex can be shortened to '\{.*\}' without changing the meaning (the .* matches the removed part) ### (but I'd guess there must be a reason why (\,.*)* was there, so better check if it's buggy - maybe )* should be )+ or something like that?) + return None + + if re.search('\[.+\]', old) or re.search('\*', old) or re.search('\?', old): + + if re.search('\{.*\,.*\}', new): + pass + +######Initialisations###### + +conf = apparmor.config.Config('ini') +cfg = conf.read_config('logprof.conf') + +if cfg['settings'].get('default_owner_prompt', False): + cfg['settings']['default_owner_prompt'] = False + +profile_dir = conf.find_first_dir(cfg['settings']['profiledir']) or '/etc/apparmor.d' +if not os.path.isdir(profile_dir): + raise AppArmorException('Can\'t find AppArmor profiles' ) ### I'd make the error message more verbose and add profile_dir (and maybe also a hint to the profiledir config option in logprof.conf) ### besides that, changing the profile_dir with a commandline argument should be possible +parser = conf.find_first_file(cfg['settings']['parser']) or '/sbin/apparmor_parser' +if not os.path.isfile(parser) or not os.access(parser, os.EX_OK): + raise AppArmorException('Can\'t find apparmor_parser') ### I'd also check for /usr/sbin/apparmor_parser (not sure if we should do it here or in the default logprof.conf) ### The error message should mention logprof.conf / option parser +filename = conf.find_first_file(cfg['settings']['logfiles']) or '/var/log/syslog' +if not os.path.isfile(filename): + raise AppArmorException('Can\'t find system log.') + +ldd = conf.find_first_file(cfg['settings']['ldd']) or '/usr/bin/ldd' +if not os.path.isfile(ldd) or not os.access(ldd, os.EX_OK): + raise AppArmorException('Can\'t find ldd') + +logger = conf.find_first_file(cfg['settings']['logger']) or '/bin/logger' +if not os.path.isfile(logger) or not os.access(logger, os.EX_OK): + raise AppArmorException('Can\'t find logger') ### same for logfiles, ldd and logger - the error message should mention logprof.conf and the config option === modified file 'apparmor/ui.py' --- apparmor/ui.py 2013-07-30 14:43:08 +0000 +++ apparmor/ui.py 2013-07-31 14:26:33 +0000 @@ -257,10 +270,149 @@ +def Text_PromptUser(question): ... + ans = readkey().lower() + + if ans: ... + elif keys.get(ans, False) == 'CMD_HELP': + sys.stdout.write('\n%s\n' %helptext) + ans = 'XXXINVALIDXXX' ... + if keys.get(ans, False) == 'CMD_HELP': + sys.stdout.write('\n%s\n' %helptext) + ans = 'again' ### one of those blocks looks superfluous ;-) vim:ft=diff