~apparmor-dev/apparmor/apparmor-ubuntu-citrain-trusty

« back to all changes in this revision

Viewing changes to utils/apparmor/aa.py

  • Committer: Seth Arnold
  • Date: 2014-03-12 02:05:16 UTC
  • mto: This revision was merged to the branch mainline in revision 1496.
  • Revision ID: seth.arnold@canonical.com-20140312020516-zjike3pmw6hi861h
[ Jamie Strandboge ]
 * debian/debhelper/dh_apparmor: exit with error if aa-easyprof does not
   exist
 * debian/control: drop Depends on apparmor-easyprof to Suggests for
   dh-apparmor
[ Seth Arnold, Jamie Strandboge, Steve Beattie, John Johansen, Tyler Hicks ]
* New upstream snapshot (LP: #1278702, #1061693, #1285653) dropping very
  large Ubuntu delta and fixing the following bugs:
  - Adjust fonts abstraction for libthai (LP: #1278702)
  - Support translated XDG user directories (LP: #1061693)
  - Adjust abstractions/web-data to include /var/www/html (LP: #1285653)
    Refresh 0002-add-debian-integration-to-lighttpd.patch to include
    /etc/lighttpd/conf-available/*.conf
  - Adjust debian/libapparmor1.symbols to reflect new upstream versioning
    for the aa_query_label() function
  - Raise exceptions in Python bindings when something fails
* ship new Python replacements for previous Perl-based tools
  - debian/apparmor-utils.install: remove usr/share/perl5/Immunix/*.pm and add
    usr/sbin/aa-autodep, usr/sbin/aa-cleanprof and usr/sbin/aa-mergeprof
  - debian/control:
    + remove various Perl dependencies
    + add python-apparmor and python3-apparmor
    + python3-apparmor Breaks: apparmor-easyprof to move the file since it
      ships dist-packages/apparmor/__init__.py now
  - debian/apparmor-utils.manpages: ship new manpages for aa-cleanprof and
    aa-mergeprof
  - debian/rules: build and install Python tools
* debian/apparmor.install:
  - install apparmorfs, dovecot, kernelvars, securityfs, sys,
    and xdg-user-dirs tunables and xdg-user-dirs.d directory
* debian/apparmor.dirs:
  - install /etc/apparmor.d/tunables/xdg-user-dirs.d
* debian/apparmor.postinst: create xdg-user-dirs.d
* debian/apparmor.postrm: remove xdg-user-dirs.d
* Remaining patches:
  - 0001-add-chromium-browser.patch
  - 0002-add-debian-integration-to-lighttpd.patch
  - 0003-ubuntu-manpage-updates.patch
  - 0004-libapparmor-layout-deb.patch (renamed from 0008)
  - 0005-libapparmor-mention-dbus-method-in-getcon-man.patch (renamed from
    0068)
  - 0006-etc-writable.patch (renamed from 0070)
  - 0007-aa-utils_are_bilingual.patch (renamed from 0077)
  - 0008-remove-ptrace.patch
  - 0009-convert-to-rules.patch
  - 0010-list-fns.patch
  - 0011-parse-mode.patch
  - 0012-add-decimal-interp.patch
  - 0013-policy_mediates.patch
  - 0014-fix-failpath.patch
  - 0015-feature_file.patch
  - 0016-fix-network.patch
  - 0017-aare-to-class.patch
  - 0018-add-mediation-unix.patch
  - 0019-parser_version.patch
  - 0020-caching.patch
  - 0021-label-class.patch
  - 0022-signal.patch
  - 0023-fix-lexer-debug.patch
  - 0024-ptrace.patch
  - 0025-use-diff-encode.patch
  - 0026-fix-serialize.patch
  - 0027-fix-af.patch
  - 0028-opt_arg.patch
  - 0029-tests-cond-dbus.patch
  - 0030-tests.diff
* Move manpages from libapparmor1 to libapparmor-dev
  - debian/libapparmor-dev.manpages: install aa_change_hat.2,
    aa_change_profile.2, aa_find_mountpoint.2, aa_getcon.2
  - debian/control: libapparmor-dev Replaces: and Breaks: libapparmor1
* Move /usr/lib/python3/dist-packages/apparmor/__init__.py from
  apparmor-easyprof to python3-apparmor
  - debian/control: python3-apparmor Breaks: apparmor-easyprof
  - debian/apparmor-easyprof.install: remove
    usr/lib/python*.*/site-packages/apparmor*
* New profiles and abstractions:
  - debian/apparmor.install: tunables/dovecot, tunables/kernelvars,
    tunables/xdg-user-dirs, tunables/xdg-user-dirs.d
* Test merge from upstream new pyutils branch (rev 2385)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# ----------------------------------------------------------------------
 
2
#    Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
 
3
#
 
4
#    This program is free software; you can redistribute it and/or
 
5
#    modify it under the terms of version 2 of the GNU General Public
 
6
#    License as published by the Free Software Foundation.
 
7
#
 
8
#    This program is distributed in the hope that it will be useful,
 
9
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
#    GNU General Public License for more details.
 
12
#
 
13
# ----------------------------------------------------------------------
 
14
# No old version logs, only 2.6 + supported
 
15
from __future__ import with_statement
 
16
import inspect
 
17
import os
 
18
import re
 
19
import shutil
 
20
import subprocess
 
21
import sys
 
22
import time
 
23
import traceback
 
24
import atexit
 
25
import tempfile
 
26
 
 
27
import apparmor.config
 
28
import apparmor.logparser
 
29
import apparmor.severity
 
30
 
 
31
from copy import deepcopy
 
32
 
 
33
from apparmor.common import (AppArmorException, open_file_read, valid_path, hasher,
 
34
                             open_file_write, convert_regexp, DebugLogger)
 
35
 
 
36
import apparmor.ui as aaui
 
37
 
 
38
from apparmor.aamode import (str_to_mode, mode_to_str, contains, split_mode,
 
39
                             mode_to_str_user, mode_contains, AA_OTHER,
 
40
                             flatten_mode, owner_flatten_mode)
 
41
 
 
42
import apparmor.rules as aarules
 
43
 
 
44
from apparmor.yasti import SendDataToYast, GetDataFromYast, shutdown_yast
 
45
 
 
46
# setup module translations
 
47
from apparmor.translations import init_translation
 
48
_ = init_translation()
 
49
 
 
50
# Setup logging incase of debugging is enabled
 
51
debug_logger = DebugLogger('aa')
 
52
 
 
53
CONFDIR = '/etc/apparmor'
 
54
running_under_genprof = False
 
55
unimplemented_warning = False
 
56
 
 
57
# The database for severity
 
58
sev_db = None
 
59
# The file to read log messages from
 
60
### Was our
 
61
filename = None
 
62
 
 
63
cfg = None
 
64
repo_cfg = None
 
65
 
 
66
parser = None
 
67
ldd = None
 
68
logger = None
 
69
profile_dir = None
 
70
extra_profile_dir = None
 
71
### end our
 
72
# To keep track of previously included profile fragments
 
73
include = dict()
 
74
 
 
75
existing_profiles = dict()
 
76
 
 
77
seen_events = 0  # was our
 
78
# To store the globs entered by users so they can be provided again
 
79
user_globs = []
 
80
 
 
81
## Variables used under logprof
 
82
### Were our
 
83
t = hasher()  # dict()
 
84
transitions = hasher()
 
85
aa = hasher()  # Profiles originally in sd, replace by aa
 
86
original_aa = hasher()
 
87
extras = hasher()  # Inactive profiles from extras
 
88
### end our
 
89
log = []
 
90
pid = dict()
 
91
 
 
92
seen = hasher()  # dir()
 
93
profile_changes = hasher()
 
94
prelog = hasher()
 
95
log_dict = hasher()  # dict()
 
96
changed = dict()
 
97
created = []
 
98
skip = hasher()
 
99
helpers = dict()  # Preserve this between passes # was our
 
100
### logprof ends
 
101
 
 
102
filelist = hasher()    # File level variables and stuff in config files
 
103
 
 
104
def on_exit():
 
105
    """Shutdowns the logger and records exit if debugging enabled"""
 
106
    debug_logger.debug('Exiting..')
 
107
    debug_logger.shutdown()
 
108
 
 
109
# Register the on_exit method with atexit
 
110
atexit.register(on_exit)
 
111
 
 
112
def check_for_LD_XXX(file):
 
113
    """Returns True if specified program contains references to LD_PRELOAD or
 
114
    LD_LIBRARY_PATH to give the Px/Ux code better suggestions"""
 
115
    found = False
 
116
    if not os.path.isfile(file):
 
117
        return False
 
118
    size = os.stat(file).st_size
 
119
    # Limit to checking files under 100k for the sake of speed
 
120
    if size > 100000:
 
121
        return False
 
122
    with open_file_read(file, encoding='ascii') as f_in:
 
123
        for line in f_in:
 
124
            if 'LD_PRELOAD' in line or 'LD_LIBRARY_PATH' in line:
 
125
                found = True
 
126
    return found
 
127
 
 
128
def fatal_error(message):
 
129
    # Get the traceback to the message
 
130
    tb_stack = traceback.format_list(traceback.extract_stack())
 
131
    tb_stack = ''.join(tb_stack)
 
132
    # Append the traceback to message
 
133
    message = message + '\n' + tb_stack
 
134
    debug_logger.error(message)
 
135
    caller = inspect.stack()[1][3]
 
136
 
 
137
    # If caller is SendDataToYast or GetDatFromYast simply exit
 
138
    if caller == 'SendDataToYast' or caller == 'GetDatFromYast':
 
139
        sys.exit(1)
 
140
 
 
141
    # Else tell user what happened
 
142
    aaui.UI_Important(message)
 
143
    shutdown_yast()
 
144
    sys.exit(1)
 
145
 
 
146
def check_for_apparmor():
 
147
    """Finds and returns the mointpoint for apparmor None otherwise"""
 
148
    filesystem = '/proc/filesystems'
 
149
    mounts = '/proc/mounts'
 
150
    support_securityfs = False
 
151
    aa_mountpoint = None
 
152
    regex_securityfs = re.compile('^\S+\s+(\S+)\s+securityfs\s')
 
153
    if valid_path(filesystem):
 
154
        with open_file_read(filesystem) as f_in:
 
155
            for line in f_in:
 
156
                if 'securityfs' in line:
 
157
                    support_securityfs = True
 
158
    if valid_path(mounts):
 
159
        with open_file_read(mounts) as f_in:
 
160
            for line in f_in:
 
161
                if support_securityfs:
 
162
                    match = regex_securityfs.search(line)
 
163
                    if match:
 
164
                        mountpoint = match.groups()[0] + '/apparmor'
 
165
                        if valid_path(mountpoint):
 
166
                            aa_mountpoint = mountpoint
 
167
    # Check if apparmor is actually mounted there
 
168
    if not valid_path(aa_mountpoint + '/profiles'):
 
169
        aa_mountpoint = None
 
170
    return aa_mountpoint
 
171
 
 
172
def which(file):
 
173
    """Returns the executable fullpath for the file, None otherwise"""
 
174
    if sys.version_info >= (3, 3):
 
175
        return shutil.which(file)
 
176
    env_dirs = os.getenv('PATH').split(':')
 
177
    for env_dir in env_dirs:
 
178
        env_path = env_dir + '/' + file
 
179
        # Test if the path is executable or not
 
180
        if os.access(env_path, os.X_OK):
 
181
            return env_path
 
182
    return None
 
183
 
 
184
def get_full_path(original_path):
 
185
    """Return the full path after resolving any symlinks"""
 
186
    path = original_path
 
187
    link_count = 0
 
188
    if not path.startswith('/'):
 
189
        path = os.getcwd() + '/' + path
 
190
    while os.path.islink(path):
 
191
        link_count += 1
 
192
        if link_count > 64:
 
193
            fatal_error(_("Followed too many links while resolving %s") % (original_path))
 
194
        direc, file = os.path.split(path)
 
195
        link = os.readlink(path)
 
196
        # If the link an absolute path
 
197
        if link.startswith('/'):
 
198
            path = link
 
199
        else:
 
200
            # Link is relative path
 
201
            path = direc + '/' + link
 
202
    return os.path.realpath(path)
 
203
 
 
204
def find_executable(bin_path):
 
205
    """Returns the full executable path for the given executable, None otherwise"""
 
206
    full_bin = None
 
207
    if os.path.exists(bin_path):
 
208
        full_bin = get_full_path(bin_path)
 
209
    else:
 
210
        if '/' not in bin_path:
 
211
            env_bin = which(bin_path)
 
212
            if env_bin:
 
213
                full_bin = get_full_path(env_bin)
 
214
    if full_bin and os.path.exists(full_bin):
 
215
        return full_bin
 
216
    return None
 
217
 
 
218
def get_profile_filename(profile):
 
219
    """Returns the full profile name"""
 
220
    if existing_profiles.get(profile, False):
 
221
        return existing_profiles[profile]
 
222
    elif profile.startswith('/'):
 
223
        # Remove leading /
 
224
        profile = profile[1:]
 
225
    else:
 
226
        profile = "profile_" + profile
 
227
    profile = profile.replace('/', '.')
 
228
    full_profilename = profile_dir + '/' + profile
 
229
    return full_profilename
 
230
 
 
231
def name_to_prof_filename(prof_filename):
 
232
    """Returns the profile"""
 
233
    if prof_filename.startswith(profile_dir):
 
234
        profile = prof_filename.split(profile_dir, 1)[1]
 
235
        return (prof_filename, profile)
 
236
    else:
 
237
        bin_path = find_executable(prof_filename)
 
238
        if bin_path:
 
239
            prof_filename = get_profile_filename(bin_path)
 
240
            if os.path.isfile(prof_filename):
 
241
                return (prof_filename, bin_path)
 
242
            else:
 
243
                return None, None
 
244
 
 
245
def complain(path):
 
246
    """Sets the profile to complain mode if it exists"""
 
247
    prof_filename, name = name_to_prof_filename(path)
 
248
    if not prof_filename:
 
249
        fatal_error(_("Can't find %s") % path)
 
250
    set_complain(prof_filename, name)
 
251
 
 
252
def enforce(path):
 
253
    """Sets the profile to enforce mode if it exists"""
 
254
    prof_filename, name = name_to_prof_filename(path)
 
255
    if not prof_filename:
 
256
        fatal_error(_("Can't find %s") % path)
 
257
    set_enforce(prof_filename, name)
 
258
 
 
259
def set_complain(filename, program):
 
260
    """Sets the profile to complain mode"""
 
261
    aaui.UI_Info(_('Setting %s to complain mode.') % (filename if program is None else program))
 
262
    # a force-complain symlink is more packaging-friendly, but breaks caching
 
263
    # create_symlink('force-complain', filename)
 
264
    change_profile_flags(filename, program, 'complain', True)
 
265
 
 
266
def set_enforce(filename, program):
 
267
    """Sets the profile to enforce mode"""
 
268
    aaui.UI_Info(_('Setting %s to enforce mode.') % (filename if program is None else program))
 
269
    delete_symlink('force-complain', filename)
 
270
    delete_symlink('disable', filename)
 
271
    change_profile_flags(filename, program, 'complain', False)
 
272
 
 
273
def delete_symlink(subdir, filename):
 
274
    path = filename
 
275
    link = re.sub('^%s' % profile_dir, '%s/%s' % (profile_dir, subdir), path)
 
276
    if link != path and os.path.islink(link):
 
277
        os.remove(link)
 
278
 
 
279
def create_symlink(subdir, filename):
 
280
    path = filename
 
281
    bname = os.path.basename(filename)
 
282
    if not bname:
 
283
        raise AppArmorException(_('Unable to find basename for %s.') % filename)
 
284
    #print(filename)
 
285
    link = re.sub('^%s' % profile_dir, '%s/%s' % (profile_dir, subdir), path)
 
286
    #print(link)
 
287
    #link = link + '/%s'%bname
 
288
    #print(link)
 
289
    symlink_dir = os.path.dirname(link)
 
290
    if not os.path.exists(symlink_dir):
 
291
        # If the symlink directory does not exist create it
 
292
        os.makedirs(symlink_dir)
 
293
 
 
294
    if not os.path.exists(link):
 
295
        try:
 
296
            os.symlink(filename, link)
 
297
        except:
 
298
            raise AppArmorException(_('Could not create %s symlink to %s.') % (link, filename))
 
299
 
 
300
def head(file):
 
301
    """Returns the first/head line of the file"""
 
302
    first = ''
 
303
    if os.path.isfile(file):
 
304
        with open_file_read(file) as f_in:
 
305
            try:
 
306
                first = f_in.readline().rstrip()
 
307
            except UnicodeDecodeError:
 
308
                pass
 
309
            return first
 
310
    else:
 
311
        raise AppArmorException(_('Unable to read first line from %s: File Not Found') % file)
 
312
 
 
313
def get_output(params):
 
314
    """Returns the return code output by running the program with the args given in the list"""
 
315
    program = params[0]
 
316
    # args = params[1:]
 
317
    ret = -1
 
318
    output = []
 
319
    # program is executable
 
320
    if os.access(program, os.X_OK):
 
321
        try:
 
322
            # Get the output of the program
 
323
            output = subprocess.check_output(params)
 
324
        except OSError as e:
 
325
            raise AppArmorException(_("Unable to fork: %s\n\t%s") % (program, str(e)))
 
326
            # If exit-codes besides 0
 
327
        except subprocess.CalledProcessError as e:
 
328
            output = e.output
 
329
            output = output.decode('utf-8').split('\n')
 
330
            ret = e.returncode
 
331
        else:
 
332
            ret = 0
 
333
            output = output.decode('utf-8').split('\n')
 
334
    # Remove the extra empty string caused due to \n if present
 
335
    if len(output) > 1:
 
336
        output.pop()
 
337
    return (ret, output)
 
338
 
 
339
def get_reqs(file):
 
340
    """Returns a list of paths from ldd output"""
 
341
    pattern1 = re.compile('^\s*\S+ => (\/\S+)')
 
342
    pattern2 = re.compile('^\s*(\/\S+)')
 
343
    reqs = []
 
344
    ret, ldd_out = get_output([ldd, file])
 
345
    if ret == 0:
 
346
        for line in ldd_out:
 
347
            if 'not a dynamic executable' in line:
 
348
                break
 
349
            if 'cannot read header' in line:
 
350
                break
 
351
            if 'statically linked' in line:
 
352
                break
 
353
            match = pattern1.search(line)
 
354
            if match:
 
355
                reqs.append(match.groups()[0])
 
356
            else:
 
357
                match = pattern2.search(line)
 
358
                if match:
 
359
                    reqs.append(match.groups()[0])
 
360
    return reqs
 
361
 
 
362
def handle_binfmt(profile, path):
 
363
    """Modifies the profile to add the requirements"""
 
364
    reqs_processed = dict()
 
365
    reqs = get_reqs(path)
 
366
    while reqs:
 
367
        library = reqs.pop()
 
368
        if not reqs_processed.get(library, False):
 
369
            if get_reqs(library):
 
370
                reqs += get_reqs(library)
 
371
            reqs_processed[library] = True
 
372
        combined_mode = match_prof_incs_to_path(profile, 'allow', library)
 
373
        if combined_mode:
 
374
            continue
 
375
        library = glob_common(library)
 
376
        if not library:
 
377
            continue
 
378
        profile['allow']['path'][library]['mode'] = profile['allow']['path'][library].get('mode', set()) | str_to_mode('mr')
 
379
        profile['allow']['path'][library]['audit'] |= profile['allow']['path'][library].get('audit', set())
 
380
 
 
381
def get_inactive_profile(local_profile):
 
382
    if extras.get(local_profile, False):
 
383
        return {local_profile: extras[local_profile]}
 
384
    return dict()
 
385
 
 
386
def create_new_profile(localfile):
 
387
    local_profile = hasher()
 
388
    local_profile[localfile]['flags'] = 'complain'
 
389
    local_profile[localfile]['include']['abstractions/base'] = 1
 
390
 
 
391
    if os.path.exists(localfile) and os.path.isfile(localfile):
 
392
        hashbang = head(localfile)
 
393
        if hashbang.startswith('#!'):
 
394
            interpreter_path = get_full_path(hashbang.lstrip('#!').strip())
 
395
 
 
396
            interpreter = re.sub('^(/usr)?/bin/', '', interpreter_path)
 
397
 
 
398
            local_profile[localfile]['allow']['path'][localfile]['mode'] = local_profile[localfile]['allow']['path'][localfile].get('mode', str_to_mode('r')) | str_to_mode('r')
 
399
 
 
400
            local_profile[localfile]['allow']['path'][localfile]['audit'] = local_profile[localfile]['allow']['path'][localfile].get('audit', set())
 
401
 
 
402
            local_profile[localfile]['allow']['path'][interpreter_path]['mode'] = local_profile[localfile]['allow']['path'][interpreter_path].get('mode', str_to_mode('ix')) | str_to_mode('ix')
 
403
 
 
404
            local_profile[localfile]['allow']['path'][interpreter_path]['audit'] = local_profile[localfile]['allow']['path'][interpreter_path].get('audit', set())
 
405
 
 
406
            if interpreter == 'perl':
 
407
                local_profile[localfile]['include']['abstractions/perl'] = True
 
408
            elif re.search('^python([23]|[23]\.[0-9]+)?$', interpreter):
 
409
                local_profile[localfile]['include']['abstractions/python'] = True
 
410
            elif interpreter == 'ruby':
 
411
                local_profile[localfile]['include']['abstractions/ruby'] = True
 
412
            elif interpreter in ['bash', 'dash', 'sh']:
 
413
                local_profile[localfile]['include']['abstractions/bash'] = True
 
414
            handle_binfmt(local_profile[localfile], interpreter_path)
 
415
        else:
 
416
 
 
417
            local_profile[localfile]['allow']['path'][localfile]['mode'] = local_profile[localfile]['allow']['path'][localfile].get('mode', str_to_mode('mr')) | str_to_mode('mr')
 
418
 
 
419
            local_profile[localfile]['allow']['path'][localfile]['audit'] = local_profile[localfile]['allow']['path'][localfile].get('audit', set())
 
420
 
 
421
            handle_binfmt(local_profile[localfile], localfile)
 
422
    # Add required hats to the profile if they match the localfile
 
423
    for hatglob in cfg['required_hats'].keys():
 
424
        if re.search(hatglob, localfile):
 
425
            for hat in sorted(cfg['required_hats'][hatglob].split()):
 
426
                local_profile[hat]['flags'] = 'complain'
 
427
 
 
428
    created.append(localfile)
 
429
    changed[localfile] = True
 
430
 
 
431
    debug_logger.debug("Profile for %s:\n\t%s" % (localfile, local_profile.__str__()))
 
432
    return {localfile: local_profile}
 
433
 
 
434
def delete_profile(local_prof):
 
435
    """Deletes the specified file from the disk and remove it from our list"""
 
436
    profile_file = get_profile_filename(local_prof)
 
437
    if os.path.isfile(profile_file):
 
438
        os.remove(profile_file)
 
439
    if aa.get(local_prof, False):
 
440
        aa.pop(local_prof)
 
441
 
 
442
    #prof_unload(local_prof)
 
443
 
 
444
def confirm_and_abort():
 
445
    ans = aaui.UI_YesNo(_('Are you sure you want to abandon this set of profile changes and exit?'), 'n')
 
446
    if ans == 'y':
 
447
        aaui.UI_Info(_('Abandoning all changes.'))
 
448
        shutdown_yast()
 
449
        for prof in created:
 
450
            delete_profile(prof)
 
451
        sys.exit(0)
 
452
 
 
453
def get_profile(prof_name):
 
454
    profile_data = None
 
455
    distro = cfg['repository']['distro']
 
456
    repo_url = cfg['repository']['url']
 
457
    # local_profiles = []
 
458
    profile_hash = hasher()
 
459
    if repo_is_enabled():
 
460
        aaui.UI_BusyStart(_('Connecting to repository...'))
 
461
        status_ok, ret = fetch_profiles_by_name(repo_url, distro, prof_name)
 
462
        aaui.UI_BusyStop()
 
463
        if status_ok:
 
464
            profile_hash = ret
 
465
        else:
 
466
            aaui.UI_Important(_('WARNING: Error fetching profiles from the repository'))
 
467
    inactive_profile = get_inactive_profile(prof_name)
 
468
    if inactive_profile:
 
469
        uname = 'Inactive local profile for %s' % prof_name
 
470
        inactive_profile[prof_name][prof_name]['flags'] = 'complain'
 
471
        inactive_profile[prof_name][prof_name].pop('filename')
 
472
        profile_hash[uname]['username'] = uname
 
473
        profile_hash[uname]['profile_type'] = 'INACTIVE_LOCAL'
 
474
        profile_hash[uname]['profile'] = serialize_profile(inactive_profile[prof_name], prof_name)
 
475
        profile_hash[uname]['profile_data'] = inactive_profile
 
476
    # If no profiles in repo and no inactive profiles
 
477
    if not profile_hash.keys():
 
478
        return None
 
479
    options = []
 
480
    tmp_list = []
 
481
    preferred_present = False
 
482
    preferred_user = cfg['repository'].get('preferred_user', 'NOVELL')
 
483
 
 
484
    for p in profile_hash.keys():
 
485
        if profile_hash[p]['username'] == preferred_user:
 
486
            preferred_present = True
 
487
        else:
 
488
            tmp_list.append(profile_hash[p]['username'])
 
489
 
 
490
    if preferred_present:
 
491
        options.append(preferred_user)
 
492
    options += tmp_list
 
493
 
 
494
    q = dict()
 
495
    q['headers'] = ['Profile', prof_name]
 
496
    q['functions'] = ['CMD_VIEW_PROFILE', 'CMD_USE_PROFILE', 'CMD_CREATE_PROFILE',
 
497
                      'CMD_ABORT', 'CMD_FINISHED']
 
498
    q['default'] = "CMD_VIEW_PROFILE"
 
499
    q['options'] = options
 
500
    q['selected'] = 0
 
501
 
 
502
    ans = ''
 
503
    while 'CMD_USE_PROFILE' not in ans and 'CMD_CREATE_PROFILE' not in ans:
 
504
        if ans == 'CMD_FINISHED':
 
505
            save_profiles()
 
506
            return
 
507
 
 
508
        ans, arg = aaui.UI_PromptUser(q)
 
509
        p = profile_hash[options[arg]]
 
510
        q['selected'] = options.index(options[arg])
 
511
        if ans == 'CMD_VIEW_PROFILE':
 
512
            if aaui.UI_mode == 'yast':
 
513
                SendDataToYast({'type': 'dialogue-view-profile',
 
514
                                'user': options[arg],
 
515
                                'profile': p['profile'],
 
516
                                'profile_type': p['profile_type']
 
517
                                })
 
518
                ypath, yarg = GetDataFromYast()
 
519
            #else:
 
520
            #    pager = get_pager()
 
521
            #    proc = subprocess.Popen(pager, stdin=subprocess.PIPE)
 
522
            #    proc.communicate('Profile submitted by %s:\n\n%s\n\n' %
 
523
            #                     (options[arg], p['profile']))
 
524
            #    proc.kill()
 
525
        elif ans == 'CMD_USE_PROFILE':
 
526
            if p['profile_type'] == 'INACTIVE_LOCAL':
 
527
                profile_data = p['profile_data']
 
528
                created.append(prof_name)
 
529
            else:
 
530
                profile_data = parse_repo_profile(prof_name, repo_url, p)
 
531
    return profile_data
 
532
 
 
533
def activate_repo_profiles(url, profiles, complain):
 
534
    read_profiles()
 
535
    try:
 
536
        for p in profiles:
 
537
            pname = p[0]
 
538
            profile_data = parse_repo_profile(pname, url, p[1])
 
539
            attach_profile_data(aa, profile_data)
 
540
            write_profile(pname)
 
541
            if complain:
 
542
                fname = get_profile_filename(pname)
 
543
                set_profile_flags(profile_dir + fname, 'complain')
 
544
                aaui.UI_Info(_('Setting %s to complain mode.') % pname)
 
545
    except Exception as e:
 
546
            sys.stderr.write(_("Error activating profiles: %s") % e)
 
547
 
 
548
def autodep(bin_name, pname=''):
 
549
    bin_full = None
 
550
    global repo_cfg
 
551
    if not bin_name and pname.startswith('/'):
 
552
        bin_name = pname
 
553
    if not repo_cfg and not cfg['repository'].get('url', False):
 
554
        repo_conf = apparmor.config.Config('shell', CONFDIR)
 
555
        repo_cfg = repo_conf.read_config('repository.conf')
 
556
        if not repo_cfg.get('repository', False) or repo_cfg['repository']['enabled'] == 'later':
 
557
            UI_ask_to_enable_repo()
 
558
    if bin_name:
 
559
        bin_full = find_executable(bin_name)
 
560
        #if not bin_full:
 
561
        #    bin_full = bin_name
 
562
        #if not bin_full.startswith('/'):
 
563
            #return None
 
564
        # Return if exectuable path not found
 
565
        if not bin_full:
 
566
            return None
 
567
    else:
 
568
        bin_full = pname  # for named profiles
 
569
 
 
570
    pname = bin_full
 
571
    read_inactive_profiles()
 
572
    profile_data = get_profile(pname)
 
573
    # Create a new profile if no existing profile
 
574
    if not profile_data:
 
575
        profile_data = create_new_profile(pname)
 
576
    file = get_profile_filename(pname)
 
577
    attach_profile_data(aa, profile_data)
 
578
    attach_profile_data(original_aa, profile_data)
 
579
    if os.path.isfile(profile_dir + '/tunables/global'):
 
580
        if not filelist.get(file, False):
 
581
            filelist[file] = hasher()
 
582
        filelist[file]['include']['tunables/global'] = True
 
583
        filelist[file]['profiles'][pname] = True
 
584
    write_profile_ui_feedback(pname)
 
585
 
 
586
def get_profile_flags(filename, program):
 
587
    # To-Do
 
588
    # XXX If more than one profile in a file then second one is being ignored XXX
 
589
    # Do we return flags for both or
 
590
    flags = ''
 
591
    with open_file_read(filename) as f_in:
 
592
        for line in f_in:
 
593
            if RE_PROFILE_START.search(line):
 
594
                matches = RE_PROFILE_START.search(line).groups()
 
595
                profile = matches[1] or matches[3]
 
596
                flags = matches[6]
 
597
                if profile == program or program is None:
 
598
                    return flags
 
599
 
 
600
    raise AppArmorException(_('%s contains no profile') % filename)
 
601
 
 
602
def change_profile_flags(filename, program, flag, set_flag):
 
603
    old_flags = get_profile_flags(filename, program)
 
604
    newflags = []
 
605
    if old_flags:
 
606
        # Flags maybe white-space and/or , separated
 
607
        old_flags = old_flags.split(',')
 
608
 
 
609
        if not isinstance(old_flags, str):
 
610
            for i in old_flags:
 
611
                newflags += i.split()
 
612
        else:
 
613
            newflags = old_flags.split()
 
614
        #newflags = [lambda x:x.strip(), oldflags]
 
615
 
 
616
    if set_flag:
 
617
        if flag not in newflags:
 
618
            newflags.append(flag)
 
619
    else:
 
620
        if flag in newflags:
 
621
            newflags.remove(flag)
 
622
 
 
623
    newflags = ','.join(newflags)
 
624
 
 
625
    set_profile_flags(filename, program, newflags)
 
626
 
 
627
def set_profile_flags(prof_filename, program, newflags):
 
628
    """Reads the old profile file and updates the flags accordingly"""
 
629
    regex_bin_flag = re.compile('^(\s*)(("??/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.*)\)\s+)?\{\s*(#.*)?$')
 
630
    regex_hat_flag = re.compile('^([a-z]*)\s+([A-Z]*)\s*(#.*)?$')
 
631
    if os.path.isfile(prof_filename):
 
632
        with open_file_read(prof_filename) as f_in:
 
633
            temp_file = tempfile.NamedTemporaryFile('w', prefix=prof_filename, suffix='~', delete=False, dir=profile_dir)
 
634
            shutil.copymode(prof_filename, temp_file.name)
 
635
            with open_file_write(temp_file.name) as f_out:
 
636
                for line in f_in:
 
637
                    comment = ''
 
638
                    if '#' in line:
 
639
                        comment = '#' + line.split('#', 1)[1].rstrip()
 
640
                    match = regex_bin_flag.search(line)
 
641
                    if not line.strip() or line.strip().startswith('#'):
 
642
                        pass
 
643
                    elif match:
 
644
                        matches = match.groups()
 
645
                        space = matches[0]
 
646
                        binary = matches[1]
 
647
                        flag = matches[6] or 'flags='
 
648
                        flags = matches[7]
 
649
                        if binary == program or program is None:
 
650
                            if newflags:
 
651
                                line = '%s%s %s(%s) {%s\n' % (space, binary, flag, newflags, comment)
 
652
                            else:
 
653
                                line = '%s%s {%s\n' % (space, binary, comment)
 
654
                    else:
 
655
                        match = regex_hat_flag.search(line)
 
656
                        if match:
 
657
                            hat, flags = match.groups()[:2]
 
658
                            if newflags:
 
659
                                line = '%s flags=(%s) {%s\n' % (hat, newflags, comment)
 
660
                            else:
 
661
                                line = '%s {%s\n' % (hat, comment)
 
662
                    f_out.write(line)
 
663
        os.rename(temp_file.name, prof_filename)
 
664
 
 
665
def profile_exists(program):
 
666
    """Returns True if profile exists, False otherwise"""
 
667
    # Check cache of profiles
 
668
 
 
669
    if existing_profiles.get(program, False):
 
670
        return True
 
671
    # Check the disk for profile
 
672
    prof_path = get_profile_filename(program)
 
673
    #print(prof_path)
 
674
    if os.path.isfile(prof_path):
 
675
        # Add to cache of profile
 
676
        existing_profiles[program] = prof_path
 
677
        return True
 
678
    return False
 
679
 
 
680
def sync_profile():
 
681
    user, passw = get_repo_user_pass()
 
682
    if not user or not passw:
 
683
        return None
 
684
    repo_profiles = []
 
685
    changed_profiles = []
 
686
    new_profiles = []
 
687
    serialize_opts = hasher()
 
688
    status_ok, ret = fetch_profiles_by_user(cfg['repository']['url'],
 
689
                                            cfg['repository']['distro'], user)
 
690
    if not status_ok:
 
691
        if not ret:
 
692
            ret = 'UNKNOWN ERROR'
 
693
        aaui.UI_Important(_('WARNING: Error synchronizing profiles with the repository:\n%s\n') % ret)
 
694
    else:
 
695
        users_repo_profiles = ret
 
696
        serialize_opts['NO_FLAGS'] = True
 
697
        for prof in sorted(aa.keys()):
 
698
            if is_repo_profile([aa[prof][prof]]):
 
699
                repo_profiles.append(prof)
 
700
            if prof in created:
 
701
                p_local = serialize_profile(aa[prof], prof, serialize_opts)
 
702
                if not users_repo_profiles.get(prof, False):
 
703
                    new_profiles.append(prof)
 
704
                    new_profiles.append(p_local)
 
705
                    new_profiles.append('')
 
706
                else:
 
707
                    p_repo = users_repo_profiles[prof]['profile']
 
708
                    if p_local != p_repo:
 
709
                        changed_profiles.append(prof)
 
710
                        changed_profiles.append(p_local)
 
711
                        changed_profiles.append(p_repo)
 
712
        if repo_profiles:
 
713
            for prof in repo_profiles:
 
714
                p_local = serialize_profile(aa[prof], prof, serialize_opts)
 
715
                if not users_repo_profiles.get(prof, False):
 
716
                    new_profiles.append(prof)
 
717
                    new_profiles.append(p_local)
 
718
                    new_profiles.append('')
 
719
                else:
 
720
                    p_repo = ''
 
721
                    if aa[prof][prof]['repo']['user'] == user:
 
722
                        p_repo = users_repo_profiles[prof]['profile']
 
723
                    else:
 
724
                        status_ok, ret = fetch_profile_by_id(cfg['repository']['url'],
 
725
                                                             aa[prof][prof]['repo']['id'])
 
726
                        if status_ok:
 
727
                            p_repo = ret['profile']
 
728
                        else:
 
729
                            if not ret:
 
730
                                ret = 'UNKNOWN ERROR'
 
731
                            aaui.UI_Important(_('WARNING: Error synchronizing profiles with the repository\n%s') % ret)
 
732
                            continue
 
733
                    if p_repo != p_local:
 
734
                        changed_profiles.append(prof)
 
735
                        changed_profiles.append(p_local)
 
736
                        changed_profiles.append(p_repo)
 
737
        if changed_profiles:
 
738
            submit_changed_profiles(changed_profiles)
 
739
        if new_profiles:
 
740
            submit_created_profiles(new_profiles)
 
741
 
 
742
def fetch_profile_by_id(url, id):
 
743
    #To-Do
 
744
    return None, None
 
745
 
 
746
def fetch_profiles_by_name(url, distro, user):
 
747
    #to-Do
 
748
    return None, None
 
749
 
 
750
def fetch_profiles_by_user(url, distro, user):
 
751
    #to-Do
 
752
    return None, None
 
753
 
 
754
def submit_created_profiles(new_profiles):
 
755
    #url = cfg['repository']['url']
 
756
    if new_profiles:
 
757
        if aaui.UI_mode == 'yast':
 
758
            title = 'New Profiles'
 
759
            message = 'Please select the newly created profiles that you would like to store in the repository'
 
760
            yast_select_and_upload_profiles(title, message, new_profiles)
 
761
        else:
 
762
            title = 'Submit newly created profiles to the repository'
 
763
            message = 'Would you like to upload newly created profiles?'
 
764
            console_select_and_upload_profiles(title, message, new_profiles)
 
765
 
 
766
def submit_changed_profiles(changed_profiles):
 
767
    #url = cfg['repository']['url']
 
768
    if changed_profiles:
 
769
        if aaui.UI_mode == 'yast':
 
770
            title = 'Changed Profiles'
 
771
            message = 'Please select which of the changed profiles would you like to upload to the repository'
 
772
            yast_select_and_upload_profiles(title, message, changed_profiles)
 
773
        else:
 
774
            title = 'Submit changed profiles to the repository'
 
775
            message = 'The following profiles from the repository were changed.\nWould you like to upload your changes?'
 
776
            console_select_and_upload_profiles(title, message, changed_profiles)
 
777
 
 
778
def yast_select_and_upload_profiles(title, message, profiles_up):
 
779
    url = cfg['repository']['url']
 
780
    profile_changes = hasher()
 
781
    profs = profiles_up[:]
 
782
    for p in profs:
 
783
        profile_changes[p[0]] = get_profile_diff(p[2], p[1])
 
784
    SendDataToYast({'type': 'dialog-select-profiles',
 
785
                    'title': title,
 
786
                    'explanation': message,
 
787
                    'default_select': 'false',
 
788
                    'disable_ask_upload': 'true',
 
789
                    'profiles': profile_changes
 
790
                    })
 
791
    ypath, yarg = GetDataFromYast()
 
792
    selected_profiles = []
 
793
    changelog = None
 
794
    changelogs = None
 
795
    single_changelog = False
 
796
    if yarg['STATUS'] == 'cancel':
 
797
        return
 
798
    else:
 
799
        selected_profiles = yarg['PROFILES']
 
800
        changelogs = yarg['CHANGELOG']
 
801
        if changelogs.get('SINGLE_CHANGELOG', False):
 
802
            changelog = changelogs['SINGLE_CHANGELOG']
 
803
            single_changelog = True
 
804
    user, passw = get_repo_user_pass()
 
805
    for p in selected_profiles:
 
806
        profile_string = serialize_profile(aa[p], p)
 
807
        if not single_changelog:
 
808
            changelog = changelogs[p]
 
809
        status_ok, ret = upload_profile(url, user, passw, cfg['repository']['distro'],
 
810
                                        p, profile_string, changelog)
 
811
        if status_ok:
 
812
            newprofile = ret
 
813
            newid = newprofile['id']
 
814
            set_repo_info(aa[p][p], url, user, newid)
 
815
            write_profile_ui_feedback(p)
 
816
        else:
 
817
            if not ret:
 
818
                ret = 'UNKNOWN ERROR'
 
819
            aaui.UI_Important(_('WARNING: An error occurred while uploading the profile %s\n%s') % (p, ret))
 
820
    aaui.UI_Info(_('Uploaded changes to repository.'))
 
821
    if yarg.get('NEVER_ASK_AGAIN'):
 
822
        unselected_profiles = []
 
823
        for p in profs:
 
824
            if p[0] not in selected_profiles:
 
825
                unselected_profiles.append(p[0])
 
826
        set_profiles_local_only(unselected_profiles)
 
827
 
 
828
def upload_profile(url, user, passw, distro, p, profile_string, changelog):
 
829
    # To-Do
 
830
    return None, None
 
831
 
 
832
def console_select_and_upload_profiles(title, message, profiles_up):
 
833
    url = cfg['repository']['url']
 
834
    profs = profiles_up[:]
 
835
    q = hasher()
 
836
    q['title'] = title
 
837
    q['headers'] = ['Repository', url]
 
838
    q['explanation'] = message
 
839
    q['functions'] = ['CMD_UPLOAD_CHANGES', 'CMD_VIEW_CHANGES', 'CMD_ASK_LATER',
 
840
                      'CMD_ASK_NEVER', 'CMD_ABORT']
 
841
    q['default'] = 'CMD_VIEW_CHANGES'
 
842
    q['options'] = [i[0] for i in profs]
 
843
    q['selected'] = 0
 
844
    ans = ''
 
845
    while 'CMD_UPLOAD_CHANGES' not in ans and 'CMD_ASK_NEVER' not in ans and 'CMD_ASK_LATER' not in ans:
 
846
        ans, arg = aaui.UI_PromptUser(q)
 
847
        if ans == 'CMD_VIEW_CHANGES':
 
848
            display_changes(profs[arg][2], profs[arg][1])
 
849
    if ans == 'CMD_NEVER_ASK':
 
850
        set_profiles_local_only([i[0] for i in profs])
 
851
    elif ans == 'CMD_UPLOAD_CHANGES':
 
852
        changelog = aaui.UI_GetString(_('Changelog Entry: '), '')
 
853
        user, passw = get_repo_user_pass()
 
854
        if user and passw:
 
855
            for p_data in profs:
 
856
                prof = p_data[0]
 
857
                prof_string = p_data[1]
 
858
                status_ok, ret = upload_profile(url, user, passw,
 
859
                                                cfg['repository']['distro'],
 
860
                                                prof, prof_string, changelog)
 
861
                if status_ok:
 
862
                    newprof = ret
 
863
                    newid = newprof['id']
 
864
                    set_repo_info(aa[prof][prof], url, user, newid)
 
865
                    write_profile_ui_feedback(prof)
 
866
                    aaui.UI_Info('Uploaded %s to repository' % prof)
 
867
                else:
 
868
                    if not ret:
 
869
                        ret = 'UNKNOWN ERROR'
 
870
                    aaui.UI_Important(_('WARNING: An error occurred while uploading the profile %s\n%s') % (prof, ret))
 
871
        else:
 
872
            aaui.UI_Important(_('Repository Error\nRegistration or Signin was unsuccessful. User login\ninformation is required to upload profiles to the repository.\nThese changes could not be sent.'))
 
873
 
 
874
def set_profiles_local_only(profs):
 
875
    for p in profs:
 
876
        aa[profs][profs]['repo']['neversubmit'] = True
 
877
        write_profile_ui_feedback(profs)
 
878
 
 
879
 
 
880
def build_x_functions(default, options, exec_toggle):
 
881
    ret_list = []
 
882
    fallback_toggle = False
 
883
    if exec_toggle:
 
884
        if 'i' in options:
 
885
            ret_list.append('CMD_ix')
 
886
            if 'p' in options:
 
887
                ret_list.append('CMD_pix')
 
888
                fallback_toggle = True
 
889
            if 'c' in options:
 
890
                ret_list.append('CMD_cix')
 
891
                fallback_toggle = True
 
892
            if 'n' in options:
 
893
                ret_list.append('CMD_nix')
 
894
                fallback_toggle = True
 
895
            if fallback_toggle:
 
896
                ret_list.append('CMD_EXEC_IX_OFF')
 
897
        if 'u' in options:
 
898
            ret_list.append('CMD_ux')
 
899
 
 
900
    else:
 
901
        if 'i' in options:
 
902
            ret_list.append('CMD_ix')
 
903
        if 'c' in options:
 
904
            ret_list.append('CMD_cx')
 
905
            fallback_toggle = True
 
906
        if 'p' in options:
 
907
            ret_list.append('CMD_px')
 
908
            fallback_toggle = True
 
909
        if 'n' in options:
 
910
            ret_list.append('CMD_nx')
 
911
            fallback_toggle = True
 
912
        if 'u' in options:
 
913
            ret_list.append('CMD_ux')
 
914
 
 
915
        if fallback_toggle:
 
916
            ret_list.append('CMD_EXEC_IX_ON')
 
917
 
 
918
    ret_list += ['CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED']
 
919
    return ret_list
 
920
 
 
921
def handle_children(profile, hat, root):
 
922
    entries = root[:]
 
923
    pid = None
 
924
    p = None
 
925
    h = None
 
926
    prog = None
 
927
    aamode = None
 
928
    mode = None
 
929
    detail = None
 
930
    to_name = None
 
931
    uhat = None
 
932
    capability = None
 
933
    family = None
 
934
    sock_type = None
 
935
    protocol = None
 
936
    global seen_events
 
937
    regex_nullcomplain = re.compile('^null(-complain)*-profile$')
 
938
 
 
939
    for entry in entries:
 
940
        if type(entry[0]) != str:
 
941
            handle_children(profile, hat, entry)
 
942
        else:
 
943
            typ = entry.pop(0)
 
944
            if typ == 'fork':
 
945
                # If type is fork then we (should) have pid, profile and hat
 
946
                pid, p, h = entry[:3]
 
947
                if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h):
 
948
                    profile = p
 
949
                    hat = h
 
950
                if hat:
 
951
                    profile_changes[pid] = profile + '//' + hat
 
952
                else:
 
953
                    profile_changes[pid] = profile
 
954
            elif typ == 'unknown_hat':
 
955
                # If hat is not known then we (should) have pid, profile, hat, mode and unknown hat in entry
 
956
                pid, p, h, aamode, uhat = entry[:5]
 
957
                if not regex_nullcomplain.search(p):
 
958
                    profile = p
 
959
                if aa[profile].get(uhat, False):
 
960
                    hat = uhat
 
961
                    continue
 
962
                new_p = update_repo_profile(aa[profile][profile])
 
963
                if new_p and UI_SelectUpdatedRepoProfile(profile, new_p) and aa[profile].get(uhat, False):
 
964
                    hat = uhat
 
965
                    continue
 
966
 
 
967
                default_hat = None
 
968
                for hatglob in cfg.options('defaulthat'):
 
969
                    if re.search(hatglob, profile):
 
970
                        default_hat = cfg['defaulthat'][hatglob]
 
971
 
 
972
                context = profile
 
973
                context = context + ' -> ^%s' % uhat
 
974
                ans = transitions.get(context, 'XXXINVALIDXXX')
 
975
 
 
976
                while ans not in ['CMD_ADDHAT', 'CMD_USEDEFAULT', 'CMD_DENY']:
 
977
                    q = hasher()
 
978
                    q['headers'] = []
 
979
                    q['headers'] += [_('Profile'), profile]
 
980
 
 
981
                    if default_hat:
 
982
                        q['headers'] += [_('Default Hat'), default_hat]
 
983
 
 
984
                    q['headers'] += [_('Requested Hat'), uhat]
 
985
 
 
986
                    q['functions'] = []
 
987
                    q['functions'].append('CMD_ADDHAT')
 
988
                    if default_hat:
 
989
                        q['functions'].append('CMD_USEDEFAULT')
 
990
                    q['functions'] += ['CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED']
 
991
 
 
992
                    q['default'] = 'CMD_DENY'
 
993
                    if aamode == 'PERMITTING':
 
994
                        q['default'] = 'CMD_ADDHAT'
 
995
 
 
996
                    seen_events += 1
 
997
 
 
998
                    ans = aaui.UI_PromptUser(q)
 
999
 
 
1000
                    if ans == 'CMD_FINISHED':
 
1001
                        save_profiles()
 
1002
                        return
 
1003
 
 
1004
                transitions[context] = ans
 
1005
 
 
1006
                if ans == 'CMD_ADDHAT':
 
1007
                    hat = uhat
 
1008
                    aa[profile][hat]['flags'] = aa[profile][profile]['flags']
 
1009
                elif ans == 'CMD_USEDEFAULT':
 
1010
                    hat = default_hat
 
1011
                elif ans == 'CMD_DENY':
 
1012
                    # As unknown hat is denied no entry for it should be made
 
1013
                    return None
 
1014
 
 
1015
            elif typ == 'capability':
 
1016
                # If capability then we (should) have pid, profile, hat, program, mode, capability
 
1017
                pid, p, h, prog, aamode, capability = entry[:6]
 
1018
                if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h):
 
1019
                    profile = p
 
1020
                    hat = h
 
1021
                if not profile or not hat:
 
1022
                    continue
 
1023
                prelog[aamode][profile][hat]['capability'][capability] = True
 
1024
 
 
1025
            elif typ == 'path' or typ == 'exec':
 
1026
                # If path or exec then we (should) have pid, profile, hat, program, mode, details and to_name
 
1027
                pid, p, h, prog, aamode, mode, detail, to_name = entry[:8]
 
1028
                if not mode:
 
1029
                    mode = set()
 
1030
                if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h):
 
1031
                    profile = p
 
1032
                    hat = h
 
1033
                if not profile or not hat or not detail:
 
1034
                    continue
 
1035
 
 
1036
                domainchange = 'nochange'
 
1037
                if typ == 'exec':
 
1038
                    domainchange = 'change'
 
1039
 
 
1040
                # Escape special characters
 
1041
                detail = detail.replace('[', '\[')
 
1042
                detail = detail.replace(']', '\]')
 
1043
                detail = detail.replace('+', '\+')
 
1044
                detail = detail.replace('*', '\*')
 
1045
                detail = detail.replace('{', '\{')
 
1046
                detail = detail.replace('}', '\}')
 
1047
 
 
1048
                # Give Execute dialog if x access requested for something that's not a directory
 
1049
                # For directories force an 'ix' Path dialog
 
1050
                do_execute = False
 
1051
                exec_target = detail
 
1052
 
 
1053
                if mode & str_to_mode('x'):
 
1054
                    if os.path.isdir(exec_target):
 
1055
                        mode = mode - apparmor.aamode.ALL_AA_EXEC_TYPE
 
1056
                        mode = mode | str_to_mode('ix')
 
1057
                    else:
 
1058
                        do_execute = True
 
1059
 
 
1060
                if mode & apparmor.aamode.AA_MAY_LINK:
 
1061
                    regex_link = re.compile('^from (.+) to (.+)$')
 
1062
                    match = regex_link.search(detail)
 
1063
                    if match:
 
1064
                        path = match.groups()[0]
 
1065
                        target = match.groups()[1]
 
1066
 
 
1067
                        frommode = str_to_mode('lr')
 
1068
                        if prelog[aamode][profile][hat]['path'].get(path, False):
 
1069
                            frommode |= prelog[aamode][profile][hat]['path'][path]
 
1070
                        prelog[aamode][profile][hat]['path'][path] = frommode
 
1071
 
 
1072
                        tomode = str_to_mode('lr')
 
1073
                        if prelog[aamode][profile][hat]['path'].get(target, False):
 
1074
                            tomode |= prelog[aamode][profile][hat]['path'][target]
 
1075
                        prelog[aamode][profile][hat]['path'][target] = tomode
 
1076
                    else:
 
1077
                        continue
 
1078
                elif mode:
 
1079
                    path = detail
 
1080
 
 
1081
                    if prelog[aamode][profile][hat]['path'].get(path, False):
 
1082
                        mode |= prelog[aamode][profile][hat]['path'][path]
 
1083
                    prelog[aamode][profile][hat]['path'][path] = mode
 
1084
 
 
1085
                if do_execute:
 
1086
                    if profile_known_exec(aa[profile][hat], 'exec', exec_target):
 
1087
                        continue
 
1088
 
 
1089
                    p = update_repo_profile(aa[profile][profile])
 
1090
                    if to_name:
 
1091
                        if UI_SelectUpdatedRepoProfile(profile, p) and profile_known_exec(aa[profile][hat], 'exec', to_name):
 
1092
                            continue
 
1093
                    else:
 
1094
                        if UI_SelectUpdatedRepoProfile(profile, p) and profile_known_exec(aa[profile][hat], 'exec', exec_target):
 
1095
                            continue
 
1096
 
 
1097
                    context_new = profile
 
1098
                    if profile != hat:
 
1099
                        context_new = context_new + '^%s' % hat
 
1100
                    context_new = context_new + ' -> %s' % exec_target
 
1101
 
 
1102
                    # ans_new = transitions.get(context_new, '')  # XXX ans meant here?
 
1103
                    combinedmode = set()
 
1104
                    combinedaudit = set()
 
1105
                    ## Check return Value Consistency
 
1106
                    # Check if path matches any existing regexps in profile
 
1107
                    cm, am, m = rematchfrag(aa[profile][hat], 'allow', exec_target)
 
1108
                    if cm:
 
1109
                        combinedmode |= cm
 
1110
                    if am:
 
1111
                        combinedaudit |= am
 
1112
 
 
1113
                    if combinedmode & str_to_mode('x'):
 
1114
                        nt_name = None
 
1115
                        for entr in m:
 
1116
                            if aa[profile][hat]['allow']['path'].get(entr, False):
 
1117
                                nt_name = aa[profile][hat]
 
1118
                                break
 
1119
                        if to_name and to_name != nt_name:
 
1120
                            pass
 
1121
                        elif nt_name:
 
1122
                            to_name = nt_name
 
1123
                    ## Check return value consistency
 
1124
                    # Check if the includes from profile match
 
1125
                    cm, am, m = match_prof_incs_to_path(aa[profile][hat], 'allow', exec_target)
 
1126
                    if cm:
 
1127
                        combinedmode |= cm
 
1128
                    if am:
 
1129
                        combinedaudit |= am
 
1130
                    if combinedmode & str_to_mode('x'):
 
1131
                        nt_name = None
 
1132
                        for entr in m:
 
1133
                            if aa[profile][hat]['allow']['path'][entry]['to']:
 
1134
                                nt_name = aa[profile][hat]['allow']['path'][entry]['to']
 
1135
                                break
 
1136
                        if to_name and to_name != nt_name:
 
1137
                            pass
 
1138
                        elif nt_name:
 
1139
                            to_name = nt_name
 
1140
 
 
1141
                    # nx is not used in profiles but in log files.
 
1142
                    # Log parsing methods will convert it to its profile form
 
1143
                    # nx is internally cx/px/cix/pix + to_name
 
1144
                    exec_mode = False
 
1145
                    if contains(combinedmode, 'pix'):
 
1146
                        if to_name:
 
1147
                            ans = 'CMD_nix'
 
1148
                        else:
 
1149
                            ans = 'CMD_pix'
 
1150
                        exec_mode = str_to_mode('pixr')
 
1151
                    elif contains(combinedmode, 'cix'):
 
1152
                        if to_name:
 
1153
                            ans = 'CMD_nix'
 
1154
                        else:
 
1155
                            ans = 'CMD_cix'
 
1156
                        exec_mode = str_to_mode('cixr')
 
1157
                    elif contains(combinedmode, 'Pix'):
 
1158
                        if to_name:
 
1159
                            ans = 'CMD_nix_safe'
 
1160
                        else:
 
1161
                            ans = 'CMD_pix_safe'
 
1162
                        exec_mode = str_to_mode('Pixr')
 
1163
                    elif contains(combinedmode, 'Cix'):
 
1164
                        if to_name:
 
1165
                            ans = 'CMD_nix_safe'
 
1166
                        else:
 
1167
                            ans = 'CMD_cix_safe'
 
1168
                        exec_mode = str_to_mode('Cixr')
 
1169
                    elif contains(combinedmode, 'ix'):
 
1170
                        ans = 'CMD_ix'
 
1171
                        exec_mode = str_to_mode('ixr')
 
1172
                    elif contains(combinedmode, 'px'):
 
1173
                        if to_name:
 
1174
                            ans = 'CMD_nx'
 
1175
                        else:
 
1176
                            ans = 'CMD_px'
 
1177
                        exec_mode = str_to_mode('px')
 
1178
                    elif contains(combinedmode, 'cx'):
 
1179
                        if to_name:
 
1180
                            ans = 'CMD_nx'
 
1181
                        else:
 
1182
                            ans = 'CMD_cx'
 
1183
                        exec_mode = str_to_mode('cx')
 
1184
                    elif contains(combinedmode, 'ux'):
 
1185
                        ans = 'CMD_ux'
 
1186
                        exec_mode = str_to_mode('ux')
 
1187
                    elif contains(combinedmode, 'Px'):
 
1188
                        if to_name:
 
1189
                            ans = 'CMD_nx_safe'
 
1190
                        else:
 
1191
                            ans = 'CMD_px_safe'
 
1192
                        exec_mode = str_to_mode('Px')
 
1193
                    elif contains(combinedmode, 'Cx'):
 
1194
                        if to_name:
 
1195
                            ans = 'CMD_nx_safe'
 
1196
                        else:
 
1197
                            ans = 'CMD_cx_safe'
 
1198
                        exec_mode = str_to_mode('Cx')
 
1199
                    elif contains(combinedmode, 'Ux'):
 
1200
                        ans = 'CMD_ux_safe'
 
1201
                        exec_mode = str_to_mode('Ux')
 
1202
                    else:
 
1203
                        options = cfg['qualifiers'].get(exec_target, 'ipcnu')
 
1204
                        if to_name:
 
1205
                            fatal_error(_('%s has transition name but not transition mode') % entry)
 
1206
 
 
1207
                        ### If profiled program executes itself only 'ix' option
 
1208
                        ##if exec_target == profile:
 
1209
                            ##options = 'i'
 
1210
 
 
1211
                        # Don't allow hats to cx?
 
1212
                        options.replace('c', '')
 
1213
                        # Add deny to options
 
1214
                        options += 'd'
 
1215
                        # Define the default option
 
1216
                        default = None
 
1217
                        if 'p' in options and os.path.exists(get_profile_filename(exec_target)):
 
1218
                            default = 'CMD_px'
 
1219
                            sys.stdout.write(_('Target profile exists: %s\n') % get_profile_filename(exec_target))
 
1220
                        elif 'i' in options:
 
1221
                            default = 'CMD_ix'
 
1222
                        elif 'c' in options:
 
1223
                            default = 'CMD_cx'
 
1224
                        elif 'n' in options:
 
1225
                            default = 'CMD_nx'
 
1226
                        else:
 
1227
                            default = 'DENY'
 
1228
 
 
1229
                        #
 
1230
                        parent_uses_ld_xxx = check_for_LD_XXX(profile)
 
1231
 
 
1232
                        sev_db.unload_variables()
 
1233
                        sev_db.load_variables(profile)
 
1234
                        severity = sev_db.rank(exec_target, 'x')
 
1235
 
 
1236
                        # Prompt portion starts
 
1237
                        q = hasher()
 
1238
                        q['headers'] = []
 
1239
                        q['headers'] += [_('Profile'), combine_name(profile, hat)]
 
1240
                        if prog and prog != 'HINT':
 
1241
                            q['headers'] += [_('Program'), prog]
 
1242
 
 
1243
                        # to_name should not exist here since, transitioning is already handeled
 
1244
                        q['headers'] += [_('Execute'), exec_target]
 
1245
                        q['headers'] += [_('Severity'), severity]
 
1246
 
 
1247
                        q['functions'] = []
 
1248
                        # prompt = '\n%s\n' % context_new  # XXX
 
1249
                        exec_toggle = False
 
1250
                        q['functions'] += build_x_functions(default, options, exec_toggle)
 
1251
 
 
1252
                        # options = '|'.join(options)
 
1253
                        seen_events += 1
 
1254
                        regex_options = re.compile('^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)$')
 
1255
 
 
1256
                        ans = ''
 
1257
                        while not regex_options.search(ans):
 
1258
                            ans = aaui.UI_PromptUser(q)[0].strip()
 
1259
                            if ans.startswith('CMD_EXEC_IX_'):
 
1260
                                exec_toggle = not exec_toggle
 
1261
                                q['functions'] = []
 
1262
                                q['functions'] += build_x_functions(default, options, exec_toggle)
 
1263
                                ans = ''
 
1264
                                continue
 
1265
 
 
1266
                            if ans == 'CMD_FINISHED':
 
1267
                                save_profiles()
 
1268
                                return
 
1269
 
 
1270
                            if ans == 'CMD_nx' or ans == 'CMD_nix':
 
1271
                                arg = exec_target
 
1272
                                ynans = 'n'
 
1273
                                if profile == hat:
 
1274
                                    ynans = aaui.UI_YesNo(_('Are you specifying a transition to a local profile?'), 'n')
 
1275
                                if ynans == 'y':
 
1276
                                    if ans == 'CMD_nx':
 
1277
                                        ans = 'CMD_cx'
 
1278
                                    else:
 
1279
                                        ans = 'CMD_cix'
 
1280
                                else:
 
1281
                                    if ans == 'CMD_nx':
 
1282
                                        ans = 'CMD_px'
 
1283
                                    else:
 
1284
                                        ans = 'CMD_pix'
 
1285
 
 
1286
                                to_name = aaui.UI_GetString(_('Enter profile name to transition to: '), arg)
 
1287
 
 
1288
                            regex_optmode = re.compile('CMD_(px|cx|nx|pix|cix|nix)')
 
1289
                            if ans == 'CMD_ix':
 
1290
                                exec_mode = str_to_mode('ix')
 
1291
                            elif regex_optmode.search(ans):
 
1292
                                match = regex_optmode.search(ans).groups()[0]
 
1293
                                exec_mode = str_to_mode(match)
 
1294
                                px_default = 'n'
 
1295
                                px_msg = _("Should AppArmor sanitise the environment when\nswitching profiles?\n\nSanitising environment is more secure,\nbut some applications depend on the presence\nof LD_PRELOAD or LD_LIBRARY_PATH.")
 
1296
                                if parent_uses_ld_xxx:
 
1297
                                    px_msg = _("Should AppArmor sanitise the environment when\nswitching profiles?\n\nSanitising environment is more secure,\nbut this application appears to be using LD_PRELOAD\nor LD_LIBRARY_PATH and sanitising the environment\ncould cause functionality problems.")
 
1298
 
 
1299
                                ynans = aaui.UI_YesNo(px_msg, px_default)
 
1300
                                if ynans == 'y':
 
1301
                                    # Disable the unsafe mode
 
1302
                                    exec_mode = exec_mode - (apparmor.aamode.AA_EXEC_UNSAFE | AA_OTHER(apparmor.aamode.AA_EXEC_UNSAFE))
 
1303
                            elif ans == 'CMD_ux':
 
1304
                                exec_mode = str_to_mode('ux')
 
1305
                                ynans = aaui.UI_YesNo(_("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')
 
1306
                                if ynans == 'y':
 
1307
                                    ynans = aaui.UI_YesNo(_("Should AppArmor sanitise the environment when\nrunning this program unconfined?\n\nNot sanitising the environment when unconfining\na program opens up significant security holes\nand should be avoided if at all possible."), 'y')
 
1308
                                    if ynans == 'y':
 
1309
                                        # Disable the unsafe mode
 
1310
                                        exec_mode = exec_mode - (apparmor.aamode.AA_EXEC_UNSAFE | AA_OTHER(apparmor.aamode.AA_EXEC_UNSAFE))
 
1311
                                else:
 
1312
                                    ans = 'INVALID'
 
1313
                        transitions[context_new] = ans
 
1314
 
 
1315
                        regex_options = re.compile('CMD_(ix|px|cx|nx|pix|cix|nix)')
 
1316
                        if regex_options.search(ans):
 
1317
                            # For inherit we need r
 
1318
                            if exec_mode & str_to_mode('i'):
 
1319
                                exec_mode |= str_to_mode('r')
 
1320
                        else:
 
1321
                            if ans == 'CMD_DENY':
 
1322
                                aa[profile][hat]['deny']['path'][exec_target]['mode'] = aa[profile][hat]['deny']['path'][exec_target].get('mode', str_to_mode('x')) | str_to_mode('x')
 
1323
                                aa[profile][hat]['deny']['path'][exec_target]['audit'] = aa[profile][hat]['deny']['path'][exec_target].get('audit', set())
 
1324
                                changed[profile] = True
 
1325
                                # Skip remaining events if they ask to deny exec
 
1326
                                if domainchange == 'change':
 
1327
                                    return None
 
1328
 
 
1329
                        if ans != 'CMD_DENY':
 
1330
                            prelog['PERMITTING'][profile][hat]['path'][exec_target] = prelog['PERMITTING'][profile][hat]['path'].get(exec_target, exec_mode) | exec_mode
 
1331
 
 
1332
                            log_dict['PERMITTING'][profile] = hasher()
 
1333
 
 
1334
                            aa[profile][hat]['allow']['path'][exec_target]['mode'] = aa[profile][hat]['allow']['path'][exec_target].get('mode', exec_mode)
 
1335
 
 
1336
                            aa[profile][hat]['allow']['path'][exec_target]['audit'] = aa[profile][hat]['allow']['path'][exec_target].get('audit', set())
 
1337
 
 
1338
                            if to_name:
 
1339
                                aa[profile][hat]['allow']['path'][exec_target]['to'] = to_name
 
1340
 
 
1341
                            changed[profile] = True
 
1342
 
 
1343
                            if exec_mode & str_to_mode('i'):
 
1344
                                #if 'perl' in exec_target:
 
1345
                                #    aa[profile][hat]['include']['abstractions/perl'] = True
 
1346
                                #elif '/bin/bash' in exec_target or '/bin/sh' in exec_target:
 
1347
                                #    aa[profile][hat]['include']['abstractions/bash'] = True
 
1348
                                hashbang = head(exec_target)
 
1349
                                if hashbang.startswith('#!'):
 
1350
                                    interpreter = hashbang[2:].strip()
 
1351
                                    interpreter_path = get_full_path(interpreter)
 
1352
                                    interpreter = re.sub('^(/usr)?/bin/', '', interpreter_path)
 
1353
 
 
1354
                                    aa[profile][hat]['path'][interpreter_path]['mode'] = aa[profile][hat]['path'][interpreter_path].get('mode', str_to_mode('ix')) | str_to_mode('ix')
 
1355
 
 
1356
                                    aa[profile][hat]['path'][interpreter_path]['audit'] = aa[profile][hat]['path'][interpreter_path].get('audit', set())
 
1357
 
 
1358
                                    if interpreter == 'perl':
 
1359
                                        aa[profile][hat]['include']['abstractions/perl'] = True
 
1360
                                    elif interpreter in ['bash', 'dash', 'sh']:
 
1361
                                        aa[profile][hat]['include']['abstractions/bash'] = True
 
1362
 
 
1363
                    # Update tracking info based on kind of change
 
1364
 
 
1365
                    if ans == 'CMD_ix':
 
1366
                        if hat:
 
1367
                            profile_changes[pid] = '%s//%s' % (profile, hat)
 
1368
                        else:
 
1369
                            profile_changes[pid] = '%s//' % profile
 
1370
                    elif re.search('^CMD_(px|nx|pix|nix)', ans):
 
1371
                        if to_name:
 
1372
                            exec_target = to_name
 
1373
                        if aamode == 'PERMITTING':
 
1374
                            if domainchange == 'change':
 
1375
                                profile = exec_target
 
1376
                                hat = exec_target
 
1377
                                profile_changes[pid] = '%s' % profile
 
1378
 
 
1379
                        # Check profile exists for px
 
1380
                        if not os.path.exists(get_profile_filename(exec_target)):
 
1381
                            ynans = 'y'
 
1382
                            if exec_mode & str_to_mode('i'):
 
1383
                                ynans = aaui.UI_YesNo(_('A profile for %s does not exist.\nDo you want to create one?') % exec_target, 'n')
 
1384
                            if ynans == 'y':
 
1385
                                helpers[exec_target] = 'enforce'
 
1386
                                if to_name:
 
1387
                                    autodep('', exec_target)
 
1388
                                else:
 
1389
                                    autodep(exec_target, '')
 
1390
                                reload_base(exec_target)
 
1391
                    elif ans.startswith('CMD_cx') or ans.startswith('CMD_cix'):
 
1392
                        if to_name:
 
1393
                            exec_target = to_name
 
1394
                        if aamode == 'PERMITTING':
 
1395
                            if domainchange == 'change':
 
1396
                                profile_changes[pid] = '%s//%s' % (profile, exec_target)
 
1397
 
 
1398
                        if not aa[profile].get(exec_target, False):
 
1399
                            ynans = 'y'
 
1400
                            if exec_mode & str_to_mode('i'):
 
1401
                                ynans = aaui.UI_YesNo(_('A profile for %s does not exist.\nDo you want to create one?') % exec_target, 'n')
 
1402
                            if ynans == 'y':
 
1403
                                hat = exec_target
 
1404
                                aa[profile][hat]['declared'] = False
 
1405
                                aa[profile][hat]['profile'] = True
 
1406
 
 
1407
                                if profile != hat:
 
1408
                                    aa[profile][hat]['flags'] = aa[profile][profile]['flags']
 
1409
 
 
1410
                                stub_profile = create_new_profile(hat)
 
1411
 
 
1412
                                aa[profile][hat]['flags'] = 'complain'
 
1413
 
 
1414
                                aa[profile][hat]['allow']['path'] = hasher()
 
1415
                                if stub_profile[hat][hat]['allow'].get('path', False):
 
1416
                                    aa[profile][hat]['allow']['path'] = stub_profile[hat][hat]['allow']['path']
 
1417
 
 
1418
                                aa[profile][hat]['include'] = hasher()
 
1419
                                if stub_profile[hat][hat].get('include', False):
 
1420
                                    aa[profile][hat]['include'] = stub_profile[hat][hat]['include']
 
1421
 
 
1422
                                aa[profile][hat]['allow']['netdomain'] = hasher()
 
1423
 
 
1424
                                file_name = aa[profile][profile]['filename']
 
1425
                                filelist[file_name]['profiles'][profile][hat] = True
 
1426
 
 
1427
                    elif ans.startswith('CMD_ux'):
 
1428
                        profile_changes[pid] = 'unconfined'
 
1429
                        if domainchange == 'change':
 
1430
                            return None
 
1431
 
 
1432
            elif typ == 'netdomain':
 
1433
                # If netdomain we (should) have pid, profile, hat, program, mode, network family, socket type and protocol
 
1434
                pid, p, h, prog, aamode, family, sock_type, protocol = entry[:8]
 
1435
 
 
1436
                if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h):
 
1437
                    profile = p
 
1438
                    hat = h
 
1439
                if not hat or not profile:
 
1440
                    continue
 
1441
                if family and sock_type:
 
1442
                    prelog[aamode][profile][hat]['netdomain'][family][sock_type] = True
 
1443
 
 
1444
    return None
 
1445
 
 
1446
PROFILE_MODE_RE = re.compile('r|w|l|m|k|a|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix')
 
1447
PROFILE_MODE_NT_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix')
 
1448
PROFILE_MODE_DENY_RE = re.compile('r|w|l|m|k|a|x')
 
1449
 
 
1450
##### Repo related functions
 
1451
 
 
1452
def UI_SelectUpdatedRepoProfile(profile, p):
 
1453
    # To-Do
 
1454
    return False
 
1455
 
 
1456
def UI_repo_signup():
 
1457
    # To-Do
 
1458
    return None, None
 
1459
 
 
1460
def UI_ask_to_enable_repo():
 
1461
    # To-Do
 
1462
    pass
 
1463
 
 
1464
def UI_ask_to_upload_profiles():
 
1465
    # To-Do
 
1466
    pass
 
1467
 
 
1468
def UI_ask_mode_toggles(audit_toggle, owner_toggle, oldmode):
 
1469
    # To-Do
 
1470
    return (audit_toggle, owner_toggle)
 
1471
 
 
1472
def parse_repo_profile(fqdbin, repo_url, profile):
 
1473
    # To-Do
 
1474
    pass
 
1475
 
 
1476
def set_repo_info(profile_data, repo_url, username, iden):
 
1477
    # To-Do
 
1478
    pass
 
1479
 
 
1480
def is_repo_profile(profile_data):
 
1481
    # To-Do
 
1482
    pass
 
1483
 
 
1484
def get_repo_user_pass():
 
1485
    # To-Do
 
1486
    pass
 
1487
def get_preferred_user(repo_url):
 
1488
    # To-Do
 
1489
    pass
 
1490
def repo_is_enabled():
 
1491
    # To-Do
 
1492
    return False
 
1493
 
 
1494
def update_repo_profile(profile):
 
1495
    # To-Do
 
1496
    return None
 
1497
 
 
1498
def order_globs(globs, path):
 
1499
    """Returns the globs in sorted order, more specific behind"""
 
1500
    # To-Do
 
1501
    # ATM its lexicographic, should be done to allow better matches later
 
1502
    return sorted(globs)
 
1503
 
 
1504
def ask_the_questions():
 
1505
    found = 0
 
1506
    global seen_events
 
1507
    for aamode in sorted(log_dict.keys()):
 
1508
        # Describe the type of changes
 
1509
        if aamode == 'PERMITTING':
 
1510
            aaui.UI_Info(_('Complain-mode changes:'))
 
1511
        elif aamode == 'REJECTING':
 
1512
            aaui.UI_Info(_('Enforce-mode changes:'))
 
1513
        else:
 
1514
            # This is so wrong!
 
1515
            fatal_error(_('Invalid mode found: %s') % aamode)
 
1516
 
 
1517
        for profile in sorted(log_dict[aamode].keys()):
 
1518
            # Update the repo profiles
 
1519
            p = update_repo_profile(aa[profile][profile])
 
1520
            if p:
 
1521
                UI_SelectUpdatedRepoProfile(profile, p)
 
1522
 
 
1523
            found += 1
 
1524
            # Sorted list of hats with the profile name coming first
 
1525
            hats = list(filter(lambda key: key != profile, sorted(log_dict[aamode][profile].keys())))
 
1526
            if log_dict[aamode][profile].get(profile, False):
 
1527
                hats = [profile] + hats
 
1528
 
 
1529
            for hat in hats:
 
1530
                for capability in sorted(log_dict[aamode][profile][hat]['capability'].keys()):
 
1531
                    # skip if capability already in profile
 
1532
                    if profile_known_capability(aa[profile][hat], capability):
 
1533
                        continue
 
1534
                    # Load variables? Don't think so.
 
1535
                    severity = sev_db.rank('CAP_%s' % capability)
 
1536
                    default_option = 1
 
1537
                    options = []
 
1538
                    newincludes = match_cap_includes(aa[profile][hat], capability)
 
1539
                    q = hasher()
 
1540
 
 
1541
                    if newincludes:
 
1542
                        options += list(map(lambda inc: '#include <%s>' % inc, sorted(set(newincludes))))
 
1543
 
 
1544
                    if options:
 
1545
                        options.append('capability %s' % capability)
 
1546
                        q['options'] = [options]
 
1547
                        q['selected'] = default_option - 1
 
1548
 
 
1549
                    q['headers'] = [_('Profile'), combine_name(profile, hat)]
 
1550
                    q['headers'] += [_('Capability'), capability]
 
1551
                    q['headers'] += [_('Severity'), severity]
 
1552
 
 
1553
                    audit_toggle = 0
 
1554
 
 
1555
                    q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_NEW',
 
1556
                                      'CMD_ABORT', 'CMD_FINISHED']
 
1557
 
 
1558
                    # In complain mode: events default to allow
 
1559
                    # In enforce mode: events default to deny
 
1560
                    q['default'] = 'CMD_DENY'
 
1561
                    if aamode == 'PERMITTING':
 
1562
                        q['default'] = 'CMD_ALLOW'
 
1563
 
 
1564
                    seen_events += 1
 
1565
 
 
1566
                    done = False
 
1567
                    while not done:
 
1568
                        ans, selected = aaui.UI_PromptUser(q)
 
1569
 
 
1570
                        if ans == 'CMD_FINISHED':
 
1571
                            save_profiles()
 
1572
                            return
 
1573
 
 
1574
                        # Ignore the log entry
 
1575
                        if ans == 'CMD_IGNORE_ENTRY':
 
1576
                            done = True
 
1577
                            break
 
1578
 
 
1579
                        if ans == 'CMD_AUDIT':
 
1580
                            audit_toggle = not audit_toggle
 
1581
                            audit = ''
 
1582
                            if audit_toggle:
 
1583
                                q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_OFF',
 
1584
                                                  'CMD_ABORT', 'CMD_FINISHED']
 
