add the ability to write our own config for #132

This commit is contained in:
ziirish 2016-06-15 10:10:35 +02:00
parent b38514d559
commit 25bb371032
10 changed files with 660 additions and 482 deletions

View file

@ -12,16 +12,16 @@ from gevent.server import StreamServer
from logging.handlers import RotatingFileHandler
from .exceptions import BUIserverException
from .misc.backend.interface import BUIbackend
from ._compat import ConfigParser, pickle
from .utils import BUIlogging
from ._compat import pickle
from .utils import BUIlogging, BUIConfig
g_port = u'10000'
g_bind = u'::'
g_ssl = u''
g_version = u'1'
g_sslcert = u''
g_sslkey = u''
g_password = u'password'
G_PORT = 10000
G_BIND = u'::'
G_SSL = False
G_VERSION = 1
G_SSLCERT = u''
G_SSLKEY = u''
G_PASSWORD = u'password'
DISCLOSURE = 5
@ -63,13 +63,18 @@ class BurpHandler(BUIbackend):
class BUIAgent(BUIbackend, BUIlogging):
BUIbackend.__abstractmethods__ = frozenset()
defaults = {
'port': g_port, 'bind': g_bind,
'ssl': g_ssl, 'sslcert': g_sslcert, 'sslkey': g_sslkey,
'version': g_version, 'password': g_password
'Global': {
'port': G_PORT,
'bind': G_BIND,
'ssl': G_SSL,
'sslcert': G_SSLCERT,
'sslkey': G_SSLKEY,
'version': G_VERSION,
'password': G_PASSWORD,
},
}
def __init__(self, conf=None, level=0, logfile=None, debug=False):
self.conf = conf
self.debug = debug
self.padding = 1
if level > logging.NOTSET:
@ -102,28 +107,21 @@ class BUIAgent(BUIbackend, BUIlogging):
handler.setLevel(lvl)
handler.setFormatter(logging.Formatter(LOG_FORMAT))
self.logger.addHandler(handler)
self._logger('info', 'conf: ' + self.conf)
self._logger('info', 'conf: ' + conf)
self._logger('info', 'level: ' + logging.getLevelName(lvl))
if not self.conf:
if not conf:
raise IOError('No configuration file found')
config = ConfigParser.ConfigParser(self.defaults)
with open(self.conf) as fp:
config.readfp(fp)
try:
self.port = self._safe_config_get(config.getint, 'port', 'Global', cast=int)
self.bind = self._safe_config_get(config.get, 'bind', 'Global')
self.vers = self._safe_config_get(config.getint, 'version', 'Global', cast=int)
try:
self.ssl = config.getboolean('Global', 'ssl')
except ValueError:
self._logger('warning', "Wrong value for 'ssl' key! Assuming 'false'")
self.ssl = False
self.sslcert = self._safe_config_get(config.get, 'sslcert', 'Global')
self.sslkey = self._safe_config_get(config.get, 'sslkey', 'Global')
self.password = self._safe_config_get(config.get, 'password', 'Global')
except ConfigParser.NoOptionError as e:
raise e
# Raise exception if errors are encountered during parsing
self.conf = BUIConfig(conf, True, self.defaults)
self.conf.default_section('Global')
self.port = self.conf.safe_get('port', 'integer')
self.bind = self.conf.safe_get('bind')
self.vers = self.conf.safe_get('version', 'integer')
self.ssl = self.conf.safe_get('ssl', 'boolean')
self.sslcert = self.conf.safe_get('sslcert')
self.sslkey = self.conf.safe_get('sslkey')
self.password = self.conf.safe_get('password')
self.cli = BurpHandler(self.vers, self.logger, self.conf)
if not self.ssl:

View file

