aa.py 126 KB
Newer Older
Kshitij Gupta's avatar
Kshitij Gupta committed
1 2
# ----------------------------------------------------------------------
#    Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
3
#    Copyright (C) 2014-2018 Christian Boltz <apparmor@cboltz.de>
Kshitij Gupta's avatar
Kshitij Gupta committed
4 5 6 7 8 9 10 11 12 13 14
#
#    This program is free software; you can redistribute it and/or
#    modify it under the terms of version 2 of the GNU General Public
#    License as published by the Free Software Foundation.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
# ----------------------------------------------------------------------
Kshitij Gupta's avatar
Kshitij Gupta committed
15
# No old version logs, only 2.6 + supported
16
from __future__ import division, with_statement
17 18
import os
import re
19
import shutil
Kshitij Gupta's avatar
Kshitij Gupta committed
20
import subprocess
21
import sys
Kshitij Gupta's avatar
Kshitij Gupta committed
22
import time
Kshitij Gupta's avatar
Kshitij Gupta committed
23
import traceback
Kshitij Gupta's avatar
Kshitij Gupta committed
24
import atexit
25
import tempfile
Kshitij Gupta's avatar
Kshitij Gupta committed
26 27

import apparmor.config
28
import apparmor.logparser
Kshitij Gupta's avatar
Kshitij Gupta committed
29 30
import apparmor.severity

Kshitij Gupta's avatar
Kshitij Gupta committed
31 32
from copy import deepcopy

33 34
from apparmor.aare import AARE

35
from apparmor.common import (AppArmorException, AppArmorBug, open_file_read, valid_path, hasher,
36
                             open_file_write, DebugLogger)
Kshitij Gupta's avatar
Kshitij Gupta committed
37

38
import apparmor.ui as aaui
39

40
from apparmor.aamode import str_to_mode, split_mode
41

42
from apparmor.regex import (RE_PROFILE_START, RE_PROFILE_END, RE_PROFILE_LINK,
43
                            RE_ABI, RE_PROFILE_ALIAS,
44 45
                            RE_PROFILE_BOOLEAN, RE_PROFILE_VARIABLE, RE_PROFILE_CONDITIONAL,
                            RE_PROFILE_CONDITIONAL_VARIABLE, RE_PROFILE_CONDITIONAL_BOOLEAN,
46
                            RE_PROFILE_CHANGE_HAT,
47
                            RE_PROFILE_HAT_DEF, RE_PROFILE_MOUNT,
Christian Boltz's avatar
Christian Boltz committed
48
                            RE_PROFILE_PIVOT_ROOT,
49
                            RE_PROFILE_UNIX, RE_RULE_HAS_COMMA, RE_HAS_COMMENT_SPLIT,
50
                            strip_quotes, parse_profile_start_line, re_match_include )
51

52 53
from apparmor.profile_list import ProfileList

54
from apparmor.profile_storage import (ProfileStorage, add_or_remove_flag, ruletypes, write_alias,
55
                            write_abi, write_includes, write_list_vars )
56

57 58
import apparmor.rules as aarules

59 60 61 62 63 64 65 66
from apparmor.rule.capability       import CapabilityRule
from apparmor.rule.change_profile   import ChangeProfileRule
from apparmor.rule.dbus             import DbusRule
from apparmor.rule.file             import FileRule
from apparmor.rule.network          import NetworkRule
from apparmor.rule.ptrace           import PtraceRule
from apparmor.rule.rlimit           import RlimitRule
from apparmor.rule.signal           import SignalRule
67
from apparmor.rule import quote_if_needed
68

69
# setup module translations
70 71
from apparmor.translations import init_translation
_ = init_translation()
72

Kshitij Gupta's avatar
Kshitij Gupta committed
73
# Setup logging incase of debugging is enabled
Kshitij Gupta's avatar
Kshitij Gupta committed
74
debug_logger = DebugLogger('aa')
75

76
# The database for severity
77 78 79
sev_db = None
# The file to read log messages from
### Was our
80
logfile = None
81

82 83
CONFDIR = None
conf = None
84 85 86 87 88 89 90 91 92 93
cfg = None
repo_cfg = None

parser = None
profile_dir = None
extra_profile_dir = None
### end our
# To keep track of previously included profile fragments
include = dict()

94 95
active_profiles = ProfileList()
extra_profiles = ProfileList()
96 97

# To store the globs entered by users so they can be provided again
98 99
# format: user_globs['/foo*'] = AARE('/foo*')
user_globs = {}
100 101

## Variables used under logprof
102
transitions = hasher()
103

104
aa = hasher()  # Profiles originally in sd, replace by aa
105
original_aa = hasher()
106
extras = hasher()  # Inactive profiles from extras
107
### end our
108
log_pid = dict()  # handed over to ReadLog, gets filled in logparser.py. The only case the previous content of this variable _might_(?) be used is aa-genprof (multiple do_logprof_pass() runs)
109

110
profile_changes = dict()
111
prelog = hasher()
112 113
changed = dict()
created = []
114
helpers = dict()  # Preserve this between passes # was our
115 116
### logprof ends

117
filelist = hasher()    # File level variables and stuff in config files
118

Kshitij Gupta's avatar
Kshitij Gupta committed
119 120
def on_exit():
    """Shutdowns the logger and records exit if debugging enabled"""
Kshitij Gupta's avatar
Kshitij Gupta committed
121 122
    debug_logger.debug('Exiting..')
    debug_logger.shutdown()
123

Kshitij Gupta's avatar
Kshitij Gupta committed
124 125 126 127
# Register the on_exit method with atexit
atexit.register(on_exit)

def check_for_LD_XXX(file):
128
    """Returns True if specified program contains references to LD_PRELOAD or
Kshitij Gupta's avatar
Kshitij Gupta committed
129
    LD_LIBRARY_PATH to give the Px/Ux code better suggestions"""
Kshitij Gupta's avatar
Kshitij Gupta committed
130 131 132
    if not os.path.isfile(file):
        return False
    size = os.stat(file).st_size
Kshitij Gupta's avatar
Kshitij Gupta committed
133
    # Limit to checking files under 100k for the sake of speed
134
    if size > 100000:
Kshitij Gupta's avatar
Kshitij Gupta committed
135
        return False
136
    with open(file, 'rb') as f_in:
Kshitij Gupta's avatar
Kshitij Gupta committed
137
        for line in f_in:
138 139 140
            if b'LD_PRELOAD' in line or b'LD_LIBRARY_PATH' in line:
                return True
    return False
Kshitij Gupta's avatar
Kshitij Gupta committed
141 142

def fatal_error(message):
Kshitij Gupta's avatar
Kshitij Gupta committed
143 144 145
    # Get the traceback to the message
    tb_stack = traceback.format_list(traceback.extract_stack())
    tb_stack = ''.join(tb_stack)
