mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-09-04 00:05:14 +00:00
Python2 compatible code except for configparser, code from week2
This commit is contained in:
@@ -1,104 +0,0 @@
|
|||||||
# ------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# Copyright (C) 2004-2006 Novell/SUSE
|
|
||||||
#
|
|
||||||
# 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 published by the Free Software Foundation.
|
|
||||||
#
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
|
|
||||||
[repository]
|
|
||||||
distro = ubuntu-intrepid
|
|
||||||
url = http://apparmor.test.opensuse.org/backend/api
|
|
||||||
preferred_user = ubuntu
|
|
||||||
|
|
||||||
[qualifiers]
|
|
||||||
# things will be painfully broken if bash has a profile
|
|
||||||
/bin/bash = icnu
|
|
||||||
/bin/ksh = icnu
|
|
||||||
/bin/dash = icnu
|
|
||||||
|
|
||||||
# these programs can't function if they're confined
|
|
||||||
/bin/mount = u
|
|
||||||
/etc/init.d/subdomain = u
|
|
||||||
/sbin/cardmgr = u
|
|
||||||
/sbin/subdomain_parser = u
|
|
||||||
/usr/sbin/genprof = u
|
|
||||||
/usr/sbin/logprof = u
|
|
||||||
/usr/lib/YaST2/servers_non_y2/ag_genprof = u
|
|
||||||
/usr/lib/YaST2/servers_non_y2/ag_logprof = u
|
|
||||||
|
|
||||||
# these ones shouln't have their own profiles
|
|
||||||
/bin/awk = icn
|
|
||||||
/bin/cat = icn
|
|
||||||
/bin/chmod = icn
|
|
||||||
/bin/chown = icn
|
|
||||||
/bin/cp = icn
|
|
||||||
/bin/gawk = icn
|
|
||||||
/bin/grep = icn
|
|
||||||
/bin/gunzip = icn
|
|
||||||
/bin/gzip = icn
|
|
||||||
/bin/kill = icn
|
|
||||||
/bin/ln = icn
|
|
||||||
/bin/ls = icn
|
|
||||||
/bin/mkdir = icn
|
|
||||||
/bin/mv = icn
|
|
||||||
/bin/readlink = icn
|
|
||||||
/bin/rm = icn
|
|
||||||
/bin/sed = icn
|
|
||||||
/bin/touch = icn
|
|
||||||
/sbin/killall5 = icn
|
|
||||||
/usr/bin/find = icn
|
|
||||||
/usr/bin/killall = icn
|
|
||||||
/usr/bin/nice = icn
|
|
||||||
/usr/bin/perl = icn
|
|
||||||
/usr/bin/tr = icn
|
|
||||||
|
|
||||||
[required_hats]
|
|
||||||
^.+/apache(|2|2-prefork)$ = DEFAULT_URI HANDLING_UNTRUSTED_INPUT #a
|
|
||||||
^.+/httpd(|2|2-prefork)$ = DEFAULT_URI HANDLING_UNTRUSTED_INPUT
|
|
||||||
|
|
||||||
[defaulthat]
|
|
||||||
^.+/apache(|2|2-prefork)$ = DEFAULT_URI
|
|
||||||
^.+/httpd(|2|2-prefork)$ = DEFAULT_URI
|
|
||||||
|
|
||||||
[globs]
|
|
||||||
# /foo/bar/lib/libbaz.so -> /foo/bar/lib/lib*
|
|
||||||
/lib/lib[^\/]+so[^\/]*$ = /lib/lib*so*
|
|
||||||
|
|
||||||
# strip kernel version numbers from kernel module accesses
|
|
||||||
^/lib/modules/[^\/]+\/ = /lib/modules/*/
|
|
||||||
|
|
||||||
# strip pid numbers from /proc accesses
|
|
||||||
^/proc/\d+/ = /proc/*/
|
|
||||||
|
|
||||||
# if it looks like a home directory, glob out the username
|
|
||||||
^/home/[^\/]+ = /home/*
|
|
||||||
|
|
||||||
# if they use any perl modules, grant access to all
|
|
||||||
^/usr/lib/perl5/.+$ = /usr/lib/perl5/**
|
|
||||||
|
|
||||||
# locale foo
|
|
||||||
^/usr/lib/locale/.+$ = /usr/lib/locale/**
|
|
||||||
^/usr/share/locale/.+$ = /usr/share/locale/**
|
|
||||||
|
|
||||||
# timezone fun
|
|
||||||
^/usr/share/zoneinfo/.+$ = /usr/share/zoneinfo/**
|
|
||||||
|
|
||||||
# /foobar/fonts/baz -> /foobar/fonts/**
|
|
||||||
/fonts/.+$ = /fonts/**
|
|
||||||
|
|
||||||
# turn /foo/bar/baz.8907234 into /foo/bar/baz.*
|
|
||||||
# BUGBUG - this one looked weird because it would suggest a glob for
|
|
||||||
# BUGBUG - libfoo.so.5.6.0 that looks like libfoo.so.5.6.*
|
|
||||||
# \.\d+$ = .*
|
|
||||||
|
|
||||||
# some various /etc/security poo -- dunno about these ones...
|
|
||||||
^/etc/security/_[^\/]+$ = /etc/security/*
|
|
||||||
^/lib/security/pam_filter/[^\/]+$ = /lib/security/pam_filter/*
|
|
||||||
^/lib/security/pam_[^\/]+\.so$ = /lib/security/pam_*.so
|
|
||||||
|
|
||||||
^/etc/pam.d/[^\/]+$ = /etc/pam.d/*
|
|
||||||
^/etc/profile.d/[^\/]+\.sh$ = /etc/profile.d/*.sh
|
|
||||||
|
|
@@ -6,10 +6,11 @@ Created on Jun 21, 2013
|
|||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
sys.path.append('../lib')
|
sys.path.append('../')
|
||||||
|
sys.path.append('../apparmor')
|
||||||
import severity
|
|
||||||
|
|
||||||
|
import AppArmor.severity as severity
|
||||||
|
from AppArmor.common import AppArmorException
|
||||||
class Test(unittest.TestCase):
|
class Test(unittest.TestCase):
|
||||||
|
|
||||||
def testInvalid(self):
|
def testInvalid(self):
|
||||||
@@ -17,11 +18,11 @@ class Test(unittest.TestCase):
|
|||||||
rank = s.rank('/dev/doublehit', 'i')
|
rank = s.rank('/dev/doublehit', 'i')
|
||||||
self.assertEqual(rank, 10, 'Wrong')
|
self.assertEqual(rank, 10, 'Wrong')
|
||||||
try:
|
try:
|
||||||
broken = severity.Severity('severity_broken.db')
|
broken = severity.Severity('severity_broken.db')
|
||||||
rank = s.rank('CAP_UNKOWN')
|
except AppArmorException:
|
||||||
rank = s.rank('CAP_K*')
|
|
||||||
except ValueError:
|
|
||||||
pass
|
pass
|
||||||
|
rank = s.rank('CAP_UNKOWN')
|
||||||
|
rank = s.rank('CAP_K*')
|
||||||
|
|
||||||
def testRank_Test(self):
|
def testRank_Test(self):
|
||||||
z = severity.Severity()
|
z = severity.Severity()
|
||||||
|
5
apparmor/__init__.py
Normal file
5
apparmor/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
'''
|
||||||
|
Created on Jun 27, 2013
|
||||||
|
|
||||||
|
@author: kshitij
|
||||||
|
'''
|
173
apparmor/aa.py
Normal file
173
apparmor/aa.py
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
#mv $0 bed/ now
|
||||||
|
#Line 470
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import apparmor.config
|
||||||
|
import apparmor.severity
|
||||||
|
import LibAppArmor
|
||||||
|
|
||||||
|
from AppArmor.common import AppArmorException, error, debug, msg, open_file_read, valid_path
|
||||||
|
|
||||||
|
DEBUGGING = False
|
||||||
|
|
||||||
|
CONFDIR = '/etc/apparmor'
|
||||||
|
running_under_genprof = False
|
||||||
|
unimplemented_warning = False
|
||||||
|
# The operating mode: yast or text, text by default
|
||||||
|
UI_mode = 'text'
|
||||||
|
# The database for severity
|
||||||
|
sev_db = None
|
||||||
|
# The file to read log messages from
|
||||||
|
### Was our
|
||||||
|
filename = None
|
||||||
|
|
||||||
|
cfg = None
|
||||||
|
repo_cfg = None
|
||||||
|
|
||||||
|
parser = None
|
||||||
|
ldd = None
|
||||||
|
logger = None
|
||||||
|
profile_dir = None
|
||||||
|
extra_profile_dir = None
|
||||||
|
### end our
|
||||||
|
# To keep track of previously included profile fragments
|
||||||
|
include = dict()
|
||||||
|
|
||||||
|
existing_profiles = dict()
|
||||||
|
|
||||||
|
seen_events = 0 # was our
|
||||||
|
# To store the globs entered by users so they can be provided again
|
||||||
|
user_globs = []
|
||||||
|
|
||||||
|
## Variables used under logprof
|
||||||
|
### Were our
|
||||||
|
t = dict()
|
||||||
|
transitions = dict()
|
||||||
|
aa = dict() # Profiles originally in sd, replace by aa
|
||||||
|
original_aa = dict()
|
||||||
|
extras = dict() # Inactive profiles from extras
|
||||||
|
### end our
|
||||||
|
log = []
|
||||||
|
pid = None
|
||||||
|
|
||||||
|
seen = dir()
|
||||||
|
profile_changes = dict()
|
||||||
|
prelog = dict()
|
||||||
|
log = dict()
|
||||||
|
changed = dict()
|
||||||
|
created = []
|
||||||
|
helpers = dict() # Preserve this between passes # was our
|
||||||
|
### logprof ends
|
||||||
|
|
||||||
|
filelist = dict() # File level variables and stuff in config files
|
||||||
|
|
||||||
|
AA_MAY_EXEC = 1
|
||||||
|
AA_MAY_WRITE = 2
|
||||||
|
AA_MAY_READ = 4
|
||||||
|
AA_MAY_APPEND = 8
|
||||||
|
AA_MAY_LINK = 16
|
||||||
|
AA_MAY_LOCK = 32
|
||||||
|
AA_EXEC_MMAP = 64
|
||||||
|
AA_EXEC_UNSAFE = 128
|
||||||
|
AA_EXEC_INHERIT = 256
|
||||||
|
AA_EXEC_UNCONFINED = 512
|
||||||
|
AA_EXEC_PROFILE = 1024
|
||||||
|
AA_EXEC_CHILD = 2048
|
||||||
|
AA_EXEC_NT = 4096
|
||||||
|
AA_LINK_SUBSET = 8192
|
||||||
|
AA_OTHER_SHIFT = 14
|
||||||
|
AA_USER_MASK = 16384 - 1
|
||||||
|
|
||||||
|
AA_EXEC_TYPE = (AA_MAY_EXEC | AA_EXEC_UNSAFE | AA_EXEC_INHERIT |
|
||||||
|
AA_EXEC_UNCONFINED | AA_EXEC_PROFILE | AA_EXEC_CHILD | AA_EXEC_NT)
|
||||||
|
|
||||||
|
ALL_AA_EXEC_TYPE = AA_EXEC_TYPE # The same value
|
||||||
|
|
||||||
|
# Modes and their values
|
||||||
|
MODE_HASH = {'x': AA_MAY_EXEC, 'X': AA_MAY_EXEC,
|
||||||
|
'w': AA_MAY_WRITE, 'W': AA_MAY_WRITE,
|
||||||
|
'r': AA_MAY_READ, 'R': AA_MAY_READ,
|
||||||
|
'a': AA_MAY_APPEND, 'A': AA_MAY_APPEND,
|
||||||
|
'l': AA_MAY_LINK, 'L': AA_MAY_LINK,
|
||||||
|
'k': AA_MAY_LOCK, 'K': AA_MAY_LOCK,
|
||||||
|
'm': AA_EXEC_MMAP, 'M': AA_EXEC_MMAP,
|
||||||
|
'i': AA_EXEC_INHERIT, 'I': AA_EXEC_INHERIT,
|
||||||
|
'u': AA_EXEC_UNCONFINED + AA_EXEC_UNSAFE, # Unconfined + Unsafe
|
||||||
|
'U': AA_EXEC_UNCONFINED,
|
||||||
|
'p': AA_EXEC_PROFILE + AA_EXEC_UNSAFE, # Profile + unsafe
|
||||||
|
'P': AA_EXEC_PROFILE,
|
||||||
|
'c': AA_EXEC_CHILD + AA_EXEC_UNSAFE, # Child + Unsafe
|
||||||
|
'C': AA_EXEC_CHILD,
|
||||||
|
'n': AA_EXEC_NT + AA_EXEC_UNSAFE,
|
||||||
|
'N': AA_EXEC_NT
|
||||||
|
}
|
||||||
|
|
||||||
|
# Used by netdomain to identify the operation types
|
||||||
|
OPERATION_TYPES = {
|
||||||
|
# New socket names
|
||||||
|
'create': 'net',
|
||||||
|
'post_create': 'net',
|
||||||
|
'bind': 'net',
|
||||||
|
'connect': 'net',
|
||||||
|
'listen': 'net',
|
||||||
|
'accept': 'net',
|
||||||
|
'sendmsg': 'net',
|
||||||
|
'recvmsg': 'net',
|
||||||
|
'getsockname': 'net',
|
||||||
|
'getpeername': 'net',
|
||||||
|
'getsockopt': 'net',
|
||||||
|
'setsockopt': 'net',
|
||||||
|
'sock_shutdown': 'net'
|
||||||
|
}
|
||||||
|
|
||||||
|
ARROWS = {'A': 'UP', 'B': 'DOWN', 'C': 'RIGHT', 'D': 'LEFT'}
|
||||||
|
|
||||||
|
def opt_type(operation):
|
||||||
|
"""Returns the operation type if known, unkown otherwise"""
|
||||||
|
operation_type = OPERATION_TYPES.get(operation, 'unknown')
|
||||||
|
return operation_type
|
||||||
|
|
||||||
|
def getkey():
|
||||||
|
"""Returns the pressed key"""
|
||||||
|
# Used incase of Y or N without pressing enter
|
||||||
|
## Needs to be done using curses? read a single key
|
||||||
|
pass
|
||||||
|
|
||||||
|
def check_for_apparmor():
|
||||||
|
"""Finds and returns the mointpoint for apparmor None otherwise"""
|
||||||
|
filesystem = '/proc/filesystems'
|
||||||
|
mounts = '/proc/mounts'
|
||||||
|
support_securityfs = False
|
||||||
|
aa_mountpoint = None
|
||||||
|
regex_securityfs = re.compile('^\S+\s+(\S+)\s+securityfs\s')
|
||||||
|
if valid_path(filesystem):
|
||||||
|
with open_file_read(filesystem) as f_in:
|
||||||
|
for line in f_in:
|
||||||
|
if 'securityfs' in line:
|
||||||
|
support_securityfs = True
|
||||||
|
if valid_path(mounts):
|
||||||
|
with open_file_read(mounts) as f_in:
|
||||||
|
for line in f_in:
|
||||||
|
if support_securityfs:
|
||||||
|
match = regex_securityfs(line)
|
||||||
|
if match:
|
||||||
|
mountpoint = match.groups()[0] + '/apparmor'
|
||||||
|
if valid_path(mountpoint):
|
||||||
|
aa_mountpoint = mountpoint
|
||||||
|
# Check if apparmor is actually mounted there
|
||||||
|
if not valid_path(aa_mountpoint + '/profiles'):
|
||||||
|
aa_mountpoint = None
|
||||||
|
return aa_mountpoint
|
||||||
|
|
||||||
|
def which(file):
|
||||||
|
"""Returns the executable fullpath for the file None otherwise"""
|
||||||
|
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
|
||||||
|
|
126
apparmor/common.py
Normal file
126
apparmor/common.py
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
from __future__ import print_function
|
||||||
|
import codecs
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
DEBUGGING = False
|
||||||
|
|
||||||
|
#
|
||||||
|
# Utility classes
|
||||||
|
#
|
||||||
|
class AppArmorException(Exception):
|
||||||
|
'''This class represents AppArmor exceptions'''
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return repr(self.value)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Utility functions
|
||||||
|
#
|
||||||
|
def error(out, exit_code=1, do_exit=True):
|
||||||
|
'''Print error message and exit'''
|
||||||
|
try:
|
||||||
|
print("ERROR: %s" % (out), file=sys.stderr)
|
||||||
|
except IOError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if do_exit:
|
||||||
|
sys.exit(exit_code)
|
||||||
|
|
||||||
|
def warn(out):
|
||||||
|
'''Print warning message'''
|
||||||
|
try:
|
||||||
|
print("WARN: %s" % (out), file=sys.stderr)
|
||||||
|
except IOError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def msg(out, output=sys.stdout):
|
||||||
|
'''Print message'''
|
||||||
|
try:
|
||||||
|
print("%s" % (out), file=output)
|
||||||
|
except IOError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def debug(out):
|
||||||
|
'''Print debug message'''
|
||||||
|
global DEBUGGING
|
||||||
|
if DEBUGGING:
|
||||||
|
try:
|
||||||
|
print("DEBUG: %s" % (out), file=sys.stderr)
|
||||||
|
except IOError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def cmd(command):
|
||||||
|
'''Try to execute the given command.'''
|
||||||
|
debug(command)
|
||||||
|
try:
|
||||||
|
sp = subprocess.Popen(command, stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT)
|
||||||
|
except OSError as ex:
|
||||||
|
return [127, str(ex)]
|
||||||
|
|
||||||
|
if sys.version_info[0] >= 3:
|
||||||
|
out = sp.communicate()[0].decode('ascii', 'ignore')
|
||||||
|
else:
|
||||||
|
out = sp.communicate()[0]
|
||||||
|
|
||||||
|
return [sp.returncode, out]
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_pipe(command1, command2):
|
||||||
|
'''Try to pipe command1 into command2.'''
|
||||||
|
try:
|
||||||
|
sp1 = subprocess.Popen(command1, stdout=subprocess.PIPE)
|
||||||
|
sp2 = subprocess.Popen(command2, stdin=sp1.stdout)
|
||||||
|
except OSError as ex:
|
||||||
|
return [127, str(ex)]
|
||||||
|
|
||||||
|
if sys.version_info[0] >= 3:
|
||||||
|
out = sp2.communicate()[0].decode('ascii', 'ignore')
|
||||||
|
else:
|
||||||
|
out = sp2.communicate()[0]
|
||||||
|
|
||||||
|
return [sp2.returncode, out]
|
||||||
|
|
||||||
|
def valid_path(path):
|
||||||
|
'''Valid path'''
|
||||||
|
# No relative paths
|
||||||
|
m = "Invalid path: %s" % (path)
|
||||||
|
if not path.startswith('/'):
|
||||||
|
debug("%s (relative)" % (m))
|
||||||
|
return False
|
||||||
|
|
||||||
|
if '"' in path: # We double quote elsewhere
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.path.normpath(path)
|
||||||
|
except Exception:
|
||||||
|
debug("%s (could not normalize)" % (m))
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_directory_contents(path):
|
||||||
|
'''Find contents of the given directory'''
|
||||||
|
if not valid_path(path):
|
||||||
|
return None
|
||||||
|
|
||||||
|
files = []
|
||||||
|
for f in glob.glob(path + "/*"):
|
||||||
|
files.append(f)
|
||||||
|
|
||||||
|
files.sort()
|
||||||
|
return files
|
||||||
|
|
||||||
|
def open_file_read(path):
|
||||||
|
'''Open specified file read-only'''
|
||||||
|
try:
|
||||||
|
orig = codecs.open(path, 'r', "UTF-8")
|
||||||
|
except Exception:
|
||||||
|
raise
|
||||||
|
|
||||||
|
return orig
|
247
apparmor/config.py
Normal file
247
apparmor/config.py
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
from __future__ import with_statement
|
||||||
|
import os
|
||||||
|
import shlex
|
||||||
|
import shutil
|
||||||
|
import stat
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
if sys.version_info < (3,0):
|
||||||
|
import ConfigParser as configparser
|
||||||
|
else:
|
||||||
|
import configparser
|
||||||
|
|
||||||
|
|
||||||
|
from AppArmor.common import AppArmorException, warn, msg, open_file_read
|
||||||
|
|
||||||
|
CONF_DIR = '/etc/apparmor'
|
||||||
|
CFG = None
|
||||||
|
REPO_CFG = None
|
||||||
|
SHELL_FILES = ['easyprof.conf', 'notify.conf', 'parser.conf', 'subdomain.conf']
|
||||||
|
class Config:
|
||||||
|
def __init__(self, conf_type):
|
||||||
|
# The type of config file that'll be read and/or written
|
||||||
|
self.conf_type = conf_type
|
||||||
|
self.input_file = None
|
||||||
|
|
||||||
|
def new_config(self):
|
||||||
|
if self.conf_type == 'shell':
|
||||||
|
config = {'': dict()}
|
||||||
|
else:
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
return config
|
||||||
|
|
||||||
|
def read_config(self, filename):
|
||||||
|
"""Reads the file and returns a config[section][attribute]=property object"""
|
||||||
|
# LP: Bug #692406
|
||||||
|
# Explicitly disabled repository
|
||||||
|
filepath = CONF_DIR + '/' + filename
|
||||||
|
self.input_file = filepath
|
||||||
|
if filename == "repository.conf":
|
||||||
|
config = dict()
|
||||||
|
config['repository'] = {'enabled': 'no'}
|
||||||
|
elif self.conf_type == 'shell':
|
||||||
|
config = self.read_shell(filepath)
|
||||||
|
else:
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
# Set the option form to string -prevents forced conversion to lowercase
|
||||||
|
#config.optionxform = str
|
||||||
|
if sys.version_info > (3,0):
|
||||||
|
config.read(filepath)
|
||||||
|
else:
|
||||||
|
config.readfp(open_file_read(filepath))
|
||||||
|
return config
|
||||||
|
|
||||||
|
def write_config(self, filename, config):
|
||||||
|
"""Writes the given config to the specified file"""
|
||||||
|
filepath = CONF_DIR + '/' + filename
|
||||||
|
permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write
|
||||||
|
try:
|
||||||
|
# Open a temporary file in the CONF_DIR to write the config file
|
||||||
|
config_file = tempfile.NamedTemporaryFile('w', prefix='aa_temp', delete=False, dir=CONF_DIR)
|
||||||
|
if os.path.exists(self.input_file):
|
||||||
|
# Copy permissions from an existing file to temporary file
|
||||||
|
shutil.copymode(self.input_file, config_file.name)
|
||||||
|
else:
|
||||||
|
# If no existing permission set the file permissions as 0600
|
||||||
|
os.chmod(config_file.name, permission_600)
|
||||||
|
if self.conf_type == 'shell':
|
||||||
|
self.write_shell(filepath, config_file, config)
|
||||||
|
else:
|
||||||
|
self.write_configparser(filepath, config_file, config)
|
||||||
|
#config.write(config_file)
|
||||||
|
config_file.close()
|
||||||
|
except IOError:
|
||||||
|
raise AppArmorException("Unable to write to %s"%filename)
|
||||||
|
else:
|
||||||
|
# Replace the target config file with the temporary file
|
||||||
|
os.rename(config_file.name, filepath)
|
||||||
|
|
||||||
|
|
||||||
|
def find_first_file(self, file_list):
|
||||||
|
"""Returns name of first matching file None otherwise"""
|
||||||
|
filename = None
|
||||||
|
if filename:
|
||||||
|
for file in file_list.split():
|
||||||
|
if os.path.isfile(file):
|
||||||
|
filename = file
|
||||||
|
break
|
||||||
|
return filename
|
||||||
|
|
||||||
|
def find_first_dir(self, dir_list):
|
||||||
|
"""Returns name of first matching directory None otherwise"""
|
||||||
|
dirname = None
|
||||||
|
if dir_list:
|
||||||
|
for direc in dir_list.split():
|
||||||
|
if os.path.isdir(direc):
|
||||||
|
dirname = direc
|
||||||
|
break
|
||||||
|
return dirname
|
||||||
|
|
||||||
|
def read_shell(self, filepath):
|
||||||
|
"""Reads the shell type conf files and returns config[''][option]=value"""
|
||||||
|
config = {'': dict()}
|
||||||
|
with open_file_read(filepath) as file:
|
||||||
|
for line in file:
|
||||||
|
result = shlex.split(line, True)
|
||||||
|
# If not a comment of empty line
|
||||||
|
if result:
|
||||||
|
# option="value" or option=value type
|
||||||
|
if '=' in result[0]:
|
||||||
|
option, value = result[0].split('=')
|
||||||
|
# option type
|
||||||
|
else:
|
||||||
|
option = result[0]
|
||||||
|
value = None
|
||||||
|
config[''][option] = value
|
||||||
|
return config
|
||||||
|
|
||||||
|
def write_shell(self, filepath, f_out, config):
|
||||||
|
"""Writes the config object in shell file format"""
|
||||||
|
# All the options in the file
|
||||||
|
options = [key for key in config[''].keys()]
|
||||||
|
# If a previous file exists modify it keeping the comments
|
||||||
|
if os.path.exists(self.input_file):
|
||||||
|
with open_file_read(self.input_file) as f_in:
|
||||||
|
for line in f_in:
|
||||||
|
result = shlex.split(line, True)
|
||||||
|
# If line is not empty or comment
|
||||||
|
if result:
|
||||||
|
# If option=value or option="value" type
|
||||||
|
if '=' in result[0]:
|
||||||
|
option, value = result[0].split('=')
|
||||||
|
if '#' in line:
|
||||||
|
comment = value.split('#', 1)[1]
|
||||||
|
comment = '#'+comment
|
||||||
|
else:
|
||||||
|
comment = ''
|
||||||
|
# If option exists in the new config file
|
||||||
|
if option in options:
|
||||||
|
# If value is different
|
||||||
|
if value != config[''][option]:
|
||||||
|
value_new = config[''][option]
|
||||||
|
if value_new != None:
|
||||||
|
# Update value
|
||||||
|
if '"' in line:
|
||||||
|
value_new = '"' + value_new + '"'
|
||||||
|
line = option + '=' + value_new + comment + '\n'
|
||||||
|
else:
|
||||||
|
# If option changed to option type from option=value type
|
||||||
|
line = option + comment + '\n'
|
||||||
|
f_out.write(line)
|
||||||
|
# Remove from remaining options list
|
||||||
|
options.remove(option)
|
||||||
|
else:
|
||||||
|
# If option type
|
||||||
|
option = result[0]
|
||||||
|
value = None
|
||||||
|
# If option exists in the new config file
|
||||||
|
if option in options:
|
||||||
|
# If its no longer option type
|
||||||
|
if config[''][option] != None:
|
||||||
|
value = config[''][option]
|
||||||
|
line = option + '=' + value + '\n'
|
||||||
|
f_out.write(line)
|
||||||
|
# Remove from remaining options list
|
||||||
|
options.remove(option)
|
||||||
|
else:
|
||||||
|
# If its empty or comment copy as it is
|
||||||
|
f_out.write(line)
|
||||||
|
# If any new options are present
|
||||||
|
if options:
|
||||||
|
for option in options:
|
||||||
|
value = config[''][option]
|
||||||
|
# option type entry
|
||||||
|
if value == None:
|
||||||
|
line = option + '\n'
|
||||||
|
# option=value type entry
|
||||||
|
else:
|
||||||
|
line = option + '=' + value + '\n'
|
||||||
|
f_out.write(line)
|
||||||
|
|
||||||
|
def write_configparser(self, filepath, f_out, config):
|
||||||
|
"""Writes/updates the given file with given config object"""
|
||||||
|
# All the sections in the file
|
||||||
|
sections = config.sections()
|
||||||
|
write = True
|
||||||
|
section = None
|
||||||
|
options = []
|
||||||
|
# If a previous file exists modify it keeping the comments
|
||||||
|
if os.path.exists(self.input_file):
|
||||||
|
with open_file_read(self.input_file) as f_in:
|
||||||
|
for line in f_in:
|
||||||
|
# If its a section
|
||||||
|
if line.lstrip().startswith('['):
|
||||||
|
# If any options from preceding section remain write them
|
||||||
|
if options:
|
||||||
|
for option in options:
|
||||||
|
line_new = ' ' + option + ' = ' + config[section][option] + '\n'
|
||||||
|
f_out.write(line_new)
|
||||||
|
options = []
|
||||||
|
if section in sections:
|
||||||
|
# Remove the written section from the list
|
||||||
|
sections.remove(section)
|
||||||
|
section = line.strip()[1:-1]
|
||||||
|
if section in sections:
|
||||||
|
# enable write for all entries in that section
|
||||||
|
write = True
|
||||||
|
options = config.options(section)
|
||||||
|
# write the section
|
||||||
|
f_out.write(line)
|
||||||
|
else:
|
||||||
|
# disable writing until next valid section
|
||||||
|
write = False
|
||||||
|
# If write enabled
|
||||||
|
elif write:
|
||||||
|
value = shlex.split(line, True)
|
||||||
|
# If the line is empty or a comment
|
||||||
|
if not value:
|
||||||
|
f_out.write(line)
|
||||||
|
else:
|
||||||
|
option, value = line.split('=', 1)
|
||||||
|
try:
|
||||||
|
# split any inline comments
|
||||||
|
value, comment = value.split('#', 1)
|
||||||
|
comment = '#' + comment
|
||||||
|
except ValueError:
|
||||||
|
comment = ''
|
||||||
|
if option.strip() in options:
|
||||||
|
if config[section][option.strip()] != value.strip():
|
||||||
|
value = value.replace(value, config[section][option.strip()])
|
||||||
|
line = option + '=' + value + comment
|
||||||
|
f_out.write(line)
|
||||||
|
options.remove(option.strip())
|
||||||
|
# If any options remain from the preceding section
|
||||||
|
if options:
|
||||||
|
for option in options:
|
||||||
|
line = ' ' + option + ' = ' + config[section][option] + '\n'
|
||||||
|
f_out.write(line)
|
||||||
|
options = []
|
||||||
|
# If any new sections are present
|
||||||
|
if section in sections:
|
||||||
|
sections.remove(section)
|
||||||
|
for section in sections:
|
||||||
|
f_out.write('\n['+section+']\n')
|
||||||
|
options = config.options(section)
|
||||||
|
for option in options:
|
||||||
|
line = ' ' + option + ' = ' + config[section][option] + '\n'
|
||||||
|
f_out.write(line)
|
@@ -1,5 +1,7 @@
|
|||||||
|
from __future__ import with_statement
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
from AppArmor.common import AppArmorException, error, debug, open_file_read, warn, msg
|
||||||
|
|
||||||
class Severity:
|
class Severity:
|
||||||
def __init__(self, dbname=None, default_rank=10):
|
def __init__(self, dbname=None, default_rank=10):
|
||||||
@@ -16,7 +18,7 @@ class Severity:
|
|||||||
for root, dirs, files in os.walk('/etc/apparmor.d'):
|
for root, dirs, files in os.walk('/etc/apparmor.d'):
|
||||||
for file in files:
|
for file in files:
|
||||||
try:
|
try:
|
||||||
with open(os.path.join(root, file), 'r') as f:
|
with open_file_read(os.path.join(root, file)) as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
line.strip()
|
line.strip()
|
||||||
# Expected format is @{Variable} = value1 value2 ..
|
# Expected format is @{Variable} = value1 value2 ..
|
||||||
@@ -24,17 +26,23 @@ class Severity:
|
|||||||
line = line.strip()
|
line = line.strip()
|
||||||
if '+=' in line:
|
if '+=' in line:
|
||||||
line = line.split('+=')
|
line = line.split('+=')
|
||||||
|
try:
|
||||||
|
self.severity['VARIABLES'][line[0]] += [i.strip('"') for i in line[1].split()]
|
||||||
|
except KeyError:
|
||||||
|
raise AppArmorException("Variable %s was not previously declared, but is being assigned additional values" % line[0])
|
||||||
else:
|
else:
|
||||||
line = line.split('=')
|
line = line.split('=')
|
||||||
self.severity['VARIABLES'][line[0]] = self.severity['VARIABLES'].get(line[0], []) + [i.strip('"') for i in line[1].split()]
|
if line[0] in self.severity['VARIABLES'].keys():
|
||||||
|
raise AppArmorException("Variable %s was previously declared" % line[0])
|
||||||
|
self.severity['VARIABLES'][line[0]] = [i.strip('"') for i in line[1].split()]
|
||||||
except IOError:
|
except IOError:
|
||||||
raise IOError("unable to open file: %s"%file)
|
raise AppArmorException("unable to open file: %s" % file)
|
||||||
if not dbname:
|
if not dbname:
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
database = open(dbname, 'r')
|
database = open_file_read(dbname)#open(dbname, 'r')
|
||||||
except IOError:
|
except IOError:
|
||||||
raise IOError("Could not open severity database %s"%dbname)
|
raise AppArmorException("Could not open severity database %s" % dbname)
|
||||||
for line in database:
|
for line in database:
|
||||||
line = line.strip() # or only rstrip and lstrip?
|
line = line.strip() # or only rstrip and lstrip?
|
||||||
if line == '' or line.startswith('#') :
|
if line == '' or line.startswith('#') :
|
||||||
@@ -44,10 +52,12 @@ class Severity:
|
|||||||
path, read, write, execute = line.split()
|
path, read, write, execute = line.split()
|
||||||
read, write, execute = int(read), int(write), int(execute)
|
read, write, execute = int(read), int(write), int(execute)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError("Insufficient values for permissions in line: %s\nin File: %s"%(line, dbname))
|
msg("\nFile: %s" % dbname)
|
||||||
|
raise AppArmorException("Insufficient values for permissions in line: %s" % (line))
|
||||||
else:
|
else:
|
||||||
if read not in range(0,11) or write not in range(0,11) or execute not in range(0,11):
|
if read not in range(0,11) or write not in range(0,11) or execute not in range(0,11):
|
||||||
raise ValueError("Inappropriate values for permissions in line: %s\nin File: %s"%(line, dbname))
|
msg("\nFile:"%(dbname))
|
||||||
|
raise AppArmorException("Inappropriate values for permissions in line: %s" % line)
|
||||||
path = path.lstrip('/')
|
path = path.lstrip('/')
|
||||||
if '*' not in path:
|
if '*' not in path:
|
||||||
self.severity['FILES'][path] = {'r': read, 'w': write, 'x': execute}
|
self.severity['FILES'][path] = {'r': read, 'w': write, 'x': execute}
|
||||||
@@ -68,13 +78,16 @@ class Severity:
|
|||||||
resource, severity = line.split()
|
resource, severity = line.split()
|
||||||
severity = int(severity)
|
severity = int(severity)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError("No severity value present in line: %s\nin File: %s"%(line, dbname))
|
msg("\nFile: %s" % dbname)
|
||||||
|
raise AppArmorException("No severity value present in line: %s" % (line))
|
||||||
else:
|
else:
|
||||||
if severity not in range(0,11):
|
if severity not in range(0,11):
|
||||||
raise ValueError("Inappropriate severity value present in line: %s\nin File: %s"%(line, dbname))
|
msg("\nFile: %s" % dbname)
|
||||||
|
raise AppArmorException("Inappropriate severity value present in line: %s" % (line))
|
||||||
self.severity['CAPABILITIES'][resource] = severity
|
self.severity['CAPABILITIES'][resource] = severity
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unexpected database line: %s \nin File: %s"%(line,dbname))
|
msg("\nFile: %s" % dbname)
|
||||||
|
raise AppArmorException("Unexpected database line: %s" % (line))
|
||||||
database.close()
|
database.close()
|
||||||
|
|
||||||
def convert_regexp(self, path):
|
def convert_regexp(self, path):
|
||||||
@@ -83,7 +96,7 @@ class Severity:
|
|||||||
internal_glob = '__KJHDKVZH_AAPROF_INTERNAL_GLOB_SVCUZDGZID__'
|
internal_glob = '__KJHDKVZH_AAPROF_INTERNAL_GLOB_SVCUZDGZID__'
|
||||||
regex = path
|
regex = path
|
||||||
for character in ['.', '+', '[', ']']: # Escape the regex symbols
|
for character in ['.', '+', '[', ']']: # Escape the regex symbols
|
||||||
regex = regex.replace(character, "\%s"%character)
|
regex = regex.replace(character, "\%s" % character)
|
||||||
# Convert the ** to regex
|
# Convert the ** to regex
|
||||||
regex = regex.replace('**', '.'+internal_glob)
|
regex = regex.replace('**', '.'+internal_glob)
|
||||||
# Convert the * to regex
|
# Convert the * to regex
|
||||||
@@ -101,6 +114,7 @@ class Severity:
|
|||||||
if resource in self.severity['CAPABILITIES'].keys():
|
if resource in self.severity['CAPABILITIES'].keys():
|
||||||
return self.severity['CAPABILITIES'][resource]
|
return self.severity['CAPABILITIES'][resource]
|
||||||
# raise ValueError("unexpected capability rank input: %s"%resource)
|
# raise ValueError("unexpected capability rank input: %s"%resource)
|
||||||
|
warn("unknown capability: %s" % resource)
|
||||||
return self.severity['DEFAULT_RANK']
|
return self.severity['DEFAULT_RANK']
|
||||||
|
|
||||||
|
|
||||||
@@ -158,7 +172,7 @@ class Severity:
|
|||||||
elif resource[0:4] == 'CAP_': # capability resource
|
elif resource[0:4] == 'CAP_': # capability resource
|
||||||
return self.handle_capability(resource)
|
return self.handle_capability(resource)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unexpected rank input: %s"%resource)
|
raise AppArmorException("Unexpected rank input: %s" % resource)
|
||||||
|
|
||||||
def handle_variable_rank(self, resource, mode):
|
def handle_variable_rank(self, resource, mode):
|
||||||
"""Returns the max possible rank for file resources containing variables"""
|
"""Returns the max possible rank for file resources containing variables"""
|
||||||
@@ -176,7 +190,7 @@ class Severity:
|
|||||||
rank = rank_new
|
rank = rank_new
|
||||||
return rank
|
return rank
|
||||||
else:
|
else:
|
||||||
print(resource)
|
#print(resource)
|
||||||
#print(self.handle_file(resource, mode))
|
#print(self.handle_file(resource, mode))
|
||||||
return self.handle_file(resource, mode)
|
return self.handle_file(resource, mode)
|
||||||
|
|
@@ -1 +0,0 @@
|
|||||||
#!/usr/bin/python3
|
|
219
lib/config.py
219
lib/config.py
@@ -1,219 +0,0 @@
|
|||||||
import configparser
|
|
||||||
import os
|
|
||||||
import shlex
|
|
||||||
import shutil
|
|
||||||
import stat
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
|
|
||||||
confdir = '/etc/apparmor'
|
|
||||||
cfg = None
|
|
||||||
repo_cfg = None
|
|
||||||
shell_files = ['easyprof.conf', 'notify.conf', 'parser.conf', 'subdomain.conf']
|
|
||||||
|
|
||||||
def read_config(filename, conf_type=None):
|
|
||||||
"""Reads the file and returns a config[section][attribute]=property object"""
|
|
||||||
# LP: Bug #692406
|
|
||||||
# Explicitly disabled repository
|
|
||||||
filepath = confdir + '/' + filename
|
|
||||||
if filename == "repository.conf":
|
|
||||||
config = dict()
|
|
||||||
config['repository'] = {'enabled': 'no'}
|
|
||||||
elif filename in shell_files or conf_type == 'shell':
|
|
||||||
config = read_shell(filepath)
|
|
||||||
else:
|
|
||||||
config = configparser.ConfigParser()
|
|
||||||
config.optionxform = str
|
|
||||||
config.read(filepath)
|
|
||||||
return config
|
|
||||||
|
|
||||||
def write_config(filename, config, conf_type=None):
|
|
||||||
"""Writes the given config to the specified file"""
|
|
||||||
filepath = confdir + '/' + filename
|
|
||||||
permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write
|
|
||||||
try:
|
|
||||||
# Open a temporary file in the confdir to write the config file
|
|
||||||
config_file = tempfile.NamedTemporaryFile('w', prefix='aa_temp', delete=False, dir=confdir)
|
|
||||||
if os.path.exists(filepath):
|
|
||||||
# Copy permissions from an existing file to temporary file
|
|
||||||
shutil.copymode(filepath, config_file.name)
|
|
||||||
else:
|
|
||||||
# If no existing permission set the file permissions as 0600
|
|
||||||
os.chmod(config_file.name, permission_600)
|
|
||||||
write_shell(filepath, config_file, config)
|
|
||||||
if filename in shell_files or conf_type == 'shell':
|
|
||||||
write_shell(filepath, config_file, config)
|
|
||||||
else:
|
|
||||||
write_configparser(filepath, config_file, config)
|
|
||||||
#config.write(config_file)
|
|
||||||
config_file.close()
|
|
||||||
except IOError:
|
|
||||||
raise IOError("Unable to write to %s"%filename)
|
|
||||||
else:
|
|
||||||
# Replace the target config file with the temporary file
|
|
||||||
os.rename(config_file.name, filepath)
|
|
||||||
|
|
||||||
|
|
||||||
def find_first_file(file_list):
|
|
||||||
"""Returns name of first matching file None otherwise"""
|
|
||||||
# I don't understand why it searches the CWD, maybe I'll find out about it in some module
|
|
||||||
filename = None
|
|
||||||
if len(file_list):
|
|
||||||
for file in file_list.split():
|
|
||||||
if os.path.isfile(file):
|
|
||||||
filename = file
|
|
||||||
break
|
|
||||||
return filename
|
|
||||||
|
|
||||||
def find_first_dir(dir_list):
|
|
||||||
"""Returns name of first matching directory None otherwise"""
|
|
||||||
dirname = None
|
|
||||||
if (len(dir_list)):
|
|
||||||
for direc in dir_list.split():
|
|
||||||
if os.path.isdir(direc):
|
|
||||||
dirname = direc
|
|
||||||
break
|
|
||||||
return dirname
|
|
||||||
|
|
||||||
def read_shell(filepath):
|
|
||||||
"""Reads the shell type conf files and returns config[''][option]=value"""
|
|
||||||
config = {'': dict()}
|
|
||||||
with open(filepath, 'r') as file:
|
|
||||||
for line in file:
|
|
||||||
result = shlex.split(line, True)
|
|
||||||
# If not a comment of empty line
|
|
||||||
if result != []:
|
|
||||||
# option="value" or option=value type
|
|
||||||
if '=' in result[0]:
|
|
||||||
option, value = result[0].split('=')
|
|
||||||
# option type
|
|
||||||
else:
|
|
||||||
option = result[0]
|
|
||||||
value = None
|
|
||||||
config[''][option] = value
|
|
||||||
return config
|
|
||||||
|
|
||||||
def write_shell(filepath, f_out, config):
|
|
||||||
"""Writes the config object in shell file format"""
|
|
||||||
# All the options in the file
|
|
||||||
options = [key for key in config[''].keys()]
|
|
||||||
# If a previous file exists modify it keeping the comments
|
|
||||||
if os.path.exists(filepath):
|
|
||||||
with open(filepath, 'r') as f_in:
|
|
||||||
for line in f_in:
|
|
||||||
result = shlex.split(line, True)
|
|
||||||
# If line is not empty or comment
|
|
||||||
if result != []:
|
|
||||||
# If option=value or option="value" type
|
|
||||||
if '=' in result[0]:
|
|
||||||
option, value = result[0].split('=')
|
|
||||||
# If option exists in the new config file
|
|
||||||
if option in options:
|
|
||||||
# If value is different
|
|
||||||
if value != config[''][option]:
|
|
||||||
value_new = config[''][option]
|
|
||||||
if value_new != None:
|
|
||||||
# Update value
|
|
||||||
if '"' in line:
|
|
||||||
value_new = '"' + value_new + '"'
|
|
||||||
line = option + '=' + value_new + '\n'
|
|
||||||
else:
|
|
||||||
# If option changed to option type from option=value type
|
|
||||||
line = option + '\n'
|
|
||||||
f_out.write(line)
|
|
||||||
# Remove from remaining options list
|
|
||||||
options.remove(option)
|
|
||||||
else:
|
|
||||||
# If option type
|
|
||||||
option = result[0]
|
|
||||||
value = None
|
|
||||||
# If option exists in the new config file
|
|
||||||
if option in options:
|
|
||||||
# If its no longer option type
|
|
||||||
if config[''][option] != None:
|
|
||||||
value = config[''][option]
|
|
||||||
line = option + '=' + value + '\n'
|
|
||||||
f_out.write(line)
|
|
||||||
# Remove from remaining options list
|
|
||||||
options.remove(option)
|
|
||||||
else:
|
|
||||||
# If its empty or comment copy as it is
|
|
||||||
f_out.write(line)
|
|
||||||
# If any new options are present
|
|
||||||
if options != []:
|
|
||||||
for option in options:
|
|
||||||
value = config[''][option]
|
|
||||||
# option type entry
|
|
||||||
if value == None:
|
|
||||||
line = option + '\n'
|
|
||||||
# option=value type entry
|
|
||||||
else:
|
|
||||||
line = option + '=' + value + '\n'
|
|
||||||
f_out.write(line)
|
|
||||||
|
|
||||||
def write_configparser(filepath, f_out, config):
|
|
||||||
# All the sections in the file
|
|
||||||
sections = config.sections()
|
|
||||||
write = True
|
|
||||||
section = None
|
|
||||||
options = []
|
|
||||||
# If a previous file exists modify it keeping the comments
|
|
||||||
if os.path.exists(filepath):
|
|
||||||
with open(filepath, 'r') as f_in:
|
|
||||||
for line in f_in:
|
|
||||||
# If its a section
|
|
||||||
if line.lstrip().startswith('['):
|
|
||||||
# If any options from preceding section remain write them
|
|
||||||
if options != []:
|
|
||||||
for option in options:
|
|
||||||
line_new = ' ' + option + ' = ' + config[section][option] + '\n'
|
|
||||||
f_out.write(line_new)
|
|
||||||
options = []
|
|
||||||
if section in sections:
|
|
||||||
# Remove the written section from the list
|
|
||||||
sections.remove(section)
|
|
||||||
section = line.strip()[1:-1]
|
|
||||||
if section in sections:
|
|
||||||
# enable write for all entries in that section
|
|
||||||
write = True
|
|
||||||
options = config.options(section)
|
|
||||||
# write the section
|
|
||||||
f_out.write(line)
|
|
||||||
else:
|
|
||||||
# disable writing until next valid section
|
|
||||||
write = False
|
|
||||||
# If write enabled
|
|
||||||
elif write:
|
|
||||||
value = shlex.split(line, True)
|
|
||||||
# If the line is empty or a comment
|
|
||||||
if value == []:
|
|
||||||
f_out.write(line)
|
|
||||||
else:
|
|
||||||
option, value = line.split('=', 1)
|
|
||||||
try:
|
|
||||||
# split any inline comments
|
|
||||||
value, comment = value.split('#', 1)
|
|
||||||
comment = '#' + comment
|
|
||||||
except ValueError:
|
|
||||||
comment = ''
|
|
||||||
if option.strip() in options:
|
|
||||||
if config[section][option.strip()] != value.strip():
|
|
||||||
value = value.replace(value, config[section][option.strip()])
|
|
||||||
line = option + '=' + value + comment
|
|
||||||
f_out.write(line)
|
|
||||||
options.remove(option.strip())
|
|
||||||
# If any options remain from the preceding section
|
|
||||||
if options != []:
|
|
||||||
for option in options:
|
|
||||||
line = ' ' + option + ' = ' + config[section][option] + '\n'
|
|
||||||
f_out.write(line)
|
|
||||||
options = []
|
|
||||||
# If any new sections are present
|
|
||||||
if section in sections:
|
|
||||||
sections.remove(section)
|
|
||||||
for section in sections:
|
|
||||||
f_out.write('\n['+section+']\n')
|
|
||||||
options = config.options(section)
|
|
||||||
for option in options:
|
|
||||||
line = ' ' + option + ' = ' + config[section][option] + '\n'
|
|
||||||
f_out.write(line)
|
|
Reference in New Issue
Block a user