1585
                                audit = 'audit'
 
1586
                            else:
 
1587
                                q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_NEW',
 
1588
                                                  'CMD_ABORT', 'CMD_FINISHED', ]
 
1589
 
 
1590
                            q['headers'] = [_('Profile'), combine_name(profile, hat),
 
1591
                                            _('Capability'), audit + capability,
 
1592
                                            _('Severity'), severity]
 
1593
 
 
1594
                        if ans == 'CMD_ALLOW':
 
1595
                            selection = ''
 
1596
                            if options:
 
1597
                                selection = options[selected]
 
1598
                            match = re_match_include(selection)
 
1599
                            if match:
 
1600
                                deleted = False
 
1601
                                inc = match  # .groups()[0]
 
1602
                                deleted = delete_duplicates(aa[profile][hat], inc)
 
1603
                                aa[profile][hat]['include'][inc] = True
 
1604
 
 
1605
                                aaui.UI_Info(_('Adding %s to profile.') % selection)
 
1606
                                if deleted:
 
1607
                                    aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted)
 
1608
 
 
1609
                            aa[profile][hat]['allow']['capability'][capability]['set'] = True
 
1610
                            aa[profile][hat]['allow']['capability'][capability]['audit'] = audit_toggle
 
1611
 
 
1612
                            changed[profile] = True
 
