2
# ----------------------------------------------------------------------
3
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
4
# Copyright (C) 2014-2017 Christian Boltz <apparmor@cboltz.de>
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of version 2 of the GNU General Public
8
# License as published by the Free Software Foundation.
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
15
# ----------------------------------------------------------------------
20
import apparmor.aamode
22
import apparmor.severity
23
import apparmor.cleanprofile as cleanprofile
24
import apparmor.ui as aaui
26
from apparmor.common import AppArmorException
27
from apparmor.regex import re_match_include
30
# setup exception handling
31
from apparmor.fail import enable_aa_exception_handler
32
enable_aa_exception_handler()
34
# setup module translations
35
from apparmor.translations import init_translation
36
_ = init_translation()
38
parser = argparse.ArgumentParser(description=_('Merge the given profiles into /etc/apparmor.d/ (or the directory specified with -d)'))
39
parser.add_argument('files', nargs='+', type=str, help=_('Profile(s) to merge'))
40
parser.add_argument('-d', '--dir', type=str, help=_('path to profiles'))
41
#parser.add_argument('-a', '--auto', action='store_true', help=_('Automatically merge profiles, exits incase of *x conflicts'))
42
args = parser.parse_args()
52
apparmor.aa.profile_dir = apparmor.aa.get_full_path(profiledir)
53
if not os.path.isdir(apparmor.aa.profile_dir):
54
raise AppArmorException(_("%s is not a directory.") %profiledir)
57
apparmor.aa.aa = apparmor.aa.hasher()
58
apparmor.aa.filelist = apparmor.aa.hasher()
59
apparmor.aa.include = dict()
60
apparmor.aa.existing_profiles = apparmor.aa.hasher()
61
apparmor.aa.original_aa = apparmor.aa.hasher()
63
def find_profiles_from_files(files):
64
profile_to_filename = dict()
65
for file_name in files:
66
apparmor.aa.read_profile(file_name, True)
67
for profile_name in apparmor.aa.filelist[file_name]['profiles'].keys():
68
profile_to_filename[profile_name] = file_name
71
return profile_to_filename
73
def find_files_from_profiles(profiles):
74
profile_to_filename = dict()
75
apparmor.aa.read_profiles()
77
for profile_name in profiles:
78
profile_to_filename[profile_name] = apparmor.aa.get_profile_filename(profile_name)
82
return profile_to_filename
85
base_profile_to_file = find_profiles_from_files(profiles)
87
profiles_to_merge = set(base_profile_to_file.keys())
89
user_profile_to_file = find_files_from_profiles(profiles_to_merge)
91
for profile_name in profiles_to_merge:
92
aaui.UI_Info("\n\n" + _("Merging profile for %s" % profile_name))
93
user_file = user_profile_to_file[profile_name]
94
base_file = base_profile_to_file.get(profile_name, None)
96
act([user_file, base_file], profile_name)
100
def act(files, merging_profile):
101
mergeprofiles = Merge(files)
102
#Get rid of common/superfluous stuff
103
mergeprofiles.clear_common()
106
if 1 == 1: # workaround to avoid lots of whitespace changes
107
mergeprofiles.ask_merge_questions()
109
q = aaui.PromptQuestion()
110
q.title = _('Changed Local Profiles')
111
q.explanation = _('The following local profiles were changed. Would you like to save them?')
112
q.functions = ['CMD_SAVE_CHANGES', 'CMD_VIEW_CHANGES', 'CMD_ABORT', 'CMD_IGNORE_ENTRY']
113
q.default = 'CMD_VIEW_CHANGES'
114
q.options = [merging_profile]
119
programs = list(mergeprofiles.user.aa.keys())
120
program = programs[0]
121
while ans != 'CMD_SAVE_CHANGES':
122
ans, arg = q.promptUser()
123
if ans == 'CMD_SAVE_CHANGES':
124
apparmor.aa.write_profile_ui_feedback(program)
125
apparmor.aa.reload_base(program)
126
elif ans == 'CMD_VIEW_CHANGES':
127
for program in programs:
128
apparmor.aa.original_aa[program] = apparmor.aa.deepcopy(apparmor.aa.aa[program])
129
#oldprofile = apparmor.serialize_profile(apparmor.original_aa[program], program, '')
130
newprofile = apparmor.aa.serialize_profile(mergeprofiles.user.aa[program], program, '')
131
aaui.UI_Changes(mergeprofiles.user.filename, newprofile, comments=True)
132
elif ans == 'CMD_IGNORE_ENTRY':
137
def __init__(self, profiles):
138
user, base = profiles
140
#Read and parse base profile and save profile data, include data from it and reset them
141
apparmor.aa.read_profile(base, True)
142
self.base = cleanprofile.Prof(base)
146
#Read and parse user profile
147
apparmor.aa.read_profile(user, True)
148
self.user = cleanprofile.Prof(user)
150
def clear_common(self):
153
#Remove off the parts in base profile which are common/superfluous from user profile
154
user_base = cleanprofile.CleanProf(False, self.user, self.base)
155
deleted += user_base.compare_profiles()
157
def ask_merge_questions(self):
159
log_dict = {'merge': other.aa}
161
apparmor.aa.loadincludes()
164
#Add the file-wide includes from the other profile to the user profile
166
for inc in other.filelist[other.filename]['include'].keys():
167
if not inc in self.user.filelist[self.user.filename]['include'].keys():
168
options.append('#include <%s>' %inc)
172
q = aaui.PromptQuestion()
174
q.selected = default_option - 1
175
q.headers = [_('File includes'), _('Select the ones you wish to add')]
176
q.functions = ['CMD_ALLOW', 'CMD_IGNORE_ENTRY', 'CMD_ABORT', 'CMD_FINISHED']
177
q.default = 'CMD_ALLOW'
179
while not done and options:
180
ans, selected = q.promptUser()
181
if ans == 'CMD_IGNORE_ENTRY':
183
elif ans == 'CMD_ALLOW':
184
selection = options[selected]
185
inc = re_match_include(selection)
186
self.user.filelist[self.user.filename]['include'][inc] = True
187
options.pop(selected)
188
aaui.UI_Info(_('Adding %s to the file.') % selection)
189
elif ans == 'CMD_FINISHED':
192
if not apparmor.aa.sev_db:
193
apparmor.aa.sev_db = apparmor.severity.Severity(apparmor.aa.CONFDIR + '/severity.db', _('unknown'))
195
apparmor.aa.ask_the_questions(log_dict)
197
if __name__ == '__main__':