146 147
    # Add the traceback to message
    message = tb_stack + '\n\n' + message
Kshitij Gupta's avatar
Kshitij Gupta committed
148
    debug_logger.error(message)
149

Kshitij Gupta's avatar
Kshitij Gupta committed
150
    # Else tell user what happened
151
    aaui.UI_Important(message)
Kshitij Gupta's avatar
Kshitij Gupta committed
152
    sys.exit(1)
153

154
def check_for_apparmor(filesystem='/proc/filesystems', mounts='/proc/mounts'):
155
    """Finds and returns the mountpoint for apparmor None otherwise"""
156 157 158 159 160 161 162
    support_securityfs = False
    aa_mountpoint = None
    if valid_path(filesystem):
        with open_file_read(filesystem) as f_in:
            for line in f_in:
                if 'securityfs' in line:
                    support_securityfs = True
163 164
                    break
    if valid_path(mounts) and support_securityfs:
165 166
        with open_file_read(mounts) as f_in:
            for line in f_in:
167 168 169 170
                split = line.split()
                if len(split) > 2 and split[2] == 'securityfs':
                    mountpoint = split[1] + '/apparmor'
                    # Check if apparmor is actually mounted there
171
                    # XXX valid_path() only checks the syntax, but not if the directory exists!
172 173 174
                    if valid_path(mountpoint) and valid_path(mountpoint + '/profiles'):
                        aa_mountpoint = mountpoint
                        break
175 176 177
    return aa_mountpoint

def which(file):
Kshitij Gupta's avatar
Kshitij Gupta committed
178
    """Returns the executable fullpath for the file, None otherwise"""
179
    if sys.version_info >= (3, 3):
180 181 182 183 184 185 186 187
        return shutil.which(file)
    env_dirs = os.getenv('PATH').split(':')
    for env_dir in env_dirs:
        env_path = env_dir + '/' + file
        # Test if the path is executable or not
        if os.access(env_path, os.X_OK):
            return env_path
    return None
188

Kshitij Gupta's avatar
Kshitij Gupta committed
189 190 191 192
def get_full_path(original_path):
    """Return the full path after resolving any symlinks"""
    path = original_path
    link_count = 0
Kshitij Gupta's avatar
Kshitij Gupta committed
193
    if not path.startswith('/'):
Kshitij Gupta's avatar
Kshitij Gupta committed
194 195 196 197
        path = os.getcwd() + '/' + path
    while os.path.islink(path):
        link_count += 1
        if link_count > 64:
Kshitij Gupta's avatar
Kshitij Gupta committed
198
            fatal_error(_("Followed too many links while resolving %s") % (original_path))
Kshitij Gupta's avatar
Kshitij Gupta committed
199 200 201 202 203 204 205 206 207 208 209
        direc, file = os.path.split(path)
        link = os.readlink(path)
        # If the link an absolute path
        if link.startswith('/'):
            path = link
        else:
            # Link is relative path
            path = direc + '/' + link
    return os.path.realpath(path)

def find_executable(bin_path):
210
    """Returns the full executable path for the given executable, None otherwise"""
Kshitij Gupta's avatar
Kshitij Gupta committed
211 212 213 214
    full_bin = None
    if os.path.exists(bin_path):
        full_bin = get_full_path(bin_path)
    else:
Kshitij Gupta's avatar
Kshitij Gupta committed
215 216
        if '/' not in bin_path:
            env_bin = which(bin_path)
Kshitij Gupta's avatar
Kshitij Gupta committed
217 218 219 220 221 222
            if env_bin:
                full_bin = get_full_path(env_bin)
    if full_bin and os.path.exists(full_bin):
        return full_bin
    return None

223 224
def get_profile_filename_from_profile_name(profile, get_new=False):
    """Returns the full profile name for the given profile name"""
225

226
    filename = active_profiles.filename_from_profile_name(profile)
227 228 229 230 231
    if filename:
        return filename

    if get_new:
        return get_new_profile_filename(profile)
232 233 234

def get_profile_filename_from_attachment(profile, get_new=False):
    """Returns the full profile name for the given attachment"""
235

236
    filename = active_profiles.filename_from_attachment(profile)
237 238 239 240 241
    if filename:
        return filename

    if get_new:
        return get_new_profile_filename(profile)
242

243 244 245
def get_new_profile_filename(profile):
    '''Compose filename for a new profile'''
    if profile.startswith('/'):
Kshitij Gupta's avatar
Kshitij Gupta committed
246
        # Remove leading /
Kshitij Gupta's avatar
Kshitij Gupta committed
247
        profile = profile[1:]
Kshitij Gupta's avatar
Kshitij Gupta committed
248
    else:
Kshitij Gupta's avatar
Kshitij Gupta committed
249
        profile = "profile_" + profile
250
    profile = profile.replace('/', '.')
Kshitij Gupta's avatar
Kshitij Gupta committed
251
    full_profilename = profile_dir + '/' + profile
Kshitij Gupta's avatar
Kshitij Gupta committed
252
    return full_profilename
253

Kshitij Gupta's avatar
Kshitij Gupta committed
254
def name_to_prof_filename(prof_filename):
Kshitij Gupta's avatar
Kshitij Gupta committed
255
    """Returns the profile"""
Kshitij Gupta's avatar
Kshitij Gupta committed
256 257 258
    if prof_filename.startswith(profile_dir):
        profile = prof_filename.split(profile_dir, 1)[1]
        return (prof_filename, profile)
Kshitij Gupta's avatar
Kshitij Gupta committed
259
    else:
Kshitij Gupta's avatar
Kshitij Gupta committed
260
        bin_path = find_executable(prof_filename)
Kshitij Gupta's avatar
Kshitij Gupta committed
261
        if bin_path:
262
            prof_filename = get_profile_filename_from_attachment(bin_path, True)
Kshitij Gupta's avatar
Kshitij Gupta committed
263 264
            if os.path.isfile(prof_filename):
                return (prof_filename, bin_path)
265 266

    return None, None
Kshitij Gupta's avatar
Kshitij Gupta committed
267 268 269

def complain(path):
    """Sets the profile to complain mode if it exists"""
Kshitij Gupta's avatar
Kshitij Gupta committed
270
    prof_filename, name = name_to_prof_filename(path)
271
    if not prof_filename:
Kshitij Gupta's avatar
Kshitij Gupta committed
272
        fatal_error(_("Can't find %s") % path)
273 274
    set_complain(prof_filename, name)

Kshitij Gupta's avatar
Kshitij Gupta committed
275
def enforce(path):
276
    """Sets the profile to enforce mode if it exists"""