1613
 
 
1614
                            aaui.UI_Info(_('Adding capability %s to profile.') % capability)
 
1615
                            done = True
 
1616
 
 
1617
                        elif ans == 'CMD_DENY':
 
1618
                            aa[profile][hat]['deny']['capability'][capability]['set'] = True
 
1619
                            changed[profile] = True
 
1620
 
 
1621
                            aaui.UI_Info(_('Denying capability %s to profile.') % capability)
 
1622
                            done = True
 
1623
                        else:
 
1624
                            done = False
 
1625
 
 
1626
                # Process all the path entries.
 
1627
                for path in sorted(log_dict[aamode][profile][hat]['path'].keys()):
 
1628
                    mode = log_dict[aamode][profile][hat]['path'][path]
 
1629
                    # Lookup modes from profile
 
1630
                    allow_mode = set()
 
1631
                    allow_audit = set()
 
1632
                    deny_mode = set()
 
1633
                    deny_audit = set()
 
1634
 
 
1635
                    fmode, famode, fm = rematchfrag(aa[profile][hat], 'allow', path)
 
1636
                    if fmode:
 
1637
                        allow_mode |= fmode
 
1638
                    if famode:
 
1639
                        allow_audit |= famode
 
1640
 
 
1641
                    cm, cam, m = rematchfrag(aa[profile][hat], 'deny', path)
 