@ -1,6 +1,5 @@
# -*- coding: utf8 -*-
from .interface import BUIacl, BUIaclLoader
from ..._compat import ConfigParser
import json
@ -8,7 +7,11 @@ import json
class ACLloader(BUIaclLoader):
"""See :class:`burpui.misc.acl.interface.BUIaclLoader`"""
def __init__(self, app=None):
"""See :func:`burpui.misc.acl.interface.BUIaclLoader.__init__`"""
"""See :func:`burpui.misc.acl.interface.BUIaclLoader.__init__`
:param app: Application context
:type app: :class:`burpui.server.BUIServer`
"""
self.app = app
self.admins = [
'admin'
@ -16,25 +19,14 @@ class ACLloader(BUIaclLoader):
self.clients = {}
self.servers = {}
self.standalone = self.app.standalone
conf = self.app.config['CFG']
c = ConfigParser.ConfigParser()
conf = self.app.conf
adms = []
with open(conf) as fp:
c.readfp(fp)
if c.has_section('BASIC:ACL'):
try:
temp = c.get('BASIC:ACL', 'admin')
try:
adms = json.loads(temp)
except Exception as e:
self.logger.error(str(e))
adms = [temp]
except Exception as e:
self.logger.warning(str(e))
for opt in c.options('BASIC:ACL'):
if 'BASIC:ACL' in conf.options:
adms = conf.safe_get('admin', 'force_list', section='BASIC:ACL')
for opt in conf.options.get('BASIC:ACL').keys():
if opt == 'admin':
continue
lit = c.get('BASIC:ACL', opt)
lit = conf.safe_get(opt, section='BASIC:ACL')
rec = []
try:
rec = json.loads(lit)

View file

@ -1,12 +1,10 @@
# -*- coding: utf8 -*-
import os
from .interface import BUIhandler, BUIuser
from importlib import import_module
from flask import session
import re
import os
import json
class UserAuthHandler(BUIhandler):
"""See :class:`burpui.misc.auth.interface.BUIhandler`"""
@ -17,10 +15,7 @@ class UserAuthHandler(BUIhandler):
self.backends = []
if self.app.auth:
me, _ = os.path.splitext(os.path.basename(__file__))
try:
back = json.loads(self.app.auth)
except:
back = re.split(' *,+ *', self.app.auth)
back = self.app.auth
for au in back:
if au == me:
self.app.logger.error('Recursive import not permited!')

View file

@ -16,9 +16,7 @@ import json
import shutil
import subprocess
import tempfile
import codecs
from ast import literal_eval
from pipes import quote
from six import iteritems, viewkeys
@ -26,19 +24,19 @@ from .interface import BUIbackend
from ..parser.burp1 import Parser
from ...utils import human_readable as _hr, BUIcompress
from ...exceptions import BUIserverException
from ..._compat import ConfigParser, unquote, PY3
from ..._compat import unquote, PY3
G_BURPPORT = u'4972'
G_BURPPORT = 4972
G_BURPHOST = u'::1'
G_BURPBIN = u'/usr/sbin/burp'
G_STRIPBIN = u'/usr/sbin/vss_strip'
G_BURPCONFCLI = u''
G_BURPCONFSRV = u'/etc/burp/burp-server.conf'
G_TMPDIR = u'/tmp/bui'
G_ZIP64 = u'False'
G_INCLUDES = u'/etc/burp'
G_ENFORCE = u'False'
G_REVOKE = u'False'
G_ZIP64 = False
G_INCLUDES = [u'/etc/burp']
G_ENFORCE = False
G_REVOKE = False
class Burp(BUIbackend):
@ -110,8 +108,8 @@ class Burp(BUIbackend):
and/or some global settings
:type server: :class:`burpui.server.BUIServer`
:param conf: Configuration file to use
:type conf: str
:param conf: Configuration to use
:type conf: :class:`burpui.utils.BUIConfig`
:param dummy: Does not instanciate the object (used for development
purpose)
@ -122,19 +120,20 @@ class Burp(BUIbackend):
self.client_version = None
self.server_version = None
self.app = None
self.zip64 = literal_eval(G_ZIP64)
self.zip64 = G_ZIP64
self.host = G_BURPHOST
self.port = int(G_BURPPORT)
self.port = G_BURPPORT
self.burpbin = G_BURPBIN
self.stripbin = G_STRIPBIN
self.burpconfcli = G_BURPCONFCLI
self.burpconfsrv = G_BURPCONFSRV
self.tmpdir = G_TMPDIR
self.includes = G_INCLUDES
self.revoke = literal_eval(G_REVOKE)
self.enforce = literal_eval(G_ENFORCE)
self.revoke = G_REVOKE
self.enforce = G_ENFORCE
self.running = []
self.defaults = {
'Burp1': {
'bport': G_BURPPORT,
'bhost': G_BURPHOST,
'burpbin': G_BURPBIN,
@ -142,54 +141,57 @@ class Burp(BUIbackend):
'bconfcli': G_BURPCONFCLI,
'bconfsrv': G_BURPCONFSRV,
'tmpdir': G_TMPDIR,
},
'Experimental': {
'zip64': G_ZIP64,
},
'Security': {
'includes': G_INCLUDES,
'revoke': G_REVOKE,
'enforce': G_ENFORCE,
},
}
if conf:
config = ConfigParser.ConfigParser(self.defaults)
with codecs.open(conf, 'r', 'utf-8') as fileobj:
config.readfp(fileobj)
self.port = self._safe_config_get(config.getint, 'bport', cast=int)
self.host = self._safe_config_get(config.get, 'bhost')
conf.update_defaults(self.defaults)
conf.default_section('Burp1')
self.port = conf.safe_get('bport', 'integer')
self.host = conf.safe_get('bhost')
self.burpbin = self._get_binary_path(
config,
conf,
'burpbin',
G_BURPBIN
)
self.stripbin = self._get_binary_path(
config,
conf,
'stripbin',
G_STRIPBIN
)
confcli = self._safe_config_get(config.get, 'bconfcli')
confsrv = self._safe_config_get(config.get, 'bconfsrv')
tmpdir = self._safe_config_get(config.get, 'tmpdir')
confcli = conf.safe_get('bconfcli')
confsrv = conf.safe_get('bconfsrv')
tmpdir = conf.safe_get('tmpdir')
# Experimental options
self.zip64 = self._safe_config_get(
config.getboolean,
self.zip64 = conf.safe_get(
'zip64',
sect='Experimental'
'boolean',
section='Experimental'
)
# Security options
self.includes = self._safe_config_get(
config.get,
self.includes = conf.safe_get(
'includes',
sect='Security'
'force_list',
section='Security'
)
self.enforce = self._safe_config_get(
config.getboolean,
self.enforce = conf.safe_get(
'enforce',
sect='Security'
'boolean',
section='Security'
)
self.revoke = self._safe_config_get(
config.getboolean,
self.revoke = conf.safe_get(
'revoke',
sect='Security'
'boolean',
section='Security'
)
if tmpdir and os.path.exists(tmpdir) and not os.path.isdir(tmpdir):
@ -268,7 +270,7 @@ class Burp(BUIbackend):
:param default: Default value in case the retrieved value is not correct
:type default: str
"""
temp = self._safe_config_get(config.get, field, sect)
temp = config.safe_get(field, section=sect)
if temp and not temp.startswith('/'):
self.logger.warning("Please provide an absolute path for the '{}' option. Fallback to '{}'".format(field, default))
@ -533,6 +535,11 @@ class Burp(BUIbackend):
backup[ckey[0]][ckey[1]] = int(val)
else:
backup[ckey] = int(val)
# Needed for graphs
if 'received' not in backup:
backup['received'] = 1
return backup
def _parse_backup_log(self, filemap, number, client=None, agent=None):

View file

@ -11,11 +11,9 @@ import re
import os
import time
import subprocess
import codecs
import sys
import json
from ast import literal_eval
from select import select
from six import iteritems, viewkeys
@ -23,7 +21,6 @@ from .burp1 import Burp as Burp1
from ..parser.burp2 import Parser
from ...utils import human_readable as _hr
from ...exceptions import BUIserverException
from ..._compat import ConfigParser
if sys.version_info < (3, 3):
TimeoutError = OSError
@ -35,11 +32,11 @@ G_STRIPBIN = u'/usr/sbin/vss_strip'
G_BURPCONFCLI = u'/etc/burp/burp.conf'
G_BURPCONFSRV = u'/etc/burp/burp-server.conf'
G_TMPDIR = u'/tmp/bui'
G_TIMEOUT = u'5'
G_ZIP64 = u'False'
G_INCLUDES = u'/etc/burp'
G_ENFORCE = u'False'
G_REVOKE = u'False'
G_TIMEOUT = 5
G_ZIP64 = False
G_INCLUDES = [u'/etc/burp']
G_ENFORCE = False
G_REVOKE = False
# Some functions are the same as in Burp1 backend
@ -54,102 +51,103 @@ class Burp(Burp1):
and/or some global settings
:type server: :class:`burpui.server.BUIServer`
:param conf: Configuration file to use
:type conf: str
:param conf: Configuration to use
:type conf: :class:`burpui.utils.BUIConfig`
"""
def __init__(self, server=None, conf=None):
global G_BURPBIN, G_STRIPBIN, G_BURPCONFCLI, G_BURPCONFSRV, G_TMPDIR, \
G_TIMEOUT, BURP_MINIMAL_VERSION
"""
:param server: ``Burp-UI`` server instance in order to access logger
and/or some global settings
:type server: :class:`burpui.server.BUIServer`
:param conf: Configuration to use
:type conf: :class:`burpui.utils.BUIConfig`
"""
self.proc = None
self.app = server
self.client_version = None
self.server_version = None
self.zip64 = literal_eval(G_ZIP64)
self.timeout = int(G_TIMEOUT)
self.zip64 = G_ZIP64
self.timeout = G_TIMEOUT
self.burpbin = G_BURPBIN
self.stripbin = G_STRIPBIN
self.burpconfcli = G_BURPCONFCLI
self.burpconfsrv = G_BURPCONFSRV
self.includes = G_INCLUDES
self.revoke = literal_eval(G_REVOKE)
self.enforce = literal_eval(G_ENFORCE)
self.revoke = G_REVOKE
self.enforce = G_ENFORCE
self.defaults = {
'Burp2': {
'burpbin': G_BURPBIN,
'stripbin': G_STRIPBIN,
'bconfcli': G_BURPCONFCLI,
'bconfsrv': G_BURPCONFSRV,
'timeout': G_TIMEOUT,
'tmpdir': G_TMPDIR,
},
'Experimental': {
'zip64': G_ZIP64,
},
'Security': {
'includes': G_INCLUDES,
'revoke': G_REVOKE,
'enforce': G_ENFORCE,
},
}
self.running = []
version = ''
if conf:
config = ConfigParser.ConfigParser(self.defaults)
with codecs.open(conf, 'r', 'utf-8') as conffile:
config.readfp(conffile)
try:
conf.update_defaults(self.defaults)
conf.default_section('Burp2')
self.burpbin = self._get_binary_path(
config,
conf,
'burpbin',
G_BURPBIN,
sect='Burp2'
)
self.stripbin = self._get_binary_path(
config,
conf,
'stripbin',
G_STRIPBIN,
sect='Burp2'
)
confcli = self._safe_config_get(
config.get,
'bconfcli',
sect='Burp2'
confcli = conf.safe_get(
'bconfcli'
)
confsrv = self._safe_config_get(
config.get,
'bconfsrv',
sect='Burp2'
confsrv = conf.safe_get(
'bconfsrv'
)
self.timeout = self._safe_config_get(
config.getint,
self.timeout = conf.safe_get(
'timeout',
sect='Burp2',
cast=int
'integer'
)
tmpdir = self._safe_config_get(
config.get,
'tmpdir',
sect='Burp2'
tmpdir = conf.safe_get(
'tmpdir'
)
# Experimental options
self.zip64 = self._safe_config_get(
config.getboolean,
self.zip64 = conf.safe_get(
'zip64',
sect='Experimental',
cast=bool
'boolean',
section='Experimental'
)
# Security options
self.includes = self._safe_config_get(
config.get,
self.includes = conf.safe_get(
'includes',
sect='Security'
'force_list',
section='Security'
)
self.enforce = self._safe_config_get(
config.getboolean,
self.enforce = conf.safe_get(
'enforce',
sect='Security'
'boolean',
section='Security'
)
self.revoke = self._safe_config_get(
config.getboolean,
self.revoke = conf.safe_get(
'revoke',
sect='Security'
'boolean',
section='Security'
)
if (tmpdir and os.path.exists(tmpdir) and
@ -193,10 +191,6 @@ class Burp(Burp1):
self.tmpdir = tmpdir
self.burpconfcli = confcli
self.burpconfsrv = confsrv
except ConfigParser.NoOptionError as exp:
self.logger.error(str(exp))
except ConfigParser.NoSectionError as exp:
self.logger.warning(str(exp))
# check the burp version because this backend only supports clients
# newer than BURP_MINIMAL_VERSION
@ -576,6 +570,10 @@ class Burp(Burp1):
if 'start' in backup and 'end' in backup:
backup['duration'] = backup['end'] - backup['start']
# Needed for graphs
if 'received' not in backup:
backup['received'] = 1
return backup
# TODO: support old clients

View file

@ -258,7 +258,7 @@ class Parser(Doc):
# don't check
return True
path = os.path.normpath(path)
cond = [path.startswith(x) for x in self.backend.includes.split(',')]
cond = [path.startswith(x) for x in self.backend.includes]
if not any(cond):
self.logger.warning(
'Tried to access non-allowed path: {}'.format(path)
@ -459,6 +459,7 @@ class Parser(Doc):
res2[u'includes'] = parsed.flatten('include', False).keys()
res2[u'includes_ext'] = parsed.include
if path in self.filecache:
self.filecache[path]['parsed'] = res2
res.update(res2)
@ -503,6 +504,7 @@ class Parser(Doc):
res2[u'includes'] = parsed.flatten('include', False).keys()
res2[u'includes_ext'] = parsed.include
if path in self.filecache:
self.filecache[path]['parsed'] = res2
res.update(res2)

View file

@ -13,29 +13,29 @@ import logging
import traceback
from .misc.auth.handler import UserAuthHandler
from ._compat import ConfigParser
from .utils import BUIConfig
from datetime import timedelta
from flask import Flask
G_PORT = '5000'
G_BIND = '::'
G_REFRESH = '180'
G_LIVEREFRESH = '5'
G_SSL = 'False'
G_STANDALONE = 'True'
G_SSLCERT = ''
G_SSLKEY = ''
G_VERSION = '1'
G_AUTH = 'basic'
G_ACL = ''
G_STORAGE = ''
G_REDIS = ''
G_SCOOKIE = 'False'
G_APPSECRET = 'random'
G_COOKIETIME = '14'
G_PREFIX = ''
G_PORT = 5000
G_BIND = u'::'
G_REFRESH = 180
G_LIVEREFRESH = 5
G_SSL = False
G_STANDALONE = True
G_SSLCERT = u''
G_SSLKEY = u''
G_VERSION = 1
G_AUTH = [u'basic']
G_ACL = u'none'
G_STORAGE = u''
G_REDIS = u''
G_SCOOKIE = False
G_APPSECRET = u'random'
G_COOKIETIME = 14
G_PREFIX = u''
class BUIServer(Flask):
@ -44,6 +44,34 @@ class BUIServer(Flask):
"""
gunicorn = False
defaults = {
'Global': {
'port': G_PORT,
'bind': G_BIND,
'ssl': G_SSL,
'standalone': G_STANDALONE,
'sslcert': G_SSLCERT,
'sslkey': G_SSLKEY,
'version': G_VERSION,
'auth': G_AUTH,
'acl': G_ACL,
'prefix': G_PREFIX,
},
'UI': {
'refresh': G_REFRESH,
'liverefresh': G_LIVEREFRESH,
},
'Security': {
'scookie': G_SCOOKIE,
'appsecret': G_APPSECRET,
'cookietime': G_COOKIETIME,
},
'Production': {
'storage': G_STORAGE,
'redis': G_REDIS,
}
}
def __init__(self):
"""The :class:`burpui.server.BUIServer` class provides the ``Burp-UI``
server.
@ -79,59 +107,46 @@ class BUIServer(Flask):
if not conf:
raise IOError('No configuration file found')
self.defaults = {
'port': G_PORT,
'bind': G_BIND,
'refresh': G_REFRESH,
'ssl': G_SSL,
'sslcert': G_SSLCERT,
'sslkey': G_SSLKEY,
'version': G_VERSION,
'auth': G_AUTH,
'standalone': G_STANDALONE,
'acl': G_ACL,
'liverefresh': G_LIVEREFRESH,
'storage': G_STORAGE,
'redis': G_REDIS,
'scookie': G_SCOOKIE,
'appsecret': G_APPSECRET,
'cookietime': G_COOKIETIME,
'prefix': G_PREFIX,
}
config = ConfigParser.ConfigParser(self.defaults)
with open(conf) as fp:
config.readfp(fp)
try:
self.port = self._safe_config_get(
config.getint,
# Raise exception if errors are encountered during parsing
self.conf = BUIConfig(conf, True, self.defaults)
self.conf.default_section('Global')
self.port = self.conf.safe_get(
'port',
cast=int
'integer'
)
self.bind = self._safe_config_get(config.get, 'bind')
self.vers = self._safe_config_get(
config.getint,
self.bind = self.conf.safe_get('bind')
self.vers = self.conf.safe_get(
'version',
cast=int
'integer'
)
self.ssl = self._safe_config_get(
config.getboolean,
self.ssl = self.conf.safe_get(
'ssl',
cast=bool
'boolean'
)
self.standalone = self._safe_config_get(
config.getboolean,
self.standalone = self.conf.safe_get(
'standalone',
cast=bool
'boolean'
)
self.sslcert = self.conf.safe_get(
'sslcert'
)
self.sslkey = self.conf.safe_get(
'sslkey'
)
self.prefix = self.conf.safe_get(
'prefix'
)
self.sslcert = self._safe_config_get(config.get, 'sslcert')
self.sslkey = self._safe_config_get(config.get, 'sslkey')
self.prefix = self._safe_config_get(config.get, 'prefix')
if self.prefix and not self.prefix.startswith('/'):
if self.prefix.lower() != 'none':
self.logger.warning("'prefix' must start with a '/'!")
self.prefix = ''
self.auth = self._safe_config_get(config.get, 'auth')
if self.auth and self.auth.lower() != 'none':
self.auth = self.conf.safe_get(
'auth',
'string_lower_list'
)
if self.auth and 'none' not in self.auth:
try:
self.uhandler = UserAuthHandler(self)
except Exception as e:
@ -142,7 +157,9 @@ class BUIServer(Flask):
)
)
raise e
self.acl_engine = self._safe_config_get(config.get, 'acl')
self.acl_engine = self.conf.safe_get(
'acl'
)
else:
self.config['LOGIN_DISABLED'] = True
# No login => no ACL
@ -182,56 +199,46 @@ class BUIServer(Flask):
self.acl = False
# UI options
self.config['REFRESH'] = self._safe_config_get(
config.getint,
self.config['REFRESH'] = self.conf.safe_get(
'refresh',
'UI',
cast=int
'integer',
'UI'
)
self.config['LIVEREFRESH'] = self._safe_config_get(
config.getint,
self.config['LIVEREFRESH'] = self.conf.safe_get(
'liverefresh',
'UI',
cast=int
'integer',
'UI'
)
# Production options
self.storage = self._safe_config_get(
config.get,
self.storage = self.conf.safe_get(
'storage',
'Production'
section='Production'
)
self.redis = self._safe_config_get(
config.get,
self.redis = self.conf.safe_get(
'redis',
'Production'
section='Production'
)
# Security options
self.scookie = self._safe_config_get(
config.getboolean,
self.scookie = self.conf.safe_get(
'scookie',
'Security',
cast=bool
'boolean',
section='Security'
)
self.config['SECRET_KEY'] = self._safe_config_get(
config.get,
self.config['SECRET_KEY'] = self.conf.safe_get(
'appsecret',
'Security'
section='Security'
)
self.config['REMEMBER_COOKIE_DURATION'] = \
self.config['PERMANENT_SESSION_LIFETIME'] = timedelta(
days=self._safe_config_get(
config.getint,
days=self.conf.safe_get(
'cookietime',
'Security',
cast=int
'integer',
section='Security'
)
)
except ConfigParser.NoOptionError as e:
self.logger.error(str(e))
self.config['STANDALONE'] = self.standalone
self.logger.info('burp version: {}'.format(self.vers))
@ -265,7 +272,7 @@ class BUIServer(Flask):
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
mod = __import__(module, fromlist=['Burp'])
Client = mod.Burp
self.cli = Client(self, conf=conf)
self.cli = Client(self, conf=self.conf)
except Exception as e:
traceback.print_exc()
self.logger.critical(
@ -278,39 +285,6 @@ class BUIServer(Flask):
self.init = True
def _safe_config_get(self, callback, key, sect='Global', cast=None):
""":func:`burpui.server.BUIServer._safe_config_get` is a wrapper to
handle Exceptions throwed by :mod:`ConfigParser`.
:param callback: Function to wrap
:type callback: callable
:param key: Key to retrieve
:type key: str
:param sect: Section of the config file to read
:type sect: str
:param cast: Cast the returned value if provided
:type case: callable
:returns: The value returned by the `callback`
"""
try:
return callback(sect, key)
except ConfigParser.NoOptionError as e:
self.logger.error(str(e))
except ConfigParser.NoSectionError as e:
self.logger.warning(str(e))
if key in self.defaults:
if cast:
try:
return cast(self.defaults[key])
except ValueError:
return None
return self.defaults[key]
return None
def manual_run(self):
"""The :func:`burpui.server.BUIServer.manual_run` functions is used to
actually launch the ``Burp-UI`` server.

View file

@ -8,18 +8,26 @@
"""
import os
import re
import math
import string
import sys
import codecs
import json
import shutil
import zipfile
import tarfile
import logging
import configobj
import validate
from inspect import currentframe, getouterframes
from ._compat import PY3
from . import __version__, __release__
if PY3:
long = int # pragma: no cover
basestring = str # pragma: no cover
class human_readable(long):
@ -273,3 +281,205 @@ class ReverseProxied(object):
self.app.warning("'prefix' must start with a '/'!")
return self.wsgi_app(environ, start_response)
class BUIConfig(object):
"""Custom config parser"""
logger = logging.getLogger('burp-ui')
def __init__(self, config, explain=False, defaults=None):
"""Wrapper around the ConfigObj class
:param config: Configuration to parse
:type config: str, list or File
:param explain: Whether to explain the parsing errors or not
:type explain: bool
:param defaults: Default options
:type defaults: dict
"""
self.conf = {}
self.section = None
self.defaults = defaults
self.validator = validate.Validator()
try:
self.conf = configobj.ConfigObj(config, encoding='utf-8')
except configobj.ConfigObjError as exp:
# We were unable to parse the config, maybe we need to
# convert/update it
self.logger.warning(
'Unable to parse the configuration... Trying to convert it'
)
# if conversion is successful, give it another try
if self._convert(config):
# This time, if it fails, the exception will be forwarded
try:
self.conf = configobj.ConfigObj(config)
except configobj.ConfigObjError as exp2:
if explain:
self._explain(exp2)
else:
raise exp2
else:
if explain:
self._explain(exp)
else:
raise exp
@property
def options(self):
"""ConfigObj object"""
return self.conf
@staticmethod
def string_lower_list(value):
if not value:
raise validate.VdtMissingValue('Option not found')
if not isinstance(value, list):
return [str(value).lower()]
return [str(x).lower() for x in value]
def update_defaults(self, new_defaults):
"""Add new defaults"""
self.defaults.update(new_defaults)
def default_section(self, section):
"""Set the default section"""
self.section = section
def _convert(self, config):
"""Convert an old config to a new one"""
sav = '{}.back'.format(config)
current_section = None
if os.path.exists(sav):
self.logger.error(
'Looks like the configuration file has already been converted'
)
return False
try:
shutil.copy(config, sav)
except IOError as exp:
self.logger.error(str(exp))
return False
try:
with codecs.open(sav, 'r', 'utf-8') as ori:
with codecs.open(config, 'w', 'utf-8') as new:
# We add some headers
new.write('# Auto-generated file from a previous version\n')
new.write('# @version@ - {}\n'.format(__version__))
new.write('# @release@ - {}\n'.format(__release__))
for line in ori.readlines():
line = line.rstrip('\n')
search = re.search(r'^\s*\[([^\]]+)\]\s*', line)
if search:
current_section = search.group(1)
# if we find old style config lines, we convert them
elif re.match(r'^\s*\S+\s*:\s*.+$', line) and \
re.match(r'^\s*[^\[]', line):
key, val = re.split(r'\s*:\s*', line, 1)
# We support *objects* but we need to serialize them
try:
jsn = json.loads(val)
# special case, we re-format the admin value
if current_section == 'BASIC:ACL' and \
key == 'admin' and \
isinstance(jsn, list):
val = ','.join(jsn)
elif isinstance(jsn, list) or \
isinstance(jsn, dict):
val = "'{}'".format(json.dumps(jsn))
except ValueError:
pass
line = '{} = {}'.format(key, val)
new.write('{}\n'.format(line))
except IOError as exp:
self.logger.error(str(exp))
return False
return True
@staticmethod
def _explain(exception):
"""Explain parsing errors
:param exception: Exception object
:type exception: :class:`configobj.ConfigObjError`
"""
message = u'\n'
for error in exception.errors:
message += error.message + '\n'
raise configobj.ConfigObjError(message.rstrip('\n'))
def safe_get(
self,
key,
cast='pass',
section=None,
defaults=None):
"""Safely return the asked option
:param key: Key name
:type key: str
:param cast: How to cast the option
:type cast: str
:param section: Section name
:type section: str
:param defaults: Default options
:type defaults: dict
:returns: The value of the asked option
"""
# The configobj validator is sooo broken. We need to workaround it...
defaults = defaults or self.defaults
section = section or self.section
if section not in self.conf:
self.logger.warning("No '{}' section found".format(section))
if defaults:
return defaults.get(section, {}).get(key)
return None
val = self.conf.get(section).get(key)
default = None
if defaults and section in defaults and \
key in defaults.get(section):
default = defaults.get(section, {}).get(key)
try:
caster = self.validator.functions.get(cast)
if not caster:
try:
caster = getattr(self, cast)
except AttributeError:
self.logger.error(
"'{}': no such validator".format(cast)
)
return val
ret = caster(val)
self.logger.debug(
'[{}]:{} - found: {}, default: {}'.format(
section,
key,
val,
default
)
)
except validate.ValidateError as exp:
self.logger.warning(
'[{}]:{} - found: {}, default: {}\n{}'.format(
section,
key,
val,
default,
str(exp)
)
)
ret = default
return ret

View file

@ -11,3 +11,4 @@ tzlocal==1.2
sphinxcontrib-httpdomain==1.3.0
six==1.10.0
pyOpenSSL==16.0.0
configobj==5.0.6

View file

@ -9,3 +9,4 @@ arrow==0.7.0
tzlocal==1.2
six==1.10.0
pyOpenSSL==16.0.0
configobj==5.0.6