Kshitij Gupta's avatar
Kshitij Gupta committed
277
    prof_filename, name = name_to_prof_filename(path)
278
    if not prof_filename:
Kshitij Gupta's avatar
Kshitij Gupta committed
279
        fatal_error(_("Can't find %s") % path)
280
    set_enforce(prof_filename, name)
281

282
def set_complain(filename, program):
283
    """Sets the profile to complain mode"""
284
    aaui.UI_Info(_('Setting %s to complain mode.') % (filename if program is None else program))
285 286
    # a force-complain symlink is more packaging-friendly, but breaks caching
    # create_symlink('force-complain', filename)
287
    delete_symlink('disable', filename)
288
    change_profile_flags(filename, program, 'complain', True)
289 290 291

def set_enforce(filename, program):
    """Sets the profile to enforce mode"""
292
    aaui.UI_Info(_('Setting %s to enforce mode.') % (filename if program is None else program))
293 294
    delete_symlink('force-complain', filename)
    delete_symlink('disable', filename)
295
    change_profile_flags(filename, program, 'complain', False)
296 297 298

def delete_symlink(subdir, filename):
    path = filename
299
    link = re.sub('^%s' % profile_dir, '%s/%s' % (profile_dir, subdir), path)
300 301 302 303 304 305 306
    if link != path and os.path.islink(link):
        os.remove(link)

def create_symlink(subdir, filename):
    path = filename
    bname = os.path.basename(filename)
    if not bname:
307
        raise AppArmorException(_('Unable to find basename for %s.') % filename)
308
    #print(filename)
309
    link = re.sub('^%s' % profile_dir, '%s/%s' % (profile_dir, subdir), path)
310 311 312
    #print(link)
    #link = link + '/%s'%bname
    #print(link)
313
    symlink_dir = os.path.dirname(link)
314 315 316
    if not os.path.exists(symlink_dir):
        # If the symlink directory does not exist create it
        os.makedirs(symlink_dir)
317

318 319 320 321
    if not os.path.exists(link):
        try:
            os.symlink(filename, link)
        except:
322
            raise AppArmorException(_('Could not create %(link)s symlink to %(file)s.') % { 'link': link, 'file': filename })
323

Kshitij Gupta's avatar
Kshitij Gupta committed
324 325 326 327 328
def head(file):
    """Returns the first/head line of the file"""
    first = ''
    if os.path.isfile(file):
        with open_file_read(file) as f_in:
Kshitij Gupta's avatar
Kshitij Gupta committed
329 330 331 332
            try:
                first = f_in.readline().rstrip()
            except UnicodeDecodeError:
                pass
333 334
            return first
    else:
335
        raise AppArmorException(_('Unable to read first line from %s: File Not Found') % file)
Kshitij Gupta's avatar
Kshitij Gupta committed
336 337

def get_output(params):
338 339 340 341 342 343 344 345 346 347 348 349 350
    '''Runs the program with the given args and returns the return code and stdout (as list of lines)'''
    try:
        # Get the output of the program
        output = subprocess.check_output(params)
        ret = 0
    except OSError as e:
        raise AppArmorException(_("Unable to fork: %(program)s\n\t%(error)s") % { 'program': params[0], 'error': str(e) })
    except subprocess.CalledProcessError as e:  # If exit code != 0
        output = e.output
        ret = e.returncode

    output = output.decode('utf-8').split('\n')

Kshitij Gupta's avatar
Kshitij Gupta committed
351
    # Remove the extra empty string caused due to \n if present
352
    if output[len(output) - 1] == '':
353
        output.pop()
354

355 356
    return (ret, output)

Kshitij Gupta's avatar
Kshitij Gupta committed
357 358 359 360 361
def get_reqs(file):
    """Returns a list of paths from ldd output"""
    pattern1 = re.compile('^\s*\S+ => (\/\S+)')
    pattern2 = re.compile('^\s*(\/\S+)')
    reqs = []
362

363
    ldd = conf.find_first_file(cfg['settings'].get('ldd')) or '/usr/bin/ldd'
364 365 366
    if not os.path.isfile(ldd) or not os.access(ldd, os.EX_OK):
        raise AppArmorException('Can\'t find ldd')

Kshitij Gupta's avatar
Kshitij Gupta committed
367
    ret, ldd_out = get_output([ldd, file])
368
    if ret == 0 or ret == 1:
Kshitij Gupta's avatar
Kshitij Gupta committed
369
        for line in ldd_out:
370
            if 'not a dynamic executable' in line:  # comes with ret == 1
Kshitij Gupta's avatar
Kshitij Gupta committed
371 372 373 374 375 376 377 378 379 380 381 382 383 384
                break
            if 'cannot read header' in line:
                break
            if 'statically linked' in line:
                break
            match = pattern1.search(line)
            if match:
                reqs.append(match.groups()[0])
            else:
                match = pattern2.search(line)
                if match:
                    reqs.append(match.groups()[0])
    return reqs

Kshitij Gupta's avatar
Kshitij Gupta committed
385 386 387 388 389 390
def handle_binfmt(profile, path):
    """Modifies the profile to add the requirements"""
    reqs_processed = dict()
    reqs = get_reqs(path)
    while reqs:
        library = reqs.pop()
391
        library = get_full_path(library)  # resolve symlinks
Kshitij Gupta's avatar
Kshitij Gupta committed
392
        if not reqs_processed.get(library, False):
Kshitij Gupta's avatar
Kshitij Gupta committed
393 394
            if get_reqs(library):
                reqs += get_reqs(library)
Kshitij Gupta's avatar
Kshitij Gupta committed
395
            reqs_processed[library] = True
396 397 398 399 400 401 402 403 404 405

        library_rule = FileRule(library, 'mr', None, FileRule.ALL, owner=False, log_event=True)

        if not is_known_rule(profile, 'file', library_rule):
            globbed_library = glob_common(library)
            if globbed_library:
                # glob_common returns a list, just use the first element (typically '/lib/libfoo.so.*')
                library_rule = FileRule(globbed_library[0], 'mr', None, FileRule.ALL, owner=False)

            profile['file'].add(library_rule)
406

407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
def get_interpreter_and_abstraction(exec_target):
    '''Check if exec_target is a script.
       If a hashbang is found, check if we have an abstraction for it.

       Returns (interpreter_path, abstraction)
       - interpreter_path is none if exec_target is not a script or doesn't have a hashbang line
       - abstraction is None if no matching abstraction exists'''

    if not os.path.exists(exec_target):
        aaui.UI_Important(_('Execute target %s does not exist!') % exec_target)
        return None, None

    if not os.path.isfile(exec_target):
        aaui.UI_Important(_('Execute target %s is not a file!') % exec_target)
        return None, None

    hashbang = head(exec_target)
    if not hashbang.startswith('#!'):
        return None, None