1642
                    if cm:
 
1643
                        deny_mode |= cm
 
1644
                    if cam:
 
1645
                        deny_audit |= cam
 
1646
 
 
1647
                    imode, iamode, im = match_prof_incs_to_path(aa[profile][hat], 'allow', path)
 
1648
                    if imode:
 
1649
                        allow_mode |= imode
 
1650
                    if iamode:
 
1651
                        allow_audit |= iamode
 
1652
 
 
1653
                    cm, cam, m = match_prof_incs_to_path(aa[profile][hat], 'deny', path)
 
1654
                    if cm:
 
1655
                        deny_mode |= cm
 
1656
                    if cam:
 
1657
                        deny_audit |= cam
 
1658
 
 
1659
                    if deny_mode & apparmor.aamode.AA_MAY_EXEC:
 
1660
                        deny_mode |= apparmor.aamode.ALL_AA_EXEC_TYPE
 
1661
 
 
1662
                    # Mask off the denied modes
 
1663
                    mode = mode - deny_mode
 
1664
 
 
1665
                    # If we get an exec request from some kindof event that generates 'PERMITTING X'
 
1666
                    # check if its already in allow_mode
 
1667
                    # if not add ix permission
 
1668
                    if mode & apparmor.aamode.AA_MAY_EXEC:
 
1669
                        # Remove all type access permission
 
1670
                        mode = mode - apparmor.aamode.ALL_AA_EXEC_TYPE
 
1671
                        if not allow_mode & apparmor.aamode.AA_MAY_EXEC:
 
1672
                            mode |= str_to_mode('ix')
 
1673
 
 
1674
                    # m is not implied by ix
 
1675
 
 
1676
                    ### If we get an mmap request, check if we already have it in allow_mode
 
1677
                    ##if mode & AA_EXEC_MMAP:
 
1678
                    ##    # ix implies m, so we don't need to add m if ix is present
 
1679
                    ##    if contains(allow_mode, 'ix'):
 
1680
                    ##        mode = mode - AA_EXEC_MMAP
 
1681
 
 
1682
                    if not mode:
 
1683
                        continue
 
1684
 
 
1685
                    matches = []
 
1686
 
 
1687
                    if fmode:
 
1688
                        matches += fm
 
1689
 
 
1690
                    if imode:
 
1691
                        matches += im
 
1692
 
 
1693
                    if not mode_contains(allow_mode, mode):
 
1694
                        default_option = 1
 
1695
                        options = []
 
1696
                        newincludes = []
 
1697
                        include_valid = False
 
1698
 
 
1699
                        for incname in include.keys():
 
1700
                            include_valid = False
 
1701
                            # If already present skip
 
1702
                            if aa[profile][hat][incname]:
 
1703
                                continue
 
1704
                            if incname.startswith(profile_dir):
 
1705
                                incname = incname.replace(profile_dir + '/', '', 1)
 
1706
 
 
1707
                            include_valid = valid_include('', incname)
 
1708
 
 
1709
                            if not include_valid:
 
1710
                                continue
 
1711
 
 
1712
                            cm, am, m = match_include_to_path(incname, 'allow', path)
 
1713
 
 
1714
                            if cm and mode_contains(cm, mode):
 
1715
                                dm = match_include_to_path(incname, 'deny', path)[0]
 
1716
                                # If the mode is denied
 
1717
                                if not mode & dm:
 
1718
                                    if not list(filter(lambda s: '/**' == s, m)):
 
1719
                                        newincludes.append(incname)
 
1720
                        # Add new includes to the options
 
1721
                        if newincludes:
 
1722
                            options += list(map(lambda s: '#include <%s>' % s, sorted(set(newincludes))))
 
1723
                        # We should have literal the path in options list too
 
1724
                        options.append(path)
 
1725
                        # Add any the globs matching path from logprof
 
1726
                        globs = glob_common(path)
 
1727
                        if globs:
 
1728
                            matches += globs
 
1729
                        # Add any user entered matching globs
 
1730
                        for user_glob in user_globs:
 
1731
                            if matchliteral(user_glob, path):
 
1732
                                matches.append(user_glob)
 
1733
 
 
1734
                        matches = list(set(matches))
 
1735
                        if path in matches:
 
1736
                            matches.remove(path)
 
1737
 
 
1738
                        options += order_globs(matches, path)
 
1739
                        default_option = len(options)
 
1740
 
 
1741
                        sev_db.unload_variables()
 
1742
                        sev_db.load_variables(get_profile_filename(profile))
 
1743
                        severity = sev_db.rank(path, mode_to_str(mode))
 
1744
                        sev_db.unload_variables()
 
1745
 
 
1746
                        audit_toggle = 0
 
1747
                        owner_toggle = 0
 
1748
                        if cfg['settings']['default_owner_prompt']:
 
1749
                            owner_toggle = cfg['settings']['default_owner_prompt']
 
1750
                        done = False
 
1751
                        while not done:
 
1752
                            q = hasher()
 
1753
                            q['headers'] = [_('Profile'), combine_name(profile, hat),
 
1754
                                            _('Path'), path]
 
1755
 
 
1756
                            if allow_mode:
 
1757
                                mode |= allow_mode
 
1758
                                tail = ''
 
1759
                                s = ''
 
1760
                                prompt_mode = None
 
1761
                                if owner_toggle == 0:
 
1762
                                    prompt_mode = flatten_mode(mode)
 
1763
                                    tail = '     ' + _('(owner permissions off)')
 
1764
                                elif owner_toggle == 1:
 
1765
                                    prompt_mode = mode
 
1766
                                elif owner_toggle == 2:
 
1767
                                    prompt_mode = allow_mode | owner_flatten_mode(mode - allow_mode)
 
1768
                                    tail = '     ' + _('(force new perms to owner)')
 
1769
                                else:
 
1770
                                    prompt_mode = owner_flatten_mode(mode)
 
1771
                                    tail = '     ' + _('(force all rule perms to owner)')
 
1772
 
 
1773
                                if audit_toggle == 1:
 
1774
                                    s = mode_to_str_user(allow_mode)
 
1775
                                    if allow_mode:
 
1776
                                        s += ', '
 
1777
                                    s += 'audit ' + mode_to_str_user(prompt_mode - allow_mode) + tail
 
1778
                                elif audit_toggle == 2:
 
1779
                                    s = 'audit ' + mode_to_str_user(prompt_mode) + tail
 
1780
                                else:
 
1781
                                    s = mode_to_str_user(prompt_mode) + tail
 
1782
 
 
1783
                                q['headers'] += [_('Old Mode'), mode_to_str_user(allow_mode),
 
1784
                                                 _('New Mode'), s]
 
1785
 
 
1786
                            else:
 
1787
                                s = ''
 
1788
                                tail = ''
 
1789
                                prompt_mode = None
 
1790
                                if audit_toggle:
 
1791
                                    s = 'audit'
 
1792
                                if owner_toggle == 0:
 
1793
                                    prompt_mode = flatten_mode(mode)
 
1794
                                    tail = '     ' + _('(owner permissions off)')
 
1795
                                elif owner_toggle == 1:
 
1796
                                    prompt_mode = mode
 
1797
                                else:
 
1798
                                    prompt_mode = owner_flatten_mode(mode)
 
1799
                                    tail = '     ' + _('(force perms to owner)')
 
1800
 
 
1801
                                s = mode_to_str_user(prompt_mode)
 
1802
                                q['headers'] += [_('Mode'), s]
 
1803
 
 
1804
                            q['headers'] += [_('Severity'), severity]
 
1805
                            q['options'] = options
 
1806
                            q['selected'] = default_option - 1
 
1807
                            q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_GLOB',
 
1808
                                              'CMD_GLOBEXT', 'CMD_NEW', 'CMD_ABORT',
 
1809
                                              'CMD_FINISHED', 'CMD_OTHER']
 
1810
                            q['default'] = 'CMD_DENY'
 
1811
                            if aamode == 'PERMITTING':
 
1812
                                q['default'] = 'CMD_ALLOW'
 
1813
 
 
1814
                            seen_events += 1
 
1815
 
 
1816
                            ans, selected = aaui.UI_PromptUser(q)
 
1817
 
 
1818
                            if ans == 'CMD_FINISHED':
 
1819
                                save_profiles()
 
1820
                                return
 
1821
 
 
1822
                            if ans == 'CMD_IGNORE_ENTRY':
 
1823
                                done = True
 
1824
                                break
 
1825
 
 
1826
                            if ans == 'CMD_OTHER':
 
1827
                                audit_toggle, owner_toggle = UI_ask_mode_toggles(audit_toggle, owner_toggle, allow_mode)
 
1828
                            elif ans == 'CMD_USER_TOGGLE':
 
1829
                                owner_toggle += 1
 
1830
                                if not allow_mode and owner_toggle == 2:
 
1831
                                    owner_toggle += 1
 
1832
                                if owner_toggle > 3:
 
1833
                                    owner_toggle = 0
 
1834
                            elif ans == 'CMD_ALLOW':
 
1835
                                path = options[selected]
 
1836
                                done = True
 
1837
                                match = re_match_include(path)  # .search('^#include\s+<(.+)>$', path)
 
1838
                                if match:
 
1839
                                    inc = match  # .groups()[0]
 
1840
                                    deleted = 0
 
1841
                                    deleted = delete_duplicates(aa[profile][hat], inc)
 
1842
                                    aa[profile][hat]['include'][inc] = True
 
1843
                                    changed[profile] = True
 
1844
                                    aaui.UI_Info(_('Adding %s to profile.') % path)
 
1845
                                    if deleted:
 
1846
                                        aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted)
 
1847
 
 
1848
                                else:
 
1849
                                    if aa[profile][hat]['allow']['path'][path].get('mode', False):
 
1850
                                        mode |= aa[profile][hat]['allow']['path'][path]['mode']
 
1851
                                    deleted = []
 
1852
                                    for entry in aa[profile][hat]['allow']['path'].keys():
 
1853
                                        if path == entry:
 
1854
                                            continue
 
1855
 
 
1856
                                        if matchregexp(path, entry):
 
1857
                                            if mode_contains(mode, aa[profile][hat]['allow']['path'][entry]['mode']):
 
1858
                                                deleted.append(entry)
 
1859
                                    for entry in deleted:
 
1860
                                        aa[profile][hat]['allow']['path'].pop(entry)
 
1861
                                    deleted = len(deleted)
 
1862
 
 
1863
                                    if owner_toggle == 0:
 
1864
                                        mode = flatten_mode(mode)
 
1865
                                    #elif owner_toggle == 1:
 
1866
                                    #    mode = mode
 
1867
                                    elif owner_toggle == 2:
 
1868
                                        mode = allow_mode | owner_flatten_mode(mode - allow_mode)
 
1869
                                    elif owner_toggle == 3:
 
1870
                                        mode = owner_flatten_mode(mode)
 
1871
 
 
1872
                                    aa[profile][hat]['allow']['path'][path]['mode'] = aa[profile][hat]['allow']['path'][path].get('mode', set()) | mode
 
1873
 
 
1874
                                    tmpmode = set()
 
1875
                                    if audit_toggle == 1:
 
1876
                                        tmpmode = mode - allow_mode
 
1877
                                    elif audit_toggle == 2:
 
1878
                                        tmpmode = mode
 
1879
 
 
1880
                                    aa[profile][hat]['allow']['path'][path]['audit'] = aa[profile][hat]['allow']['path'][path].get('audit', set()) | tmpmode
 
1881
 
 
1882
                                    changed[profile] = True
 
1883
 
 
1884
                                    aaui.UI_Info(_('Adding %s %s to profile') % (path, mode_to_str_user(mode)))
 
1885
                                    if deleted:
 
1886
                                        aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted)
 
1887
 
 
1888
                            elif ans == 'CMD_DENY':
 
1889
                                path = options[selected].strip()
 
1890
                                # Add new entry?
 
1891
                                aa[profile][hat]['deny']['path'][path]['mode'] = aa[profile][hat]['deny']['path'][path].get('mode', set()) | (mode - allow_mode)
 
1892
 
 
1893
                                aa[profile][hat]['deny']['path'][path]['audit'] = aa[profile][hat]['deny']['path'][path].get('audit', set())
 
1894
 
 
1895
                                changed[profile] = True
 
1896
 
 
1897
                                done = True
 
1898
 
 
1899
                            elif ans == 'CMD_NEW':
 
1900
                                arg = options[selected]
 
1901
                                if not re_match_include(arg):
 
1902
                                    ans = aaui.UI_GetString(_('Enter new path: '), arg)
 
1903
                                    if ans:
 
1904
                                        if not matchliteral(ans, path):
 
1905
                                            ynprompt = _('The specified path does not match this log entry:\n\n  Log Entry: %s\n  Entered Path:  %s\nDo you really want to use this path?') % (path, ans)
 
1906
                                            key = aaui.UI_YesNo(ynprompt, 'n')
 
1907
                                            if key == 'n':
 
1908
                                                continue
 
1909
 
 
1910
                                        user_globs.append(ans)
 
1911
                                        options.append(ans)
 
1912
                                        default_option = len(options)
 
1913
 
 
1914
                            elif ans == 'CMD_GLOB':
 
1915
                                newpath = options[selected].strip()
 
1916
                                if not re_match_include(newpath):
 
1917
                                    newpath = glob_path(newpath)
 
1918
 
 
1919
                                    if newpath not in options:
 
1920
                                        options.append(newpath)
 
1921
                                        default_option = len(options)
 
1922
                                    else:
 
1923
                                        default_option = options.index(newpath) + 1
 
1924
 
 
1925
                            elif ans == 'CMD_GLOBEXT':
 
1926
                                newpath = options[selected].strip()
 
1927
                                if not re_match_include(newpath):
 
1928
                                    newpath = glob_path_withext(newpath)
 
1929
 
 
1930
                                    if newpath not in options:
 
1931
                                        options.append(newpath)
 
1932
                                        default_option = len(options)
 
1933
                                    else:
 
1934
                                        default_option = options.index(newpath) + 1
 
1935
 
 
1936
                            elif re.search('\d', ans):
 
1937
                                default_option = ans
 
1938
 
 
1939
                #
 
1940
                for family in sorted(log_dict[aamode][profile][hat]['netdomain'].keys()):
 
1941
                    # severity handling for net toggles goes here
 
1942
                    for sock_type in sorted(log_dict[profile][profile][hat]['netdomain'][family].keys()):
 
1943
                        if profile_known_network(aa[profile][hat], family, sock_type):
 
1944
                            continue
 
1945
                        default_option = 1
 
1946
                        options = []
 
1947
                        newincludes = match_net_includes(aa[profile][hat], family, sock_type)
 
1948
                        q = hasher()
 
1949
                        if newincludes:
 
1950
                            options += list(map(lambda s: '#include <%s>' % s, sorted(set(newincludes))))
 
1951
                        if options:
 
1952
                            options.append('network %s %s' % (family, sock_type))
 
1953
                            q['options'] = options
 
1954
                            q['selected'] = default_option - 1
 
1955
 
 
1956
                        q['headers'] = [_('Profile'), combine_name(profile, hat)]
 
1957
                        q['headers'] += [_('Network Family'), family]
 
1958
                        q['headers'] += [_('Socket Type'), sock_type]
 
1959
 
 
1960
                        audit_toggle = 0
 
1961
                        q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_NEW',
 
1962
                                          'CMD_ABORT', 'CMD_FINISHED']
 
1963
                        q['default'] = 'CMD_DENY'
 
1964
 
 
1965
                        if aamode == 'PERMITTING':
 
1966
                            q['default'] = 'CMD_ALLOW'
 
1967
 
 
1968
                        seen_events += 1
 
1969
 
 
1970
                        done = False
 
1971
                        while not done:
 
1972
                            ans, selected = aaui.UI_PromptUser(q)
 
1973
 
 
1974
                            if ans == 'CMD_FINISHED':
 
1975
                                save_profiles()
 
1976
                                return
 
1977
 
 
1978
                            if ans == 'CMD_IGNORE_ENTRY':
 
1979
                                done = True
 
1980
                                break
 
1981
 
 
1982
                            if ans.startswith('CMD_AUDIT'):
 
1983
                                audit_toggle = not audit_toggle
 
1984
                                audit = ''
 
1985
                                if audit_toggle:
 
1986
                                    audit = 'audit'
 
1987
                                    q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_OFF',
 
1988
                                                      'CMD_ABORT', 'CMD_FINISHED']
 
1989
                                else:
 
1990
                                    q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_NEW',
 
1991
                                                      'CMD_ABORT', 'CMD_FINISHED']
 
1992
                                q['headers'] = [_('Profile'), combine_name(profile, hat)]
 
1993
                                q['headers'] += [_('Network Family'), audit + family]
 
1994
                                q['headers'] += [_('Socket Type'), sock_type]
 
1995
 
 
1996
                            elif ans == 'CMD_ALLOW':
 
1997
                                selection = options[selected]
 
1998
                                done = True
 
1999
                                if re_match_include(selection):  # re.search('#include\s+<.+>$', selection):
 
2000
                                    inc = re_match_include(selection)  # re.search('#include\s+<(.+)>$', selection).groups()[0]
 
2001
                                    deleted = 0
 
2002
                                    deleted = delete_duplicates(aa[profile][hat], inc)
 
2003
 
 
2004
                                    aa[profile][hat]['include'][inc] = True
 
2005
 
 
2006
                                    changed[profile] = True
 
2007
 
 
2008
                                    aaui.UI_Info(_('Adding %s to profile') % selection)
 
2009
                                    if deleted:
 
2010
                                        aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted)
 
2011
 
 
2012
                                else:
 
2013
                                    aa[profile][hat]['allow']['netdomain']['audit'][family][sock_type] = audit_toggle
 
2014
                                    aa[profile][hat]['allow']['netdomain']['rule'][family][sock_type] = True
 
2015
 
 
2016
                                    changed[profile] = True
 
2017
 
 
2018
                                    aaui.UI_Info(_('Adding network access %s %s to profile.') % (family, sock_type))
 
2019
 
 
2020
                            elif ans == 'CMD_DENY':
 
2021
                                done = True
 
2022
                                aa[profile][hat]['deny']['netdomain']['rule'][family][sock_type] = True
 
2023
                                changed[profile] = True
 
2024
                                aaui.UI_Info(_('Denying network access %s %s to profile') % (family, sock_type))
 
2025
 
 
2026
                            else:
 
2027
                                done = False
 
2028
 
 
2029
def glob_path(newpath):
 
2030
    """Glob the given file path"""
 
2031
    if newpath[-1] == '/':
 
2032
        if newpath[-4:] == '/**/' or newpath[-3:] == '/*/':
 
2033
            # /foo/**/ and /foo/*/ => /**/
 
2034
            newpath = re.sub('/[^/]+/\*{1,2}/$', '/**/', newpath)  # re.sub('/[^/]+/\*{1,2}$/', '/\*\*/', newpath)
 
2035
        elif re.search('/[^/]+\*\*[^/]*/$', newpath):
 
2036
            # /foo**/ and /foo**bar/ => /**/
 
2037
            newpath = re.sub('/[^/]+\*\*[^/]*/$', '/**/', newpath)
 
2038
        elif re.search('/\*\*[^/]+/$', newpath):
 
2039
            # /**bar/ => /**/
 
2040
            newpath = re.sub('/\*\*[^/]+/$', '/**/', newpath)
 
2041
        else:
 
2042
            newpath = re.sub('/[^/]+/$', '/*/', newpath)
 
2043
    else:
 
2044
            if newpath[-3:] == '/**' or newpath[-2:] == '/*':
 
2045
                # /foo/** and /foo/* => /**
 
2046
                newpath = re.sub('/[^/]+/\*{1,2}$', '/**', newpath)
 
2047
            elif re.search('/[^/]*\*\*[^/]+$', newpath):
 
2048
                # /**foo and /foor**bar => /**
 
2049
                newpath = re.sub('/[^/]*\*\*[^/]+$', '/**', newpath)
 
2050
            elif re.search('/[^/]+\*\*$', newpath):
 
2051
                # /foo** => /**
 
2052
                newpath = re.sub('/[^/]+\*\*$', '/**', newpath)
 
2053
            else:
 
2054
                newpath = re.sub('/[^/]+$', '/*', newpath)
 
2055
    return newpath
 
2056
 
 
2057
def glob_path_withext(newpath):
 
2058
    """Glob given file path with extension"""
 
2059
    # match /**.ext and /*.ext
 
2060
    match = re.search('/\*{1,2}(\.[^/]+)$', newpath)
 
2061
    if match:
 
2062
        # /foo/**.ext and /foo/*.ext => /**.ext
 
2063
        newpath = re.sub('/[^/]+/\*{1,2}\.[^/]+$', '/**' + match.groups()[0], newpath)
 
2064
    elif re.search('/[^/]+\*\*[^/]*\.[^/]+$', newpath):
 
2065
        # /foo**.ext and /foo**bar.ext => /**.ext
 
2066
        match = re.search('/[^/]+\*\*[^/]*(\.[^/]+)$', newpath)
 
2067
        newpath = re.sub('/[^/]+\*\*[^/]*\.[^/]+$', '/**' + match.groups()[0], newpath)
 
2068
    elif re.search('/\*\*[^/]+\.[^/]+$', newpath):
 
2069
        # /**foo.ext => /**.ext
 
2070
        match = re.search('/\*\*[^/]+(\.[^/]+)$', newpath)
 
2071
        newpath = re.sub('/\*\*[^/]+\.[^/]+$', '/**' + match.groups()[0], newpath)
 
2072
    else:
 
2073
        match = re.search('(\.[^/]+)$', newpath)
 
2074
        if match:
 
2075
            newpath = re.sub('/[^/]+(\.[^/]+)$', '/*' + match.groups()[0], newpath)
 
2076
    return newpath
 
2077
 
 
2078
def delete_net_duplicates(netrules, incnetrules):
 
2079
    deleted = 0
 
2080
    hasher_obj = hasher()
 
2081
    copy_netrules = deepcopy(netrules)
 
2082
    if incnetrules and netrules:
 
2083
        incnetglob = False
 
2084
        # Delete matching rules from abstractions
 
2085
        if incnetrules.get('all', False):
 
2086
            incnetglob = True
 
2087
        for fam in copy_netrules['rule'].keys():
 
2088
            if incnetglob or (type(incnetrules['rule'][fam]) != type(hasher_obj) and incnetrules['rule'][fam]):
 
2089
                if type(netrules['rule'][fam]) == type(hasher_obj):
 
2090
                    deleted += len(netrules['rule'][fam].keys())
 
2091
                else:
 
2092
                    deleted += 1
 
2093
                netrules['rule'].pop(fam)
 
2094
            elif type(netrules['rule'][fam]) != type(hasher_obj) and netrules['rule'][fam]:
 
2095
                continue
 
2096
            else:
 
2097
                for socket_type in copy_netrules['rule'][fam].keys():
 
2098
                    if incnetrules['rule'][fam].get(socket_type, False):
 
2099
                        netrules['rule'][fam].pop(socket_type)
 
2100
                        deleted += 1
 
2101
    return deleted
 
2102
 
 
2103
def delete_cap_duplicates(profilecaps, inccaps):
 
2104
    deleted = []
 
2105
    if profilecaps and inccaps:
 
2106
        for capname in profilecaps.keys():
 
2107
            if inccaps[capname].get('set', False) == 1:
 
2108
                deleted.append(capname)
 
2109
        for capname in deleted:
 
2110
            profilecaps.pop(capname)
 
2111
 
 
2112
    return len(deleted)
 
2113
 
 
2114
def delete_path_duplicates(profile, incname, allow):
 
2115
    deleted = []
 
2116
    for entry in profile[allow]['path'].keys():
 
2117
        if entry == '#include <%s>' % incname:
 
2118
            continue
 
2119
        cm, am, m = match_include_to_path(incname, allow, entry)
 
2120
        if cm and mode_contains(cm, profile[allow]['path'][entry]['mode']) and mode_contains(am, profile[allow]['path'][entry]['audit']):
 
2121
            deleted.append(entry)
 
2122
 
 
2123
    for entry in deleted:
 
2124
        profile[allow]['path'].pop(entry)
 
2125
    return len(deleted)
 
2126
 
 
2127
def delete_duplicates(profile, incname):
 
2128
    deleted = 0
 
2129
    # Allow rules covered by denied rules shouldn't be deleted
 
2130
    # only a subset allow rules may actually be denied
 
2131
 
 
2132
    if include.get(incname, False):
 
2133
        deleted += delete_net_duplicates(profile['allow']['netdomain'], include[incname][incname]['allow']['netdomain'])
 
2134
 
 
2135
        deleted += delete_net_duplicates(profile['deny']['netdomain'], include[incname][incname]['deny']['netdomain'])
 
2136
 
 
2137
        deleted += delete_cap_duplicates(profile['allow']['capability'], include[incname][incname]['allow']['capability'])
 
2138
 
 
2139
        deleted += delete_cap_duplicates(profile['deny']['capability'], include[incname][incname]['deny']['capability'])
 
2140
 
 
2141
        deleted += delete_path_duplicates(profile, incname, 'allow')
 
2142
        deleted += delete_path_duplicates(profile, incname, 'deny')
 
2143
 
 
2144
    elif filelist.get(incname, False):
 
2145
        deleted += delete_net_duplicates(profile['allow']['netdomain'], filelist[incname][incname]['allow']['netdomain'])
 
2146
 
 
2147
        deleted += delete_net_duplicates(profile['deny']['netdomain'], filelist[incname][incname]['deny']['netdomain'])
 
2148
 
 
2149
        deleted += delete_cap_duplicates(profile['allow']['capability'], filelist[incname][incname]['allow']['capability'])
 
2150
 
 
2151
        deleted += delete_cap_duplicates(profile['deny']['capability'], filelist[incname][incname]['deny']['capability'])
 
2152
 
 
2153
        deleted += delete_path_duplicates(profile, incname, 'allow')
 
2154
        deleted += delete_path_duplicates(profile, incname, 'deny')
 
2155
 
 
2156
    return deleted
 
2157
 
 
2158
def match_net_include(incname, family, type):
 
2159
    includelist = [incname]
 
2160
    checked = []
 
2161
    name = None
 
2162
    if includelist:
 
2163
        name = includelist.pop(0)
 
2164
    while name:
 
2165
        checked.append(name)
 
2166
        if netrules_access_check(include[name][name]['allow']['netdomain'], family, type):
 
2167
            return True
 
2168
 
 
2169
        if include[name][name]['include'].keys() and name not in checked:
 
