From a33c95f8b17c7b1d392adb59812eb620b070a310 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Wed, 17 Jul 2013 20:38:13 +0530 Subject: [PATCH] Some fixes from review16 and updated codebase --- Testing/severity_test.py | 2 +- apparmor/aa.py | 266 +++++++++++++++++++++++++++++++++++++-- apparmor/common.py | 1 - apparmor/ui.py | 255 +++++++++++++++++++++++++++++++++++++ apparmor/yasti.py | 82 ++++++++++++ 5 files changed, 596 insertions(+), 10 deletions(-) create mode 100644 apparmor/ui.py create mode 100644 apparmor/yasti.py diff --git a/Testing/severity_test.py b/Testing/severity_test.py index 081f0f72c..aa923f1de 100644 --- a/Testing/severity_test.py +++ b/Testing/severity_test.py @@ -6,7 +6,7 @@ Created on Jun 21, 2013 import sys import unittest -sys.path.append('../apparmor') +sys.path.append('../') import apparmor.severity as severity from apparmor.common import AppArmorException diff --git a/apparmor/aa.py b/apparmor/aa.py index 43985fc80..4055ac1ba 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -1,4 +1,4 @@ -#1082 +#1321 #382-430 #480-525 #global variable names corruption @@ -7,10 +7,12 @@ import inspect import logging import os import re +import shutil import subprocess import sys import traceback import atexit +import tempfile import apparmor.config import apparmor.severity @@ -20,6 +22,8 @@ from apparmor.common import (AppArmorException, error, debug, msg, open_file_read, readkey, valid_path, hasher, open_file_write) +from apparmor.ui import * + DEBUGGING = False debug_logger = None @@ -491,6 +495,7 @@ def delete_profile(local_prof): os.remove(profile_file) if aa.get(local_prof, False): aa.pop(local_prof) + prof_unload(local_prof) def get_profile(prof_name): profile_data = None @@ -619,28 +624,36 @@ def autodep(bin_name, pname=''): def set_profile_flags(prof_filename, newflags): """Reads the old profile file and updates the flags accordingly""" regex_bin_flag = re.compile('^(\s*)(("??\/.+?"??)|(profile\s+("??.+?"??)))\s+(flags=\(.+\)\s+)*\{\s*$/') - regex_hat_flag = re.compile('^(\s*\^\S+)\s+(flags=\(.+\)\s+)*\{\s*$') + regex_hat_flag = re.compile('^([a-z]*)\s+([A-Z]*)((\s+#\S*)*)\s*$') + a=re.compile('^([a-z]*)\s+([A-Z]*)((\s+#\S*)*)\s*$') + regex_hat_flag = re.compile('^(\s*\^\S+)\s+(flags=\(.+\)\s+)*\{\s*(#*\S*)$') if os.path.isfile(prof_filename): with open_file_read(prof_filename) as f_in: - with open_file_write(prof_filename + '.new') as f_out: + tempfile = tempfile.NamedTemporaryFile('w', prefix=prof_filename , delete=False, dir='/etc/apparmor.d/') + shutil.copymode('/etc/apparmor.d/' + prof_filename, tempfile.name) + with open_file_write(tempfile.name) as f_out: for line in f_in: + if '#' in line: + comment = '#' + line.split('#', 1)[1].rstrip() + else: + comment = '' match = regex_bin_flag.search(line) if match: space, binary, flags = match.groups() if newflags: - line = '%s%s flags=(%s) {\n' % (space, binary, newflags) + line = '%s%s flags=(%s) {%s\n' % (space, binary, newflags, comment) else: - line = '%s%s {\n' % (space, binary) + line = '%s%s {%s\n' % (space, binary, comment) else: match = regex_hat_flag.search(line) if match: hat, flags = match.groups() if newflags: - line = '%s flags=(%s) {\n' % (hat, newflags) + line = '%s flags=(%s) {%s\n' % (hat, newflags, comment) else: - line = '%s {\n' % hat + line = '%s {%s\n' % (hat, comment) f_out.write(line) - os.rename(prof_filename+'.new', prof_filename) + os.rename(tempfile.name, prof_filename) def profile_exists(program): """Returns True if profile exists, False otherwise""" @@ -654,3 +667,240 @@ def profile_exists(program): existing_profiles[program] = True return True return False + +def sync_profile(): + user, passw = get_repo_user_pass() + if not user or not passw: + return None + repo_profiles = [] + changed_profiles = [] + new_profiles = [] + serialize_opts = hasher() + status_ok, ret = fetch_profiles_by_user(cfg['repository']['url'], + cfg['repository']['distro'], user) + if not status_ok: + if not ret: + ret = 'UNKNOWN ERROR' + UI_Important('WARNING: Error synchronizing profiles with the repository:\n%s\n' % ret) + else: + users_repo_profiles = ret + serialuze_opts['NO_FLAGS'] = True + for prof in sorted(aa.keys()): + if is_repo_profile([aa[prof][prof]]): + repo_profiles.append(prof) + if prof in created: + p_local = seralize_profile(aa[prof], prof, serialize_opts) + if not users_repo_profiles.get(prof, False): + new_profiles.append(prof) + new_profiles.append(p_local) + new_profiles.append('') + else: + p_repo = users_repo_profiles[prof]['profile'] + if p_local != p_repo: + changed_profiles.append(prof) + changed_profiles.append(p_local) + changed_profiles.append(p_repo) + if repo_profiles: + for prof in repo_profiles: + p_local = serialize_profile(aa[prof], prof, serialize_opts) + if not users_repo_profiles.get(prof, False): + new_profiles.append(prof) + new_profiles.append(p_local) + new_profiles.append('') + else: + p_repo = '' + if aa[prof][prof]['repo']['user'] == user: + p_repo = users_repo_profiles[prof]['profile'] + else: + status_ok, ret = fetch_profile_by_id(cfg['repository']['url'], + aa[prof][prof]['repo']['id']) + if status_ok: + p_repo = ret['profile'] + else: + if not ret: + ret = 'UNKNOWN ERROR' + UI_Important('WARNING: Error synchronizing profiles witht he repository\n%s\n' % ret) + continue + if p_repo != p_local: + changed_profiles.append(prof) + changed_profiles.append(p_local) + changed_profiles.append(p_repo) + if changed_profiles: + submit_changed_profiles(changed_profiles) + if new_profiles: + submit_created_profiles(new_profiles) + +def submit_created_profiles(new_profiles): + #url = cfg['repository']['url'] + if new_profiles: + if UI_mode == 'yast': + title = 'New Profiles' + message = 'Please select the newly created profiles that you would like to store in the repository' + yast_select_and_upload_profiles(title, message, new_profiles) + else: + title = 'Submit newly created profiles to the repository' + message = 'Would you like to upload newly created profiles?' + console_select_and_upload_profiles(title, message, new_profiles) + +def submit_changed_profiles(changed_profiles): + #url = cfg['repository']['url'] + if changed_profiles: + if UI_mode == 'yast': + title = 'Changed Profiles' + message = 'Please select which of the changed profiles would you like to upload to the repository' + yast_select_and_upload_profiles(title, message, changed_profiles) + else: + title = 'Submit changed profiles to the repository' + message = 'The following profiles from the repository were changed.\nWould you like to upload your changes?' + console_select_and_upload_profiles(title, message, changed_profiles) + +def yast_select_and_upload_profiles(title, message, profiles_up): + url = cfg['repository']['url'] + profile_changes = hasher() + profs = profiles_up[:] + for p in profs: + profile_changes[p[0]] = get_profile_diff(p[2], p[1]) + SendDataToYast({ + 'type': 'dialog-select-profiles', + 'title': title, + 'explanation': message, + 'default_select': 'false', + 'disable_ask_upload': 'true', + 'profiles': profile_changes + }) + ypath, yarg = GetDataFromYast() + selected_profiles = [] + changelog = None + changelogs = None + single_changelog = False + if yarg['STATUS'] == 'cancel': + return + else: + selected_profiles = yarg['PROFILES'] + changelogs = yarg['CHANGELOG'] + if changelogs.get('SINGLE_CHANGELOG', False): + changelog = changelogs['SINGLE_CHANGELOG'] + single_changelog = True + user, passw = get_repo_user_pass() + for p in selected_profiles: + profile_string = serialize_profile(aa[p], p) + if not single_changelog: + changelog = changelogs[p] + status_ok, ret = upload_profile(url, user, passw, cfg['repository']['distro'], + p, profile_string, changelog) + if status_ok: + newprofile = ret + newid = newprofile['id'] + set_repo_info(aa[p][p], url, user, newid) + write_profile_ui_feedback(p) + else: + if not ret: + ret = 'UNKNOWN ERROR' + UI_Important('WARNING: An error occured while uploading the profile %s\n%s\n' % (p, ret)) + UI_Info('Uploaded changes to repository.') + if yarg.get('NEVER_ASK_AGAIN'): + unselected_profiles = [] + for p in profs: + if p[0] not in selected_profiles: + unselected_profiles.append(p[0]) + set_profiles_local_only(unselected_profiles) + +def console_select_and_upload_profiles(title, message, profiles_up): + url = cfg['repository']['url'] + profs = profiles_up[:] + q = hasher() + q['title'] = title + q['headers'] = ['Repository', url] + q['explanation'] = message + q['functions'] = ['CMD_UPLOAD_CHANGES', 'CMD_VIEW_CHANGES', 'CMD_ASK_LATER', + 'CMD_ASK_NEVER', 'CMD_ABORT'] + q['default'] = 'CMD_VIEW_CHANGES' + q['options'] = [i[0] for i in profs] + q['selected'] = 0 + ans = '' + while 'CMD_UPLOAD_CHANGES' not in ans and 'CMD_ASK_NEVER' not in ans and 'CMD_ASK_LATER' not in ans: + ans, arg = UI_PromptUser(q) + if ans == 'CMD_VIEW_CHANGES': + display_changes(profs[arg][2], profs[arg][1]) + if ans == 'CMD_NEVER_ASK': + set_profiles_local_only([i[0] for i in profs]) + elif ans == 'CMD_UPLOAD_CHANGES': + changelog = UI_GetString('Changelog Entry: ', '') + user, passw = get_repo_user_pass() + if user and passw: + for p_data in profs: + prof = p_data[0] + prof_string = p_data[1] + status_ok, ret = upload_profile(url, user, passw, + cfg['repository']['distro'], + prof, prof_string, changelog ) + if status_ok: + newprof = ret + newid = newprof['id'] + set_repo_info(aa[prof][prof], url, user, newid) + write_profile_ui_feedback(prof) + UI_Info('Uploaded %s to repository' % prof) + else: + if not ret: + ret = 'UNKNOWN ERROR' + UI_Important('WARNING: An error occured while uploading the profile %s\n%s\n' % (prof, ret)) + else: + UI_Important('Repository Error\nRegistration or Sigin was unsuccessful. User login\n' + + 'information is required to upload profiles to the repository.\n' + + 'These changes could not be sent.\n') + +def set_profile_local_only(profs): + for p in profs: + aa[profs][profs]['repo']['neversubmit'] = True + writeback_ui_feedback(profs) + +def confirm_and_abort(): + ans = UI_YesNo('Are you sure you want to abandon this set of profile changes and exit?', 'n') + if ans == 'y': + UI_Info('Abandoning all changes.') + shutdown_yast() + for prof in created: + delete_profile(prof) + sys.exit(0) + +def confirm_and_finish(): + sys.stdout.write('Finishing\n') + sys.exit(0) + +def build_x_functions(default, options, exec_toggle): + ret_list = [] + if exec_toggle: + if 'i' in options: + ret_list.append('CMD_ix') + if 'p' in options: + ret_list.append('CMD_pix') + ret_list.append('CMD_EXEC_IX_OFF') + elif 'c' in options: + ret_list.append('CMD_cix') + ret_list.append('CMD_EXEC_IX_OFF') + elif 'n' in options: + ret_list.append('CMD_nix') + ret_list.append('CMD_EXEC_IX_OFF') + elif 'u' in options: + ret_list.append('CMD_ux') + else: + if 'i' in options: + ret_list.append('CMD_ix') + elif 'c' in options: + ret_list.append('CMD_cx') + ret_list.append('CMD_EXEC_IX_ON') + elif 'p' in options: + ret_list.append('CMD_px') + ret_list.append('CMD_EXEC_IX_OFF') + elif 'n' in options: + ret_list.append('CMD_nx') + ret_list.append('CMD_EXEC_IX_OFF') + elif 'u' in options: + ret_list.append('CMD_ux') + ret_list += ['CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED'] + return ret_list + +def handle_children(profile, hat, root): + entries = root[:] + for entry in entries: + \ No newline at end of file diff --git a/apparmor/common.py b/apparmor/common.py index 41bfb7a4b..8653eca48 100644 --- a/apparmor/common.py +++ b/apparmor/common.py @@ -19,7 +19,6 @@ class AppArmorException(Exception): self.value = value def __str__(self): - return self.value return repr(self.value) # diff --git a/apparmor/ui.py b/apparmor/ui.py new file mode 100644 index 000000000..1ce7de2fe --- /dev/null +++ b/apparmor/ui.py @@ -0,0 +1,255 @@ +# 1728 +import sys +import gettext +import locale +from yasti import yastLog, SendDataToYast, GetDataFromYast + +from apparmor.common import readkey + +DEBUGGING = False +debug_logger = None +UI_mode = 'text' + +def init_localisation(): + locale.setlocale(locale.LC_ALL, '') + #cur_locale = locale.getlocale() + filename = 'res/messages_%s.mo' % locale.getlocale()[0][0:2] + try: + trans = gettext.GNUTranslations(open( filename, 'rb')) + except IOError: + trans = gettext.NullTranslations() + trans.install() + +def UI_Info(text): + if DEBUGGING: + debug_logger.info(text) + if UI_mode == 'text': + sys.stdout.write(text + '\n') + else: + yastLog(text) + +def UI_Important(text): + if DEBUGGING: + debug_logger.debug(text) + if UI_mode == 'text': + sys.stdout.write('\n' + text + '\n') + else: + SendDataToYast({ + 'type': 'dialog-error', + 'message': text + }) + path, yarg = GetDataFromYast() + +def UI_YesNo(text, default): + if DEBUGGING: + debug_logger.debug('UI_YesNo: %s: %s %s' %(UI_mode, text, default)) + ans = default + if UI_mode == 'text': + yes = '(Y)es' + no = '(N)o' + usrmsg = 'PromptUser: Invalid hotkey for' + yeskey = 'y' + nokey = 'n' + sys.stdout.write('\n' + text + '\n') + if default == 'y': + sys.stdout.write('\n[%s] / %s\n' % (yes, no)) + else: + sys.stdout.write('\n%s / [%s]\n' % (yes, no)) + ans = readkey() + if ans: + ans = ans.lower() + else: + ans = default + else: + SendDataTooYast({ + 'type': 'dialog-yesno', + 'question': text + }) + ypath, yarg = GetDataFromYast() + ans = yarg['answer'] + if not ans: + ans = default + return ans + +def UI_YesNoCancel(text, default): + if DEBUGGING: + debug_logger.debug('UI_YesNoCancel: %s: %s %s' % (UI_mode, text, default)) + + if UI_mode == 'text': + yes = '(Y)es' + no = '(N)o' + cancel = '(C)ancel' + yeskey = 'y' + nokey = 'n' + cancelkey = 'c' + ans = 'XXXINVALIDXXX' + while ans != 'c' and ans != 'n' and ans != 'y': + sys.stdout.write('\n' + text + '\n') + if default == 'y': + sys.stdout.write('\n[%s] / %s / %s\n' % (yes, no, cancel)) + elif default == 'n': + sys.stdout.write('\n%s / [%s] / %s\n' % (yes, no, cancel)) + else: + sys.stdout.write('\n%s / %s / [%s]\n' % (yes, no, cancel)) + ans = readkey() + if ans: + ans = ans.lower() + else: + ans = default + else: + SendDataToYast({ + 'type': 'dialog-yesnocancel', + 'question': text + }) + ypath, yarg = GetDataFromYast() + ans = yarg['answer'] + if not ans: + ans = default + return ans + +def UI_GetString(text, default): + if DEBUGGING: + debug_logger.debug('UI_GetString: %s: %s %s' % (UI_mode, text, default)) + string = default + if UI_mode == 'text': + sys.stdout.write('\n' + text + '\n') + string = sys.stdin.readline() + else: + SendDataToYast({ + 'type': 'dialog-getstring', + 'label': text, + 'default': default + }) + ypath, yarg = GetDatFromYast() + string = yarg['string'] + return string + +def UI_GetFile(file): + if DEBUGGING: + debug_logger.debug('UI_GetFile: %s' % UI_mode) + filename = None + if UI_mode == 'text': + sys.stdout.write(file['description'] + '\n') + filename = sys.stdin.read() + else: + file['type'] = 'dialog-getfile' + SendDataToYast(file) + ypath, yarg = GetDataFromYast() + if yarg['answer'] == 'okay': + filename = yarg['filename'] + return filename + +def UI_BusyStart(message): + if DEBUGGING: + debug_logger.debug('UI_BusyStart: %s' % UI_mode) + if UI_mode == 'text': + UI_Info(message) + else: + SendDataToYast({ + 'type': 'dialog-busy-start', + 'message': message + }) + ypath, yarg = GetDataFromYast() + +def UI_BusyStop(): + if DEBUGGING: + debug_logger.debug('UI_BusyStop: %s' % UI_mode) + if UI_mode != 'text': + SendDataToYast({'type': 'dialog-busy-stop'}) + ypath, yarg = GetDataFromYast() + +CMDS = { + 'CMD_ALLOW': '(A)llow', + 'CMD_OTHER': '(M)ore', + 'CMD_AUDIT_NEW': 'Audi(t)', + 'CMD_AUDIT_OFF': 'Audi(t) off', + 'CMD_AUDIT_FULL': 'Audit (A)ll', + #'CMD_OTHER': '(O)pts', + 'CMD_USER_ON': '(O)wner permissions on', + 'CMD_USER_OFF': '(O)wner permissions off', + 'CMD_DENY': '(D)eny', + 'CMD_ABORT': 'Abo(r)t', + 'CMD_FINISHED': '(F)inish', + 'CMD_ix': '(I)nherit', + 'CMD_px': '(P)rofile', + 'CMD_px_safe': '(P)rofile Clean Exec', + 'CMD_cx': '(C)hild', + 'CMD_cx_safe': '(C)hild Clean Exec', + 'CMD_nx': 'Named', + 'CMD_nx_safe': 'Named Clean Exec', + 'CMD_ux': '(U)nconfined', + 'CMD_ux_safe': '(U)nconfined Clean Exec', + 'CMD_pix': '(P)rofile Inherit', + 'CMD_pix_safe': '(P)rofile Inherit Clean Exec', + 'CMD_cix': '(C)hild Inherit', + 'CMD_cix_safe': '(C)hild Inherit Clean Exec', + 'CMD_nix': '(N)amed Inherit', + 'CMD_nix_safe': '(N)amed Inherit Clean Exec', + 'CMD_EXEC_IX_ON': '(X) ix On', + 'CMD_EXEC_IX_OFF': '(X) ix Off', + 'CMD_SAVE': '(S)ave Changes', + 'CMD_CONTINUE': '(C)ontinue Profiling', + 'CMD_NEW': '(N)ew', + 'CMD_GLOB': '(G)lob', + 'CMD_GLOBEXT': 'Glob with (E)xtension', + 'CMD_ADDHAT': '(A)dd Requested Hat', + 'CMD_USEDEFAULT': '(U)se Default Hat', + 'CMD_SCAN': '(S)can system log for AppArmor events', + 'CMD_HELP': '(H)elp', + 'CMD_VIEW_PROFILE': '(V)iew Profile', + 'CMD_USE_PROFILE': '(U)se Profile', + 'CMD_CREATE_PROFILE': '(C)reate New Profile', + 'CMD_UPDATE_PROFILE': '(U)pdate Profile', + 'CMD_IGNORE_UPDATE': '(I)gnore Update', + 'CMD_SAVE_CHANGES': '(S)ave Changes', + 'CMD_UPLOAD_CHANGES': '(U)pload Changes', + 'CMD_VIEW_CHANGES': '(V)iew Changes', + 'CMD_VIEW': '(V)iew', + 'CMD_ENABLE_REPO': '(E)nable Repository', + 'CMD_DISABLE_REPO': '(D)isable Repository', + 'CMD_ASK_NEVER': '(N)ever Ask Again', + 'CMD_ASK_LATER': 'Ask Me (L)ater', + 'CMD_YES': '(Y)es', + 'CMD_NO': '(N)o', + 'CMD_ALL_NET': 'Allow All (N)etwork', + 'CMD_NET_FAMILY': 'Allow Network Fa(m)ily', + 'CMD_OVERWRITE': '(O)verwrite Profile', + 'CMD_KEEP': '(K)eep Profile', + 'CMD_CONTINUE': '(C)ontinue' + } + +def UI_PromptUser(q): + cmd = None + arg = None + if UI_mode == 'text': + cmd, arg = Text_PromptUser(q) + else: + q['type'] = 'wizard' + SendDataToYast(q) + ypath, yarg = GetDataFromYast() + if not cmd: + cmd = 'CMD_ABORT' + arg = yarg['selected'] + if cmd == 'CMD_ABORT': + confirm_and_abort() + cmd == 'XXXINVALIDXXX' + elif cmd == 'CMD_FINISHED': + confirm_and_finish() + cmd == 'XXXINVALIDXXX' + return (cmd, arg) + +def UI_ShortMessage(title, message): + SendDataToYast({ + 'type': 'short-dialog-message', + 'headline': title, + 'message': message + }) + ypath, yarg = GetDataFromYast() + +def UI_longMessage(title, message): + SendDataToYast({ + 'type': 'long-dialog-message', + 'headline': title, + 'message': message + }) + ypath, yarg = GetDataFromYast() diff --git a/apparmor/yasti.py b/apparmor/yasti.py new file mode 100644 index 000000000..a2928c08b --- /dev/null +++ b/apparmor/yasti.py @@ -0,0 +1,82 @@ +import re +import ycp + +def yastLog(text): + ycp.y2milestone(text) + +def SendDataToYast(data): + if DEBUGGING: + debug_logger.info('SendDataToYast: Waiting for YCP command') + for line in sys.stdin: + ycommand, ypath, yargument = ParseCommand(line) + if ycommand and ycommand == 'Read': + if DEBUGGING: + debug_logger.info('SendDataToYast: Sending--%s' % data) + Return(data) + return True + else: + if DEBUGGING: + debug_logger.info('SendDataToYast: Expected \'Read\' but got-- %s' % line) + fatal_error('SendDataToYast: didn\'t receive YCP command before connection died') + +def GetDataFromYast(): + if DEBUGGING: + debug_logger.inf('GetDataFromYast: Waiting for YCP command') + for line in sys.stdin: + if DEBUGGING: + debug_logger.info('GetDataFromYast: YCP: %s' % line) + ycommand, ypath, yarg = ParseCommand(line) + if DEBUGGING: + debug_logger.info('GetDataFromYast: Recieved--\n%s' % yarg) + if ycommand and ycommand == 'Write': + Return('true') + return ypath, yarg + else: + if DEBUGGING: + debug_logger.info('GetDataFromYast: Expected Write but got-- %s' % line) + fatal_error('GetDataFromYast: didn\'t receive YCP command before connection died') + +def ParseCommand(commands): + term = ParseTerm(commands) + if term: + command = term[0] + term = term[1:] + else: + command = '' + path = '' + pathref = None + if term: + pathref = term[0] + term = term[1:] + if pathref: + if pathref.strip(): + path = pathref.strip() + elif command != 'result': + ycp.y2error('The first arguement is not a path. (%s)' % pathref) + argument = None + if term: + argument = term[0] + if len(term) > 1: + ycp.y2warning('Superfluous command arguments ignored') + return (command, path, argument) + +def ParseTerm(input): + regex_term = re.compile('^\s*`?(\w*)\s*') + term = regex_term.search(input) + ret = [] + symbol = None + if term: + symbol = term.groups()[0] + else: + ycp.y2error('No term symbol') + ret.append(symbol) + input = regex_term.sub('', input) + if not input.startswith('('): + ycp.y2error('No term parantheses') + argref, err, rest = ParseYcpTermBody(input) + if err: + ycp.y2error('%s (%s)' % (err, rest)) + else: + ret += argref + return ret +