427 428
    # get the interpreter (without parameters)
    interpreter = hashbang[2:].strip().split()[0]
429 430 431 432 433 434 435 436 437
    interpreter_path = get_full_path(interpreter)
    interpreter = re.sub('^(/usr)?/bin/', '', interpreter_path)

    if interpreter in ['bash', 'dash', 'sh']:
        abstraction = 'abstractions/bash'
    elif interpreter == 'perl':
        abstraction = 'abstractions/perl'
    elif re.search('^python([23]|[23]\.[0-9]+)?$', interpreter):
        abstraction = 'abstractions/python'
438
    elif re.search('^ruby([0-9]+(\.[0-9]+)*)?$', interpreter):
439 440 441 442 443 444
        abstraction = 'abstractions/ruby'
    else:
        abstraction = None

    return interpreter_path, abstraction

Kshitij Gupta's avatar
Kshitij Gupta committed
445 446 447 448 449
def get_inactive_profile(local_profile):
    if extras.get(local_profile, False):
        return {local_profile: extras[local_profile]}
    return dict()

450
def create_new_profile(localfile, is_stub=False):
451
    local_profile = hasher()
452
    local_profile[localfile] = ProfileStorage('NEW', localfile, 'create_new_profile()')
Kshitij Gupta's avatar
Kshitij Gupta committed
453
    local_profile[localfile]['flags'] = 'complain'
454 455
    local_profile[localfile]['include']['abstractions/base'] = 1

456
    if os.path.exists(localfile) and os.path.isfile(localfile):
457
        interpreter_path, abstraction = get_interpreter_and_abstraction(localfile)
458

459
        if interpreter_path:
460 461
            local_profile[localfile]['file'].add(FileRule(localfile,        'r',  None, FileRule.ALL, owner=False))
            local_profile[localfile]['file'].add(FileRule(interpreter_path, None, 'ix', FileRule.ALL, owner=False))
462

463 464 465
            if abstraction:
                local_profile[localfile]['include'][abstraction] = True

466
            handle_binfmt(local_profile[localfile], interpreter_path)
Kshitij Gupta's avatar
Kshitij Gupta committed
467
        else:
468
            local_profile[localfile]['file'].add(FileRule(localfile,        'mr', None, FileRule.ALL, owner=False))
469

Kshitij Gupta's avatar
Kshitij Gupta committed
470
            handle_binfmt(local_profile[localfile], localfile)
471
    # Add required hats to the profile if they match the localfile
Kshitij Gupta's avatar
Kshitij Gupta committed
472 473 474
    for hatglob in cfg['required_hats'].keys():
        if re.search(hatglob, localfile):
            for hat in sorted(cfg['required_hats'][hatglob].split()):
475
                if not local_profile.get(hat, False):
476
                    local_profile[hat] = ProfileStorage('NEW', hat, 'create_new_profile() required_hats')
Kshitij Gupta's avatar
Kshitij Gupta committed
477
                local_profile[hat]['flags'] = 'complain'
478

479 480 481
    if not is_stub:
        created.append(localfile)
        changed[localfile] = True
482

Kshitij Gupta's avatar
Kshitij Gupta committed
483
    debug_logger.debug("Profile for %s:\n\t%s" % (localfile, local_profile.__str__()))
Kshitij Gupta's avatar
Kshitij Gupta committed
484
    return {localfile: local_profile}
485

Kshitij Gupta's avatar
Kshitij Gupta committed
486 487
def delete_profile(local_prof):
    """Deletes the specified file from the disk and remove it from our list"""
488
    profile_file = get_profile_filename_from_profile_name(local_prof, True)
Kshitij Gupta's avatar
Kshitij Gupta committed
489 490 491 492
    if os.path.isfile(profile_file):
        os.remove(profile_file)
    if aa.get(local_prof, False):
        aa.pop(local_prof)
493

494 495 496
    #prof_unload(local_prof)

def confirm_and_abort():
497
    ans = aaui.UI_YesNo(_('Are you sure you want to abandon this set of profile changes and exit?'), 'n')
498
    if ans == 'y':
499
        aaui.UI_Info(_('Abandoning all changes.'))
500 501 502
        for prof in created:
            delete_profile(prof)
        sys.exit(0)
503

Kshitij Gupta's avatar
Kshitij Gupta committed
504 505 506 507
def get_profile(prof_name):
    profile_data = None
    distro = cfg['repository']['distro']
    repo_url = cfg['repository']['url']
508
    # local_profiles = []
Kshitij Gupta's avatar
Kshitij Gupta committed
509 510
    profile_hash = hasher()
    if repo_is_enabled():
511
        aaui.UI_BusyStart(_('Connecting to repository...'))
Kshitij Gupta's avatar
Kshitij Gupta committed
512
        status_ok, ret = fetch_profiles_by_name(repo_url, distro, prof_name)
513
        aaui.UI_BusyStop()
Kshitij Gupta's avatar
Kshitij Gupta committed
514 515 516
        if status_ok:
            profile_hash = ret
        else:
517
            aaui.UI_Important(_('WARNING: Error fetching profiles from the repository'))
Kshitij Gupta's avatar
Kshitij Gupta committed
518 519 520 521
    inactive_profile = get_inactive_profile(prof_name)
    if inactive_profile:
        uname = 'Inactive local profile for %s' % prof_name
        inactive_profile[prof_name][prof_name]['flags'] = 'complain'
522
        orig_filename = inactive_profile[prof_name][prof_name]['filename']  # needed for CMD_VIEW_PROFILE
523
        inactive_profile[prof_name][prof_name]['filename'] = ''
Kshitij Gupta's avatar
Kshitij Gupta committed
524 525
        profile_hash[uname]['username'] = uname
        profile_hash[uname]['profile_type'] = 'INACTIVE_LOCAL'
526
        profile_hash[uname]['profile'] = serialize_profile(inactive_profile[prof_name], prof_name, {})
Kshitij Gupta's avatar
Kshitij Gupta committed
527
        profile_hash[uname]['profile_data'] = inactive_profile
528

529 530
        # no longer necessary after splitting active and extra profiles
        # existing_profiles.pop(prof_name)  # remove profile filename from list to force storing in /etc/apparmor.d/ instead of extra_profile_dir
531

Kshitij Gupta's avatar
Kshitij Gupta committed
532 533 534 535 536 537 538
    # If no profiles in repo and no inactive profiles
    if not profile_hash.keys():
        return None
    options = []
    tmp_list = []
    preferred_present = False
    preferred_user = cfg['repository'].get('preferred_user', 'NOVELL')
539