2170
            includelist += include[name][name]['include'].keys()
 
2171
 
 
2172
        if len(includelist):
 
2173
            name = includelist.pop(0)
 
2174
        else:
 
2175
            name = False
 
2176
 
 
2177
    return False
 
2178
 
 
2179
def match_cap_includes(profile, cap):
 
2180
    newincludes = []
 
2181
    for incname in include.keys():
 
2182
        if valid_include(profile, incname) and include[incname][incname]['allow']['capability'][cap].get('set', False) == 1:
 
2183
            newincludes.append(incname)
 
2184
 
 
2185
    return newincludes
 
2186
 
 
2187
def re_match_include(path):
 
2188
    """Matches the path for include and returns the include path"""
 
2189
    regex_include = re.compile('^\s*#?include\s*<(.*)>\s*(#.*)?$')
 
2190
    match = regex_include.search(path)
 
2191
    if match:
 
2192
        return match.groups()[0]
 
2193
    else:
 
2194
        return None
 
2195
 
 
2196
def valid_include(profile, incname):
 
2197
    if profile and profile['include'].get(incname, False):
 
2198
        return False
 
2199
 
 
2200
    if cfg['settings']['custom_includes']:
 
2201
        for incm in cfg['settings']['custom_includes'].split():
 
2202
            if incm == incname:
 
2203
                return True
 
2204
 
 
2205
    if incname.startswith('abstractions/') and os.path.isfile(profile_dir + '/' + incname):
 
2206
        return True
 
2207
 
 
2208
    return False
 
2209
 
 
2210
def match_net_includes(profile, family, nettype):
 
2211
    newincludes = []
 
2212
    for incname in include.keys():
 
2213
 
 
2214
        if valid_include(profile, incname) and match_net_include(incname, family, type):
 
2215
            newincludes.append(incname)
 
2216
 
 
2217
    return newincludes
 
2218
 
 
2219
def do_logprof_pass(logmark='', passno=0, pid=pid):
 
2220
    # set up variables for this pass
 
2221
#    t = hasher()
 
2222
#    transitions = hasher()
 
2223
#    seen = hasher()  # XXX global?
 
2224
    global log
 
2225
    log = []
 
2226
    global existing_profiles
 
2227
    global sev_db
 
2228
#    aa = hasher()
 
2229
#    profile_changes = hasher()
 
2230
#     prelog = hasher()
 
2231
#     log_dict = hasher()
 
2232
#     changed = dict()
 
2233
#    skip = hasher()  # XXX global?
 
2234
#    filelist = hasher()
 
2235
 
 
2236
    aaui.UI_Info(_('Reading log entries from %s.') % filename)
 
2237
 
 
2238
    if not passno:
 
2239
        aaui.UI_Info(_('Updating AppArmor profiles in %s.') % profile_dir)
 
2240
        read_profiles()
 
2241
 
 
2242
    if not sev_db:
 
2243
        sev_db = apparmor.severity.Severity(CONFDIR + '/severity.db', _('unknown'))
 
2244
    #print(pid)
 
2245
    #print(existing_profiles)
 
2246
    ##if not repo_cf and cfg['repostory']['url']:
 
2247
    ##    repo_cfg = read_config('repository.conf')
 
2248
    ##    if not repo_cfg['repository'].get('enabled', False) or repo_cfg['repository]['enabled'] not in ['yes', 'no']:
 
2249
    ##    UI_ask_to_enable_repo()
 
2250
    log_reader = apparmor.logparser.ReadLog(pid, filename, existing_profiles, profile_dir, log)
 
2251
    log = log_reader.read_log(logmark)
 
2252
    #read_log(logmark)
 
2253
 
 
2254
    for root in log:
 
2255
        handle_children('', '', root)
 
2256
    #for root in range(len(log)):
 
2257
        #log[root] = handle_children('', '', log[root])
 
2258
    #print(log)
 
2259
    for pid in sorted(profile_changes.keys()):
 
2260
        set_process(pid, profile_changes[pid])
 
2261
 
 
2262
    collapse_log()
 
2263
 
 
2264
    ask_the_questions()
 
2265
 
 
2266
    if aaui.UI_mode == 'yast':
 
2267
        # To-Do
 
2268
        pass
 
2269
 
 
2270
    finishing = False
 
2271
    # Check for finished
 
2272
    save_profiles()
 
2273
 
 
2274
    ##if not repo_cfg['repository'].get('upload', False) or repo['repository']['upload'] == 'later':
 
2275
    ##    UI_ask_to_upload_profiles()
 
2276
    ##if repo_enabled():
 
2277
    ##    if repo_cgf['repository']['upload'] == 'yes':
 
2278
    ##        sync_profiles()
 
2279
    ##    created = []
 
2280
 
 
2281
    # If user selects 'Finish' then we want to exit logprof
 
2282
    if finishing:
 
2283
        return 'FINISHED'
 
2284
    else:
 
2285
        return 'NORMAL'
 
2286
 
 
2287
 
 
2288
def save_profiles():
 
2289
    # Ensure the changed profiles are actual active profiles
 
2290
    for prof_name in changed.keys():
 
2291
        if not is_active_profile(prof_name):
 
2292
            changed.pop(prof_name)
 
2293
 
 
2294
    changed_list = sorted(changed.keys())
 
2295
 
 
2296
    if changed_list:
 
2297
 
 
2298
        if aaui.UI_mode == 'yast':
 
2299
            # To-Do
 
2300
            # selected_profiles = []  # XXX selected_profiles_ref?
 
2301
            profile_changes = dict()
 
2302
            for prof in changed_list:
 
2303
                oldprofile = serialize_profile(original_aa[prof], prof)
 
2304
                newprofile = serialize_profile(aa[prof], prof)
 
2305
                profile_changes[prof] = get_profile_diff(oldprofile, newprofile)
 
2306
            explanation = _('Select which profile changes you would like to save to the\nlocal profile set.')
 
2307
            title = _('Local profile changes')
 
2308
            SendDataToYast({'type': 'dialog-select-profiles',
 
2309
                            'title': title,
 
2310
                            'explanation': explanation,
 
2311
                            'dialog_select': 'true',
 
2312
                            'get_changelog': 'false',
 
2313
                            'profiles': profile_changes
 
2314
                            })
 
2315
            ypath, yarg = GetDataFromYast()
 
2316
            if yarg['STATUS'] == 'cancel':
 
2317
                return None
 
2318
            else:
 
2319
                selected_profiles_ref = yarg['PROFILES']
 
2320
                for profile_name in selected_profiles_ref:
 
2321
                    write_profile_ui_feedback(profile_name)
 
2322
                    reload_base(profile_name)
 
2323
 
 
2324
        else:
 
2325
            q = hasher()
 
2326
            q['title'] = 'Changed Local Profiles'
 
2327
            q['headers'] = []
 
2328
            q['explanation'] = _('The following local profiles were changed. Would you like to save them?')
 
2329
            q['functions'] = ['CMD_SAVE_CHANGES', 'CMD_SAVE_SELECTED', 'CMD_VIEW_CHANGES', 'CMD_VIEW_CHANGES_CLEAN', 'CMD_ABORT']
 
2330
            q['default'] = 'CMD_VIEW_CHANGES'
 
2331
            q['options'] = changed
 
2332
            q['selected'] = 0
 
2333
            ans = ''
 
2334
            arg = None
 
2335
            while ans != 'CMD_SAVE_CHANGES':
 
2336
                if not changed:
 
2337
                    return
 
2338
                ans, arg = aaui.UI_PromptUser(q)
 
2339
                if ans == 'CMD_SAVE_SELECTED':
 
2340
                    profile_name = list(changed.keys())[arg]
 
2341
                    write_profile_ui_feedback(profile_name)
 
2342
                    reload_base(profile_name)
 
2343
                    #changed.pop(profile_name)
 
2344
                    #q['options'] = changed
 
2345
 
 
2346
                elif ans == 'CMD_VIEW_CHANGES':
 
2347
                    which = list(changed.keys())[arg]
 
2348
                    oldprofile = None
 
2349
                    if aa[which][which].get('filename', False):
 
2350
                        oldprofile = aa[which][which]['filename']
 
2351
                    else:
 
2352
                        oldprofile = get_profile_filename(which)
 
2353
                    newprofile = serialize_profile_from_old_profile(aa[which], which, '')
 
2354
 
 
2355
                    display_changes_with_comments(oldprofile, newprofile)
 
2356
 
 
2357
                elif ans == 'CMD_VIEW_CHANGES_CLEAN':
 
2358
                    which = list(changed.keys())[arg]
 
2359
                    oldprofile = serialize_profile(original_aa[which], which, '')
 
2360
                    newprofile = serialize_profile(aa[which], which, '')
 
2361
 
 
2362
                    display_changes(oldprofile, newprofile)
 
2363
 
 
2364
            for profile_name in changed_list:
 
2365
                write_profile_ui_feedback(profile_name)
 
2366
                reload_base(profile_name)
 
2367
 
 
2368
def get_pager():
 
2369
    pass
 
2370
 
 
2371
def generate_diff(oldprofile, newprofile):
 
2372
    oldtemp = tempfile.NamedTemporaryFile('w')
 
2373
 
 
2374
    oldtemp.write(oldprofile)
 
2375
    oldtemp.flush()
 
2376
 
 
2377
    newtemp = tempfile.NamedTemporaryFile('w')
 
2378
    newtemp.write(newprofile)
 
2379
    newtemp.flush()
 
2380
 
 
2381
    difftemp = tempfile.NamedTemporaryFile('w', delete=False)
 
2382
 
 
2383
    subprocess.call('diff -u -p %s %s > %s' % (oldtemp.name, newtemp.name, difftemp.name), shell=True)
 
2384
 
 
2385
    oldtemp.close()
 
2386
    newtemp.close()
 
2387
    return difftemp
 
2388
 
 
2389
def get_profile_diff(oldprofile, newprofile):
 
2390
    difftemp = generate_diff(oldprofile, newprofile)
 
2391
    diff = []
 
2392
    with open_file_read(difftemp.name) as f_in:
 
2393
        for line in f_in:
 
2394
            if not (line.startswith('---') and line .startswith('+++') and line.startswith('@@')):
 
2395
                    diff.append(line)
 
2396
 
 
2397
    difftemp.delete = True
 
2398
    difftemp.close()
 
2399
    return ''.join(diff)
 
2400
 
 
2401
def display_changes(oldprofile, newprofile):
 
2402
    if aaui.UI_mode == 'yast':
 
2403
        aaui.UI_LongMessage(_('Profile Changes'), get_profile_diff(oldprofile, newprofile))
 
2404
    else:
 
2405
        difftemp = generate_diff(oldprofile, newprofile)
 
2406
        subprocess.call('less %s' % difftemp.name, shell=True)
 
2407
        difftemp.delete = True
 
2408
        difftemp.close()
 
2409
 
 
2410
def display_changes_with_comments(oldprofile, newprofile):
 
2411
    """Compare the new profile with the existing profile inclusive of all the comments"""
 
2412
    if not os.path.exists(oldprofile):
 
2413
        raise AppArmorException(_("Can't find existing profile %s to compare changes.") % oldprofile)
 
2414
    if aaui.UI_mode == 'yast':
 
2415
        #To-Do
 
2416
        pass
 
2417
    else:
 
2418
        newtemp = tempfile.NamedTemporaryFile('w')
 
2419
        newtemp.write(newprofile)
 
2420
        newtemp.flush()
 
2421
 
 
2422
        difftemp = tempfile.NamedTemporaryFile('w')
 
2423
 
 
2424
        subprocess.call('diff -u -p %s %s > %s' % (oldprofile, newtemp.name, difftemp.name), shell=True)
 
2425
 
 
2426
        newtemp.close()
 
2427
        subprocess.call('less %s' % difftemp.name, shell=True)
 
2428
        difftemp.close()
 
2429
 
 
2430
def set_process(pid, profile):
 
2431
    # If process not running don't do anything
 
2432
    if not os.path.exists('/proc/%s/attr/current' % pid):
 
2433
        return None
 
2434
 
 
2435
    process = None
 
2436
    try:
 
2437
        process = open_file_read('/proc/%s/attr/current' % pid)
 
2438
    except IOError:
 
2439
        return None
 
2440
    current = process.readline().strip()
 
2441
    process.close()
 
2442
 
 
2443
    if not re.search('^null(-complain)*-profile$', current):
 
2444
        return None
 
2445
 
 
2446
    stats = None
 
2447
    try:
 
2448
        stats = open_file_read('/proc/%s/stat' % pid)
 
2449
    except IOError:
 
2450
        return None
 
2451
    stat = stats.readline().strip()
 
2452
    stats.close()
 
2453
 
 
2454
    match = re.search('^\d+ \((\S+)\) ', stat)
 
2455
    if not match:
 
2456
        return None
 
2457
 
 
2458
    try:
 
2459
        process = open_file_write('/proc/%s/attr/current' % pid)
 
2460
    except IOError:
 
2461
        return None
 
2462
    process.write('setprofile %s' % profile)
 
2463
    process.close()
 
2464
 
 
2465
def collapse_log():
 
2466
    for aamode in prelog.keys():
 
2467
        for profile in prelog[aamode].keys():
 
2468
            for hat in prelog[aamode][profile].keys():
 
2469
 
 
2470
                for path in prelog[aamode][profile][hat]['path'].keys():
 
2471
                    mode = prelog[aamode][profile][hat]['path'][path]
 
2472
 
 
2473
                    combinedmode = set()
 
2474
                    # Is path in original profile?
 
2475
                    if aa[profile][hat]['allow']['path'].get(path, False):
 
2476
                        combinedmode |= aa[profile][hat]['allow']['path'][path]['mode']
 
2477
 
 
2478
                    # Match path to regexps in profile
 
2479
                    combinedmode |= rematchfrag(aa[profile][hat], 'allow', path)[0]
 
2480
 
 
2481
                    # Match path from includes
 
2482
 
 
2483
                    combinedmode |= match_prof_incs_to_path(aa[profile][hat], 'allow', path)[0]
 
2484
 
 
2485
                    if not combinedmode or not mode_contains(combinedmode, mode):
 
2486
                        if log_dict[aamode][profile][hat]['path'].get(path, False):
 
2487
                            mode |= log_dict[aamode][profile][hat]['path'][path]
 
2488
 
 
2489
                        log_dict[aamode][profile][hat]['path'][path] = mode
 
2490
 
 
2491
                for capability in prelog[aamode][profile][hat]['capability'].keys():
 
2492
                    # If capability not already in profile
 
2493
                    if not aa[profile][hat]['allow']['capability'][capability].get('set', False):
 
2494
                        log_dict[aamode][profile][hat]['capability'][capability] = True
 
2495
 
 
2496
                nd = prelog[aamode][profile][hat]['netdomain']
 
2497
                for family in nd.keys():
 
2498
                    for sock_type in nd[family].keys():
 
2499
                        if not profile_known_network(aa[profile][hat], family, sock_type):
 
2500
                            log_dict[aamode][profile][hat]['netdomain'][family][sock_type] = True
 
2501
 
 
2502
 
 
2503
def validate_profile_mode(mode, allow, nt_name=None):
 
2504
    if allow == 'deny':
 
2505
        pattern = '^(%s)+$' % PROFILE_MODE_DENY_RE.pattern
 
2506
        if re.search(pattern, mode):
 
2507
            return True
 
2508
        else:
 
2509
            return False
 
2510
 
 
2511
    elif nt_name:
 
2512
        pattern = '^(%s)+$' % PROFILE_MODE_NT_RE.pattern
 
2513
        if re.search(pattern, mode):
 
2514
            return True
 
2515
        else:
 
2516
            return False
 
2517
 
 
2518
    else:
 
2519
        pattern = '^(%s)+$' % PROFILE_MODE_RE.pattern
 
2520
        if re.search(pattern, mode):
 
2521
            return True
 
2522
        else:
 
2523
            return False
 
2524
 
 
2525
# rpm backup files, dotfiles, emacs backup files should not be processed
 
2526
# The skippable files type needs be synced with apparmor initscript
 
2527
def is_skippable_file(path):
 
2528
    """Returns True if filename matches something to be skipped"""
 
2529
    if (re.search('(^|/)\.[^/]*$', path) or re.search('\.rpm(save|new)$', path)
 
2530
            or re.search('\.dpkg-(old|new)$', path) or re.search('\.swp$', path)
 
2531
            or path[-1] == '~' or path == 'README'):
 
2532
        return True
 
2533
 
 
2534
def is_skippable_dir(path):
 
2535
    if re.search('(disable|cache|force-complain|lxc)', path):
 
2536
        return True
 
2537
    return False
 
2538
 
 
2539
def check_include_syntax(errors):
 
2540
    # To-Do
 
2541
    pass
 
2542
 
 
2543
def check_profile_syntax(errors):
 
2544
    # To-Do
 
2545
    pass
 
2546
 
 
2547
def read_profiles():
 
2548
    try:
 
2549
        os.listdir(profile_dir)
 
2550
    except:
 
2551
        fatal_error(_("Can't read AppArmor profiles in %s") % profile_dir)
 
2552
 
 
2553
    for file in os.listdir(profile_dir):
 
2554
        if os.path.isfile(profile_dir + '/' + file):
 
2555
            if is_skippable_file(file):
 
2556
                continue
 
2557
            else:
 
2558
                read_profile(profile_dir + '/' + file, True)
 
2559
 
 
2560
def read_inactive_profiles():
 
2561
    if not os.path.exists(extra_profile_dir):
 
2562
        return None
 
2563
    try:
 
2564
        os.listdir(profile_dir)
 
2565
    except:
 
2566
        fatal_error(_("Can't read AppArmor profiles in %s") % extra_profile_dir)
 
2567
 
 
2568
    for file in os.listdir(profile_dir):
 
2569
        if os.path.isfile(extra_profile_dir + '/' + file):
 
2570
            if is_skippable_file(file):
 
2571
                continue
 
2572
            else:
 
2573
                read_profile(extra_profile_dir + '/' + file, False)
 
2574
 
 
2575
def read_profile(file, active_profile):
 
2576
    data = None
 
2577
    try:
 
2578
        with open_file_read(file) as f_in:
 
2579
            data = f_in.readlines()
 
2580
    except IOError:
 
2581
        debug_logger.debug("read_profile: can't read %s - skipping" % file)
 
2582
        return None
 
2583
 
 
2584
    profile_data = parse_profile_data(data, file, 0)
 
2585
 
 
2586
    if profile_data and active_profile:
 
2587
        attach_profile_data(aa, profile_data)
 
2588
        attach_profile_data(original_aa, profile_data)
 
2589
    elif profile_data:
 
2590
        attach_profile_data(extras, profile_data)
 
2591
 
 
2592
 
 
2593
def attach_profile_data(profiles, profile_data):
 
2594
    # Make deep copy of data to avoid changes to
 
2595
    # arising due to mutables
 
2596
    for p in profile_data.keys():
 
2597
        profiles[p] = deepcopy(profile_data[p])
 
2598
 
 
2599
## Profile parsing regex
 
2600
RE_PROFILE_START = re.compile('^\s*(("??/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.+)\)\s+)?\{\s*(#.*)?$')
 
2601
RE_PROFILE_END = re.compile('^\s*\}\s*(#.*)?$')
 
