add: parser now support multi/pair options

This commit is contained in:
ziirish 2018-02-03 16:23:46 +01:00
parent 3f65509f02
commit 97f22df758
No known key found for this signature in database
GPG key ID: 72DB229A64B54E46
8 changed files with 529 additions and 214 deletions

View file

@ -245,6 +245,8 @@ class ServerSettings(Resource):
'multi_srv': '_multi_srv_parser_{}'.format(server),
'values': '_suggest_parser_{}'.format(server),
'defaults': '_defaults_parser_{}'.format(server),
'advanced_type': '_advanced_parser_{}'.format(server),
'pair_srv': '_pair_srv_parser_{}'.format(server),
}
cache_results = {}
for name, key in iteritems(cache_keys):
@ -266,6 +268,8 @@ class ServerSettings(Resource):
string=cache_results['string_srv'],
integer=cache_results['integer_srv'],
multi=cache_results['multi_srv'],
pair=cache_results['pair_srv'],
advanced=cache_results['advanced_type'],
server_doc=cache_results['doc'],
suggest=cache_results['values'],
placeholders=cache_results['placeholders'],

View file

@ -303,6 +303,7 @@ def setup_burp(bconfcli, bconfsrv, client, host, redis, database, plugins, dry):
import difflib
import tempfile
parser = app.client.get_parser()
orig = source = None
conf_orig = []
if dry:
@ -477,87 +478,6 @@ def setup_burp(bconfcli, bconfsrv, client, host, redis, database, plugins, dry):
getattr(app.client, 'burpconfsrv')
dest_bconfcli = bconfcli
if not os.path.exists(bconfcli):
clitpl = """
mode = client
port = 4971
status_port = 4972
server = ::1
password = abcdefgh
cname = {0}
protocol = 1
pidfile = /tmp/burp.client.pid
syslog = 0
stdout = 1
progress_counter = 1
network_timeout = 72000
server_can_restore = 0
cross_all_filesystems=0
ca_burp_ca = /usr/sbin/burp_ca
ca_csr_dir = /etc/burp/CA-client
ssl_cert_ca = /etc/burp/ssl_cert_ca-client-{0}.pem
ssl_cert = /etc/burp/ssl_cert-bui-client.pem
ssl_key = /etc/burp/ssl_cert-bui-client.key
ssl_key_password = password
ssl_peer_cn = burpserver
include = /home
exclude_fs = sysfs
exclude_fs = tmpfs
nobackup = .nobackup
exclude_comp=bz2
exclude_comp=gz
""".format(client)
if dry:
(_, dest_bconfcli) = tempfile.mkstemp()
with open(dest_bconfcli, 'w') as confcli:
confcli.write(clitpl)
parser = app.client.get_parser()
confcli = Config(dest_bconfcli, parser, 'srv')
confcli.set_default(dest_bconfcli)
confcli.parse()
if confcli.get('cname') != client:
confcli['cname'] = client
if confcli.get('server') != host:
confcli['server'] = host
if confcli.dirty:
if dry:
(_, dstfile) = tempfile.mkstemp()
else:
dstfile = bconfcli
confcli.store(conf=bconfcli, dest=dstfile, insecure=True)
if dry:
before = []
after = []
try:
with open(bconfcli) as fil:
before = fil.readlines()
except:
pass
try:
with open(dstfile) as fil:
after = fil.readlines()
os.unlink(dstfile)
except:
pass
if dest_bconfcli != bconfcli:
# the file did not exist
os.unlink(dest_bconfcli)
before = []
diff = difflib.unified_diff(before, after, fromfile=bconfcli, tofile='{}.new'.format(bconfcli))
out = ''
for line in diff:
out += _color_diff(line)
if out:
click.echo_via_pager(out)
if not os.path.exists(bconfsrv):
click.echo(
click.style(
@ -591,8 +511,8 @@ exclude_comp=gz
)
)
status_port = confsrv.get('status_port', [4972])
if 'max_status_children' not in confsrv:
confsrv['max_status_children'] = 15
click.echo(
click.style(
'We need to set the number of \'max_status_children\'. '
@ -600,9 +520,20 @@ exclude_comp=gz
fg='blue'
)
)
confsrv['max_status_children'] = 15
status_port = status_port[0]
else:
max_status_children = confsrv.get('max_status_children')
if max_status_children < 15:
found = False
for idx, value in enumerate(max_status_children):
if value >= 15:
found = True
if idx >= len(status_port):
status_port = status_port[-1]
else:
status_port = status_port[idx]
break
if not found:
click.echo(
click.style(
'We need to raise the number of \'max_status_children\'. '
@ -610,7 +541,8 @@ exclude_comp=gz
fg='yellow'
)
)
confsrv['max_status_children'] = 15
confsrv['max_status_children'][-1] = 15
status_port = status_port[-1]
if 'restore_client' not in confsrv:
confsrv['restore_client'] = client
@ -674,6 +606,90 @@ exclude_comp=gz
)
bconfagent = os.devnull
if not os.path.exists(bconfcli):
clitpl = """
mode = client
port = 4971
status_port = 4972
server = ::1
password = abcdefgh
cname = {0}
protocol = 1
pidfile = /tmp/burp.client.pid
syslog = 0
stdout = 1
progress_counter = 1
network_timeout = 72000
server_can_restore = 0
cross_all_filesystems=0
ca_burp_ca = /usr/sbin/burp_ca
ca_csr_dir = /etc/burp/CA-client
ssl_cert_ca = /etc/burp/ssl_cert_ca-client-{0}.pem
ssl_cert = /etc/burp/ssl_cert-bui-client.pem
ssl_key = /etc/burp/ssl_cert-bui-client.key
ssl_key_password = password
ssl_peer_cn = burpserver
include = /home
exclude_fs = sysfs
exclude_fs = tmpfs
nobackup = .nobackup
exclude_comp=bz2
exclude_comp=gz
""".format(client)
if dry:
(_, dest_bconfcli) = tempfile.mkstemp()
with open(dest_bconfcli, 'w') as confcli:
confcli.write(clitpl)
parser = app.client.get_parser()
confcli = Config(dest_bconfcli, parser, 'srv')
confcli.set_default(dest_bconfcli)
confcli.parse()
if confcli.get('cname') != client:
confcli['cname'] = client
if confcli.get('server') != host:
confcli['server'] = host
if confcli.get('status_port')[0] != status_port:
c_status_port = confcli.get_raw('status_port')
c_status_port[0] = status_port
if confcli.dirty:
if dry:
(_, dstfile) = tempfile.mkstemp()
else:
dstfile = bconfcli
confcli.store(conf=bconfcli, dest=dstfile, insecure=True)
if dry:
before = []
after = []
try:
with open(bconfcli) as fil:
before = fil.readlines()
except:
pass
try:
with open(dstfile) as fil:
after = fil.readlines()
os.unlink(dstfile)
except:
pass
if dest_bconfcli != bconfcli:
# the file did not exist
os.unlink(dest_bconfcli)
before = []
diff = difflib.unified_diff(before, after, fromfile=bconfcli, tofile='{}.new'.format(bconfcli))
out = ''
for line in diff:
out += _color_diff(line)
if out:
click.echo_via_pager(out)
if not os.path.exists(bconfagent):
agenttpl = """
@ -915,8 +931,8 @@ def diag(client, host, tips):
)
)
max_status_children = confsrv.get('max_status_children', -1)
if 'max_status_children' not in confsrv or max_status_children < 15:
max_status_children = confsrv.get('max_status_children', [-1])
if all([x < 15 for x in max_status_children]):
click.echo(
click.style(
'\'max_status_children\' is to low, you need to set it to '
@ -1047,14 +1063,17 @@ def diag(client, host, tips):
@click.option('-v', '--verbose', is_flag=True,
help='Dump parts of the config (Please double check no sensitive'
' data leaked)')
def sysinfo(verbose):
@click.option('-l', '--load', is_flag=True,
help='Load all configured modules for full summary')
def sysinfo(verbose, load):
"""Returns a couple of system informations to help debugging."""
from .desc import __release__, __version__
try:
msg = app.load_modules(True)
except Exception as e:
msg = str(e)
if load:
try:
msg = app.load_modules(True)
except Exception as e:
msg = str(e)
backend_version = app.vers
if not app.standalone:
@ -1074,33 +1093,34 @@ def sysinfo(verbose):
click.echo('WebSocket embedded: {}'.format(click.style(embedded_ws, fg=colors[embedded_ws])))
click.echo('WebSocket available: {}'.format(click.style(available_ws, colors[available_ws])))
click.echo('Config file: {}'.format(app.config.conffile))
if not app.standalone and not msg:
click.echo('Agents:')
for agent, obj in iteritems(app.client.servers):
client_version = server_version = 'unknown'
if load:
if not app.standalone and not msg:
click.echo('Agents:')
for agent, obj in iteritems(app.client.servers):
client_version = server_version = 'unknown'
try:
app.client.status(agent=agent)
client_version = app.client.get_client_version(agent=agent)
server_version = app.client.get_server_version(agent=agent)
except BUIserverException:
pass
alive = obj.ping()
if alive:
status = click.style('ALIVE', fg='green')
else:
status = click.style('DISCONNECTED', fg='red')
click.echo(' - {} ({})'.format(agent, status))
click.echo(' * client version: {}'.format(client_version))
click.echo(' * server version: {}'.format(server_version))
elif not msg:
server_version = 'unknown'
try:
app.client.status(agent=agent)
client_version = app.client.get_client_version(agent=agent)
server_version = app.client.get_server_version(agent=agent)
app.client.status()
server_version = app.client.get_server_version()
except BUIserverException:
pass
alive = obj.ping()
if alive:
status = click.style('ALIVE', fg='green')
else:
status = click.style('DISCONNECTED', fg='red')
click.echo(' - {} ({})'.format(agent, status))
click.echo(' * client version: {}'.format(client_version))
click.echo(' * server version: {}'.format(server_version))
elif not msg:
server_version = 'unknown'
try:
app.client.status()
server_version = app.client.get_server_version()
except BUIserverException:
pass
click.echo('Burp client version: {}'.format(app.client.client_version))
click.echo('Burp server version: {}'.format(server_version))
click.echo('Burp client version: {}'.format(app.client.client_version))
click.echo('Burp server version: {}'.format(server_version))
if verbose:
click.echo('>>>>> Extra verbose informations:')
click.echo(click.style(

View file

@ -431,6 +431,7 @@ class Parser(Doc):
u'boolean': [],
u'integer': [],
u'multi': [],
u'pair': [],
u'includes': [],
u'includes_ext': [],
u'hierarchy': [],
@ -451,6 +452,7 @@ class Parser(Doc):
res2[u'boolean'] = parsed.boolean
res2[u'integer'] = parsed.integer
res2[u'multi'] = parsed.multi
res2[u'pair'] = parsed.pair
res2[u'includes'] = [
x
for x in parsed.flatten('include', False).keys()

View file

@ -18,13 +18,28 @@ class Parser(Burp1):
"""Extends :class:`burpui.misc.parser.burp1.Parser`"""
pver = 2
pairs_srv = [
(u'port', u'max_children'),
(u'status_port', u'max_status_children'),
pair_srv = [
u'port',
u'max_children',
u'status_port',
u'max_status_children',
]
pair_associations = {
u'port': u'max_children',
u'max_children': u'port',
u'status_port': u'max_status_children',
u'max_status_children': u'status_port',
}
integer_srv = Burp1.integer_srv
for rem in ['port', 'max_children', 'status_port', 'max_status_children']:
integer_srv.remove(rem)
advanced_type = Burp1.advanced_type
advanced_type.update({
u'port': u'integer',
u'max_children': u'integer',
u'status_port': u'integer',
u'max_status_children': u'integer',
})
multi_srv = Burp1.multi_srv + [
u'label',
]

View file

@ -241,6 +241,11 @@ class Doc(BUIparser):
u'ssl_key',
u'timer_script',
]
advanced_type = {
u'keep': u'integer',
}
pair_srv = []
pair_associations = {}
multi_srv = [
u'exclude_comp',
u'exclude_ext',

View file

@ -105,6 +105,11 @@ class Option(object):
"""Option to string"""
return self.dump()
def __eq__(self, other):
other_name = getattr(other, 'name', None)
other_value = getattr(other, 'value', None)
return self.name == other_name and self.value == other_value
class OptionStr(Option):
"""Option type String
@ -125,7 +130,10 @@ class OptionInt(Option):
def parse(self):
"""Parse the option value"""
return int(self.value)
try:
return int(self.value)
except (ValueError, TypeError):
return 0
class OptionBool(Option):
@ -172,74 +180,6 @@ class OptionBool(Option):
return self._parse(self.value)
class OptionMulti(Option):
"""Option type Multi
Example:
keep = 7
keep = 4
"""
type = 'multi'
def __init__(self, name, value=None):
self.name = name
self._dirty = True
self._is_reset = []
if value:
if not isinstance(value, list):
self.value = [value]
else:
self.value = value
else:
self.value = []
self.idx = len(self.value) - 1
def append(self, value, reset=None):
self._dirty = True
self.value.append(value)
if reset is not None:
self.set_reset(reset)
return self.value
def remove(self, value):
self._dirty = True
self.value.remove(value)
return self.value
def index(self, value):
return self.value.index(value)
def len(self):
return len(self.value)
def dump_init(self, start=0):
"""Initialize the dump"""
self.dump_idx = start
def dump(self, start=0):
"""Return the option representation to store in configuration file"""
self.dump_init(start)
ret = u''
if start > len(self.value):
return ret
for idx in range(start, len(self.value)):
ret += '{}\n'.format(self.dump_index(idx))
return ret.rstrip('\n')
def dump_index(self, index):
if index > len(self.value):
index = len(self.value) - 1
val = self.value[index]
delim = self.delim
self.idx = index
if self.is_reset():
delim = self.reset_delim
return '{} {} {}'.format(self.name, delim, val)
def get_reset(self):
return self._is_reset
class OptionInc(Option):
"""Option type Include
@ -377,6 +317,256 @@ class OptionTpl(Option):
return ''
option_for_type = {
'integer': OptionInt,
'string': OptionStr,
'boolean': OptionBool,
'include': OptionInc,
}
class OptionMulti(Option):
"""Option type Multi
Example:
keep = 7
keep = 4
"""
type = 'multi'
def __init__(self, parser, name, value=None):
self.parser = parser
self.name = name
self._dirty = True
self._is_reset = []
self.content_type = self.parser.advanced_type.get(name, 'string')
self.associate = getattr(self.parser, 'pair_associations', {}).get(self.name)
if value:
container = self._obj_for_type()
value = container(name, value)
if not isinstance(value, list):
self.value = [value]
else:
self.value = value
else:
self.value = []
self.idx = len(self.value) - 1
def _obj_for_type(self):
return option_for_type.get(self.content_type, OptionStr)
def _wrap_object(self, value):
container = self._obj_for_type()
return container(self.name, value)
def append(self, value, reset=None):
self._dirty = True
self.value.append(self._wrap_object(value))
if reset is not None:
self.set_reset(reset)
return self.value
def remove(self, value):
idx = self.value(value)
del self.value[idx]
self._dirty = True
return self.value
def index(self, value):
for i, x in enumerate(self):
if x == value:
return i
raise ValueError('{} is not in list'.format(value))
def len(self):
return len(self.value)
def dump(self, start=0, strict=True):
"""Return the option representation to store in configuration file"""
ret = u''
if start > len(self.value):
return ret
for idx in range(start, len(self.value)):
ret += '{}\n'.format(self.dump_index(idx, True))
return ret.rstrip('\n')
def dump_index(self, index, strict=True):
if index >= len(self.value):
return ''
val = self.value[index]
delim = self.delim
self.idx = index
if self.is_reset():
delim = self.reset_delim
return '{} {} {}'.format(self.name, delim, sanitize_string('{}'.format(val.parse()), strict))
def parse(self):
return [x.parse() for x in self.value]
def get_reset(self):
return self._is_reset
def __len__(self):
return len(self.value)
def __getitem__(self, ii):
return self.value[ii].parse()
def __delitem__(self, ii):
del self.value[ii]
self._dirty = True
def __setitem__(self, ii, val):
self.value[ii] = self._wrap_object(val)
self._dirty = True
def insert(self, ii, val):
self.value.insert(ii, self._wrap_object(val))
def __iter__(self):
for x in self.value:
yield x.parse()
class OptionPair(Option, dict):
"""Option type Pair
Example:
port = 4971
max_children = 5
"""
type = 'pair'
def __init__(self, parser, name, value=None):
self.parser = parser
self.name = name
self.association = getattr(self.parser, 'pair_associations', {}).get(self.name)
self._dirty = True
self.value = {}
if value:
value = OptionMulti(self.parser, self.name, value)
self.value[self.name] = value
# is there a special := syntax for this option
self._is_reset = []
def append(self, name, value):
self._dirty = True
if name != self.name:
self.association = name
if name in self.value:
self.value[name].append(value)
else:
self.value[name] = OptionMulti(self.parser, name, value)
return self.value
def remove(self, name, value):
self._dirty = True
try:
self.value.get(name, []).remove(value)
except ValueError:
pass
return self.value
def index(self, name, value):
try:
return self.value.get(name, []).index(value)
except ValueError:
return -1
def len(self, name):
if name in self.value:
return self.value[name].len()
return -1
def dump(self, start=0, strict=True):
"""Return the option representation to store in configuration file"""
ret = u''
try:
self_length = len(self.value[self.name])
except KeyError:
self_length = -1
try:
associate_length = len(self.value[self.association])
except KeyError:
associate_length = -1
if start >= self_length and start >= associate_length:
return ret
for idx in range(start, max(self_length, associate_length)):
v1 = self.dump_index(self.name, idx, strict)
v2 = self.dump_index(self.association, idx, strict)
if v1:
ret += '{}\n'.format(v1)
if v2:
ret += '{}\n'.format(v2)
return ret.rstrip('\n')
def dump_index(self, name, index, strict):
length = self.len(name)
try:
length = len(self.value[name])
except KeyError:
length = -1
if index >= length:
return u''
return self.value.get(name).dump_index(index, strict)
def parse(self):
ret = {}
for key, opts in iteritems(self.value):
ret[key] = opts.parse()
return ret
def __setitem__(self, key, item):
self.append(key, item)
def __getitem__(self, key):
return self.value.get(key, OptionMulti(self.parser, key))
def get(self, key, default=None):
try:
return self.value[key]
except KeyError:
if default:
return default
return OptionMulti(self.parser, key)
def __len__(self):
return len(self.value)
def __delitem__(self, key):
del self.value[key]
def clear(self):
self.value.clear()
def copy(self):
return self.value.copy()
def has_key(self, key):
return key in self.value
def update(self, *args, **kwargs):
return self.value.update(*args, **kwargs)
def keys(self):
return self.value.keys()
def values(self):
return self.value.values()
def items(self):
return self.value.items()
def pop(self, *args):
return self.value.pop(*args)
def __contains__(self, item):
return item in self.value
def __iter__(self):
return iter(self.value)
class File(dict):
"""Object representing a configuration file
@ -412,6 +602,7 @@ class File(dict):
self.name = name
self.parent = parent
self.updated = []
self.associations = set()
self.reset = {}
self.options = OrderedDict()
self.types = {
@ -419,6 +610,7 @@ class File(dict):
'integer': OrderedDict(),
'include': OrderedDict(),
'multi': OrderedDict(),
'pair': OrderedDict(),
'string': OrderedDict(),
'template': OrderedDict(),
}
@ -508,6 +700,13 @@ class File(dict):
ret[key] = opt.parse() if parse else opt
return ret
def flatten_obj(self, name, obj, parse=True):
return {
'name': name,
'value': obj.parse() if parse else obj,
'reset': obj.get_reset()
}
@property
def dirty(self):
if not self._dirty:
@ -522,6 +721,10 @@ class File(dict):
def integer(self):
return self.flatten('integer')
@property
def pair(self):
return self.flatten('pair')
@property
def include(self):
return self.flatten('include')
@ -561,7 +764,7 @@ class File(dict):
if opt == u'.':
return 'include'
for typ in ['boolean', 'integer', 'multi', 'string']:
for typ in ['boolean', 'integer', 'multi', 'string', 'pair']:
if opt in self._options_for_type(typ):
return typ
return None
@ -574,9 +777,11 @@ class File(dict):
if typ == 'integer':
return OptionInt(key, value)
if typ == 'multi':
opt = self.options.get(key, OptionMulti(key))
opt = self.options.get(key, OptionMulti(self.parser, key))
opt.append(value)
return opt
if typ == 'pair':
return OptionPair(self.parser, key, value)
if typ == 'include':
key = value
return OptionInc(
@ -603,11 +808,15 @@ class File(dict):
def get(self, key, default=None):
try:
if key in self._options_for_type('pair'):
return self.options[key].get(key)
return self.options[key]
except KeyError:
return default
def __getitem__(self, key):
if key in self._options_for_type('pair'):
return self.options[key].get(key)
return self.options[key]
def __setitem__(self, key, value):
@ -617,8 +826,18 @@ class File(dict):
elif key in self._options_for_type('integer'):
opt = OptionInt(key, value)
elif key in self._options_for_type('multi'):
opt = self.options.get(key, OptionMulti(key))
opt = self.options.get(key, OptionMulti(self.parser, key))
opt.append(value)
elif key in self._options_for_type('pair'):
association = self.parser.pair_associations.get(key)
if key not in self.options and association not in self.options:
self.associations.add(association)
opt = OptionPair(self.parser, key)
elif association in self.options:
opt = self.options.get(association)
else:
opt = self.options.get(key)
opt.append(key, value)
elif key == u'.':
key = value
if self._parsing_templates:
@ -641,13 +860,17 @@ class File(dict):
ret = u''
for key, opts in iteritems(self.types):
ret += '{} =>\n'.format(key)
for _, opt in iteritems(opts):
for key2, opt in iteritems(opts):
if key2 in self.associations:
continue
ret += '\t' + repr(opt) + '\n'
return ret.rstrip('\n')
def __str__(self):
ret = u''
for _, val in iteritems(self.options):
for key, val in iteritems(self.options):
if key in self.associations:
continue
tmp = str(val)
if tmp:
ret += tmp + '\n'
@ -727,6 +950,7 @@ class File(dict):
return ret
def _write_key(self, fil, key, data, index=None, dry=False):
strict = 'regex' not in key
if not dry:
self._changed = True
@ -736,16 +960,13 @@ class File(dict):
fil.write('. {}\n'.format(val))
return val
# don't need to parse data again if index > 0
if index is not None and index > 0:
fil.write('{}\n'.format(self[key].dump_index(index)))
if index is not None and index >= 0:
fil.write('{}\n'.format(self[key].dump_index(index, strict)))
return None
strict = 'regex' not in key
if key not in self.updated:
if key in self.parser.boolean_srv or key in self.parser.boolean_cli:
val = data.get(key)
elif key in self.parser.multi_srv or key in self.parser.multi_cli:
val = [sanitize_string(x, strict) for x in data.getlist(key)]
else:
val = sanitize_string(str(data.get(key)), strict)
if key in self:
@ -760,11 +981,7 @@ class File(dict):
if index is not None:
return ret[index]
return ret
if index is not None:
self[key].dump_init()
fil.write('{}\n'.format(self[key].dump_index(index)))
else:
fil.write('{}\n'.format(self[key]))
fil.write('{}\n'.format(self[key]))
def parse(self, force=False):
"""Parse the current config"""
@ -790,7 +1007,10 @@ class File(dict):
val = val.replace('gzip', 'zlib')
self[key] = val
if key in self:
self[key].set_reset(reset is not None)
try:
self[key].set_reset(reset is not None)
except AttributeError:
pass
self._dirty = False
@ -864,7 +1084,9 @@ class File(dict):
newkeys = list(set(viewkeys(data)) - set(oldkeys))
multi_index_map = {}
already_multi = []
pair_index_map = {}
already_multi = set()
already_pair = set()
already_file = []
written = []
self.updated = []
@ -942,16 +1164,20 @@ class File(dict):
# The line is still present or has been un-commented,
# rewrite it with eventual changes
multi = key in getattr(self.parser, 'multi_{}'.format(self.mode))
pair = key in getattr(self.parser, 'pair_{}'.format(self.mode), [])
if pair and key not in pair_index_map:
pair_index_map[key] = 0
if multi and key not in multi_index_map:
multi_index_map[key] = 0
if key in written:
_dump(line, comment=(not self._line_is_comment(line)))
else:
if multi:
length = len(self[key])
if key not in already_multi and \
(key not in self or
(key in self and
self[key].len() > multi_index_map[key])):
length > multi_index_map[key])):
self._write_key(
fil,
key,
@ -962,9 +1188,8 @@ class File(dict):
else:
_dump(line, comment=(not self._line_is_comment(line)))
continue
if self[key].len() == multi_index_map[key]:
if key not in already_multi:
already_multi.append(key)
if length == multi_index_map[key]:
already_multi.add(key)
continue
# dump the rest of the multi if there are no
# more keys in the conf
@ -972,9 +1197,35 @@ class File(dict):
rest = self[key].dump(multi_index_map[key])
if rest:
fil.write('{}\n'.format(rest))
multi_index_map[key] = self[key].len()
if key not in already_multi:
already_multi.append(key)
multi_index_map[key] = length
already_multi.add(key)
elif pair:
length = len(self[key])
if key not in already_pair and \
(key not in self or
(key in self and
length > pair_index_map[key])):
self._write_key(
fil,
key,
data,
pair_index_map[key]
)
pair_index_map[key] += 1
else:
_dump(line, comment=(not self._line_is_comment(line)))
continue
if length == pair_index_map[key]:
already_pair.add(key)
continue
# dump the rest of the pair if there are no more
# keys in the conf
if not _is_key_after(key, idx + 1):
rest = self[key].dump(pair_index_map[key])
if rest:
fil.write('{}\n'.format(rest))
pair_index_map[key] = length
already_pair.add(key)
else:
# The line was a comment and there was a further
# matching setting, so we just jump to the
@ -1006,6 +1257,7 @@ class File(dict):
else:
_dump(line, comment=(key in written and not self._line_is_comment(line)))
return [[NOTIF_OK, 'Configuration successfully saved.']]
# Write the new keys
for key in newkeys:
if key.endswith(RESET_IDENTIFIER):
@ -1021,6 +1273,10 @@ class File(dict):
for key, idx in iteritems(multi_index_map):
if key not in already_multi and idx < self[key].len():
fil.write('{}\n'.format(self[key].dump(idx)))
# write the rest of the pair settings
for key, idx in iteritems(pair_index_map):
if key not in already_pair and idx < self[key].len():
fil.wrrite('{}\n'.format(self[key].dump(idx)))
# Write the rest of file inclusions
if 'includes' in data:
for inc in data.getlist('includes'):
@ -1028,6 +1284,8 @@ class File(dict):
self._write_key(fil, '.', inc)
except Exception as exp:
import traceback
traceback.print_exc()
return [[NOTIF_ERROR, str(exp)]]
self.parse(True)
@ -1279,6 +1537,7 @@ class Config(File):
# now update caches with new values
for _, fil in iteritems(self.files):
self.options.update(fil.options)
self.associations.update(fil.associations)
# FIXME: find a way to cache efficiently
# fil.clean()
@ -1290,12 +1549,17 @@ class Config(File):
def _get(self, key, default=None, raw=False):
self._refresh()
try:
obj = self.options[key]
if key in self._options_for_type('pair'):
obj = self.options[key].get(key)
else:
obj = self.options[key]
except KeyError:
if default:
return default
if self.parser and key in self.parser.defaults:
obj = self._new_opt(key, self.parser.defaults[key])
else:
return default
return None
return obj if raw else obj.parse()
def get_raw(self, key, default=None):
@ -1315,6 +1579,8 @@ class Config(File):
def __getitem__(self, key):
self._refresh()
if key in self._options_for_type('pair'):
return self.options[key].get(key)
return self.options[key]
def __setitem__(self, key, value):

View file

@ -57,7 +57,7 @@ appStart () {
# wait for redis to be up
sleep 3
LOGFILE=$(mktemp)
LOGFILE=$(doas burpui mktemp)
echo "Setting up burp & burp-ui:"
echo "bui-manage -c $BURPUI_CONFIG setup_burp -b $BURP_CLIENT_CONFIG -s $BURP_SERVER_CONFIG -h $BURP_SERVER_ADDR -c $BURPUI_CLIENT_NAME -r $REDIS_SERVER -d $DATABASE_URL -p $BURPUI_PLUGINS"
doas burpui "bui-manage -c $BURPUI_CONFIG setup_burp -b $BURP_CLIENT_CONFIG -s $BURP_SERVER_CONFIG -h $BURP_SERVER_ADDR -c $BURPUI_CLIENT_NAME -r $REDIS_SERVER -d $DATABASE_URL -p $BURPUI_PLUGINS 2>&1 | tee $LOGFILE"

View file

@ -138,6 +138,7 @@ class BurpuiAPITestCase(TestCase):
u'boolean': [],
u'integer': [],
u'multi': [],
u'pair': [],
u'includes': [],
u'includes_ext': [],
u'hierarchy': [{u'children': [], u'title': u'null', u'dir': u'/dev', u'full': u'/dev/null', u'name': u'null', u'parent': None}],
@ -147,6 +148,8 @@ class BurpuiAPITestCase(TestCase):
(u'string', self.bui.client.get_parser_attr('string_srv')),
(u'integer', self.bui.client.get_parser_attr('integer_srv')),
(u'multi', self.bui.client.get_parser_attr('multi_srv')),
(u'pair', self.bui.client.get_parser_attr('pair_srv')),
(u'advanced', self.bui.client.get_parser_attr('advanced_type')),
(u'server_doc', self.bui.client.get_parser_attr('doc')),
(u'suggest', self.bui.client.get_parser_attr('values')),
(u'placeholders', self.bui.client.get_parser_attr('placeholders')),