Kshitij Gupta's avatar
Kshitij Gupta committed
540 541 542 543 544
    for p in profile_hash.keys():
        if profile_hash[p]['username'] == preferred_user:
            preferred_present = True
        else:
            tmp_list.append(profile_hash[p]['username'])
545

Kshitij Gupta's avatar
Kshitij Gupta committed
546 547 548
    if preferred_present:
        options.append(preferred_user)
    options += tmp_list
549

550 551
    q = aaui.PromptQuestion()
    q.headers = ['Profile', prof_name]
552
    q.functions = ['CMD_VIEW_PROFILE', 'CMD_USE_PROFILE', 'CMD_CREATE_PROFILE', 'CMD_ABORT']
553 554 555
    q.default = "CMD_VIEW_PROFILE"
    q.options = options
    q.selected = 0
556

Kshitij Gupta's avatar
Kshitij Gupta committed
557 558
    ans = ''
    while 'CMD_USE_PROFILE' not in ans and 'CMD_CREATE_PROFILE' not in ans:
559
        ans, arg = q.promptUser()
Kshitij Gupta's avatar
Kshitij Gupta committed
560
        p = profile_hash[options[arg]]
561
        q.selected = options.index(options[arg])
Kshitij Gupta's avatar
Kshitij Gupta committed
562
        if ans == 'CMD_VIEW_PROFILE':
563
            pager = get_pager()
564
            subprocess.call([pager, orig_filename])
Kshitij Gupta's avatar
Kshitij Gupta committed
565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581
        elif ans == 'CMD_USE_PROFILE':
            if p['profile_type'] == 'INACTIVE_LOCAL':
                profile_data = p['profile_data']
                created.append(prof_name)
            else:
                profile_data = parse_repo_profile(prof_name, repo_url, p)
    return profile_data

def activate_repo_profiles(url, profiles, complain):
    read_profiles()
    try:
        for p in profiles:
            pname = p[0]
            profile_data = parse_repo_profile(pname, url, p[1])
            attach_profile_data(aa, profile_data)
            write_profile(pname)
            if complain:
582
                fname = get_profile_filename_from_profile_name(pname, True)
583
                change_profile_flags(profile_dir + fname, None, 'complain', True)
584
                aaui.UI_Info(_('Setting %s to complain mode.') % pname)
Kshitij Gupta's avatar
Kshitij Gupta committed
585
    except Exception as e:
586
            sys.stderr.write(_("Error activating profiles: %s") % e)
Kshitij Gupta's avatar
Kshitij Gupta committed
587 588 589

def autodep(bin_name, pname=''):
    bin_full = None
Kshitij Gupta's avatar
Kshitij Gupta committed
590
    global repo_cfg
Kshitij Gupta's avatar
Kshitij Gupta committed
591
    if not repo_cfg and not cfg['repository'].get('url', False):
592
        repo_conf = apparmor.config.Config('shell', CONFDIR)
593
        repo_cfg = repo_conf.read_config('repository.conf')
Kshitij Gupta's avatar
Kshitij Gupta committed
594 595 596 597 598 599 600 601 602 603 604
        if not repo_cfg.get('repository', False) or repo_cfg['repository']['enabled'] == 'later':
            UI_ask_to_enable_repo()
    if bin_name:
        bin_full = find_executable(bin_name)
        #if not bin_full:
        #    bin_full = bin_name
        #if not bin_full.startswith('/'):
            #return None
        # Return if exectuable path not found
        if not bin_full:
            return None
605 606 607
    else:
        bin_full = pname  # for named profiles

Kshitij Gupta's avatar
Kshitij Gupta committed
608
    pname = bin_full
609
    read_inactive_profiles()
Kshitij Gupta's avatar
Kshitij Gupta committed
610 611 612 613
    profile_data = get_profile(pname)
    # Create a new profile if no existing profile
    if not profile_data:
        profile_data = create_new_profile(pname)
614
    file = get_profile_filename_from_profile_name(pname, True)
615
    profile_data[pname][pname]['filename'] = None  # will be stored in /etc/apparmor.d when saving, so it shouldn't carry the extra_profile_dir filename
Kshitij Gupta's avatar
Kshitij Gupta committed
616
    attach_profile_data(aa, profile_data)
617
    attach_profile_data(original_aa, profile_data)
Kshitij Gupta's avatar
Kshitij Gupta committed
618 619
    if os.path.isfile(profile_dir + '/tunables/global'):
        if not filelist.get(file, False):
Kshitij Gupta's avatar
Kshitij Gupta committed
620 621
            filelist[file] = hasher()
        filelist[file]['include']['tunables/global'] = True
622 623
        filelist[file]['profiles'][pname] = hasher()
        filelist[file]['profiles'][pname][pname] = True
Kshitij Gupta's avatar
Kshitij Gupta committed
624
    write_profile_ui_feedback(pname)
625

626
def get_profile_flags(filename, program):
627 628
    # To-Do
    # XXX If more than one profile in a file then second one is being ignored XXX
629
    # Do we return flags for both or
630
    flags = ''
631 632 633
    with open_file_read(filename) as f_in:
        for line in f_in:
            if RE_PROFILE_START.search(line):
634
                matches = parse_profile_start_line(line, filename)
635 636 637 638
                if (matches['attachment'] is not None):
                    profile_glob = AARE(matches['attachment'], True)
                else:
                    profile_glob = AARE(matches['profile'], True)
639
                flags = matches['flags']
640
                if (program is not None and profile_glob.match(program)) or program is None or program == matches['profile']:
641
                    return flags
642

643
    raise AppArmorException(_('%s contains no profile') % filename)
644

645
def change_profile_flags(prof_filename, program, flag, set_flag):
Kshitij Gupta's avatar
Kshitij Gupta committed
646
    """Reads the old profile file and updates the flags accordingly"""
647 648
    # TODO: count the number of matching lines (separated by profile and hat?) and return it
    #       so that code calling this function can make sure to only report success if there was a match
649
    # TODO: change child profile flags even if program is specified
650 651 652

    found = False

653 654
    if not flag or flag.strip() == '':
        raise AppArmorBug('New flag for %s is empty' % prof_filename)
655

656 657 658 659 660 661 662 663 664
    with open_file_read(prof_filename) as f_in:
        temp_file = tempfile.NamedTemporaryFile('w', prefix=prof_filename, suffix='~', delete=False, dir=profile_dir)
        shutil.copymode(prof_filename, temp_file.name)
        with open_file_write(temp_file.name) as f_out:
            for line in f_in:
                if RE_PROFILE_START.search(line):
                    matches = parse_profile_start_line(line, prof_filename)
                    space = matches['leadingspace'] or ''
                    profile = matches['profile']
665 666
                    old_flags = matches['flags']
                    newflags = ', '.join(add_or_remove_flag(old_flags, flag, set_flag))