2602
RE_PROFILE_CAP = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?capability\s+(\S+)\s*,\s*(#.*)?$')
 
2603
RE_PROFILE_LINK = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?link\s+(((subset)|(<=))\s+)?([\"\@\/].*?"??)\s+->\s*([\"\@\/].*?"??)\s*,\s*(#.*)?$')
 
2604
RE_PROFILE_CHANGE_PROFILE = re.compile('^\s*change_profile\s+->\s*("??.+?"??),(#.*)?$')
 
2605
RE_PROFILE_ALIAS = re.compile('^\s*alias\s+("??.+?"??)\s+->\s*("??.+?"??)\s*,(#.*)?$')
 
2606
RE_PROFILE_RLIMIT = re.compile('^\s*set\s+rlimit\s+(.+)\s+(<=)?\s*(.+)\s*,(#.*)?$')
 
2607
RE_PROFILE_BOOLEAN = re.compile('^\s*(\$\{?\w*\}?)\s*=\s*(true|false)\s*,?\s*(#.*)?$', flags=re.IGNORECASE)
 
2608
RE_PROFILE_VARIABLE = re.compile('^\s*(@\{?\w+\}?)\s*(\+?=)\s*(@*.+?)\s*,?\s*(#.*)?$')
 
2609
RE_PROFILE_CONDITIONAL = re.compile('^\s*if\s+(not\s+)?(\$\{?\w*\}?)\s*\{\s*(#.*)?$')
 
2610
RE_PROFILE_CONDITIONAL_VARIABLE = re.compile('^\s*if\s+(not\s+)?defined\s+(@\{?\w+\}?)\s*\{\s*(#.*)?$')
 
2611
RE_PROFILE_CONDITIONAL_BOOLEAN = re.compile('^\s*if\s+(not\s+)?defined\s+(\$\{?\w+\}?)\s*\{\s*(#.*)?$')
 
2612
RE_PROFILE_PATH_ENTRY = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?(owner\s+)?([\"@/].*?)\s+(\S+)(\s+->\s*(.*?))?\s*,\s*(#.*)?$')
 
2613
RE_PROFILE_NETWORK = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?network(.*)\s*(#.*)?$')
 
2614
RE_PROFILE_CHANGE_HAT = re.compile('^\s*\^(\"??.+?\"??)\s*,\s*(#.*)?$')
 
2615
RE_PROFILE_HAT_DEF = re.compile('^\s*\^(\"??.+?\"??)\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$')
 
2616
RE_NETWORK_FAMILY_TYPE = re.compile('\s+(\S+)\s+(\S+)\s*,$')
 
2617
RE_NETWORK_FAMILY = re.compile('\s+(\S+)\s*,$')
 
2618
RE_PROFILE_DBUS = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?(dbus[^#]*\s*,)\s*(#.*)?$')
 
2619
 
 
2620
# match anything that's not " or #, or matching quotes with anything except quotes inside
 
2621
__re_no_or_quoted_hash = '([^#"]|"[^"]*")*'
 
2622
 
 
2623
RE_RULE_HAS_COMMA = re.compile('^' + __re_no_or_quoted_hash +
 
2624
    ',\s*(#.*)?$')  # match comma plus any trailing comment
 
2625
RE_HAS_COMMENT_SPLIT = re.compile('^(?P<not_comment>' + __re_no_or_quoted_hash + ')' + # store in 'not_comment' group
 
2626
    '(?P<comment>#.*)$')  # match trailing comment and store in 'comment' group
 
2627
 
 
2628
def parse_profile_data(data, file, do_include):
 
2629
    profile_data = hasher()
 
2630
    profile = None
 
2631
    hat = None
 
2632
    in_contained_hat = None
 
2633
    repo_data = None
 
2634
    parsed_profiles = []
 
2635
    initial_comment = ''
 
2636
    lastline = None
 
2637
 
 
2638
    if do_include:
 
2639
        profile = file
 
2640
        hat = file
 
2641
    for lineno, line in enumerate(data):
 
2642
        line = line.strip()
 
2643
        if not line:
 
2644
            continue
 
2645
        # we're dealing with a multiline statement
 
2646
        if lastline:
 
2647
            line = '%s %s' % (lastline, line)
 
2648
            lastline = None
 
2649
        # Starting line of a profile
 
2650
        if RE_PROFILE_START.search(line):
 
2651
            matches = RE_PROFILE_START.search(line).groups()
 
2652
 
 
2653
            if profile:
 
2654
                #print(profile, hat)
 
2655
                if profile != hat or not matches[3]:
 
2656
                    raise AppArmorException(_('%s profile in %s contains syntax errors in line: %s.') % (profile, file, lineno + 1))
 
2657
            # Keep track of the start of a profile
 
2658
            if profile and profile == hat and matches[3]:
 
2659
                # local profile
 
2660
                hat = matches[3]
 
2661
                in_contained_hat = True
 
2662
                profile_data[profile][hat]['profile'] = True
 
2663
            else:
 
2664
                if matches[1]:
 
2665
                    profile = matches[1]
 
2666
                else:
 
2667
                    profile = matches[3]
 
2668
                #print(profile)
 
2669
                if len(profile.split('//')) >= 2:
 
2670
                    profile, hat = profile.split('//')[:2]
 
2671
                else:
 
2672
                    hat = None
 
2673
                in_contained_hat = False
 
2674
                if hat:
 
2675
                    profile_data[profile][hat]['external'] = True
 
2676
                else:
 
2677
                    hat = profile
 
2678
            # Profile stored
 
2679
            existing_profiles[profile] = file
 
2680
 
 
2681
            flags = matches[6]
 
2682
 
 
2683
            profile = strip_quotes(profile)
 
2684
            if hat:
 
2685
                hat = strip_quotes(hat)
 
2686
            # save profile name and filename
 
2687
            profile_data[profile][hat]['name'] = profile
 
2688
            profile_data[profile][hat]['filename'] = file
 
2689
            filelist[file]['profiles'][profile][hat] = True
 
2690
 
 
2691
            profile_data[profile][hat]['flags'] = flags
 
2692
 
 
2693
            profile_data[profile][hat]['allow']['netdomain'] = hasher()
 
2694
            profile_data[profile][hat]['allow']['path'] = hasher()
 
2695
            profile_data[profile][hat]['allow']['dbus'] = list()
 
2696
            # Save the initial comment
 
2697
            if initial_comment:
 
2698
                profile_data[profile][hat]['initial_comment'] = initial_comment
 
2699
 
 
2700
            initial_comment = ''
 
2701
 
 
2702
            if repo_data:
 
2703
                profile_data[profile][profile]['repo']['url'] = repo_data['url']
 
2704
                profile_data[profile][profile]['repo']['user'] = repo_data['user']
 
2705
 
 
2706
        elif RE_PROFILE_END.search(line):
 
2707
            # If profile ends and we're not in one
 
2708
            if not profile:
 
2709
                raise AppArmorException(_('Syntax Error: Unexpected End of Profile reached in file: %s line: %s') % (file, lineno + 1))
 
2710
 
 
2711
            if in_contained_hat:
 
2712
                hat = profile
 
2713
                in_contained_hat = False
 
2714
            else:
 
2715
                parsed_profiles.append(profile)
 
2716
                profile = None
 
2717
 
 
2718
            initial_comment = ''
 
2719
 
 
2720
        elif RE_PROFILE_CAP.search(line):
 
2721
            matches = RE_PROFILE_CAP.search(line).groups()
 
2722
 
 
2723
            if not profile:
 
2724
                raise AppArmorException(_('Syntax Error: Unexpected capability entry found in file: %s line: %s') % (file, lineno + 1))
 
2725
 
 
2726
            audit = False
 
2727
            if matches[0]:
 
2728
                audit = True
 
2729
 
 
2730
            allow = 'allow'
 
2731
            if matches[1] and matches[1].strip() == 'deny':
 
2732
                allow = 'deny'
 
2733
 
 
2734
            capability = matches[2]
 
2735
 
 
2736
            profile_data[profile][hat][allow]['capability'][capability]['set'] = True
 
2737
            profile_data[profile][hat][allow]['capability'][capability]['audit'] = audit
 
2738
 
 
2739
        elif RE_PROFILE_LINK.search(line):
 
2740
            matches = RE_PROFILE_LINK.search(line).groups()
 
2741
 
 
2742
            if not profile:
 
2743
                raise AppArmorException(_('Syntax Error: Unexpected link entry found in file: %s line: %s') % (file, lineno + 1))
 
2744
 
 
2745
            audit = False
 
2746
            if matches[0]:
 
2747
                audit = True
 
2748
 
 
2749
            allow = 'allow'
 
2750
            if matches[1] and matches[1].strip() == 'deny':
 
2751
                allow = 'deny'
 
2752
 
 
2753
            subset = matches[3]
 
2754
            link = strip_quotes(matches[6])
 
2755
            value = strip_quotes(matches[7])
 
2756
            profile_data[profile][hat][allow]['link'][link]['to'] = value
 
2757
            profile_data[profile][hat][allow]['link'][link]['mode'] = profile_data[profile][hat][allow]['link'][link].get('mode', set()) | apparmor.aamode.AA_MAY_LINK
 
2758
 
 
2759
            if subset:
 
2760
                profile_data[profile][hat][allow]['link'][link]['mode'] |= apparmor.aamode.AA_LINK_SUBSET
 
2761
 
 
2762
            if audit:
 
2763
                profile_data[profile][hat][allow]['link'][link]['audit'] = profile_data[profile][hat][allow]['link'][link].get('audit', set()) | apparmor.aamode.AA_LINK_SUBSET
 
2764
            else:
 
2765
                profile_data[profile][hat][allow]['link'][link]['audit'] = set()
 
2766
 
 
2767
        elif RE_PROFILE_CHANGE_PROFILE.search(line):
 
2768
            matches = RE_PROFILE_CHANGE_PROFILE.search(line).groups()
 
2769
 
 
2770
            if not profile:
 
2771
                raise AppArmorException(_('Syntax Error: Unexpected change profile entry found in file: %s line: %s') % (file, lineno + 1))
 
2772
 
 
2773
            cp = strip_quotes(matches[0])
 
2774
            profile_data[profile][hat]['changes_profile'][cp] = True
 
2775
 
 
2776
        elif RE_PROFILE_ALIAS.search(line):
 
2777
            matches = RE_PROFILE_ALIAS.search(line).groups()
 
2778
 
 
2779
            from_name = strip_quotes(matches[0])
 
2780
            to_name = strip_quotes(matches[1])
 
2781
 
 
2782
            if profile:
 
2783
                profile_data[profile][hat]['alias'][from_name] = to_name
 
2784
            else:
 
2785
                if not filelist.get(file, False):
 
2786
                    filelist[file] = hasher()
 
2787
                filelist[file]['alias'][from_name] = to_name
 
2788
 
 
2789
        elif RE_PROFILE_RLIMIT.search(line):
 
2790
            matches = RE_PROFILE_RLIMIT.search(line).groups()
 
2791
 
 
2792
            if not profile:
 
2793
                raise AppArmorException(_('Syntax Error: Unexpected rlimit entry found in file: %s line: %s') % (file, lineno + 1))
 
2794
 
 
2795
            from_name = matches[0]
 
2796
            to_name = matches[2]
 
2797
 
 
2798
            profile_data[profile][hat]['rlimit'][from_name] = to_name
 
2799
 
 
2800
        elif RE_PROFILE_BOOLEAN.search(line):
 
2801
            matches = RE_PROFILE_BOOLEAN.search(line)
 
2802
 
 
2803
            if not profile:
 
2804
                raise AppArmorException(_('Syntax Error: Unexpected boolean definition found in file: %s line: %s') % (file, lineno + 1))
 
2805
 
 
2806
            bool_var = matches[0]
 
2807
            value = matches[1]
 
2808
 
 
2809
            profile_data[profile][hat]['lvar'][bool_var] = value
 
2810
 
 
2811
        elif RE_PROFILE_VARIABLE.search(line):
 
2812
            # variable additions += and =
 
2813
            matches = RE_PROFILE_VARIABLE.search(line).groups()
 
2814
 
 
2815
            list_var = strip_quotes(matches[0])
 
2816
            var_operation = matches[1]
 
2817
            value = strip_quotes(matches[2])
 
2818
 
 
2819
            if profile:
 
2820
                if not profile_data[profile][hat].get('lvar', False):
 
2821
                    profile_data[profile][hat]['lvar'][list_var] = []
 
2822
                store_list_var(profile_data[profile]['lvar'], list_var, value, var_operation)
 
2823
            else:
 
2824
                if not filelist[file].get('lvar', False):
 
2825
                    filelist[file]['lvar'][list_var] = []
 
2826
                store_list_var(filelist[file]['lvar'], list_var, value, var_operation)
 
2827
 
 
2828
        elif RE_PROFILE_CONDITIONAL.search(line):
 
2829
            # Conditional Boolean
 
2830
            pass
 
2831
 
 
2832
        elif RE_PROFILE_CONDITIONAL_VARIABLE.search(line):
 
2833
            # Conditional Variable defines
 
2834
            pass
 
2835
 
 
2836
        elif RE_PROFILE_CONDITIONAL_BOOLEAN.search(line):
 
2837
            # Conditional Boolean defined
 
2838
            pass
 
2839
 
 
2840
        elif RE_PROFILE_PATH_ENTRY.search(line):
 
2841
            matches = RE_PROFILE_PATH_ENTRY.search(line).groups()
 
2842
 
 
2843
            if not profile:
 
2844
                raise AppArmorException(_('Syntax Error: Unexpected path entry found in file: %s line: %s') % (file, lineno + 1))
 
2845
 
 
2846
            audit = False
 
2847
            if matches[0]:
 
2848
                audit = True
 
2849
 
 
2850
            allow = 'allow'
 
2851
            if matches[1] and matches[1].strip() == 'deny':
 
2852
                allow = 'deny'
 
2853
 
 
2854
            user = False
 
2855
            if matches[2]:
 
2856
                user = True
 
2857
 
 
2858
            path = matches[3].strip()
 
2859
            mode = matches[4]
 
2860
            nt_name = matches[6]
 
2861
            if nt_name:
 
2862
                nt_name = nt_name.strip()
 
2863
 
 
2864
            p_re = convert_regexp(path)
 
2865
            try:
 
2866
                re.compile(p_re)
 
2867
            except:
 
2868
                raise AppArmorException(_('Syntax Error: Invalid Regex %s in file: %s line: %s') % (path, file, lineno + 1))
 
2869
 
 
2870
            if not validate_profile_mode(mode, allow, nt_name):
 
2871
                raise AppArmorException(_('Invalid mode %s in file: %s line: %s') % (mode, file, lineno + 1))
 
2872
 
 
2873
            tmpmode = set()
 
2874
            if user:
 
2875
                tmpmode = str_to_mode('%s::' % mode)
 
2876
            else:
 
2877
                tmpmode = str_to_mode(mode)
 
2878
 
 
2879
            profile_data[profile][hat][allow]['path'][path]['mode'] = profile_data[profile][hat][allow]['path'][path].get('mode', set()) | tmpmode
 
2880
 
 
2881
            if nt_name:
 
2882
                profile_data[profile][hat][allow]['path'][path]['to'] = nt_name
 
2883
 
 
2884
            if audit:
 
2885
                profile_data[profile][hat][allow]['path'][path]['audit'] = profile_data[profile][hat][allow]['path'][path].get('audit', set()) | tmpmode
 
2886
            else:
 
2887
                profile_data[profile][hat][allow]['path'][path]['audit'] = set()
 
2888
 
 
2889
        elif re_match_include(line):
 
2890
            # Include files
 
2891
            include_name = re_match_include(line)
 
2892
            if include_name.startswith('local/'):
 
2893
                profile_data[profile][hat]['localinclude'][include_name] = True
 
2894
 
 
2895
            if profile:
 
2896
                profile_data[profile][hat]['include'][include_name] = True
 
2897
            else:
 
2898
                if not filelist.get(file):
 
2899
                    filelist[file] = hasher()
 
2900
                filelist[file]['include'][include_name] = True
 
2901
            # If include is a directory
 
2902
            if os.path.isdir(profile_dir + '/' + include_name):
 
2903
                for path in os.listdir(profile_dir + '/' + include_name):
 
2904
                    path = path.strip()
 
2905
                    if is_skippable_file(path):
 
2906
                        continue
 
2907
                    if os.path.isfile(profile_dir + '/' + include_name + '/' + path):
 
2908
                        file_name = include_name + '/' + path
 
2909
                        file_name = file_name.replace(profile_dir + '/', '')
 
2910
                        if not include.get(file_name, False):
 
2911
                            load_include(file_name)
 
2912
            else:
 
2913
                if not include.get(include_name, False):
 
2914
                    load_include(include_name)
 
2915
 
 
2916
        elif RE_PROFILE_NETWORK.search(line):
 
2917
            matches = RE_PROFILE_NETWORK.search(line).groups()
 
2918
 
 
2919
            if not profile:
 
2920
                raise AppArmorException(_('Syntax Error: Unexpected network entry found in file: %s line: %s') % (file, lineno + 1))
 
2921
 
 
2922
            audit = False
 
2923
            if matches[0]:
 
2924
                audit = True
 
2925
            allow = 'allow'
 
2926
            if matches[1] and matches[1].strip() == 'deny':
 
2927
                allow = 'deny'
 
2928
            network = matches[2]
 
2929
 
 
2930
            if RE_NETWORK_FAMILY_TYPE.search(network):
 
2931
                nmatch = RE_NETWORK_FAMILY_TYPE.search(network).groups()
 
2932
                fam, typ = nmatch[:2]
 
2933
                ##Simply ignore any type subrules if family has True (seperately for allow and deny)
 
2934
                ##This will lead to those type specific rules being lost when written
 
2935
                #if type(profile_data[profile][hat][allow]['netdomain']['rule'].get(fam, False)) == dict:
 
2936
                profile_data[profile][hat][allow]['netdomain']['rule'][fam][typ] = 1
 
2937
                profile_data[profile][hat][allow]['netdomain']['audit'][fam][typ] = audit
 
2938
            elif RE_NETWORK_FAMILY.search(network):
 
2939
                fam = RE_NETWORK_FAMILY.search(network).groups()[0]
 
2940
                profile_data[profile][hat][allow]['netdomain']['rule'][fam] = True
 
2941
                profile_data[profile][hat][allow]['netdomain']['audit'][fam] = audit
 
2942
            else:
 
2943
                profile_data[profile][hat][allow]['netdomain']['rule']['all'] = True
 
2944
                profile_data[profile][hat][allow]['netdomain']['audit']['all'] = audit  # True
 
2945
 
 
2946
        elif RE_PROFILE_DBUS.search(line):
 
2947
            matches = RE_PROFILE_DBUS.search(line).groups()
 
2948
 
 
2949
            if not profile:
 
2950
                raise AppArmorException(_('Syntax Error: Unexpected dbus entry found in file: %s line: %s') % (file, lineno + 1))
 
2951
 
 
2952
            audit = False
 
2953
            if matches[0]:
 
2954
                audit = True
 
2955
            allow = 'allow'
 
2956
            if matches[1] and matches[1].strip() == 'deny':
 
2957
                allow = 'deny'
 
2958
            dbus = matches[2]
 
2959
 
 
2960
            #parse_dbus_rule(profile_data[profile], dbus, audit, allow)
 
2961
            dbus_rule = parse_dbus_rule(dbus)
 
2962
            dbus_rule.audit = audit
 
2963
            dbus_rule.deny = (allow == 'deny')
 
2964
 
 
2965
            dbus_rules = profile_data[profile][hat][allow].get('dbus', list())
 
2966
            dbus_rules.append(dbus_rule)
 
2967
            profile_data[profile][hat][allow]['dbus'] = dbus_rules
 
2968
 
 
2969
        elif RE_PROFILE_CHANGE_HAT.search(line):
 
2970
            matches = RE_PROFILE_CHANGE_HAT.search(line).groups()
 
2971
 
 
2972
            if not profile:
 
2973
                raise AppArmorException(_('Syntax Error: Unexpected change hat declaration found in file: %s line: %s') % (file, lineno + 1))
 
2974
 
 
2975
            hat = matches[0]
 
2976
            hat = strip_quotes(hat)
 
2977
 
 
2978
            if not profile_data[profile][hat].get('declared', False):
 
2979
                profile_data[profile][hat]['declared'] = True
 
2980
 
 
2981
        elif RE_PROFILE_HAT_DEF.search(line):
 
2982
            # An embedded hat syntax definition starts
 
2983
            matches = RE_PROFILE_HAT_DEF.search(line).groups()
 
2984
            if not profile:
 
2985
                raise AppArmorException(_('Syntax Error: Unexpected hat definition found in file: %s line: %s') % (file, lineno + 1))
 
2986
 
 
2987
            in_contained_hat = True
 
2988
            hat = matches[0]
 
2989
            hat = strip_quotes(hat)
 
2990
            flags = matches[3]
 
2991
 
 
2992
            profile_data[profile][hat]['flags'] = flags
 
2993
            profile_data[profile][hat]['declared'] = False
 
2994
            #profile_data[profile][hat]['allow']['path'] = hasher()
 
2995
            #profile_data[profile][hat]['allow']['netdomain'] = hasher()
 
2996
 
 
2997
            if initial_comment:
 
2998
                profile_data[profile][hat]['initial_comment'] = initial_comment
 
2999
            initial_comment = ''
 
3000
            if filelist[file]['profiles'][profile].get(hat, False):
 
3001
                raise AppArmorException(_('Error: Multiple definitions for hat %s in profile %s.') % (hat, profile))
 
3002
            filelist[file]['profiles'][profile][hat] = True
 
3003
 
 
3004
        elif line[0] == '#':
 
3005
            # Handle initial comments
 
3006
            if not profile:
 
3007
                if line.startswith('# Last Modified:'):
 
3008
                    continue
 
3009
                elif line.startswith('# REPOSITORY:'): # TODO: allow any number of spaces/tabs
 
3010
                    parts = line.split()
 
3011
                    if len(parts) == 3 and parts[2] == 'NEVERSUBMIT':
 
3012
                        repo_data = {'neversubmit': True}
 
3013
                    elif len(parts) == 5:
 
3014
                        repo_data = {'url': parts[2],
 
3015
                                     'user': parts[3],
 
3016
                                     'id': parts[4]}
 
3017
                    else:
 
3018
                        aaui.UI_Important(_('Warning: invalid "REPOSITORY:" line in %s, ignoring.') % file)
 
3019
                        initial_comment = initial_comment + line + '\n'
 
3020
                else:
 
3021
                    initial_comment = initial_comment + line + '\n'
 
3022
 
 
3023
        elif not RE_RULE_HAS_COMMA.search(line):
 
3024
            # Bah, line continues on to the next line
 
3025
            if RE_HAS_COMMENT_SPLIT.search(line):
 
3026
                # filter trailing comments
 
3027
                lastline = RE_HAS_COMMENT_SPLIT.search(line).group('not_comment')
 
3028
            else:
 
3029
                lastline = line
 
3030
        else:
 
3031
            raise AppArmorException(_('Syntax Error: Unknown line found in file: %s line: %s') % (file, lineno + 1))
 
3032
 
 
3033
    # Below is not required I'd say
 
3034
    if not do_include:
 
3035
        for hatglob in cfg['required_hats'].keys():
 
3036
            for parsed_prof in sorted(parsed_profiles):
 
3037
                if re.search(hatglob, parsed_prof):
 
3038
                    for hat in cfg['required_hats'][hatglob].split():
 
3039
                        if not profile_data[parsed_prof].get(hat, False):
 
3040
                            profile_data[parsed_prof][hat] = hasher()
 
3041
 
 
3042
    # End of file reached but we're stuck in a profile
 
3043
    if profile and not do_include:
 
3044
        raise AppArmorException(_("Syntax Error: Missing '}' . Reached end of file %s  while inside profile %s") % (file, profile))
 
3045
 
 
3046
    return profile_data
 
3047
 
 
3048
# RE_DBUS_ENTRY = re.compile('^dbus\s*()?,\s*$')
 
3049
#   use stuff like '(?P<action>(send|write|w|receive|read|r|rw))'
 
3050
 
 
3051
def parse_dbus_rule(line):
 
3052
    # XXX Do real parsing here
 
3053
    return aarules.Raw_DBUS_Rule(line)
 
3054
 
 
3055
    #matches = RE_DBUS_ENTRY.search(line).groups()
 
3056
    #if len(matches) == 1:
 
3057
        # XXX warn?
 
3058
        # matched nothing
 
3059
    #    print('no matches')
 
3060
    #    return aarules.DBUS_Rule()
 
3061
    #print(line)
 
3062
 
 
3063
def separate_vars(vs):
 
3064
    """Returns a list of all the values for a variable"""
 
3065
    data = []
 
3066
 
 
3067
    #data = [i.strip('"') for i in vs.split()]
 
3068
    RE_VARS = re.compile('\s*((\".+?\")|([^\"]\S+))\s*(.*)$')
 
3069
    while RE_VARS.search(vs):
 
3070
        matches = RE_VARS.search(vs).groups()
 
3071
        data.append(strip_quotes(matches[0]))
 
3072
        vs = matches[3]
 
3073
 
 
3074
    return data
 
3075
 
 
3076
def is_active_profile(pname):
 
3077
    if aa.get(pname, False):
 
3078
        return True
 
3079
    else:
 
3080
        return False
 
3081
 
 
3082
def store_list_var(var, list_var, value, var_operation):
 
3083
    """Store(add new variable or add values to variable) the variables encountered in the given list_var"""
 
3084
    vlist = separate_vars(value)
 
3085
    if var_operation == '=':
 
3086
        if not var.get(list_var, False):
 
3087
            var[list_var] = set(vlist)
 
3088
        else:
 
3089
            #print('Ignored: New definition for variable for:',list_var,'=', value, 'operation was:',var_operation,'old value=', var[list_var])
 
3090
            raise AppArmorException(_('An existing variable redefined: %s') % list_var)
 
3091
    elif var_operation == '+=':
 
3092
        if var.get(list_var, False):
 
3093
            var[list_var] = set(var[list_var] + vlist)
 
3094
        else:
 
3095
            raise AppArmorException(_('Values added to a non-existing variable: %s') % list_var)
 
3096
    else:
 
3097
        raise AppArmorException(_('Unknown variable operation: %s') % var_operation)
 
3098
 
 
3099
 
 
3100
def strip_quotes(data):
 
3101
    if data[0] + data[-1] == '""':
 
3102
        return data[1:-1]
 
3103
    else:
 
3104
        return data
 
3105
 
 
3106
def quote_if_needed(data):
 
3107
    # quote data if it contains whitespace
 
3108
    if ' ' in data:
 
3109
        data = '"' + data + '"'
 
3110
    return data
 
3111
 
 
3112
def escape(escape):
 
3113
    escape = strip_quotes(escape)
 
3114
    escape = re.sub('((?<!\\))"', r'\1\\', escape)
 
3115
    if re.search('(\s|^$|")', escape):
 
3116
        return '"%s"' % escape
 
3117
    return escape
 
3118
 
 
3119
def write_header(prof_data, depth, name, embedded_hat, write_flags):
 
3120
    pre = '  ' * depth
 
3121
    data = []
 
3122
    name = quote_if_needed(name)
 
3123
 
 
3124
    if (not embedded_hat and re.search('^[^/]|^"[^/]', name)) or (embedded_hat and re.search('^[^^]', name)):
 
3125
        name = 'profile %s' % name
 
3126
 
 
3127
    if write_flags and prof_data['flags']:
 
3128
        data.append('%s%s flags=(%s) {' % (pre, name, prof_data['flags']))
 
3129
    else:
 
3130
        data.append('%s%s {' % (pre, name))
 
3131
 
 
3132
    return data
 
3133
 
 
3134
def write_single(prof_data, depth, allow, name, prefix, tail):
 
3135
    pre = '  ' * depth
 
3136
    data = []
 
3137
    ref, allow = set_ref_allow(prof_data, allow)
 
3138
 
 
3139
    if ref.get(name, False):
 
3140
        for key in sorted(ref[name].keys()):
 
3141
            qkey = quote_if_needed(key)
 
3142
            data.append('%s%s%s%s%s' % (pre, allow, prefix, qkey, tail))
 
3143
        if ref[name].keys():
 
3144
            data.append('')
 
3145
 
 
3146
    return data
 
3147
 
 
3148
def set_allow_str(allow):
 
3149
    if allow == 'deny':
 
3150
        return 'deny '
 
3151
    elif allow == 'allow':
 
3152
        return ''
 
3153
    elif allow == '':
 
3154
        return ''
 
3155
    else:
 
3156
        raise AppArmorException(_("Invalid allow string: %(allow)s"))
 
3157
 
 
3158
def set_ref_allow(prof_data, allow):
 
3159
    if allow:
 
3160
        return prof_data[allow], set_allow_str(allow)
 
3161
    else:
 
3162
        return prof_data, ''
 
3163
 
 
3164
 
 
3165
def write_pair(prof_data, depth, allow, name, prefix, sep, tail, fn):
 
3166
    pre = '  ' * depth
 
3167
    data = []
 
3168
    ref, allow = set_ref_allow(prof_data, allow)
 
3169
 
 
3170
    if ref.get(name, False):
 
3171
        for key in sorted(ref[name].keys()):
 
3172
            value = fn(ref[name][key])  # eval('%s(%s)' % (fn, ref[name][key]))
 
3173
            data.append('%s%s%s%s%s%s' % (pre, allow, prefix, key, sep, value))
 
3174
        if ref[name].keys():
 
3175
            data.append('')
 
3176
 
 
3177
    return data
 
3178
 
 
3179
def write_includes(prof_data, depth):
 
3180
    return write_single(prof_data, depth, '', 'include', '#include <', '>')
 
3181
 
 
3182
def write_change_profile(prof_data, depth):
 
3183
    return write_single(prof_data, depth, '', 'change_profile', 'change_profile -> ', ',')
 
3184
 
 
3185
def write_alias(prof_data, depth):
 
3186
    return write_pair(prof_data, depth, '', 'alias', 'alias ', ' -> ', ',', quote_if_needed)
 
3187
 
 
3188
def write_rlimits(prof_data, depth):
 
3189
    return write_pair(prof_data, depth, '', 'rlimit', 'set rlimit ', ' <= ', ',', quote_if_needed)
 
3190
 
 
3191
def var_transform(ref):
 
3192
    data = []
 
3193
    for value in ref:
 
3194
        data.append(quote_if_needed(value))
 
3195
    return ' '.join(data)
 
3196
 
 
3197
def write_list_vars(prof_data, depth):
 
3198
    return write_pair(prof_data, depth, '', 'lvar', '', ' = ', '', var_transform)
 
3199
 
 
3200
def write_cap_rules(prof_data, depth, allow):
 
3201
    pre = '  ' * depth
 
3202
    data = []
 
3203
    allowstr = set_allow_str(allow)
 
3204
 
 
3205
    if prof_data[allow].get('capability', False):
 
3206
        for cap in sorted(prof_data[allow]['capability'].keys()):
 
3207
            audit = ''
 
3208
            if prof_data[allow]['capability'][cap].get('audit', False):
 
3209
                audit = 'audit '
 
3210
            if prof_data[allow]['capability'][cap].get('set', False):
 
3211
                data.append('%s%s%scapability %s,' % (pre, audit, allowstr, cap))
 
3212
        data.append('')
 
3213
 
 
3214
    return data
 
3215
 
 
3216
def write_capabilities(prof_data, depth):
 
3217
    #data = write_single(prof_data, depth, '', 'set_capability', 'set capability ', ',')
 
3218
    data = write_cap_rules(prof_data, depth, 'deny')
 
3219
    data += write_cap_rules(prof_data, depth, 'allow')
 
3220
    return data
 
3221
 
 
3222
def write_net_rules(prof_data, depth, allow):
 
3223
    pre = '  ' * depth
 
3224
    data = []
 
3225
    allowstr = set_allow_str(allow)
 
3226
    audit = ''
 
3227
    if prof_data[allow].get('netdomain', False):
 
3228
        if prof_data[allow]['netdomain'].get('rule', False) == 'all':
 
3229
            if prof_data[allow]['netdomain']['audit'].get('all', False):
 
3230
                audit = 'audit '
 
3231
            data.append('%s%snetwork,' % (pre, audit))
 
3232
        else:
 
3233
            for fam in sorted(prof_data[allow]['netdomain']['rule'].keys()):
 
3234
                if prof_data[allow]['netdomain']['rule'][fam] is True:
 
3235
                    if prof_data[allow]['netdomain']['audit'][fam]:
 
3236
                        audit = 'audit'
 
3237
                    data.append('%s%s%snetwork %s' % (pre, audit, allowstr, fam))
 
3238
                else:
 
3239
                    for typ in sorted(prof_data[allow]['netdomain']['rule'][fam].keys()):
 
3240
                        if prof_data[allow]['netdomain']['audit'][fam].get(typ, False):
 
3241
                            audit = 'audit'
 
3242
                        data.append('%s%s%snetwork %s %s,' % (pre, audit, allowstr, fam, typ))
 
3243
        if prof_data[allow].get('netdomain', False):
 
3244
            data.append('')
 
3245
 
 
3246
    return data
 
3247
 
 
3248
def write_netdomain(prof_data, depth):
 
3249
    data = write_net_rules(prof_data, depth, 'deny')
 
3250
    data += write_net_rules(prof_data, depth, 'allow')
 
3251
    return data
 
3252
 
 
3253
def write_dbus_rules(prof_data, depth, allow):
 
3254
    pre = '  ' * depth
 
3255
    data = []
 
3256
 
 
3257
    # no dbus rules, so return
 
3258
    if not prof_data[allow].get('dbus', False):
 
3259
        return data
 
3260
 
 
3261
    for dbus_rule in prof_data[allow]['dbus']:
 
3262
        data.append('%s%s' % (pre, dbus_rule.serialize()))
 
3263
    data.append('')
 
3264
    return data
 
3265
 
 
3266
def write_dbus(prof_data, depth):
 
3267
    data = write_dbus_rules(prof_data, depth, 'deny')
 
3268
    data += write_net_rules(prof_data, depth, 'allow')
 
3269
    return data
 
3270
 
 
3271
def write_link_rules(prof_data, depth, allow):
 
3272
    pre = '  ' * depth
 
3273
    data = []
 
3274
    allowstr = set_allow_str(allow)
 
3275
 
 
3276
    if prof_data[allow].get('link', False):
 
3277
        for path in sorted(prof_data[allow]['link'].keys()):
 
3278
            to_name = prof_data[allow]['link'][path]['to']
 
3279
            subset = ''
 
3280
            if prof_data[allow]['link'][path]['mode'] & apparmor.aamode.AA_LINK_SUBSET:
 
3281
                subset = 'subset'
 
3282
            audit = ''
 
3283
            if prof_data[allow]['link'][path].get('audit', False):
 
3284
                audit = 'audit '
 
3285
            path = quote_if_needed(path)
 
3286
            to_name = quote_if_needed(to_name)
 
3287
            data.append('%s%s%slink %s%s -> %s,' % (pre, audit, allowstr, subset, path, to_name))
 
3288
        data.append('')
 
3289
 
 
3290
    return data
 
3291
 
 
3292
def write_links(prof_data, depth):
 
3293
    data = write_link_rules(prof_data, depth, 'deny')
 
3294
    data += write_link_rules(prof_data, depth, 'allow')
 
3295
 
 
3296
    return data
 
3297
 
 
3298
def write_path_rules(prof_data, depth, allow):
 
3299
    pre = '  ' * depth
 
3300
    data = []
 
3301
    allowstr = set_allow_str(allow)
 
3302
 
 
3303
    if prof_data[allow].get('path', False):
 
3304
        for path in sorted(prof_data[allow]['path'].keys()):
 
3305
            mode = prof_data[allow]['path'][path]['mode']
 
3306
            audit = prof_data[allow]['path'][path]['audit']
 
3307
            tail = ''
 
3308
            if prof_data[allow]['path'][path].get('to', False):
 
3309
                tail = ' -> %s' % prof_data[allow]['path'][path]['to']
 
3310
            user, other = split_mode(mode)
 
3311
            user_audit, other_audit = split_mode(audit)
 
3312
 
 
3313
            while user or other:
 
3314
                ownerstr = ''
 
3315
                tmpmode = 0
 
3316
                tmpaudit = False
 
3317
                if user - other:
 
3318
                    # if no other mode set
 
3319
                    ownerstr = 'owner '
 
3320
                    tmpmode = user - other
 
3321
                    tmpaudit = user_audit
 
3322
                    user = user - tmpmode
 
3323
                else:
 
3324
                    if user_audit - other_audit & user:
 
3325
                        ownerstr = 'owner '
 
3326
                        tmpaudit = user_audit - other_audit & user
 
3327
                        tmpmode = user & tmpaudit
 
3328
                        user = user - tmpmode
 
3329
                    else:
 
3330
                        ownerstr = ''
 
3331
                        tmpmode = user | other
 
3332
                        tmpaudit = user_audit | other_audit
 
3333
                        user = user - tmpmode
 
3334
                        other = other - tmpmode
 
3335
 
 
3336
                if tmpmode & tmpaudit:
 
3337
                    modestr = mode_to_str(tmpmode & tmpaudit)
 
3338
                    path = quote_if_needed(path)
 
3339
                    data.append('%saudit %s%s%s %s%s,' % (pre, allowstr, ownerstr, path, modestr, tail))
 
3340
                    tmpmode = tmpmode - tmpaudit
 
3341
 
 
3342
                if tmpmode:
 
3343
                    modestr = mode_to_str(tmpmode)
 
3344
                    path = quote_if_needed(path)
 
3345
                    data.append('%s%s%s%s %s%s,' % (pre, allowstr, ownerstr, path, modestr, tail))
 
3346
 
 
3347
        data.append('')
 
3348
    return data
 
3349
 
 
3350
def write_paths(prof_data, depth):
 
3351
    data = write_path_rules(prof_data, depth, 'deny')
 
3352
    data += write_path_rules(prof_data, depth, 'allow')
 
3353
 
 
3354
    return data
 
3355
 
 
3356
def write_rules(prof_data, depth):
 
3357
    data = write_alias(prof_data, depth)
 
3358
    data += write_list_vars(prof_data, depth)
 
3359
    data += write_includes(prof_data, depth)
 
3360
    data += write_rlimits(prof_data, depth)
 
3361
    data += write_capabilities(prof_data, depth)
 
3362
    data += write_netdomain(prof_data, depth)
 
3363
    data += write_dbus(prof_data, depth)
 
3364
    data += write_links(prof_data, depth)
 
3365
    data += write_paths(prof_data, depth)
 
3366
    data += write_change_profile(prof_data, depth)
 
3367
 
 
3368
    return data
 
3369
 
 
3370
def write_piece(profile_data, depth, name, nhat, write_flags):
 
3371
    pre = '  ' * depth
 
3372
    data = []
 
3373
    wname = None
 
3374
    inhat = False
 
3375
    if name == nhat:
 
3376
        wname = name
 
3377
    else:
 
3378
        wname = name + '//' + nhat
 
3379
        name = nhat
 
3380
        inhat = True
 
3381
    data += write_header(profile_data[name], depth, wname, False, write_flags)
 
3382
    data += write_rules(profile_data[name], depth + 1)
 
3383
 
 
3384
    pre2 = '  ' * (depth + 1)
 
3385
    # External hat declarations
 
3386
    for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))):
 
3387
        if profile_data[hat].get('declared', False):
 
3388
            data.append('%s^%s,' % (pre2, hat))
 
3389
 
 
3390
    if not inhat:
 
3391
        # Embedded hats
 
3392
        for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))):
 
3393
            if not profile_data[hat]['external'] and not profile_data[hat]['declared']:
 
3394
                data.append('')
 
3395
                if profile_data[hat]['profile']:
 
3396
                    data += list(map(str, write_header(profile_data[hat], depth + 1, hat, True, write_flags)))
 
3397
                else:
 
3398
                    data += list(map(str, write_header(profile_data[hat], depth + 1, '^' + hat, True, write_flags)))
 
3399
 
 
3400
                data += list(map(str, write_rules(profile_data[hat], depth + 2)))
 
3401
 
 
3402
                data.append('%s}' % pre2)
 
3403
 
 
3404
        data.append('%s}' % pre)
 
3405
 
 
3406
        # External hats
 
3407
        for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))):
 