667

668 669 670
                    if (matches['attachment'] is not None):
                        profile_glob = AARE(matches['attachment'], True)
                    else:
671 672
                        profile_glob = AARE(matches['profile'], False)  # named profiles can come without an attachment path specified ("profile foo {...}")

673
                    if (program is not None and profile_glob.match(program)) or program is None or program == matches['profile']:
674
                        found = True
675
                        if program is not None and program != profile:
676 677
                            aaui.UI_Info(_('Warning: profile %s represents multiple programs') % profile)

678 679 680 681 682
                        header_data = {
                            'attachment': matches['attachment'] or '',
                            'flags': newflags,
                            'profile_keyword': matches['profile_keyword'],
                            'header_comment': matches['comment'] or '',
683
                            'xattrs': matches['xattrs'],
684 685 686
                        }
                        line = write_header(header_data, len(space)/2, profile, False, True)
                        line = '%s\n' % line[0]
687 688 689 690 691
                elif RE_PROFILE_HAT_DEF.search(line):
                    matches = RE_PROFILE_HAT_DEF.search(line)
                    space = matches.group('leadingspace') or ''
                    hat_keyword = matches.group('hat_keyword')
                    hat = matches.group('hat')
692 693
                    old_flags = matches['flags']
                    newflags = ', '.join(add_or_remove_flag(old_flags, flag, set_flag))
694 695 696 697 698 699 700 701
                    comment = matches.group('comment') or ''
                    if comment:
                        comment = ' %s' % comment

                    if newflags:
                        line = '%s%s%s flags=(%s) {%s\n' % (space, hat_keyword, hat, newflags, comment)
                    else:
                        line = '%s%s%s {%s\n' % (space, hat_keyword, hat, comment)
702 703
                f_out.write(line)
    os.rename(temp_file.name, prof_filename)
Kshitij Gupta's avatar
Kshitij Gupta committed
704

705 706
    if not found:
        if program is None:
707
            raise AppArmorException("%(file)s doesn't contain a valid profile (syntax error?)" % {'file': prof_filename})
708
        else:
709
            raise AppArmorException("%(file)s doesn't contain a valid profile for %(profile)s (syntax error?)" % {'file': prof_filename, 'profile': program})
710

Kshitij Gupta's avatar
Kshitij Gupta committed
711 712 713
def profile_exists(program):
    """Returns True if profile exists, False otherwise"""
    # Check cache of profiles
714

715
    if active_profiles.filename_from_attachment(program):
Kshitij Gupta's avatar
Kshitij Gupta committed
716 717
        return True
    # Check the disk for profile
718
    prof_path = get_profile_filename_from_attachment(program, True)
719
    #print(prof_path)
Kshitij Gupta's avatar
Kshitij Gupta committed
720 721
    if os.path.isfile(prof_path):
        # Add to cache of profile
722 723 724
        raise AppArmorBug('Reached strange condition in profile_exists(), please open a bugreport!')
        # active_profiles[program] = prof_path
        # return True
Kshitij Gupta's avatar
Kshitij Gupta committed
725
    return False
726 727 728 729 730 731 732 733 734 735 736 737 738

def sync_profile():
    user, passw = get_repo_user_pass()
    if not user or not passw:
        return None
    repo_profiles = []
    changed_profiles = []
    new_profiles = []
    status_ok, ret = fetch_profiles_by_user(cfg['repository']['url'],
                                            cfg['repository']['distro'], user)
    if not status_ok:
        if not ret:
            ret = 'UNKNOWN ERROR'
739
        aaui.UI_Important(_('WARNING: Error synchronizing profiles with the repository:\n%s\n') % ret)
740 741
    else:
        users_repo_profiles = ret
742
        serialize_opts = {'FLAGS': False}
743 744 745 746
        for prof in sorted(aa.keys()):
            if is_repo_profile([aa[prof][prof]]):
                repo_profiles.append(prof)
            if prof in created:
747
                p_local = serialize_profile(aa[prof], prof, serialize_opts)
748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776
                if not users_repo_profiles.get(prof, False):
                    new_profiles.append(prof)
                    new_profiles.append(p_local)
                    new_profiles.append('')
                else:
                    p_repo = users_repo_profiles[prof]['profile']
                    if p_local != p_repo:
                        changed_profiles.append(prof)
                        changed_profiles.append(p_local)
                        changed_profiles.append(p_repo)
        if repo_profiles:
            for prof in repo_profiles:
                p_local = serialize_profile(aa[prof], prof, serialize_opts)
                if not users_repo_profiles.get(prof, False):
                    new_profiles.append(prof)
                    new_profiles.append(p_local)
                    new_profiles.append('')
                else:
                    p_repo = ''
                    if aa[prof][prof]['repo']['user'] == user:
                        p_repo = users_repo_profiles[prof]['profile']
                    else:
                        status_ok, ret = fetch_profile_by_id(cfg['repository']['url'],
                                                             aa[prof][prof]['repo']['id'])
                        if status_ok:
                            p_repo = ret['profile']
                        else:
                            if not ret:
                                ret = 'UNKNOWN ERROR'
777
                            aaui.UI_Important(_('WARNING: Error synchronizing profiles with the repository\n%s') % ret)
778 779 780 781 782 783 784 785 786
                            continue
                    if p_repo != p_local:
                        changed_profiles.append(prof)
                        changed_profiles.append(p_local)
                        changed_profiles.append(p_repo)
        if changed_profiles:
            submit_changed_profiles(changed_profiles)
        if new_profiles:
            submit_created_profiles(new_profiles)
787 788 789 790 791 792 793 794 795 796 797 798 799

def fetch_profile_by_id(url, id):
    #To-Do
    return None, None

def fetch_profiles_by_name(url, distro, user):
    #to-Do
    return None, None

def fetch_profiles_by_user(url, distro, user):
    #to-Do
    return None, None

800 801 802
def submit_created_profiles(new_profiles):
    #url = cfg['repository']['url']
    if new_profiles:
803 804 805
        title = 'Submit newly created profiles to the repository'
        message = 'Would you like to upload newly created profiles?'
        console_select_and_upload_profiles(title, message, new_profiles)
806 807 808 809

def submit_changed_profiles(changed_profiles):
    #url = cfg['repository']['url']
    if changed_profiles:
810 811 812
        title = 'Submit changed profiles to the repository'
        message = 'The following profiles from the repository were changed.\nWould you like to upload your changes?'
        console_select_and_upload_profiles(title, message, changed_profiles)
813

Kshitij Gupta's avatar
Kshitij Gupta committed
814 815 816 817
def upload_profile(url, user, passw, distro, p, profile_string, changelog):
    # To-Do
    return None, None

818 819
def console_select_and_upload_profiles(title, message, profiles_up):
    url = cfg['repository']['url']
820
    profiles = profiles_up[:]
821 822 823 824
    q = aaui.PromptQuestion()
    q.title = title
    q.headers = ['Repository', url]
    q.explanation = message
825
    q.functions = ['CMD_UPLOAD_CHANGES', 'CMD_VIEW_CHANGES', 'CMD_ASK_LATER',
826
                      'CMD_ASK_NEVER', 'CMD_ABORT']
827
    q.default = 'CMD_VIEW_CHANGES'
828
    q.options = [i[0] for i in profiles]
829
    q.selected = 0
830 831
    ans = ''
    while 'CMD_UPLOAD_CHANGES' not in ans and 'CMD_ASK_NEVER' not in ans and 'CMD_ASK_LATER' not in ans:
832
        ans, arg = q.promptUser()
833
        if ans == 'CMD_VIEW_CHANGES':
834
            aaui.UI_Changes(profiles[arg][2], profiles[arg][1])
835
    if ans == 'CMD_NEVER_ASK':
836
        set_profiles_local_only([i[0] for i in profiles])
837
    elif ans == 'CMD_UPLOAD_CHANGES':
838
        changelog = aaui.UI_GetString(_('Changelog Entry: '), '')
839 840
        user, passw = get_repo_user_pass()
        if user and passw:
841
            for p_data in profiles:
842 843
                prof = p_data[0]
                prof_string = p_data[1]
844
                status_ok, ret = upload_profile(url, user, passw,
845
                                                cfg['repository']['distro'],
846
                                                prof, prof_string, changelog)
847 848 849 850 851
                if status_ok:
                    newprof = ret
                    newid = newprof['id']
                    set_repo_info(aa[prof][prof], url, user, newid)
                    write_profile_ui_feedback(prof)
852
                    aaui.UI_Info('Uploaded %s to repository' % prof)
853 854 855
                else:
                    if not ret:
                        ret = 'UNKNOWN ERROR'
856
                    aaui.UI_Important(_('WARNING: An error occurred while uploading the profile %(profile)s\n%(ret)s') % { 'profile': prof, 'ret': ret })
857
        else:
858
            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.'))
859

860 861 862 863
def set_profiles_local_only(profiles):
    for p in profiles:
        aa[profiles][profiles]['repo']['neversubmit'] = True
        write_profile_ui_feedback(profiles)
864 865 866 867


def build_x_functions(default, options, exec_toggle):
    ret_list = []
868
    fallback_toggle = False
869 870 871 872 873
    if exec_toggle:
        if 'i' in options:
            ret_list.append('CMD_ix')
            if 'p' in options:
                ret_list.append('CMD_pix')
874 875
                fallback_toggle = True
            if 'c' in options:
876
                ret_list.append('CMD_cix')
877 878
                fallback_toggle = True
            if 'n' in options:
879
                ret_list.append('CMD_nix')
880 881
                fallback_toggle = True
            if fallback_toggle:
882
                ret_list.append('CMD_EXEC_IX_OFF')
883
        if 'u' in options:
884
            ret_list.append('CMD_ux')
885

886 887 888
    else:
        if 'i' in options:
            ret_list.append('CMD_ix')
889
        if 'c' in options:
890
            ret_list.append('CMD_cx')
891 892
            fallback_toggle = True
        if 'p' in options:
893
            ret_list.append('CMD_px')
894 895
            fallback_toggle = True
        if 'n' in options:
896
            ret_list.append('CMD_nx')
897 898
            fallback_toggle = True
        if 'u' in options:
899
            ret_list.append('CMD_ux')
900 901 902 903

        if fallback_toggle:
            ret_list.append('CMD_EXEC_IX_ON')

904 905 906 907 908
    ret_list += ['CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED']
    return ret_list

def handle_children(profile, hat, root):
    entries = root[:]
Kshitij Gupta's avatar
Kshitij Gupta committed
909 910 911 912 913 914 915 916 917 918 919 920 921 922
    pid = None
    p = None
    h = None
    prog = None
    aamode = None
    mode = None
    detail = None
    to_name = None
    uhat = None
    capability = None
    family = None
    sock_type = None
    protocol = None
    regex_nullcomplain = re.compile('^null(-complain)*-profile$')
923

924
    for entry in entries:
925 926 927 928 929
        if type(entry[0]) != str:
            handle_children(profile, hat, entry)
        else:
            typ = entry.pop(0)
            if typ == 'fork':
930
                # If type is fork then we (should) have pid, profile and hat
931 932 933 934 935 936 937 938
                pid, p, h = entry[:3]
                if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h):
                    profile = p
                    hat = h
                if hat:
                    profile_changes[pid] = profile + '//' + hat
                else:
                    profile_changes[pid] = profile
939
            elif typ == 'unknown_hat':
940
                # If hat is not known then we (should) have pid, profile, hat, mode and unknown hat in entry
941 942 943 944 945 946 947 948 949 950
                pid, p, h, aamode, uhat = entry[:5]
                if not regex_nullcomplain.search(p):
                    profile = p
                if aa[profile].get(uhat, False):
                    hat = uhat
                    continue
                new_p = update_repo_profile(aa[profile][profile])
                if new_p and UI_SelectUpdatedRepoProfile(profile, new_p) and aa[profile].get(uhat, False):
                    hat = uhat
                    continue
951

952 953 954 955
                default_hat = None
                for hatglob in cfg.options('defaulthat'):
                    if re.search(hatglob, profile):
                        default_hat = cfg['defaulthat'][hatglob]
956

957 958 959
                context = profile
                context = context + ' -> ^%s' % uhat
                ans = transitions.get(context, 'XXXINVALIDXXX')
960

961
                while ans not in ['CMD_ADDHAT', 'CMD_USEDEFAULT', 'CMD_DENY']:
962 963
                    q = aaui.PromptQuestion()
                    q.headers += [_('Profile'), profile]
964

965
                    if default_hat:
966
                        q.headers += [_('Default Hat'), default_hat]
967

968
                    q.headers += [_('Requested Hat'), uhat]
969

970
                    q.functions.append('CMD_ADDHAT')
971
                    if default_hat:
972 973
                        q.functions.append('CMD_USEDEFAULT')
                    q.functions += ['CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED']
974

975
                    q.default = 'CMD_DENY'
976
                    if aamode == 'PERMITTING':
977
                        q.default = 'CMD_ADDHAT'
978

979
                    ans = q.promptUser()[0]
980

981 982 983 984
                    if ans == 'CMD_FINISHED':
                        save_profiles()
                        return

985
                transitions[context] = ans
986

987 988
                if ans == 'CMD_ADDHAT':
                    hat = uhat