3408
            if name == nhat and profile_data[hat].get('external', False):
 
3409
                data.append('')
 
3410
                data += list(map(lambda x: '  %s' % x, write_piece(profile_data, depth - 1, name, nhat, write_flags)))
 
3411
                data.append('  }')
 
3412
 
 
3413
    return data
 
3414
 
 
3415
def serialize_profile(profile_data, name, options):
 
3416
    string = ''
 
3417
    include_metadata = False
 
3418
    include_flags = True
 
3419
    data = []
 
3420
 
 
3421
    if options:  # and type(options) == dict:
 
3422
        if options.get('METADATA', False):
 
3423
            include_metadata = True
 
3424
        if options.get('NO_FLAGS', False):
 
3425
            include_flags = False
 
3426
 
 
3427
    if include_metadata:
 
3428
        string = '# Last Modified: %s\n' % time.asctime()
 
3429
 
 
3430
        if (profile_data[name].get('repo', False) and
 
3431
                profile_data[name]['repo']['url'] and
 
3432
                profile_data[name]['repo']['user'] and
 
3433
                profile_data[name]['repo']['id']):
 
3434
            repo = profile_data[name]['repo']
 
3435
            string += '# REPOSITORY: %s %s %s\n' % (repo['url'], repo['user'], repo['id'])
 
3436
        elif profile_data[name]['repo']['neversubmit']:
 
3437
            string += '# REPOSITORY: NEVERSUBMIT\n'
 
3438
 
 
3439
#     if profile_data[name].get('initial_comment', False):
 
3440
#         comment = profile_data[name]['initial_comment']
 
3441
#         comment.replace('\\n', '\n')
 
3442
#         string += comment + '\n'
 
3443
 
 
3444
    prof_filename = get_profile_filename(name)
 
3445
    if filelist.get(prof_filename, False):
 
3446
        data += write_alias(filelist[prof_filename], 0)
 
3447
        data += write_list_vars(filelist[prof_filename], 0)
 
3448
        data += write_includes(filelist[prof_filename], 0)
 
3449
 
 
3450
    #Here should be all the profiles from the files added write after global/common stuff
 
3451
    for prof in sorted(filelist[prof_filename]['profiles'].keys()):
 
3452
        if prof != name:
 
3453
            if original_aa[prof][prof].get('initial_comment', False):
 
3454
                comment = profile_data[name]['initial_comment']
 
3455
                comment.replace('\\n', '\n')
 
3456
                data += [comment + '\n']
 
3457
            data += write_piece(original_aa[prof], 0, prof, prof, include_flags)
 
3458
        else:
 
3459
            if profile_data[name].get('initial_comment', False):
 
3460
                comment = profile_data[name]['initial_comment']
 
3461
                comment.replace('\\n', '\n')
 
3462
                data += [comment + '\n']
 
3463
            data += write_piece(profile_data, 0, name, name, include_flags)
 
3464
 
 
3465
    string += '\n'.join(data)
 
3466
 
 
3467
    return string + '\n'
 
3468
 
 
3469
def serialize_profile_from_old_profile(profile_data, name, options):
 
3470
    data = []
 
3471
    string = ''
 
3472
    include_metadata = False
 
3473
    include_flags = True
 
3474
    prof_filename = get_profile_filename(name)
 
3475
 
 
3476
    write_filelist = deepcopy(filelist[prof_filename])
 
3477
    write_prof_data = deepcopy(profile_data)
 
3478
 
 
3479
    if options:  # and type(options) == dict:
 
3480
        if options.get('METADATA', False):
 
3481
            include_metadata = True
 
3482
        if options.get('NO_FLAGS', False):
 
3483
            include_flags = False
 
3484
 
 
3485
    if include_metadata:
 
3486
        string = '# Last Modified: %s\n' % time.asctime()
 
3487
 
 
3488
        if (profile_data[name].get('repo', False) and
 
3489
                profile_data[name]['repo']['url'] and
 
3490
                profile_data[name]['repo']['user'] and
 
3491
                profile_data[name]['repo']['id']):
 
3492
            repo = profile_data[name]['repo']
 
3493
            string += '# REPOSITORY: %s %s %s\n' % (repo['url'], repo['user'], repo['id'])
 
3494
        elif profile_data[name]['repo']['neversubmit']:
 
3495
            string += '# REPOSITORY: NEVERSUBMIT\n'
 
3496
 
 
3497
    if not os.path.isfile(prof_filename):
 
3498
        raise AppArmorException(_("Can't find existing profile to modify"))
 
3499
 
 
3500
    # profiles_list = filelist[prof_filename].keys()  # XXX
 
3501
 
 
3502
    with open_file_read(prof_filename) as f_in:
 
3503
        profile = None
 
3504
        hat = None
 
3505
        write_methods = {'alias': write_alias,
 
3506
                         'lvar': write_list_vars,
 
3507
                         'include': write_includes,
 
3508
                         'rlimit': write_rlimits,
 
3509
                         'capability': write_capabilities,
 
3510
                         'netdomain': write_netdomain,
 
3511
                         'dbus': write_dbus,
 
3512
                         'link': write_links,
 
3513
                         'path': write_paths,
 
3514
                         'change_profile': write_change_profile,
 
3515
                         }
 
3516
        # prof_correct = True  # XXX correct?
 
3517
        segments = {'alias': False,
 
3518
                    'lvar': False,
 
3519
                    'include': False,
 
3520
                    'rlimit': False,
 
3521
                    'capability': False,
 
3522
                    'netdomain': False,
 
3523
                    'dbus': False,
 
3524
                    'link': False,
 
3525
                    'path': False,
 
3526
                    'change_profile': False,
 
3527
                    'include_local_started': False,
 
3528
                    }
 
3529
        #data.append('reading prof')
 
3530
        for line in f_in:
 
3531
            correct = True
 
3532
            line = line.rstrip('\n')
 
3533
            #data.append(' ')#data.append('read: '+line)
 
3534
            if RE_PROFILE_START.search(line):
 
3535
                matches = RE_PROFILE_START.search(line).groups()
 
3536
                if profile and profile == hat and matches[3]:
 
3537
                    hat = matches[3]
 
3538
                    in_contained_hat = True
 
3539
                    if write_prof_data[profile][hat]['profile']:
 
3540
                        pass
 
3541
                else:
 
3542
                    if matches[1]:
 
3543
                        profile = matches[1]
 
3544
                    else:
 
3545
                        profile = matches[3]
 
3546
                    if len(profile.split('//')) >= 2:
 
3547
                        profile, hat = profile.split('//')[:2]
 
3548
                    else:
 
3549
                        hat = None
 
3550
                    in_contained_hat = False
 
3551
                    if hat and not write_prof_data[profile][hat]['external']:
 
3552
                        correct = False
 
3553
                    else:
 
3554
                        hat = profile
 
3555
 
 
3556
                flags = matches[6]
 
3557
                profile = strip_quotes(profile)
 
3558
                if hat:
 
3559
                    hat = strip_quotes(hat)
 
3560
 
 
3561
                if not write_prof_data[hat]['name'] == profile:
 
3562
                    correct = False
 
3563
 
 
3564
                if not write_filelist['profiles'][profile][hat] is True:
 
3565
                    correct = False
 
3566
 
 
3567
                if not write_prof_data[hat]['flags'] == flags:
 
3568
                    correct = False
 
3569
 
 
3570
                #Write the profile start
 
3571
                if correct:
 
3572
                    if write_filelist:
 
3573
                        data += write_alias(write_filelist, 0)
 
3574
                        data += write_list_vars(write_filelist, 0)
 
3575
                        data += write_includes(write_filelist, 0)
 
3576
                    data.append(line)
 
3577
                else:
 
3578
                    if write_prof_data[hat]['name'] == profile:
 
3579
                        depth = len(line) - len(line.lstrip())
 
3580
                        data += write_header(write_prof_data[name], int(depth / 2), name, False, include_flags)
 
3581
 
 
3582
            elif RE_PROFILE_END.search(line):
 
3583
                # DUMP REMAINDER OF PROFILE
 
3584
                if profile:
 
3585
                    depth = len(line) - len(line.lstrip())
 
3586
                    if True in segments.values():
 
3587
                        for segs in list(filter(lambda x: segments[x], segments.keys())):
 
3588
 
 
3589
                            data += write_methods[segs](write_prof_data[name], int(depth / 2))
 
3590
                            segments[segs] = False
 
3591
                            if write_prof_data[name]['allow'].get(segs, False):
 
3592
                                write_prof_data[name]['allow'].pop(segs)
 
3593
                            if write_prof_data[name]['deny'].get(segs, False):
 
3594
                                write_prof_data[name]['deny'].pop(segs)
 
3595
 
 
3596
                    data += write_alias(write_prof_data[name], depth)
 
3597
                    data += write_list_vars(write_prof_data[name], depth)
 
3598
                    data += write_includes(write_prof_data[name], depth)
 
3599
                    data += write_rlimits(write_prof_data, depth)
 
3600
                    data += write_capabilities(write_prof_data[name], depth)
 
3601
                    data += write_netdomain(write_prof_data[name], depth)
 
3602
                    data += write_dbus(write_prof_data[name], depth)
 
3603
                    data += write_links(write_prof_data[name], depth)
 
3604
                    data += write_paths(write_prof_data[name], depth)
 
3605
                    data += write_change_profile(write_prof_data[name], depth)
 
3606
 
 
3607
                    write_prof_data.pop(name)
 
3608
 
 
3609
                    #Append local includes
 
3610
                    data.append(line)
 
3611
 
 
3612
                if not in_contained_hat:
 
3613
                    # Embedded hats
 
3614
                    depth = int((len(line) - len(line.lstrip())) / 2)
 
3615
                    pre2 = '  ' * (depth + 1)
 
3616
                    for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))):
 
3617
                        if not profile_data[hat]['external'] and not profile_data[hat]['declared']:
 
3618
                            data.append('')
 
3619
                            if profile_data[hat]['profile']:
 
3620
                                data += list(map(str, write_header(profile_data[hat], depth + 1, hat, True, include_flags)))
 
3621
                            else:
 
3622
                                data += list(map(str, write_header(profile_data[hat], depth + 1, '^' + hat, True, include_flags)))
 
3623
 
 
3624
                            data += list(map(str, write_rules(profile_data[hat], depth + 2)))
 
3625
 
 
3626
                            data.append('%s}' % pre2)
 
3627
 
 
3628
                    # External hats
 
3629
                    for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))):
 
3630
                        if profile_data[hat].get('external', False):
 
3631
                            data.append('')
 
3632
                            data += list(map(lambda x: '  %s' % x, write_piece(profile_data, depth - 1, name, name, include_flags)))
 
3633
                            data.append('  }')
 
3634
 
 
3635
                if in_contained_hat:
 
3636
                    #Hat processed, remove it
 
3637
                    hat = profile
 
3638
                    in_contained_hat = False
 
3639
                else:
 
3640
                    profile = None
 
3641
 
 
3642
            elif RE_PROFILE_CAP.search(line):
 
3643
                matches = RE_PROFILE_CAP.search(line).groups()
 
3644
                audit = False
 
3645
                if matches[0]:
 
3646
                    audit = matches[0]
 
3647
 
 
3648
                allow = 'allow'
 
3649
                if matches[1] and matches[1].strip() == 'deny':
 
3650
                    allow = 'deny'
 
3651
 
 
3652
                capability = matches[2]
 
3653
 
 
3654
                if not write_prof_data[hat][allow]['capability'][capability].get('set', False):
 
3655
                    correct = False
 
3656
                if not write_prof_data[hat][allow]['capability'][capability].get(audit, False) == audit:
 
3657
                    correct = False
 
3658
 
 
3659
                if correct:
 
3660
                    if not segments['capability'] and True in segments.values():
 
3661
                        for segs in list(filter(lambda x: segments[x], segments.keys())):
 
3662
                            depth = len(line) - len(line.lstrip())
 
3663
                            data += write_methods[segs](write_prof_data[name], int(depth / 2))
 
3664
                            segments[segs] = False
 
3665
                            if write_prof_data[name]['allow'].get(segs, False):
 
3666
                                write_prof_data[name]['allow'].pop(segs)
 
3667
                            if write_prof_data[name]['deny'].get(segs, False):
 
3668
                                write_prof_data[name]['deny'].pop(segs)
 
3669
                    segments['capability'] = True
 
3670
                    write_prof_data[hat][allow]['capability'].pop(capability)
 
3671
                    data.append(line)
 
3672
 
 
3673
                    #write_prof_data[hat][allow]['capability'][capability].pop(audit)
 
3674
 
 
3675
                    #Remove this line
 
3676
                else:
 
3677
                    # To-Do
 
3678
                    pass
 
3679
            elif RE_PROFILE_LINK.search(line):
 
3680
                matches = RE_PROFILE_LINK.search(line).groups()
 
3681
                audit = False
 
3682
                if matches[0]:
 
3683
                    audit = True
 
3684
                allow = 'allow'
 
3685
                if matches[1] and matches[1].strip() == 'deny':
 
3686
                    allow = 'deny'
 
3687
 
 
3688
                subset = matches[3]
 
3689
                link = strip_quotes(matches[6])
 
3690
                value = strip_quotes(matches[7])
 
3691
                if not write_prof_data[hat][allow]['link'][link]['to'] == value:
 
3692
                    correct = False
 
3693
                if not write_prof_data[hat][allow]['link'][link]['mode'] & apparmor.aamode.AA_MAY_LINK:
 
3694
                    correct = False
 
3695
                if subset and not write_prof_data[hat][allow]['link'][link]['mode'] & apparmor.aamode.AA_LINK_SUBSET:
 
3696
                    correct = False
 
3697
                if audit and not write_prof_data[hat][allow]['link'][link]['audit'] & apparmor.aamode.AA_LINK_SUBSET:
 
3698
                    correct = False
 
3699
 
 
3700
                if correct:
 
3701
                    if not segments['link'] and True in segments.values():
 
3702
                        for segs in list(filter(lambda x: segments[x], segments.keys())):
 
3703
                            depth = len(line) - len(line.lstrip())
 
3704
                            data += write_methods[segs](write_prof_data[name], int(depth / 2))
 
3705
                            segments[segs] = False
 
3706
                            if write_prof_data[name]['allow'].get(segs, False):
 
3707
                                write_prof_data[name]['allow'].pop(segs)
 
3708
                            if write_prof_data[name]['deny'].get(segs, False):
 
3709
                                write_prof_data[name]['deny'].pop(segs)
 
3710
                    segments['link'] = True
 
3711
                    write_prof_data[hat][allow]['link'].pop(link)
 
3712
                    data.append(line)
 
3713
                else:
 
3714
                    # To-Do
 
3715
                    pass
 
3716
 
 
3717
            elif RE_PROFILE_CHANGE_PROFILE.search(line):
 
3718
                matches = RE_PROFILE_CHANGE_PROFILE.search(line).groups()
 
3719
                cp = strip_quotes(matches[0])
 
3720
 
 
3721
                if not write_prof_data[hat]['changes_profile'][cp] is True:
 
3722
                    correct = False
 
3723
 
 
3724
                if correct:
 
3725
                    if not segments['change_profile'] and True in segments.values():
 
3726
                        for segs in list(filter(lambda x: segments[x], segments.keys())):
 
3727
                            depth = len(line) - len(line.lstrip())
 
3728
                            data += write_methods[segs](write_prof_data[name], int(depth / 2))
 
3729
                            segments[segs] = False
 
3730
                            if write_prof_data[name]['allow'].get(segs, False):
 
3731
                                write_prof_data[name]['allow'].pop(segs)
 
3732
                            if write_prof_data[name]['deny'].get(segs, False):
 
3733
                                write_prof_data[name]['deny'].pop(segs)
 
3734
                    segments['change_profile'] = True
 
3735
                    write_prof_data[hat]['change_profile'].pop(cp)
 
3736
                    data.append(line)
 
3737
                else:
 
3738
                    #To-Do
 
3739
                    pass
 
3740
 
 
3741
            elif RE_PROFILE_ALIAS.search(line):
 
3742
                matches = RE_PROFILE_ALIAS.search(line).groups()
 
3743
 
 
3744
                from_name = strip_quotes(matches[0])
 
3745
                to_name = strip_quotes(matches[1])
 
3746
 
 
3747
                if profile:
 
3748
                    if not write_prof_data[hat]['alias'][from_name] == to_name:
 
3749
                        correct = False
 
3750
                else:
 
3751
                    if not write_filelist['alias'][from_name] == to_name:
 
3752
                        correct = False
 
3753
 
 
3754
                if correct:
 
3755
                    if not segments['alias'] and True in segments.values():
 
3756
                        for segs in list(filter(lambda x: segments[x], segments.keys())):
 
3757
                            depth = len(line) - len(line.lstrip())
 
3758
                            data += write_methods[segs](write_prof_data[name], int(depth / 2))
 
3759
                            segments[segs] = False
 
3760
                            if write_prof_data[name]['allow'].get(segs, False):
 
3761
                                write_prof_data[name]['allow'].pop(segs)
 
3762
                            if write_prof_data[name]['deny'].get(segs, False):
 
3763
                                write_prof_data[name]['deny'].pop(segs)
 
3764
                    segments['alias'] = True
 
3765
                    if profile:
 
3766
                        write_prof_data[hat]['alias'].pop(from_name)
 
3767
                    else:
 
3768
                        write_filelist['alias'].pop(from_name)
 
3769
                    data.append(line)
 
3770
                else:
 
3771
                    #To-Do
 
3772
                    pass
 
3773
 
 
3774
            elif RE_PROFILE_RLIMIT.search(line):
 
3775
                matches = RE_PROFILE_RLIMIT.search(line).groups()
 
3776
 
 
3777
                from_name = matches[0]
 
3778
                to_name = matches[2]
 
3779
 
 
3780
                if not write_prof_data[hat]['rlimit'][from_name] == to_name:
 
3781
                    correct = False
 
3782
 
 
3783
                if correct:
 
3784
                    if not segments['rlimit'] and True in segments.values():
 
3785
                        for segs in list(filter(lambda x: segments[x], segments.keys())):
 
3786
                            depth = len(line) - len(line.lstrip())
 
3787
                            data += write_methods[segs](write_prof_data[name], int(depth / 2))
 
3788
                            segments[segs] = False
 
3789
                            if write_prof_data[name]['allow'].get(segs, False):
 
3790
                                write_prof_data[name]['allow'].pop(segs)
 
3791
                            if write_prof_data[name]['deny'].get(segs, False):
 
3792
                                write_prof_data[name]['deny'].pop(segs)
 
3793
                    segments['rlimit'] = True
 
3794
                    write_prof_data[hat]['rlimit'].pop(from_name)
 
3795
                    data.append(line)
 
3796
                else:
 
3797
                    #To-Do
 
3798
                    pass
 
3799
 
 
3800
            elif RE_PROFILE_BOOLEAN.search(line):
 
3801
                matches = RE_PROFILE_BOOLEAN.search(line).groups()
 
3802
                bool_var = matches[0]
 
3803
                value = matches[1]
 
3804
 
 
3805
                if not write_prof_data[hat]['lvar'][bool_var] == value:
 
3806
                    correct = False
 
3807
 
 
3808
                if correct:
 
3809
                    if not segments['lvar'] and True in segments.values():
 
3810
                        for segs in list(filter(lambda x: segments[x], segments.keys())):
 
3811
                            depth = len(line) - len(line.lstrip())
 
3812
                            data += write_methods[segs](write_prof_data[name], int(depth / 2))
 
3813
                            segments[segs] = False
 
3814
                            if write_prof_data[name]['allow'].get(segs, False):
 
3815
                                write_prof_data[name]['allow'].pop(segs)
 
3816
                            if write_prof_data[name]['deny'].get(segs, False):
 
3817
                                write_prof_data[name]['deny'].pop(segs)
 
3818
                    segments['lvar'] = True
 
3819
                    write_prof_data[hat]['lvar'].pop(bool_var)
 
3820
                    data.append(line)
 
3821
                else:
 
3822
                    #To-Do
 
3823
                    pass
 
3824
            elif RE_PROFILE_VARIABLE.search(line):
 
3825
                matches = RE_PROFILE_VARIABLE.search(line).groups()
 
3826
                list_var = strip_quotes(matches[0])
 
3827
                var_operation = matches[1]
 
3828
                value = strip_quotes(matches[2])
 
3829
                var_set = hasher()
 
3830
                if profile:
 
3831
                    store_list_var(var_set, list_var, value, var_operation)
 
3832
                    if not var_set[list_var] == write_prof_data['lvar'].get(list_var, False):
 
3833
                        correct = False
 
3834
                else:
 
3835
                    store_list_var(var_set, list_var, value, var_operation)
 
3836
                    if not var_set[list_var] == write_filelist['lvar'].get(list_var, False):
 
3837
                        correct = False
 
3838
 
 
3839
                if correct:
 
3840
                    if not segments['lvar'] and True in segments.values():
 
3841
                        for segs in list(filter(lambda x: segments[x], segments.keys())):
 
3842
                            depth = len(line) - len(line.lstrip())
 
3843
                            data += write_methods[segs](write_prof_data[name], int(depth / 2))
 
3844
                            segments[segs] = False
 
3845
                            if write_prof_data[name]['allow'].get(segs, False):
 
3846
                                write_prof_data[name]['allow'].pop(segs)
 
3847
                            if write_prof_data[name]['deny'].get(segs, False):
 
3848
                                write_prof_data[name]['deny'].pop(segs)
 
3849
                    segments['lvar'] = True
 
3850
                    if profile:
 
3851
                        write_prof_data[hat]['lvar'].pop(list_var)
 
3852
                    else:
 
3853
                        write_filelist['lvar'].pop(list_var)
 
3854
                    data.append(line)
 
3855
                else:
 
3856
                    #To-Do
 
3857
                    pass
 
3858
 
 
3859
            elif RE_PROFILE_PATH_ENTRY.search(line):
 
3860
                matches = RE_PROFILE_PATH_ENTRY.search(line).groups()
 
3861
                audit = False
 
3862
                if matches[0]:
 
3863
                    audit = True
 
3864
                allow = 'allow'
 
3865
                if matches[1] and matches[1].split() == 'deny':
 
3866
                    allow = 'deny'
 
3867
 
 
3868
                user = False
 
3869
                if matches[2]:
 
3870
                    user = True
 
3871
 
 
3872
                path = matches[3].strip()
 
3873
                mode = matches[4]
 
3874
                nt_name = matches[6]
 
3875
                if nt_name:
 
3876
                    nt_name = nt_name.strip()
 
3877
 
 
3878
                tmpmode = set()
 
3879
                if user:
 
3880
                    tmpmode = str_to_mode('%s::' % mode)
 
3881
                else:
 
3882
                    tmpmode = str_to_mode(mode)
 
3883
 
 
3884
                if not write_prof_data[hat][allow]['path'][path].get('mode', set()) & tmpmode:
 
3885
                    correct = False
 
3886
 
 
3887
                if nt_name and not write_prof_data[hat][allow]['path'][path].get('to', False) == nt_name:
 
3888
                    correct = False
 
3889
 
 
3890
                if audit and not write_prof_data[hat][allow]['path'][path].get('audit', set()) & tmpmode:
 
3891
                    correct = False
 
3892
 
 
3893
                if correct:
 
3894
                    if not segments['path'] and True in segments.values():
 
3895
                        for segs in list(filter(lambda x: segments[x], segments.keys())):
 
3896
                            depth = len(line) - len(line.lstrip())
 
3897
                            data += write_methods[segs](write_prof_data[name], int(depth / 2))
 
3898
                            segments[segs] = False
 
3899
                            if write_prof_data[name]['allow'].get(segs, False):
 
3900
                                write_prof_data[name]['allow'].pop(segs)
 
3901
                            if write_prof_data[name]['deny'].get(segs, False):
 
3902
                                write_prof_data[name]['deny'].pop(segs)
 
3903
                    segments['path'] = True
 
3904
                    write_prof_data[hat][allow]['path'].pop(path)
 
3905
                    data.append(line)
 
3906
                else:
 
3907
                    #To-Do
 
3908
                    pass
 
3909
 
 
3910
            elif re_match_include(line):
 
3911
                include_name = re_match_include(line)
 
3912
                if profile:
 
3913
                    if write_prof_data[hat]['include'].get(include_name, False):
 
3914
                        if not segments['include'] and True in segments.values():
 
3915
                            for segs in list(filter(lambda x: segments[x], segments.keys())):
 
3916
                                depth = len(line) - len(line.lstrip())
 
3917
                                data += write_methods[segs](write_prof_data[name], int(depth / 2))
 
3918
                                segments[segs] = False
 
3919
                                if write_prof_data[name]['allow'].get(segs, False):
 
3920
                                    write_prof_data[name]['allow'].pop(segs)
 
3921
                                if write_prof_data[name]['deny'].get(segs, False):
 
3922
                                    write_prof_data[name]['deny'].pop(segs)
 
3923
                            segments['include'] = True
 
3924
                        write_prof_data[hat]['include'].pop(include_name)
 
3925
                        data.append(line)
 
3926
                else:
 
3927
                    if write_filelist['include'].get(include_name, False):
 
3928
                        write_filelist['include'].pop(include_name)
 
3929
                        data.append(line)
 
3930
 
 
3931
            elif RE_PROFILE_NETWORK.search(line):
 
3932
                matches = RE_PROFILE_NETWORK.search(line).groups()
 
3933
                audit = False
 
3934
                if matches[0]:
 
3935
                    audit = True
 
3936
                allow = 'allow'
 
3937
                if matches[1] and matches[1].strip() == 'deny':
 
3938
                    allow = 'deny'
 
3939
                network = matches[2]
 
3940
                if RE_NETWORK_FAMILY_TYPE.search(network):
 
3941
                    nmatch = RE_NETWORK_FAMILY_TYPE.search(network).groups()
 
3942
                    fam, typ = nmatch[:2]
 
3943
                    if write_prof_data[hat][allow]['netdomain']['rule'][fam][typ] and write_prof_data[hat][allow]['netdomain']['audit'][fam][typ] == audit:
 
3944
                        write_prof_data[hat][allow]['netdomain']['rule'][fam].pop(typ)
 
3945
                        write_prof_data[hat][allow]['netdomain']['audit'][fam].pop(typ)
 
3946
                        data.append(line)
 
3947
                    else:
 
3948
                        correct = False
 
3949
 
 
3950
                elif RE_NETWORK_FAMILY.search(network):
 
3951
                    fam = RE_NETWORK_FAMILY.search(network).groups()[0]
 
3952
                    if write_prof_data[hat][allow]['netdomain']['rule'][fam] and write_prof_data[hat][allow]['netdomain']['audit'][fam] == audit:
 
3953
                        write_prof_data[hat][allow]['netdomain']['rule'].pop(fam)
 
3954
                        write_prof_data[hat][allow]['netdomain']['audit'].pop(fam)
 
3955
                        data.append(line)
 
3956
                    else:
 
3957
                        correct = False
 
3958
                else:
 
3959
                    if write_prof_data[hat][allow]['netdomain']['rule']['all'] and write_prof_data[hat][allow]['netdomain']['audit']['all'] == audit:
 
3960
                        write_prof_data[hat][allow]['netdomain']['rule'].pop('all')
 
3961
                        write_prof_data[hat][allow]['netdomain']['audit'].pop('all')
 
3962
                        data.append(line)
 
3963
                    else:
 
3964
                        correct = False
 
3965
 
 
3966
                if correct:
 
3967
                    if not segments['netdomain'] and True in segments.values():
 
3968
                        for segs in list(filter(lambda x: segments[x], segments.keys())):
 
3969
                            depth = len(line) - len(line.lstrip())
 
3970
                            data += write_methods[segs](write_prof_data[name], int(depth / 2))
 
3971
                            segments[segs] = False
 
3972
                            if write_prof_data[name]['allow'].get(segs, False):
 
3973
                                write_prof_data[name]['allow'].pop(segs)
 
3974
                            if write_prof_data[name]['deny'].get(segs, False):
 
3975
                                write_prof_data[name]['deny'].pop(segs)
 
3976
                    segments['netdomain'] = True
 
3977
 
 
3978
            elif RE_PROFILE_CHANGE_HAT.search(line):
 
3979
                matches = RE_PROFILE_CHANGE_HAT.search(line).groups()
 
3980
                hat = matches[0]
 
3981
                hat = strip_quotes(hat)
 
3982
                if not write_prof_data[hat]['declared']:
 
3983
                    correct = False
 
3984
                if correct:
 
3985
                    data.append(line)
 
3986
                else:
 
3987
                    #To-Do
 
3988
                    pass
 
3989
            elif RE_PROFILE_HAT_DEF.search(line):
 
3990
                matches = RE_PROFILE_HAT_DEF.search(line).groups()
 
3991
                in_contained_hat = True
 
3992
                hat = matches[0]
 
3993
                hat = strip_quotes(hat)
 
3994
                flags = matches[3]
 
3995
                if not write_prof_data[hat]['flags'] == flags:
 
3996
                    correct = False
 
3997
                if not write_prof_data[hat]['declared'] is False:
 
3998
                    correct = False
 
3999
                if not write_filelist['profile'][profile][hat]:
 
4000
                    correct = False
 
4001
                if correct:
 
4002
                    data.append(line)
 
4003
                else:
 
4004
                    #To-Do
 
4005
                    pass
 
4006
            else:
 
4007
                if correct:
 
4008
                    data.append(line)
 
4009
                else:
 
4010
                    #To-Do
 
4011
                    pass
 
4012
#     data.append('prof done')
 
4013
#     if write_filelist:
 
4014
#         data += write_alias(write_filelist, 0)
 
4015
#         data += write_list_vars(write_filelist, 0)
 
4016
#         data += write_includes(write_filelist, 0)
 
4017
#     data.append('from filelist over')
 
4018
#     data += write_piece(write_prof_data, 0, name, name, include_flags)
 
4019
 
 
4020
    string += '\n'.join(data)
 
4021
 
 
4022
    return string + '\n'
 
4023
 
 
4024
def write_profile_ui_feedback(profile):
 
4025
    aaui.UI_Info(_('Writing updated profile for %s.') % profile)
 
4026
    write_profile(profile)
 
4027
 
 
4028
def write_profile(profile):
 
4029
    prof_filename = None
 
4030
    if aa[profile][profile].get('filename', False):
 
4031
        prof_filename = aa[profile][profile]['filename']
 
4032
    else:
 
4033
        prof_filename = get_profile_filename(profile)
 
4034
 
 
4035
    newprof = tempfile.NamedTemporaryFile('w', suffix='~', delete=False, dir=profile_dir)
 
4036
    if os.path.exists(prof_filename):
 
4037
        shutil.copymode(prof_filename, newprof.name)
 
4038
    else:
 
4039
        #permission_600 = stat.S_IRUSR | stat.S_IWUSR    # Owner read and write
 
4040
        #os.chmod(newprof.name, permission_600)
 
4041
        pass
 
4042
 
 
4043
    serialize_options = {}
 
4044
    serialize_options['METADATA'] = True
 
4045
 
 
4046
    profile_string = serialize_profile(aa[profile], profile, serialize_options)
 
4047
    newprof.write(profile_string)
 
4048
    newprof.close()
 
4049
 
 
4050
    os.rename(newprof.name, prof_filename)
 
4051
 
 
4052
    changed.pop(profile)
 
4053
    original_aa[profile] = deepcopy(aa[profile])
 
4054
 
 
4055
def matchliteral(aa_regexp, literal):
 
4056
    p_regexp = '^' + convert_regexp(aa_regexp) + '$'
 
4057
    match = False
 
4058
    try:
 
4059
        match = re.search(p_regexp, literal)
 
4060
    except:
 
4061
        return None
 
4062
    return match
 
4063
 
 
4064
def profile_known_exec(profile, typ, exec_target):
 
4065
    if typ == 'exec':
 
4066
        cm = None
 
4067
        am = None
 
4068
        m = []
 
4069
 
 
4070
        cm, am, m = rematchfrag(profile, 'deny', exec_target)
 
4071
        if cm & apparmor.aamode.AA_MAY_EXEC:
 
4072
            return -1
 
4073
 
 
4074
        cm, am, m = match_prof_incs_to_path(profile, 'deny', exec_target)
 
4075
        if cm & apparmor.aamode.AA_MAY_EXEC:
 
4076
            return -1
 
4077
 
 
4078
        cm, am, m = rematchfrag(profile, 'allow', exec_target)
 
4079
        if cm & apparmor.aamode.AA_MAY_EXEC:
 
4080
            return 1
 
4081
 
 
4082
        cm, am, m = match_prof_incs_to_path(profile, 'allow', exec_target)
 
4083
        if cm & apparmor.aamode.AA_MAY_EXEC:
 
4084
            return 1
 
4085
 
 
4086
    return 0
 
4087
 
 
4088
def profile_known_capability(profile, capname):
 
4089
    if profile['deny']['capability'][capname].get('set', False):
 
4090
        return -1
 
4091
 
 
4092
    if profile['allow']['capability'][capname].get('set', False):
 
4093
        return 1
 
4094
 
 
4095
    for incname in profile['include'].keys():
 
4096
        if include[incname][incname]['deny']['capability'][capname].get('set', False):
 
4097
            return -1
 
4098
        if include[incname][incname]['allow']['capability'][capname].get('set', False):
 
4099
            return 1
 
4100
 
 
4101
    return 0
 
4102
 
 
4103
def profile_known_network(profile, family, sock_type):
 
4104
    if netrules_access_check(profile['deny']['netdomain'], family, sock_type):
 
4105
        return -1
 
4106
    if netrules_access_check(profile['allow']['netdomain'], family, sock_type):
 
4107
        return 1
 
4108
 
 
4109
    for incname in profile['include'].keys():
 
4110
        if netrules_access_check(include[incname][incname]['deny']['netdomain'], family, sock_type):
 
4111
            return -1
 
4112
        if netrules_access_check(include[incname][incname]['allow']['netdomain'], family, sock_type):
 
4113
            return 1
 
4114
 
 
4115
    return 0
 
4116
 
 
4117
def netrules_access_check(netrules, family, sock_type):
 
4118
    if not netrules:
 
4119
        return 0
 
4120
    all_net = False
 
4121
    all_net_family = False
 
4122
    net_family_sock = False
 
4123
    if netrules['rule'].get('all', False):
 
4124
        all_net = True
 
4125
    if netrules['rule'].get(family, False) is True:
 
4126
        all_net_family = True
 
4127
    if (netrules['rule'].get(family, False) and
 
4128
            type(netrules['rule'][family]) == dict and
 
4129
            netrules['rule'][family][sock_type]):
 
4130
        net_family_sock = True
 
4131
 
 
4132
    if all_net or all_net_family or net_family_sock:
 
4133
        return True
 
4134
    else:
 
4135
        return False
 
4136
 
 
4137
def reload_base(bin_path):
 
4138
    if not check_for_apparmor():
 
4139
        return None
 
4140
 
 
4141
    prof_filename = get_profile_filename(bin_path)
 
4142
 
 
4143
    subprocess.call("cat '%s' | %s -I%s -r >/dev/null 2>&1" % (prof_filename, parser, profile_dir), shell=True)
 
4144
 
 
4145
def reload(bin_path):
 
4146
    bin_path = find_executable(bin_path)
 
4147
    if not bin_path:
 
4148
        return None
 
4149
 
 
4150
    return reload_base(bin_path)
 
4151
 
 
4152
def get_include_data(filename):
 
4153
    data = []
 
4154
    filename = profile_dir + '/' + filename
 
4155
    if os.path.exists(filename):
 
4156
        with open_file_read(filename) as f_in:
 
4157
            data = f_in.readlines()
 
4158
    else:
 
4159
        raise AppArmorException(_('File Not Found: %s') % filename)
 
4160
    return data
 
4161
 
 
4162
def load_include(incname):
 
4163
    load_includeslist = [incname]
 
4164
    if include.get(incname, {}).get(incname, False):
 
4165
        return 0
 
4166
    while load_includeslist:
 
4167
        incfile = load_includeslist.pop(0)
 
4168
        if os.path.isfile(profile_dir + '/' + incfile):
 
4169
            data = get_include_data(incfile)
 
4170
            incdata = parse_profile_data(data, incfile, True)
 
4171
            #print(incdata)
 
4172
            if not incdata:
 
4173
                # If include is empty, simply push in a placeholder for it
 
4174
                # because other profiles may mention them
 
4175
                incdata = hasher()
 
4176
                incdata[incname] = hasher()
 
4177
            attach_profile_data(include, incdata)
 
4178
        #If the include is a directory means include all subfiles
 
4179
        elif os.path.isdir(profile_dir + '/' + incfile):
 
4180
            load_includeslist += list(map(lambda x: incfile + '/' + x, os.listdir(profile_dir + '/' + incfile)))
 
4181
 
 
4182
    return 0
 
4183
 
 
4184
def rematchfrag(frag, allow, path):
 
4185
    combinedmode = set()
 
4186
    combinedaudit = set()
 
4187
    matches = []
 
4188
    if not frag:
 
4189
        return combinedmode, combinedaudit, matches
 
4190
    for entry in frag[allow]['path'].keys():
 
4191
        match = matchliteral(entry, path)
 
4192
        if match:
 
4193
            #print(frag[allow]['path'][entry]['mode'])
 
4194
            combinedmode |= frag[allow]['path'][entry].get('mode', set())
 
4195
            combinedaudit |= frag[allow]['path'][entry].get('audit', set())
 
4196
            matches.append(entry)
 
4197
 
 
4198
    return combinedmode, combinedaudit, matches
 
4199
 
 
4200
def match_include_to_path(incname, allow, path):
 
4201
    combinedmode = set()
 
4202
    combinedaudit = set()
 
4203
    matches = []
 
4204
    includelist = [incname]
 
4205
    while includelist:
 
4206
        incfile = str(includelist.pop(0))
 
4207
        # ret = load_include(incfile)
 
4208
        load_include(incfile)
 
4209
        if not include.get(incfile, {}):
 
4210
            continue
 
4211
        cm, am, m = rematchfrag(include[incfile].get(incfile, {}), allow, path)
 
4212
        #print(incfile, cm, am, m)
 
4213
        if cm:
 
4214
            combinedmode |= cm
 
4215
            combinedaudit |= am
 
4216
            matches += m
 
4217
 
 
4218
        if include[incfile][incfile][allow]['path'][path]:
 
4219
            combinedmode |= include[incfile][incfile][allow]['path'][path]['mode']
 
4220
            combinedaudit |= include[incfile][incfile][allow]['path'][path]['audit']
 
4221
 
 
4222
        if include[incfile][incfile]['include'].keys():
 
4223
            includelist += include[incfile][incfile]['include'].keys()
 
4224
 
 
4225
    return combinedmode, combinedaudit, matches
 
4226
 
 
4227
def match_prof_incs_to_path(frag, allow, path):
 
4228
    combinedmode = set()
 
4229
    combinedaudit = set()
 
4230
    matches = []
 
4231
 
 
4232
    includelist = list(frag['include'].keys())
 
4233
    while includelist:
 
4234
        incname = includelist.pop(0)
 
4235
        cm, am, m = match_include_to_path(incname, allow, path)
 
4236
        if cm:
 
4237
            combinedmode |= cm
 
4238
            combinedaudit |= am
 
4239
            matches += m
 
4240
 
 
4241
    return combinedmode, combinedaudit, matches
 
4242
 
 
4243
def suggest_incs_for_path(incname, path, allow):
 
4244
    combinedmode = set()
 
4245
    combinedaudit = set()
 
4246
    matches = []
 
4247
 
 
4248
    includelist = [incname]
 
4249
    while includelist:
 
4250
        inc = includelist.pop(0)
 
4251
        cm, am, m = rematchfrag(include[inc][inc], 'allow', path)
 
4252
        if cm:
 
4253
            combinedmode |= cm
 
4254
            combinedaudit |= am
 
4255
            matches += m
 
4256
 
 
4257
        if include[inc][inc]['allow']['path'].get(path, False):
 
4258
            combinedmode |= include[inc][inc]['allow']['path'][path]['mode']
 
4259
            combinedaudit |= include[inc][inc]['allow']['path'][path]['audit']
 
4260
 
 
4261
        if include[inc][inc]['include'].keys():
 
4262
            includelist += include[inc][inc]['include'].keys()
 
4263
 
 
4264
    return combinedmode, combinedaudit, matches
 
4265
 
 
4266
def check_qualifiers(program):
 
4267
    if cfg['qualifiers'].get(program, False):
 
4268
        if cfg['qualifiers'][program] != 'p':
 
4269
            fatal_error(_("%s is currently marked as a program that should not have its own\nprofile.  Usually, programs are marked this way if creating a profile for \nthem is likely to break the rest of the system.  If you know what you\'re\ndoing and are certain you want to create a profile for this program, edit\nthe corresponding entry in the [qualifiers] section in /etc/apparmor/logprof.conf.") % program)
 
4270
    return False
 
4271
 
 
4272
def get_subdirectories(current_dir):
 
4273
    """Returns a list of all directories directly inside given directory"""
 
4274
    if sys.version_info < (3, 0):
 
4275
        return os.walk(current_dir).next()[1]
 
4276
    else:
 
4277
        return os.walk(current_dir).__next__()[1]
 
4278
 
 
4279
def loadincludes():
 
4280
    incdirs = get_subdirectories(profile_dir)
 
4281
    for idir in incdirs:
 
4282
        if is_skippable_dir(idir):
 
4283
            continue
 
4284
        for dirpath, dirname, files in os.walk(profile_dir + '/' + idir):
 
4285
            if is_skippable_dir(dirpath):
 
4286
                continue
 
4287
            for fi in files:
 
4288
                if is_skippable_file(fi):
 
4289
                    continue
 
4290
                else:
 
4291
                    fi = dirpath + '/' + fi
 
4292
                    fi = fi.replace(profile_dir + '/', '', 1)
 
4293
                    load_include(fi)
 
4294
 
 
4295
def glob_common(path):
 
4296
    globs = []
 
4297
 
 
4298
    if re.search('[\d\.]+\.so$', path) or re.search('\.so\.[\d\.]+$', path):
 
4299
        libpath = path
 
4300
        libpath = re.sub('[\d\.]+\.so$', '*.so', libpath)
 
4301
        libpath = re.sub('\.so\.[\d\.]+$', '.so.*', libpath)
 
4302
        if libpath != path:
 
4303
            globs.append(libpath)
 
4304
 
 
4305
    for glob in cfg['globs']:
 
4306
        if re.search(glob, path):
 
4307
            globbedpath = path
 
4308
            globbedpath = re.sub(glob, cfg['globs'][glob], path)
 
4309
            if globbedpath != path:
 
4310
                globs.append(globbedpath)
 
4311
 
 
4312
    return sorted(set(globs))
 
4313
 
 
4314
def combine_name(name1, name2):
 
4315
    if name1 == name2:
 
4316
        return name1
 
4317
    else:
 
4318
        return '%s^%s' % (name1, name2)
 
4319
 
 
4320
def split_name(name):
 
4321
    names = name.split('^')
 
4322
    if len(names) == 1:
 
4323
        return name, name
 
4324
    else:
 
4325
        return names[0], names[1]
 
4326
def commonprefix(new, old):
 
4327
    match = re.search(r'^([^\0]*)[^\0]*(\0\1[^\0]*)*$', '\0'.join([new, old]))
 
4328
    if match:
 
4329
        return match.groups()[0]
 
4330
    return match
 
4331
 
 
4332
def commonsuffix(new, old):
 
4333
    match = commonprefix(new[-1::-1], old[-1::-1])
 
4334
    if match:
 
4335
        return match[-1::-1]
 
4336
 
 
4337
def matchregexp(new, old):
 
4338
    if re.search('\{.*(\,.*)*\}', old):
 
4339
        return None
 
4340
 
 
4341
#     if re.search('\[.+\]', old) or re.search('\*', old) or re.search('\?', old):
 
4342
#
 
4343
#         new_reg = convert_regexp(new)
 
4344
#         old_reg = convert_regexp(old)
 
4345
#
 
4346
#         pref = commonprefix(new, old)
 
4347
#         if pref:
 
4348
#             if convert_regexp('(*,**)$') in pref:
 
4349
#                 pref = pref.replace(convert_regexp('(*,**)$'), '')
 
4350
#             new = new.replace(pref, '', 1)
 
4351
#             old = old.replace(pref, '', 1)
 
4352
#
 
4353
#         suff = commonsuffix(new, old)
 
4354
#         if suffix:
 
4355
#             pass
 
4356
    new_reg = convert_regexp(new)
 
4357
    if re.search(new_reg, old):
 
4358
        return True
 
4359
 
 
4360
    return None
 
4361
 
 
4362
######Initialisations######
 
4363
 
 
4364
conf = apparmor.config.Config('ini', CONFDIR)
 
4365
cfg = conf.read_config('logprof.conf')
 
4366
 
 
4367
#print(cfg['settings'])
 
4368
#if 'default_owner_prompt' in cfg['settings']:
 
4369
if cfg['settings'].get('default_owner_prompt', False):
 
4370
    cfg['settings']['default_owner_prompt'] = ''
 
4371
 
 
4372
profile_dir = conf.find_first_dir(cfg['settings']['profiledir']) or '/etc/apparmor.d'
 
4373
if not os.path.isdir(profile_dir):
 
4374
    raise AppArmorException('Can\'t find AppArmor profiles')
 
4375
 
 
4376
extra_profile_dir = conf.find_first_dir(cfg['settings']['inactive_profiledir']) or '/etc/apparmor/profiles/extras/'
 
4377
 
 
4378
parser = conf.find_first_file(cfg['settings']['parser']) or '/sbin/apparmor_parser'
 
4379
if not os.path.isfile(parser) or not os.access(parser, os.EX_OK):
 
4380
    raise AppArmorException('Can\'t find apparmor_parser')
 
4381
 
 
4382
filename = conf.find_first_file(cfg['settings']['logfiles']) or '/var/log/syslog'
 
4383
if not os.path.isfile(filename):
 
4384
    raise AppArmorException('Can\'t find system log.')
 
4385
 
 
4386
ldd = conf.find_first_file(cfg['settings']['ldd']) or '/usr/bin/ldd'
 
4387
if not os.path.isfile(ldd) or not os.access(ldd, os.EX_OK):
 
4388
    raise AppArmorException('Can\'t find ldd')
 
4389
 
 
4390
logger = conf.find_first_file(cfg['settings']['logger']) or '/bin/logger'
 
4391
if not os.path.isfile(logger) or not os.access(logger, os.EX_OK):
 
4392
    raise AppArmorException('Can\'t find logger')