989
                    aa[profile][hat] = ProfileStorage(profile, hat, 'handle_children addhat')
990
                    aa[profile][hat]['flags'] = aa[profile][profile]['flags']
991
                    changed[profile] = True
992 993 994
                elif ans == 'CMD_USEDEFAULT':
                    hat = default_hat
                elif ans == 'CMD_DENY':
995
                    # As unknown hat is denied no entry for it should be made
996
                    return None
997

998
            elif typ == 'capability':
999
                # If capability then we (should) have pid, profile, hat, program, mode, capability
1000 1001 1002 1003 1004 1005 1006
                pid, p, h, prog, aamode, capability = entry[:6]
                if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h):
                    profile = p
                    hat = h
                if not profile or not hat:
                    continue
                prelog[aamode][profile][hat]['capability'][capability] = True
1007

1008 1009 1010 1011 1012 1013 1014 1015 1016 1017
            elif typ == 'dbus':
                # If dbus then we (should) have pid, profile, hat, program, mode, access, bus, name, path, interface, member, peer_profile
                pid, p, h, prog, aamode, access, bus, path, name, interface, member, peer_profile = entry
                if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h):
                    profile = p
                    hat = h
                if not profile or not hat:
                    continue
                prelog[aamode][profile][hat]['dbus'][access][bus][path][name][interface][member][peer_profile] = True

1018 1019 1020 1021 1022 1023 1024 1025 1026 1027
            elif typ == 'ptrace':
                # If ptrace then we (should) have pid, profile, hat, program, mode, access and peer
                pid, p, h, prog, aamode, access, peer = entry
                if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h):
                    profile = p
                    hat = h
                if not profile or not hat:
                    continue
                prelog[aamode][profile][hat]['ptrace'][peer][access] = True

1028 1029 1030 1031 1032 1033 1034 1035 1036 1037
            elif typ == 'signal':
                # If signal then we (should) have pid, profile, hat, program, mode, access, signal and peer
                pid, p, h, prog, aamode, access, signal, peer = entry
                if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h):
                    profile = p
                    hat = h
                if not profile or not hat:
                    continue
                prelog[aamode][profile][hat]['signal'][peer][access][signal] = True

1038
            elif typ == 'path' or typ == 'exec':
1039
                # If path or exec then we (should) have pid, profile, hat, program, mode, details and to_name
1040 1041
                pid, p, h, prog, aamode, mode, detail, to_name = entry[:8]
                if not mode:
Kshitij Gupta's avatar
Kshitij Gupta committed
1042
                    mode = set()
1043 1044 1045 1046 1047
                if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h):
                    profile = p
                    hat = h
                if not profile or not hat or not detail:
                    continue
1048 1049

                # Give Execute dialog if x access requested for something that's not a directory
1050 1051 1052
                # For directories force an 'ix' Path dialog
                do_execute = False
                exec_target = detail
1053

1054 1055
                if mode & str_to_mode('x'):
                    if os.path.isdir(exec_target):
1056
                        raise AppArmorBug('exec permissions requested for directory %s. This should not happen - please open a bugreport!' % exec_target)
1057 1058
                    elif typ != 'exec':
                        raise AppArmorBug('exec permissions requested for %(exec_target)s, but mode is %(mode)s instead of exec. This should not happen - please open a bugreport!' % {'exec_target': exec_target, 'mode':mode})
1059 1060
                    else:
                        do_execute = True
1061
                        domainchange = 'change'
1062

1063
                if mode and mode != str_to_mode('x'):  # x is already handled in handle_children, so it must not become part of prelog
1064
                    path = detail
1065

1066 1067 1068
                    if prelog[aamode][profile][hat]['path'].get(path, False):
                        mode |= prelog[aamode][profile][hat]['path'][path]
                    prelog[aamode][profile][hat]['path'][path] = mode
1069

1070
                if do_execute:
1071 1072 1073
                    if not aa[profile][hat]:
                        continue  # ignore log entries for non-existing profiles

1074 1075
                    exec_event = FileRule(exec_target, None, FileRule.ANY_EXEC, FileRule.ALL, owner=False, log_event=True)
                    if is_known_rule(aa[profile][hat], 'file', exec_event):
1076
                        continue
1077

1078 1079
                    p = update_repo_profile(aa[profile][profile])
                    if to_name:
1080
                        if UI_SelectUpdatedRepoProfile(profile, p) and is_known_rule(aa[profile][hat], 'file', exec_event):  # we need an exec_event with target=to_name here
1081 1082
                            continue
                    else:
1083
                        if UI_SelectUpdatedRepoProfile(profile, p) and is_known_rule(aa[profile][hat], 'file', exec_event):  # we need an exec_event with target=exec_target here
1084
                            continue
1085

1086 1087 1088
                    context_new = profile
                    if profile != hat:
                        context_new = context_new + '^%s' % hat
Kshitij Gupta's avatar
Kshitij Gupta committed
1089
                    context_new = context_new + ' -> %s' % exec_target
1090

Kshitij Gupta's avatar
Kshitij Gupta committed
1091 1092 1093 1094
                    # nx is not used in profiles but in log files.
                    # Log parsing methods will convert it to its profile form
                    # nx is internally cx/px/cix/pix + to_name
                    exec_mode = False
1095
                    file_perm = None
1096 1097

                    if True:
Kshitij Gupta's avatar
Kshitij Gupta committed
1098 1099
                        options = cfg['qualifiers'].get(exec_target, 'ipcnu')
                        if to_name:
Kshitij Gupta's avatar
Kshitij Gupta committed
1100
                            fatal_error(_('%s has transition name but not transition mode') % entry)
1101

1102 1103 1104
                        ### If profiled program executes itself only 'ix' option
                        ##if exec_target == profile:
                            ##options = 'i'
1105

Kshitij Gupta's avatar
Kshitij Gupta committed
1106 1107 1108 1109 1110 1111
                        # Don't allow hats to cx?
                        options.replace('c', '')
                        # Add deny to options
                        options += 'd'
                        # Define the default option
                        default = None
1112
                        if 'p' in options and os.path.exists(get_profile_filename_from_attachment(exec_target, True)):
Kshitij Gupta's avatar
Kshitij Gupta committed
1113
                            default = 'CMD_px'
1114
                            sys.stdout.write(_('Target profile exists: %s\n') % get_profile_filename_from_attachment(exec_target, True))
Kshitij Gupta's avatar
Kshitij Gupta committed
1115 1116 1117 1118 1119 1120 1121 1122
                        elif 'i' in options:
                            default = 'CMD_ix'
                        elif 'c' in options:
                            default = 'CMD_cx'
                        elif 'n' in options:
                            default = 'CMD_nx'
                        else: