diff --git a/burpui/_compat.py b/burpui/_compat.py index ed4f2c0b..a51cad05 100644 --- a/burpui/_compat.py +++ b/burpui/_compat.py @@ -22,10 +22,10 @@ except ImportError: if sys.version_info[0] >= 3: PY3 = True - from urllib.parse import unquote # noqa + from urllib.parse import unquote, quote # noqa else: PY3 = False - from urllib import unquote # noqa + from urllib import unquote, quote # noqa if 'gunicorn' in os.environ.get('SERVER_SOFTWARE', ''): IS_GUNICORN = True diff --git a/burpui/api/settings.py b/burpui/api/settings.py index b1c6815a..b316aff1 100644 --- a/burpui/api/settings.py +++ b/burpui/api/settings.py @@ -364,9 +364,13 @@ class ClientSettings(Resource): if api.bui.acl and not self.is_admin: self.abort(403, 'Sorry, you don\'t have rights to access the setting panel') + args = self.parser_delete.parse_args() + delcert = args.get('delcert', False) + revoke = args.get('revoke', False) + # clear the cache when we remove a client api.cache.clear() - return {'notif': api.bui.cli.delete_client(client, agent=server)}, 200 + return api.bui.cli.delete_client(client, delcert=delcert, revoke=revoke, agent=server), 200 @ns.route('/path-expander', @@ -378,6 +382,7 @@ class PathExpander(Resource): parser = api.parser() parser.add_argument('path', required=True, help="No 'path' provided") + parser.add_argument('source', required=False, help="Which file is it included in") @ns.doc( params={ @@ -400,10 +405,16 @@ class PathExpander(Resource): if api.bui.acl and not self.is_admin: self.abort(403, 'Sorry, you don\'t have rights to access the setting panel') - path = self.parser.parse_args()['path'] - paths = api.bui.cli.expand_path(path, client, server) + args = self.parser.parse_args() + path = args['path'] + source = args['source'] + if path: + path = unquote(path) + if source: + source = unquote(source) + paths = api.bui.cli.expand_path(path, source, client, server) if not paths: - self.abort(500, 'Path not found') + self.abort(403, 'Path not found') return {'result': paths} diff --git a/burpui/misc/backend/burp1.py b/burpui/misc/backend/burp1.py index a38d528c..2990de10 100644 --- a/burpui/misc/backend/burp1.py +++ b/burpui/misc/backend/burp1.py @@ -37,6 +37,7 @@ 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' @@ -131,6 +132,7 @@ class Burp(BUIbackend): self.tmpdir = G_TMPDIR self.includes = G_INCLUDES self.revoke = literal_eval(G_REVOKE) + self.enforce = literal_eval(G_ENFORCE) self.running = [] self.defaults = { 'bport': G_BURPPORT, @@ -143,6 +145,7 @@ class Burp(BUIbackend): 'zip64': G_ZIP64, 'includes': G_INCLUDES, 'revoke': G_REVOKE, + 'enforce': G_ENFORCE, } if conf: config = ConfigParser.ConfigParser(self.defaults) @@ -178,6 +181,11 @@ class Burp(BUIbackend): 'includes', sect='Security' ) + self.enforce = self._safe_config_get( + config.getboolean, + 'enforce', + sect='Security' + ) self.revoke = self._safe_config_get( config.getboolean, 'revoke', @@ -242,6 +250,7 @@ class Burp(BUIbackend): self.logger.info('tmpdir: {}'.format(self.tmpdir)) self.logger.info('zip64: {}'.format(self.zip64)) self.logger.info('includes: {}'.format(self.includes)) + self.logger.info('enforce: {}'.format(self.enforce)) self.logger.info('revoke: {}'.format(self.revoke)) try: # make the connection @@ -1026,16 +1035,16 @@ class Burp(BUIbackend): pass return self.parser.store_conf(data, conf) - def expand_path(self, path=None, client=None, agent=None): + def expand_path(self, path=None, source=None, client=None, agent=None): """See :func:`burpui.misc.backend.interface.BUIbackend.expand_path`""" if not path: return [] - return self.parser.path_expander(path, client) + return self.parser.path_expander(path, source, client) def delete_client(self, client=None, delcert=False, revoke=False, agent=None): """See :func:`burpui.misc.backend.interface.BUIbackend.delete_client`""" if not client: - return [2, "No client provided"] + return [[2, "No client provided"]] return self.parser.remove_client(client, delcert, revoke) def clients_list(self, agent=None): diff --git a/burpui/misc/backend/burp2.py b/burpui/misc/backend/burp2.py index 6f50ab1e..cacab51b 100644 --- a/burpui/misc/backend/burp2.py +++ b/burpui/misc/backend/burp2.py @@ -38,6 +38,7 @@ 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' @@ -72,6 +73,7 @@ class Burp(Burp1): self.burpconfsrv = G_BURPCONFSRV self.includes = G_INCLUDES self.revoke = literal_eval(G_REVOKE) + self.enforce = literal_eval(G_ENFORCE) self.defaults = { 'burpbin': G_BURPBIN, 'stripbin': G_STRIPBIN, @@ -82,6 +84,7 @@ class Burp(Burp1): 'zip64': G_ZIP64, 'includes': G_INCLUDES, 'revoke': G_REVOKE, + 'enforce': G_ENFORCE, } self.running = [] version = '' @@ -138,6 +141,11 @@ class Burp(Burp1): 'includes', sect='Security' ) + self.enforce = self._safe_config_get( + config.getboolean, + 'enforce', + sect='Security' + ) self.revoke = self._safe_config_get( config.getboolean, 'revoke', @@ -221,6 +229,7 @@ class Burp(Burp1): self.logger.info('tmpdir: {}'.format(self.tmpdir)) self.logger.info('zip64: {}'.format(self.zip64)) self.logger.info('includes: {}'.format(self.includes)) + self.logger.info('enforce: {}'.format(self.enforce)) self.logger.info('revoke: {}'.format(self.revoke)) try: # make the connection diff --git a/burpui/misc/backend/interface.py b/burpui/misc/backend/interface.py index 66d6ab65..6f97eb86 100644 --- a/burpui/misc/backend/interface.py +++ b/burpui/misc/backend/interface.py @@ -601,7 +601,7 @@ class BUIbackend(object): raise NotImplementedError("Sorry, the current Backend does not implement this method!") # pragma: no cover @abstractmethod - def expand_path(self, path=None, client=None, agent=None): + def expand_path(self, path=None, source=None, client=None, agent=None): """The :func:`burpui.misc.backend.interface.BUIbackend.expand_path` function is used to expand path of file inclusions glob the user can set in the setting panel. @@ -610,6 +610,9 @@ class BUIbackend(object): :param path: The glob/path to expand :type path: str + :param source: In which file are we working + :type source: str + :param client: The client name when working on client files :type client: str diff --git a/burpui/misc/parser/burp1.py b/burpui/misc/parser/burp1.py index cadb8bdf..cd732c86 100644 --- a/burpui/misc/parser/burp1.py +++ b/burpui/misc/parser/burp1.py @@ -1,925 +1,228 @@ # -*- coding: utf8 -*- +""" +.. module:: burpui.misc.parser.burp1 + :platform: Unix + :synopsis: Burp-UI configuration file parser for Burp1. +.. moduleauthor:: Ziirish +""" import re import os import json import shutil import codecs +from copy import deepcopy from glob import glob +from hashlib import md5 +from six import iteritems -from .interface import BUIparser +from .doc import Doc +from .utils import Config, File +from .openssl import OSSLConf from ...exceptions import BUIserverException -class Parser(BUIparser): +class Parser(Doc): """:class:`burpui.misc.parser.burp1.Parser` provides a consistent interface to parse burp configuration files. It implements :class:`burpui.misc.parser.interface.BUIparser`. """ pver = 1 - defaults = { - u'address': u'', # IP - u'atime': False, # bool - u'autoupgrade_dir': u'', # dir - u'ca_burp_ca': u'', # file - u'ca_conf': u'', # file - u'ca_name': u'', - u'ca_server_name': u'', - u'client_can_delete': True, # bool - u'client_can_diff': True, # bool - u'client_can_force_backup': True, # bool - u'client_can_list': True, # bool - u'client_can_restore': True, # bool - u'client_can_verify': True, # bool - u'client_lockdir': u'', - u'clientconfdir': u'', # dir - u'compression': u'gzip9', - u'cross_all_filesystems': False, # bool - u'cross_filesystem': u'', - u'daemon': True, # bool - u'dedup_group': u'', - u'directory_tree': True, # bool - u'directory': u'', - u'exclude_comp': u'', # multi - u'exclude_ext': u'', # multi - u'exclude_fs': u'', # multi - u'exclude_regex': u'', # multi - u'exclude': u'', # multi - u'fork': True, # bool - u'group': u'', - u'hard_quota': u'', - u'hardlinked_archive': False, # bool - u'include_ext': u'', # multi - u'include_glob': u'', # multi - u'include_regex': u'', # multi - u'include': u'', # multi - u'keep': [7, 6, 4], # multi # int - u'librsync': True, # bool - u'max_children': 5, # int - u'max_file_size': u'', - u'max_hardlinks': 10000, # int - u'max_status_children': 5, # int - u'max_storage_subdirs': 30000, # int - u'min_file_size': u'', - u'mode': u'', - u'monitor_browse_cache': False, # bool - u'network_timeout': 7200, # int - u'nobackup': u'', - u'notify_failure_arg': u'', # multi - u'notify_failure_script': u'', # file - u'notify_success_arg': u'', # multi - u'notify_success_changes_only': False, # bool - u'notify_success_script': u'', # file - u'notify_success_warnings_only': False, # bool - u'password': u'', - u'password_check': True, # bool - u'path_length_warn': True, # bool - u'pidfile': u'', - u'port': 4971, # int - u'protocol': False, # int - u'ratelimit': False, # int - u'read_all_blockdevs': False, # bool - u'read_all_fifos': False, # bool - u'read_blockdev': u'', - u'read_fifo': u'', - u'restore_client': u'', # multi - u'scan_problem_raises_error': False, # bool - u'server_script_arg': u'', # multi - u'server_script_notify': False, # bool - u'server_script_post_arg': u'', # multi - u'server_script_post_notify': False, # bool - u'server_script_post_run_on_fail': False, # bool - u'server_script_post': u'', # file - u'server_script_pre_arg': u'', # multi - u'server_script_pre_notify': False, # bool - u'server_script_pre': u'', # file - u'server_script': u'', # file - u'soft_quota': u'', - u'split_vss': False, # bool - u'ssl_cert_ca': u'', # file - u'ssl_cert': u'', # file - u'ssl_ciphers': u'', - u'ssl_compression': u'zlib5', - u'ssl_dhfile': u'', # file - u'ssl_key_password': u'', - u'ssl_key': u'', # file - u'ssl_peer_cn': u'', - u'status_address': u'', # 127.0.0.1 / ::1 - u'status_port': 4972, # int - u'stdout': True, # bool - u'strip_vss': False, # bool - u'syslog': False, # bool - u'timer_arg': u'', # multi - u'timer_script': u'', # file - u'timestamp_format': u'', - u'umask': u'0022', # mode - u'user': u'', - u'version_warn': True, # bool - u'vss_drives': u'', - u'working_dir_recovery_method': u'', - } - placeholders = { - u'.': u"path or glob", - u'atime': u"0|1", - u'autoupgrade_dir': u"path", - u'ca_burp_ca': u"path", - u'ca_conf': u"path", - u'ca_name': u"name", - u'ca_server_name': u"name", - u'client_can_delete': u"0|1", - u'client_can_force_backup': u"0|1", - u'client_can_list': u"0|1", - u'client_can_restore': u"0|1", - u'client_can_verify': u"0|1", - u'client_lockdir': u"path", - u'clientconfdir': u"path", - u'compression': u"gzip[0-9]", - u'cross_all_filesystems': u"0|1", - u'cross_filesystem': u"path", - u'daemon': u"0|1", - u'dedup_group': u"string", - u'directory_tree': u"0|1", - u'directory': u"path", - u'exclude_comp': u"extension", - u'exclude_ext': u"extension", - u'exclude_fs': u"fstype", - u'exclude_regex': u"regular expression", - u'exclude': u"path", - u'fork': u"0|1", - u'group': u"groupname", - u'hard_quota': u"b/Kb/Mb/Gb", - u'hardlinked_archive': u"0|1", - u'include_ext': u"extension", - u'include_regex': u"regular expression", - u'include': u"path", - u'include_glob': u"glob", - u'keep': u"number", - u'librsync': u"0|1", - u'lockfile': u"path", - u'manual_delete': u"path", - u'max_children': u"number", - u'max_file_size': u"b/Kb/Mb/Gb", - u'max_hardlinks': u"number", - u'max_status_children': u"number", - u'max_storage_subdirs': u"number", - u'min_file_size': u"b/Kb/Mb/Gb", - u'network_timeout': u"s", - u'nobackup': u"file name", - u'notify_failure_arg': u"string", - u'notify_failure_script': u"path", - u'notify_success_arg': u"string", - u'notify_success_changes_only': u"0|1", - u'notify_success_script': u"path", - u'notify_success_warnings_only': u"0|1", - u'password': u"password", - u'password_check': u"0|1", - u'pidfile': u"path", - u'port': u"port number", - u'ratelimit': u"Mb/s", - u'read_all_blockdevs': u"0|1", - u'read_all_fifos': u"0|1", - u'read_blockdev': u"path", - u'read_fifo': u"path", - u'restore_client': u"client", - u'resume_partial': u"0|1", - u'scan_problem_raises_error': u"0|1", - u'server_script_arg': u"path", - u'server_script_notify': u"0|1", - u'server_script_post_arg': u"string", - u'server_script_post_notify': u"0|1", - u'server_script_post_run_on_fail': u"0|1", - u'server_script_post': u"path", - u'server_script_pre_arg': u"string", - u'server_script_pre_notify': u"0|1", - u'server_script_pre': u"path", - u'server_script': u"path", - u'soft_quota': u"b/Kb/Mb/Gb", - u'ssl_cert_ca': u"path", - u'ssl_cert_password': u"password", - u'ssl_cert': u"path", - u'ssl_ciphers': u"cipher list", - u'ssl_dhfile': u"path", - u'ssl_key_password': u"password", - u'ssl_key': u"path", - u'ssl_peer_cn': u"string", - u'status_port': u"port number", - u'stdout': u"0|1", - u'strip_vss': u"0|1", - u'suplit_vss': u"0|1", - u'syslog': u"0|1", - u'timer_arg': u"string", - u'timer_script': u"path", - u'timestamp_format': u"strftime format", - u'umask': u"umask", - u'user': u"username", - u'version_warn': u"0|1", - u'vss_drives': u"list of drive letters", - u'working_dir_recovery_method': u"resume|use|delete", - } - values = { - u'compression': [u'gzip{0}'.format(x) for x in range(0, 10)], - u'mode': [u'client', u'server'], - u'ssl_compression': [u'zlib{0}'.format(x) for x in range(0, 10)], - u'status_address': [u'127.0.0.1', u'::1'], # 127.0.0.1 / ::1 - u'working_dir_recovery_method': [u'use', u'delete', u'resume'], - } - files = [ - u'ca_burp_ca', - u'ca_conf', - u'notify_failure_script', - u'notify_success_script', - u'server_script_post', - u'server_script_pre', - u'server_script', - u'ssl_cert_ca', - u'ssl_cert', - u'ssl_dhfile', - u'ssl_key', - u'timer_script', - ] - multi_srv = [ - u'exclude_comp', - u'exclude_ext', - u'exclude_fs', - u'exclude_regex', - u'exclude', - u'include_ext', - u'include_glob', - u'include_regex', - u'include', - u'keep', - u'notify_failure_arg', - u'notify_success_arg', - u'restore_client', - u'server_script_arg', - u'server_script_post_arg', - u'server_script_pre_arg', - u'timer_arg', - ] - boolean_srv = [ - u'atime', - u'client_can_delete', - u'client_can_diff', - u'client_can_force_backup', - u'client_can_list', - u'client_can_restore', - u'client_can_verify', - u'cross_all_filesystems', - u'daemon', - u'directory_tree', - u'fork', - u'hardlinked_archive', - u'librsync', - u'monitor_browse_cache', - u'notify_success_changes_only', - u'notify_success_warnings_only', - u'password_check', - u'path_length_warn', - u'read_all_blockdevs', - u'read_all_fifos', - u'scan_problem_raises_error', - u'server_script_notify', - u'server_script_post_notify', - u'server_script_post_run_on_fail', - u'server_script_pre_notify', - u'split_vss', - u'stdout', - u'strip_vss', - u'syslog', - u'version_warn', - ] - integer_srv = [ - u'max_children', - u'max_hardlinks', - u'max_status_children', - u'max_storage_subdirs', - u'network_timeout', - u'port', - u'protocol', - u'ratelimit', - u'status_port', - ] - string_srv = [ - u'address', - u'ca_burp_ca', - u'ca_conf', - u'ca_name', - u'ca_server_name', - u'client_lockdir', - u'compression', - u'dedup_group', - u'directory', - u'group', - u'hard_quota', - u'mode', - u'notify_failure_script', - u'notify_success_script', - u'pidfile', - u'server_script_post', - u'server_script_pre', - u'server_script', - u'soft_quota', - u'ssl_cert_ca', - u'ssl_cert', - u'ssl_ciphers', - u'ssl_compression', - u'ssl_dhfile', - u'ssl_key_password', - u'ssl_key', - u'status_address', - u'timestamp_format', - u'umask', - u'user', - u'working_dir_recovery_method', - u'min_file_size', - u'max_file_size', - u'cross_filesystem', - u'nobackup', - u'read_fifo', - u'read_blockdev', - u'vss_drives', - ] - fields_cli = [ - u'atime', - u'client_can_delete', - u'client_can_force_backup', - u'client_can_list', - u'client_can_restore', - u'client_can_verify', - u'compression', - u'cross_all_filesystems', - u'cross_filesystem', - u'dedup_group', - u'directory_tree', - u'directory', - u'exclude_comp', - u'exclude_ext', - u'exclude_fs', - u'exclude_regex', - u'exclude', - u'hard_quota', - u'include_ext', - u'include_regex', - u'include', - u'keep', - u'librsync', - u'max_file_size', - u'min_file_size', - u'nobackup', - u'notify_failure_arg', - u'notify_failure_script' - u'notify_success_arg', - u'notify_success_script', - u'notify_success_warnings_only', - u'password_check', - u'password', - u'path_length_warn', - u'protocol', - u'read_all_blockdevs', - u'read_all_fifos', - u'read_blockdev', - u'read_fifo', - u'restore_client', - u'scan_problem_raises_error', - u'server_script_arg', - u'server_script_notify', - u'server_script_post_arg', - u'server_script_post_notify', - u'server_script_post_run_on_fail', - u'server_script_post', - u'server_script_pre_arg', - u'server_script_pre_notify', - u'server_script_pre', - u'server_script', - u'soft_quota', - u'split_vss', - u'ssl_peer_cn', - u'strip_vss', - u'syslog' - u'timer_arg', - u'timer_script', - u'timestamp_format', - u'version_warn', - u'vss_drives', - u'working_dir_recovery_method', - ] - string_cli = list(set(string_srv) & set(fields_cli)) - string_cli += [u'ssl_peer_cn', u'password'] - boolean_cli = list(set(boolean_srv) & set(fields_cli)) - integer_cli = list(set(integer_srv) & set(fields_cli)) - multi_cli = list(set(multi_srv) & set(fields_cli)) - doc = { - u'.': u"Read additional configuration files. On Windows, the glob is" - " unimplemented - you will need to specify an actual file.", - u'address': u"Defines the main TCP address that the server listens" - " on. The default is either '::' or '0.0.0.0', dependent" - " upon compile time options.", - u'atime': u"This allows you to control whether the client uses" - " O_NOATIME when opening files and directories. The" - " default is 0, which enables O_NOATIME. This means that" - " the client can read files and directories without" - " updating the access times. However, this is only" - " possible if you are running as root, or are the owner of" - " the file or directory. If this is not the case (perhaps" - " you only have group or world access to the files), you" - " will get errors until you set atime=1. With atime=1, the" - " access times will be updated on the files and" - " directories that get backed up.", - u'autoupgrade_dir': u"Path to autoupgrade directory from which" - " upgrades are downloaded. The option can be" - " left unset in order not to autoupgrade" - " clients. Please see docs/autoupgrade.txt in" - " the source package for more help with this" - " option.", - u'ca_burp_ca': u"Path to the burp_ca script when using the ca_conf" - " option.", - u'ca_conf': u"Path to certificate authority configuration file. The" - " CA configuration file will usually be" - " /etc/burp/CA.cnf. The CA directory indicated by CA.cnf" - " will usually be /etc/burp/CA. If ca_conf is set and" - " the CA directory does not exist, the server will" - " create, populate it, and the paths indicated by" - " ssl_cert_ca, ssl_cert, ssl_key and ssl_dhfile will be" - " overwritten. For more detailed information on this and" - " the other ca_* options, please see docs/burp_ca.txt.", - u'ca_name': u"Name of the CA that the server will generate when" - " using the ca_conf option.", - u'ca_server_name': u"The name that the server will put into its own" - " SSL certficates when using the ca_conf option.", - u'client_can_delete': u"Turn this off to prevent clients from" - " deleting backups with the '-a D' option. The" - " default is that clients can delete backups." - " Restore clients can override this setting.", - u'client_can_force_backup': u"Turn this off to prevent clients from" - " forcing backups with the '-a b'" - " option. Timed backups will still work." - " The default is that clients can force" - " backups.", - u'client_can_list': u"Turn this off to prevent clients from listing" - " backups with the '-a l' option. The default is" - " that clients can list backups. Restore clients" - " can override this setting.", - u'client_can_restore': u"Turn this off to prevent clients from" - " initiating restores with the '-a r' option." - " The default is that clients can initiate" - " restores. Restore clients can override this" - " setting.", - u'client_can_verify': u"Turn this off to prevent clients from" - " initiating a verify job with the '-a v'" - " option. The default is that clients can" - " initiate a verify job. Restore clients can" - " override this setting.", - u'client_lockdir': u"Path to the directory in which to keep" - " per-client lock files. By default, this is set" - " to the path given by the 'directory' option.", - u'clientconfdir': u"Path to the directory that contains client" - " configuration files.", - u'compression': u"Choose the level of gzip compression for files" - " stored in backups. Setting 0 or gzip0 turns" - " compression off. The default is gzip9. This option" - " can be overridden by the client configuration" - " files in clientconfdir on the server.", - u'cross_all_filesystems': u"Allow backups to cross all filesystem" - " mountpoints.", - u'cross_filesystem': u"Allow backups to cross a particular" - " filesystem mountpoint.", - u'daemon': u"Whether to daemonise. The default is 1.", - u'dedup_group': u"Enables you to group clients together for file" - " deduplication purposes. For example, you might" - " want to set 'dedup_group=xp' for each Windows XP" - " client, and then run the bedup program on a cron" - " job every other day with the option '-g xp'.", - u'directory_tree': u"When turned on (which is the default) and the" - " client is on version 1.3.6 or greater, the" - " structure of the storage directory will mimic" - " that of the original filesystem on the client.", - u'directory': u"Path to the directory in which to store backups.", - u'exclude_comp': u"Extensions to exclude from compression. Case" - " insensitive. You can have multiple exclude" - " compression lines. For example, set 'gz' to" - " exclude gzipped files from compression.", - u'exclude_ext': u"Extensions to exclude from the backup. Case" - " insensitive. You can have multiple exclude" - " extension lines. For example, set 'vdi' to exclude" - " VirtualBox disk images.", - u'exclude_fs': u"File systems to exclude from the backup. Case" - " insensitive. You can have multiple exclude file" - " system lines. For example, set 'tmpfs' to exclude" - " tmpfs. Burp has an internal mapping of file system" - " names to file system IDs. If you know the file" - " system ID, you can use that instead. For example," - " 'exclude_fs = 0x01021994' will also exclude tmpfs.", - u'exclude_regex': u"Exclude paths that match the regular expression.", - u'exclude': u"Path to exclude from the backup. You can have multiple" - " exclude lines. Use forward slashes '/', not" - " backslashes '\\' as path delimiters.", - u'fork': u"Whether to fork children. The default is 1.", - u'group': u"Run as a particular group. This can be overridden by the" - " client configuration files in clientconfdir on the" - " server.", - u'hard_quota': u"Do not back up the client if the estimated size of" - " all files is greater than the specified size." - " Example: 'hard_quota = 100Gb'. Set to 0 (the" - " default) to have no limit.", - u'hardlinked_archive': u"On the server, defines whether to keep" - " hardlinked files in the backups, or whether" - " to generate reverse deltas and delete the" - " original files. Can be set to either 0" - " (off) or 1 (on). Disadvantage: More disk" - " space will be used Advantage: Restores will" - " be faster, and since no reverse deltas need" - " to be generated, the time and effort the" - " server needs at the end of a backup is" - " reduced.", - u'include_ext': u"Extensions to include in the backup. Case" - " insensitive. Nothing else will be included in the" - " backup. You can have multiple include extension" - " lines. For example, set 'txt' to include files" - " that end in '.txt'. You need to specify an" - " 'include' line so that burp knows where to start" - " looking.", - u'include_regex': u"Not implemented.", - u'include': u"Path to include in the backup. You can have multiple" - " include lines. Use forward slashes '/', not" - " backslashes '\\' as path delimiters.", - u'include_glob': u"Include paths that match the glob expression. For" - " example, '/home/*/Documents' will include" - " '/home/user1/Documents' and" - " '/home/user2/Documents' if directories 'user1'" - " and 'user2' exist in '/home'. The Windows" - " implementation currently limit the expression to" - " contain only one '*'.", - u'keep': u"Number of backups to keep. This can be overridden by the" - " client configuration files in clientconfdir on the" - " server. Specify multiple 'keep' entries on separate lines" - " in order to keep multiple periods of backups. For" - " example, assuming that you are doing a backup a day," - " keep=7 keep=4 keep=6 (on separate lines) will keep 7" - " daily backups, 4 weekly backups (7x4=28), and 6 multiples" - " of 4 weeks (7x4x6=168) - roughly 6 monthly backups." - " Effectively, you will be guaranteed to be able to restore" - " up to 168 days ago, with the number of available backups" - " exponentially decreasing as you go back in time to that" - " point. In this example, every 7th backup will be" - " hardlinked to allow burp to safely delete intermediate" - " backups when necessary. You can have as many 'keep' lines" - " as you like, as long as they don't exceed 52560000 when" - " multiplied together. That is, a backup every minute for" - " 100 years.", - u'librsync': u"When set to 0, delta differencing will not take" - " place. That is, when a file changes, the server will" - " request the whole new file. The default is 1. This" - " option can be overridden by the client configuration" - " files in clientconfdir on the server.", - u'lockfile': u"Path to the lockfile that ensures that two server" - " processes cannot run simultaneously.", - u'manual_delete': u"If a path is given, the server will move" - " directories to be deleted into the directory" - " specified by the path, but will not actually" - " delete them. The path must be on the same file" - " system as the backup storage. The idea is that a" - " busy server may be configured to run the" - " deletions outside of the backup timebands, when" - " the server is less busy, via a cron job. The" - " default is unset, which means that the server" - " will automatically delete the directories at the" - " end of a backup. This option can be overridden" - " by the client configuration files in" - " clientconfdir on the server.", - u'max_children': u"Defines the number of child processes to fork" - " (the number of clients that can simultaneously" - " connect. The default is 5.", - u'max_file_size': u"Do not back up files that are greater than the" - " specified size. Example: 'max_file_size = 10Mb'." - " Set to 0 (the default) to have no limit.", - u'max_hardlinks': u"On the server, the number of times that a single" - " file can be hardlinked. The bedup program also" - " obeys this setting. The default is 10000.", - u'max_status_children': u"Defines the number of status child" - " processes to fork (the number of status" - " clients that can simultaneously connect." - " The default is 5.", - u'max_storage_subdirs': u"Defines the number of subdirectories in" - " the data storage areas. The maximum number" - " of subdirectories that ext3 allows is" - " 32000. If you do not set this option, it" - " defaults to 30000.", - u'min_file_size': u"Do not back up files that are less than the" - " specified size. Example: 'min_file_size = 10Mb'." - " Set to 0 (the default) to have no limit.", - u'mode': u"Required to run in server mode.", - u'monitor_browse_cache': u"Whether or not the server should cache" - " the directory tree when a monitor client" - " is browsing.
Advantage: browsing is" - " faster.
Disadvantage: more memory is" - " used.", - u'network_timeout': u"Set the network timeout in seconds. If no data" - " is sent or received over a period of this" - " length, burp will give up. The default is 7200" - " seconds (2 hours).", - u'nobackup': u"If this file system entry exists, the directory" - " containing it will not be backed up.", - u'notify_failure_arg': u"The same as notify_success_arg, but for" - " backups that failed.", - u'notify_failure_script': u"The same as notify_success_script, but" - " for backups that failed.", - u'notify_success_arg': u"A user-definable argument to the notify" - " success script. You can have many of these." - " The notify_success_arg options can be" - " overridden by the client configuration" - " files in clientconfdir on the server.", - u'notify_success_script': u"Path to the script to run when a backup" - " succeeds. User arguments are appended" - " after the first five reserved arguments." - " An example notify script is provided." - " The notify_success_script option can be" - " overridden by the client configuration" - " files in clientconfdir on the server.", - u'notify_success_warnings_only': u"Set to 1 to send success" - " notifications when there were" - " warnings. If this and" - " notify_success_changes_only are" - " not turned on, success" - " notifications are always sent.", - u'password': u"Defines the password to send to the server.", - u'password_check': u"Allows you to turn client password checking on" - " or off. The default is on. SSL certificates" - " will still be checked if you turn passwords" - " off. This option can be overridden by the" - " client configuration files in clientconfdir on" - " the server.", - u'path_length_warn': u"When this is on, which is the default, a" - " warning will be issued when the client sends" - " a path that is too long to replicate in the" - " storage area tree structure. The file will" - " still be saved in a numbered file outside of" - " the tree structure, regardless of the setting" - " of this option. This option can be overridden" - " by the client configuration files in" - " clientconfdir on the server.", - u'pidfile': u"Synonym for lockfile.", - u'port': u"Defines the main TCP port that the server listens on.", - u'protocol': u"Choose which style of backups and restores to use. 0" - " (the default) automatically decides based on the" - " server version and which protocol is set on the" - " server side. 1 forces protocol1 style (file level" - " granularity with a pseudo mirrored storage on the" - " server and optional rsync). 2 forces protocol2 style" - " (inline deduplication with variable length blocks)." - " If you choose a forced setting, it will be an error" - " if the server also chooses a forced setting.", - u'ratelimit': u"Set the network send rate limit, in Mb/s. If this" - " option is not given, burp will send data as fast as" - " it can.", - u'read_all_blockdevs': u"Open all block devices for reading and back" - " up the contents as if they were regular" - " files.", - u'read_all_fifos': u"Open all fifos for reading and back up the" - " contents as if they were regular files.", - u'read_blockdev': u"Do not back up the given block device itself," - " but open it for reading and back up the contents" - " as if it were a regular file.", - u'read_fifo': u"Do not back up the given fifo itself, but open it" - " for reading and back up the contents as if it were a" - " regular file.", - u'restore_client': u"A client that is permitted to list, verify," - " restore and delete files belonging to any other" - " client. You may specify multiple" - " restore_clients. If this is too permissive, you" - " may set a restore_client for individual" - " original clients in the individual" - " clientconfdir files. Note that restoring a" - " backup from a Windows computer onto a Linux" - " computer will currently leave the VSS headers" - " in place at the beginning of each file. This" - " will be addressed in a future version of burp.", - u'resume_partial': u"Turn this on to enable 'resume partial' code." - " Requires 'working_dir_recovery_method=resume'." - " When resuming an interrupted transfer of a" - " single file, it attempts to use previously" - " transferred blocks of that file in order to be" - " more efficient. However, situations have been" - " reported where the file on the server side just" - " gets bigger forever, so this feature now" - " defaults to being turned off.", - u'scan_problem_raises_error': u"When enabled, this causes problems" - " in the phase1 scan (such as an" - " 'include' being missing) to be" - " treated as fatal errors. The default" - " is off.", - u'server_script_arg': u"Goes with server_script and overrides" - " server_script_pre_arg and" - " server_script_post_arg.", - u'server_script_notify': u"Turn on to send a notification emails" - " when the server pre and post scripts" - " return non-zero. The output of the script" - " will be included it the email. The" - " default is off. Requires the" - " notify_failure options to be set.", - u'server_script_post_arg': u"A user-definable argument to the server" - " post script. You can have many of these.", - u'server_script_post_notify': u"Turn on to send a notification email" - " when the server post script returns" - " non-zero. The output of the script" - " will be included in the email. The" - " default is off. Requires the" - " notify_failure options to be set.", - u'server_script_post_run_on_fail': u"If this is set to 1," - " server_script_post will always" - " be run. The default is 0, which" - " means that if the task asked" - " for by the client fails," - " server_script_post will not be" - " run.", - u'server_script_post': u"Path to a script to run on the server" - " before the client disconnects. The" - " arguments to it are 'post', '(client" - " command)', 'reserved3' to 'reserved5', and" - " then arguments defined by" - " server_script_post_arg. This command and" - " related options can be overriddden by the" - " client configuration files in clientconfdir" - " on the server.", - u'server_script_pre_arg': u"A user-definable argument to the server" - " pre script. You can have many of these.", - u'server_script_pre_notify': u"Turn on to send a notification email" - " when the server pre script returns" - " non-zero. The output of the script" - " will be included in the email. The" - " default is off. Most people will not" - " want this turned on because clients" - " usually contact the server at 20" - " minute intervals and this could cause" - " a lot of emails to be generated." - " Requires the notify_failure options" - " to be set.", - u'server_script_pre': u"Path to a script to run on the server after" - " each successfully authenticated connection" - " but before any work is carried out. The" - " arguments to it are 'pre', '(client" - " command)', 'reserved3' to 'reserved5', and" - " then arguments defined by" - " server_script_pre_arg. If the script returns" - " non-zero, the task asked for by the client" - " will not be run. This command and related" - " options can be overriddden by the client" - " configuration files in clientconfdir on the" - " server.", - u'server_script': u"You can use this to save space in your config" - " file when you want to run the same server script" - " twice. It overrides server_script_pre and" - " server_script_post. This command and related" - " options can be overriddden by the client" - " configuration files in clientconfdir on the" - " server.", - u'soft_quota': u"A warning will be issued when the estimated size of" - " all files is greater than the specified size and" - " smaller than hard_quota. Example: 'soft_quota =" - " 95Gb'. Set to 0 (the default) to have no warning.", - u'split_vss': u"When backing up Windows computers with burp protocol" - " 1, this option allows you to save the VSS header" - " data separate from the file data. The default is" - " off, which means that the VSS header data is saved" - " prepended to the file data.", - u'ssl_cert_ca': u"The path to the SSL CA certificate. This file will" - " probably be the same on both the server and the" - " client. The file should contain just the" - " certificate in PEM format. For more information on" - " this, and the other ssl_* options, please see" - " " - " docs/burp_ca.txt.", - u'ssl_cert_password': u"Synonym for ssl_key_password.", - u'ssl_cert': u"The path to the server SSL certificate. It works for" - " me when the file contains the concatenation of the" - " certificate and private key in PEM format.", - u'ssl_ciphers': u"Allowed SSL ciphers. See openssl ciphers for" - " details.", - u'ssl_compression': u"Choose the level of zlib compression over SSL." - " Setting 0 or zlib0 turnsSSL compression off." - " Setting non-zero gives zlib5 compression (it" - " is not currently possible for openssl to set" - " any other level). The default is 5. 'gzip' is" - " a synonym of 'zlib'.is a synonym of 'zlib'.", - u'ssl_dhfile': u"Path to Diffie-Hellman parameter file. To generate" - " one with openssl, use a command like this: openssl" - " dhparam -out dhfile.pem -5 1024", - u'ssl_key_password': u"The SSL key password.", - u'ssl_key': u"The path to the server SSL private key in PEM format.", - u'status_address': u"Defines the main TCP address that the server" - " listens on for status requests. The default is" - " either '::1' or '127.0.0.1', dependent upon" - " compile time options.", - u'status_port': u"Defines the TCP port that the server listens on" - " for status requests.", - u'stdout': u"Log to stdout. Defaults to on.", - u'strip_vss': u"When backing up Windows computers with burp protocol" - " 1, this option allows you to prevent the VSS header" - " data being backed up. The default is off. To restore" - " a backup that has no VSS information on Windows, you" - " need to give the client the '-x' command line option.", - u'syslog': u"Log to syslog. Defaults to off.", - u'timer_arg': u"A user-definable argument to the timer script. You" - " can have many of these. The timer_arg options can be" - " overridden by the client configuration files in" - " clientconfdir on the server.", - u'timer_script': u"Path to the script to run when a client connects" - " with the timed backup option. If the script" - " exits with code 0, a backup will run. The first" - " two arguments are the client name and the path" - " to the 'current' storage directory. The next" - " three arguments are reserved, and user arguments" - " are appended after that. An example timer script" - " is provided. The timer_script option can be" - " overridden by the client configuration files in" - " clientconfdir on the server.", - u'timestamp_format': u"This allows you to tweak the format of the" - " timestamps of individual backups. See 'man" - " strftime' to see available substitutions." - " If this option is unset, burp uses" - " \"%Y-%m-%d %H:%M:%S\".", - u'umask': u"Set the file creation umask. Default is 0022.", - u'user': u"Run as a particular user. This can be overridden by the" - " client configuration files in clientconfdir on the server.", - u'version_warn': u"When this is on, which is the default, a warning" - " will be issued when the client version does not" - " match the server version. This option can be" - " overridden by the client configuration files in" - " clientconfdir on the server.", - u'vss_drives': u"When backing up Windows computers, this option" - " allows you to specify which drives have VSS" - " snapshots taken of them. If you omit this option," - " burp will automatically decide based on the" - " 'include' options. If you want no drives to have" - " snapshots taken of them, you can specify '0'.", - u'working_dir_recovery_method': u"This option tells the server what" - " to do when it finds the working" - " directory of an interrupted backup" - " (perhaps somebody pulled the plug" - " on the server, or something). This" - " can be overridden by the client" - " configurations files in" - " clientconfdir on the server." - " Options are... ", - } def __init__(self, backend=None): - """See :func:`burpui.misc.parser.interface.BUIparser.__init__`""" - super(Parser, self).__init__(backend) + """ + :param backend: Backend context + :type backend: :class:`burpui.misc.backend.burp1.Burp` + """ + self.backend = backend + self.conf = backend.burpconfsrv + self.confcli = backend.burpconfcli self.logger.info('Parser initialized with: {}'.format(self.conf)) - self.logger.info('includes: {}'.format(self.backend.includes)) self.clients = [] + self.server_conf = {} + self.client_conf = {} self.clientconfdir = None self.workingdir = None self.root = None + self.md5 = {} + self.filecache = {} if self.conf: self.root = os.path.dirname(self.conf) # first run to setup vars - self.read_server_conf() + self._load_all_conf() + ca_conf = self.server_conf.get('ca_conf') + if self._is_secure_path(ca_conf): + self.openssl_conf = OSSLConf(ca_conf) + else: + self.openssl_conf = OSSLConf(os.devnull) + + @staticmethod + def _line_is_comment(line): + """Check whether a given line is a comment or not""" + if not line: + return False + return line.startswith('#') + + @staticmethod + def _line_is_file_include(line): + """Check whether a given line is a file inclusion or not""" + if not line: + return False + return line.startswith('.') + + @staticmethod + def _include_get_file(line): + """Return the path of the included file(s)""" + if not line: + return None + _, fil = re.split(r'\s+', line, 1) + return fil + + @staticmethod + def _get_line_key(line, ignore_comments=True): + """Return the key of a given line""" + if not line: + return '' + if '=' not in line: + return line + (key, _) = re.split(r'\s+|=', line, 1) + if not ignore_comments: + key = key.strip('#') + return key.strip() + + @staticmethod + def _line_removed(line, keys): + """Check whether a given line has been removed in the updated version""" + if not line: + return False + (key, _) = re.split(r'\s+|=', line, 1) + key = key.strip() + return key not in keys + + @staticmethod + def _md5(path): + """Return the md5sum of a given file""" + hash_md5 = md5() + with open(path, "rb") as bfile: + for chunk in iter(lambda: bfile.read(4096), b""): + hash_md5.update(chunk) + return hash_md5.hexdigest() + + def _file_changed(self, path): + """Check whether a given file has changed since last read""" + chksum = self._md5(path) + if path in self.md5 and self.md5[path] == chksum: + self.logger.debug("'{}' is already in cache".format(path)) + return False, chksum + return True, chksum + + def _refresh_cache(self, purge=False): + """Force cache refresh""" + # empty all the caches + if purge: + self.server_conf = {} + self.client_conf = {} + self.md5 = {} + self.filecache = {} + self._list_clients(True) + + def _load_conf_srv(self): + """Load the server configuration file""" + self.server_conf = Config() + data, path, cached = self._readfile(self.conf) + if not cached: + parsed = self._parse_lines(data, path, 'srv') + self.server_conf.add_file(parsed, self.conf) + self.server_conf.set_default(self.conf) + self._parse_conf_recursive(self.server_conf) + + def _load_conf_cli(self, name=None): + """Load a given client configuration file (or all)""" + if name: + clients = [name] + else: + clients = self._list_clients(True) + + for cli in clients: + self.client_conf[cli['name']] = deepcopy(self.server_conf) + data, path, cached = self._readfile(cli['value']) + if not cached: + parsed = self._parse_lines(data, path, 'cli') + self.client_conf[cli['name']].add_file(parsed, path) + self.client_conf[cli['name']].set_default(path) + self._parse_conf_recursive( + self.client_conf[cli['name']], + client=True + ) + + def _load_all_conf(self): + """Load all configurations""" + self._load_conf_srv() + self._load_conf_cli() + + def _parse_conf_recursive(self, conf=None, parsed=None, client=False): + """Parses a conf recursively + + :param conf: Configuration to parse + :type conf: :class:`burpui.misc.parser.utils.Config` + + :param parsed: Current configuration being parsed + :type parsed: :class:`burpui.misc.parser.utils.File` + + :param mode: Parser mode + :type mode: str + """ + if not conf: + return + + mode = 'srv' + if client: + mode = 'cli' + + curr = parsed + if not parsed: + curr = conf.get_default() + + for key, val in iteritems(curr.flatten('include', False)): + for path in val: + fil, path, cached = self._readfile(path, client) + if not cached: + tmp = self._parse_lines(fil, path, mode) + conf.add_file(tmp, path) + self._parse_conf_recursive(conf, tmp, client) def _readfile(self, path=None, client=False): + ret = [] if not path: - return [] + return ret, path, False if not self._is_secure_path(path): - return [] + return ret, path, False if path != self.conf and not path.startswith('/'): if client: path = os.path.join(self.clientconfdir, path) else: path = os.path.join(self.root, path) - self.logger.debug('reading file: %s', path) - with codecs.open(path, 'r', 'utf-8') as fil: - ret = [x.rstrip('\n') for x in fil.readlines()] - return ret + changed, chksum = self._file_changed(path) + if not changed: + return self.filecache[path]['raw'], path, True - def _parse_lines_srv(self, fil): - return self._parse_lines(fil, 'srv') + self.logger.debug('reading file: {}'.format(path)) + try: + with codecs.open(path, 'r', 'utf-8') as fil: + ret = [x.rstrip('\n') for x in fil.readlines()] + except IOError: + return ret, path, False - def _parse_lines_cli(self, fil): - return self._parse_lines(fil, 'cli') + self.filecache[path] = {'raw': ret} + self.md5[path] = chksum - def _parse_lines(self, fil, mode='srv'): - dic = [] - boolean = [] - multi = [] - integer = [] - includes = [] - includes_ext = [] - for line in fil: + return ret, path, False + + def _parse_lines(self, data, name=None, mode='srv'): + conffile = File(self, name, mode=mode) + for line in data: if re.match(r'^\s*#', line): continue res = re.search(r'\s*([^=\s]+)\s*=?\s*(.*)$', line) @@ -929,51 +232,29 @@ class Parser(BUIparser): # We are gonna use this for server-side initiated restoration if mode == 'srv' and key == u'directory': self.workingdir = val - if key in getattr(self, 'boolean_{}'.format(mode)): - boolean.append({'name': key, 'value': int(val) == 1}) - continue - elif key in getattr(self, 'integer_{}'.format(mode)): - integer.append({'name': key, 'value': int(val)}) - continue - if key == u'.': - i = val - if not val.startswith('/'): - if mode == 'srv': - i = os.path.join(self.root, val) - else: - i = os.path.join(self.clientconfdir, val) - for path in glob(i): - includes_ext.append({'name': path, 'value': val}) - includes.append(val) - continue - if key in getattr(self, 'multi_{}'.format(mode)): - found = False - for mul in multi: - if mul['name'] == key: - mul['value'].append(val) - found = True - break - if not found: - multi.append({'name': key, 'value': [val]}) - continue if key == u'clientconfdir': - if mode != 'srv': - continue - if not val.startswith('/'): - self.clientconfdir = os.path.join(self.root, val) - else: - self.clientconfdir = val - dic.append({'name': key, 'value': val}) + if mode == 'srv': + if not val.startswith('/'): + self.clientconfdir = os.path.join(self.root, val) + else: + self.clientconfdir = val + elif key == u'compression': + val = val.replace('zlib', 'gzip') + elif key == u'ssl_compression': + val = val.replace('gzip', 'zlib') + conffile[key] = val - return dic, boolean, multi, integer, includes, includes_ext + return conffile def _is_secure_path(self, path=None): """Check if the accessed path is allowed or not""" + if not path: + return False if not self.backend.includes: # 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.split(',')] if not any(cond): self.logger.warning( 'Tried to access non-allowed path: {}'.format(path) @@ -981,12 +262,91 @@ class Parser(BUIparser): return False return True - def path_expander(self, pattern=None, client=None): + def _list_clients(self, force=False): + if not self.clientconfdir: + return [] + + if self.clients and not force: + return self.clients + + res = [] + for cli in os.listdir(self.clientconfdir): + full_file = os.path.join(self.clientconfdir, cli) + if (os.path.isfile(full_file) and not cli.startswith('.') + and not cli.endswith('~')): + res.append({ + 'name': cli, + 'value': full_file + }) + + self.clients = res + return res + + def _write_key(self, fil, key, data, conf, mode=None): + if key in self.boolean_srv or key in self.boolean_cli: + val = 0 + if data.get(key) == 'true': + val = 1 + conf[key] = (val == 1) + fil.write('{} = {}\n'.format(key, val)) + elif key == '.': + conf[key] = data + fil.write('. {}\n'.format(data)) + elif key in self.multi_srv or key in self.multi_cli: + conf[key] = data.getlist(key) + for val in data.getlist(key): + fil.write('{} = {}\n'.format(key, val)) + else: + val = data.get(key) + # special key + if key == 'clientconfdir' and mode == 'srv' and \ + val != self.clientconfdir: + self.clientconfdir = val + self._refresh_cache(purge=True) + if key == 'ca_conf' and mode == 'srv' and \ + val != self.server_conf.get(key): + self.openssl_conf = OSSLConf(val) + fil.write('{} = {}\n'.format(key, val)) + conf[key] = val + + def _get_server_path(self, name=None, fil=None): + """Returns the path of the 'server *fil*' file""" + self.read_server_conf() + + if not name: + raise BUIserverException('Missing name') + + if not self.workingdir: + raise BUIserverException('Unable to find burp spool dir') + + found = False + for cli in self.clients: + if cli['name'] == name: + found = True + break + + if not found: + raise BUIserverException('Client \'{}\' not found'.format(name)) + + return os.path.join(self.workingdir, name, fil) + + def _get_server_restore_path(self, name=None): + """Returns the path of the 'server restore' file""" + return self._get_server_path(name, 'restore') + + def _get_server_backup_path(self, name=None): + """Returns the path of the 'server backup' file""" + return self._get_server_path(name, 'backup') + + def path_expander(self, pattern=None, source=None, client=None): """See :func:`burpui.misc.parser.interface.BUIparser.path_expander`""" if not pattern: return [] if not pattern.startswith('/'): - if client: + if source and (source.startswith(self.clientconfdir) or + source.startswith(self.root)): + pattern = os.path.join(os.path.dirname(source), pattern) + elif client: pattern = os.path.join(self.clientconfdir, pattern) else: pattern = os.path.join(self.root, pattern) @@ -996,19 +356,34 @@ class Parser(BUIparser): return [ x for x in glob(pattern) if os.path.isfile(x) and not x.endswith('~') and - self._is_secure_path(x) + not x.endswith('.back') and self._is_secure_path(x) ] - def remove_client(self, client=None): + def remove_client(self, client=None, delcert=False, revoke=False): """See :func:`burpui.misc.parser.interface.BUIparser.remove_client`""" + res = [] if not client: - return [2, "No client provided"] + return [[2, "No client provided"]] try: - os.unlink(os.path.join(self.clientconfdir, client)) - self._list_clients(True) - return [0, "'{}' successfully removed".format(client)] + path = os.path.join(self.clientconfdir, client) + os.unlink(path) + res.append([0, "'{}' successfully removed".format(client)]) + if client in self.client_conf: + del self.client_conf[client] + if path in self.md5: + # we always set both at the same time so we are sure both exist + del self.md5[path] + del self.filecache[path] + if revoke and self.backend.cli.revocation_enabled(): + # revoke cert + pass + if delcert: + ca_dir = self.openssl_conf.values.get('CA_DIR') + + self._refresh_cache() except OSError as exp: - return [2, str(exp)] + res.append([2, str(exp)]) + return res def read_client_conf(self, client=None, conf=None): """ @@ -1021,7 +396,7 @@ class Parser(BUIparser): u'multi': [], u'includes': [], u'includes_ext': [], - u'clients': self._list_clients() + u'clients': self._list_clients(), } if not client and not conf: return res @@ -1033,23 +408,26 @@ class Parser(BUIparser): mconf = os.path.join(self.clientconfdir, client) try: - fil = self._readfile(mconf, True) + fil, path, cache = self._readfile(mconf, True) except Exception: return res - (strings, - boolean, - multi, - integer, - includes, - includes_ext) = self._parse_lines_cli(fil) - res[u'common'] = strings - res[u'boolean'] = boolean - res[u'integer'] = integer - res[u'multi'] = multi - res[u'includes'] = includes - res[u'includes_ext'] = includes_ext + if cache and 'parsed' in self.filecache[path]: + res.update(self.filecache[path]['parsed']) + return res + parsed = self.client_conf[client].get_file(path) + res2 = {} + res2[u'common'] = parsed.string + res2[u'boolean'] = parsed.boolean + res2[u'integer'] = parsed.integer + res2[u'multi'] = parsed.multi + res2[u'includes'] = parsed.flatten('include', False).keys() + res2[u'includes_ext'] = parsed.include + + self.filecache[path]['parsed'] = res2 + + res.update(res2) return res def read_server_conf(self, conf=None): @@ -1064,7 +442,7 @@ class Parser(BUIparser): u'multi': [], u'includes': [], u'includes_ext': [], - u'clients': self._list_clients() + u'clients': self._list_clients(), } if not conf: mconf = self.conf @@ -1074,43 +452,26 @@ class Parser(BUIparser): return res try: - fil = self._readfile(mconf) + fil, path, cache = self._readfile(mconf) except Exception: return res - (strings, - boolean, - multi, - integer, - includes, - includes_ext) = self._parse_lines_srv(fil) - res[u'common'] = strings - res[u'boolean'] = boolean - res[u'integer'] = integer - res[u'multi'] = multi - res[u'includes'] = includes - res[u'includes_ext'] = includes_ext + if cache and 'parsed' in self.filecache[path]: + res.update(self.filecache[path]['parsed']) + return res - return res + parsed = self.server_conf.get_file(path) + res2 = {} + res2[u'common'] = parsed.string + res2[u'boolean'] = parsed.boolean + res2[u'integer'] = parsed.integer + res2[u'multi'] = parsed.multi + res2[u'includes'] = parsed.flatten('include', False).keys() + res2[u'includes_ext'] = parsed.include - def _list_clients(self, force=False): - if not self.clientconfdir: - return [] + self.filecache[path]['parsed'] = res2 - if self.clients and not force: - return self.clients - - res = [] - for fil in os.listdir(self.clientconfdir): - full_file = os.path.join(self.clientconfdir, fil) - if (os.path.isfile(full_file) and not fil.startswith('.') - and not fil.endswith('~')): - res.append({ - 'name': fil, - 'value': os.path.join(self.clientconfdir, fil) - }) - - self.clients = res + res.update(res2) return res def list_clients(self): @@ -1131,11 +492,11 @@ class Parser(BUIparser): return [[2, 'Sorry, no client defined']] elif client and not conf: conf = os.path.join(self.clientconfdir, client) - ret = self.store_conf(data, conf, mode='cli') - self._list_clients(True) # refresh client list + ret = self.store_conf(data, conf, client, mode='cli') + self._refresh_cache() # refresh client list return ret - def store_conf(self, data, conf=None, mode='srv'): + def store_conf(self, data, conf=None, client=None, mode='srv'): """See :func:`burpui.misc.parser.interface.BUIparser.store_conf`""" mconf = None if not conf: @@ -1163,12 +524,9 @@ class Parser(BUIparser): except OSError as exp: return [[1, str(exp)]] - if self.clientconfdir in dirname: - ref = '{}.bui.init.back~'.format(mconf) - bak = '{}.bak~'.format(mconf) - else: - ref = '{}.bui.init.back'.format(mconf) - bak = '{}.bak'.format(mconf) + ref = '{}.bui.init.back~'.format(mconf) + bak = '{}.back~'.format(mconf) + if not os.path.isfile(ref) and os.path.isfile(mconf): try: shutil.copy(mconf, ref) @@ -1180,6 +538,11 @@ class Parser(BUIparser): except IOError as exp: return [[2, str(exp)]] + if client: + conffile = self.client_conf[client].get_file(mconf) + else: + conffile = self.server_conf.get_file(mconf) + errs = [] for key in data.keys(): if key in self.files: @@ -1225,6 +588,7 @@ class Parser(BUIparser): not self._line_is_file_include(line)): # The line was removed, we comment it fil.write('#{}\n'.format(line)) + del conffile[self._get_line_key(line)] elif self._line_is_file_include(line): # The line is a file inclusion, we check if the line # was already present @@ -1232,16 +596,23 @@ class Parser(BUIparser): if ori in data.getlist('includes_ori'): idx = data.getlist('includes_ori').index(ori) inc = data.getlist('includes')[idx] - self._write_key(fil, '.', inc) + self._write_key(fil, '.', inc, conf=conffile) already_file.append(inc) else: fil.write('#{}\n'.format(line)) + del conffile[ori] elif self._get_line_key(line, False) in data.viewkeys(): # The line is still present or has been un-commented, # rewrite it with eventual changes key = self._get_line_key(line, False) if key not in already_multi: - self._write_key(fil, key, data) + self._write_key( + fil, + key, + data, + conf=conffile, + mode=mode + ) if key in getattr(self, 'multi_{}'.format(mode)): already_multi.append(key) written.append(key) @@ -1252,97 +623,22 @@ class Parser(BUIparser): for key in newkeys: if (key not in written and key not in ['includes', 'includes_ori']): - self._write_key(fil, key, data) + self._write_key( + fil, + key, + data, + conf=conffile, + mode=mode + ) # Write the rest of file inclusions for inc in data.getlist('includes'): if inc not in already_file: - self._write_key(fil, '.', inc) + self._write_key(fil, '.', inc, conf=conffile) except Exception as exp: return [[2, str(exp)]] return [[0, 'Configuration successfully saved.']] - def _write_key(self, fil, key, data): - if key in self.boolean_srv: - val = 0 - if data.get(key) == 'true': - val = 1 - fil.write('{} = {}\n'.format(key, val)) - elif key == '.': - fil.write('. {}\n'.format(data)) - elif key in self.multi_srv: - for val in data.getlist(key): - fil.write('{} = {}\n'.format(key, val)) - else: - fil.write('{} = {}\n'.format(key, data.get(key))) - - @staticmethod - def _line_is_comment(line): - if not line: - return False - return line.startswith('#') - - @staticmethod - def _line_is_file_include(line): - if not line: - return False - return line.startswith('.') - - @staticmethod - def _include_get_file(line): - if not line: - return None - _, fil = re.split(r'\s+', line, 1) - return fil - - @staticmethod - def _get_line_key(line, ignore_comments=True): - if not line: - return '' - if '=' not in line: - return line - (key, _) = re.split(r'\s+|=', line, 1) - if not ignore_comments: - key = key.strip('#') - return key.strip() - - @staticmethod - def _line_removed(line, keys): - if not line: - return False - (key, _) = re.split(r'\s+|=', line, 1) - key = key.strip() - return key not in keys - - def _get_server_path(self, name=None, fil=None): - """Returns the path of the 'server *fil*' file""" - self.read_server_conf() - - if not name: - raise BUIserverException('Missing name') - - if not self.workingdir: - raise BUIserverException('Unable to find burp spool dir') - - found = False - for cli in self.clients: - if cli['name'] == name: - found = True - break - - if not found: - raise BUIserverException('Client \'{}\' not found'.format(name)) - - return os.path.join(self.workingdir, name, fil) - - def _get_server_restore_path(self, name=None): - """Returns the path of the 'server restore' file""" - return self._get_server_path(name, 'restore') - - def _get_server_backup_path(self, name=None): - """Returns the path of the 'server backup' file""" - return self._get_server_path(name, 'backup') - def cancel_restore(self, name=None): """See :func:`burpui.misc.parser.interface.BUIparser.cancel_restore`""" path = self._get_server_restore_path(name) @@ -1404,9 +700,9 @@ class Parser(BUIparser): if 'restore' not in flist: raise BUIserverException('Wrong call') - full_reg = u'' + full_reg = ur'' for rest in flist['restore']: - reg = u'' + reg = ur'' if rest['folder'] and rest['key'] != '/': reg += '^' + re.escape(rest['key']) + '/|' else: @@ -1467,7 +763,7 @@ class Parser(BUIparser): """See :func:`burpui.misc.parser.interface.BUIparser.read_backup`""" path = self._get_server_backup_path(name) ret = os.path.exists(path) - return os.path.exists(path) + return ret def server_initiated_backup(self, name=None): """See diff --git a/burpui/misc/parser/burp2.py b/burpui/misc/parser/burp2.py index ee118868..3213debb 100644 --- a/burpui/misc/parser/burp2.py +++ b/burpui/misc/parser/burp2.py @@ -1,4 +1,10 @@ # -*- coding: utf8 -*- +""" +.. module:: burpui.misc.parser.burp2 + :platform: Unix + :synopsis: Burp-UI configuration file parser for Burp2. +.. moduleauthor:: Ziirish +""" from .burp1 import Parser as Burp1 diff --git a/burpui/misc/parser/doc.py b/burpui/misc/parser/doc.py new file mode 100644 index 00000000..19c56546 --- /dev/null +++ b/burpui/misc/parser/doc.py @@ -0,0 +1,873 @@ +# -*- coding: utf8 -*- +""" +.. module:: burpui.misc.parser.doc + :platform: Unix + :synopsis: Burp-UI parser documentation for Burp1. +.. moduleauthor:: Ziirish +""" +from .interface import BUIparser + + +class Doc(BUIparser): + """:class:`burpui.misc.parser.doc.Doc` provides a consistent interface + to parse burp configuration files. + + It implements :class:`burpui.misc.parser.interface.BUIparser`. + """ + defaults = { + u'address': u'', # IP + u'atime': False, # bool + u'autoupgrade_dir': u'', # dir + u'ca_burp_ca': u'', # file + u'ca_conf': u'', # file + u'ca_name': u'', + u'ca_server_name': u'', + u'client_can_delete': True, # bool + u'client_can_diff': True, # bool + u'client_can_force_backup': True, # bool + u'client_can_list': True, # bool + u'client_can_restore': True, # bool + u'client_can_verify': True, # bool + u'client_lockdir': u'', + u'clientconfdir': u'', # dir + u'compression': u'gzip9', + u'cross_all_filesystems': False, # bool + u'cross_filesystem': u'', + u'daemon': True, # bool + u'dedup_group': u'', + u'directory_tree': True, # bool + u'directory': u'', + u'exclude_comp': u'', # multi + u'exclude_ext': u'', # multi + u'exclude_fs': u'', # multi + u'exclude_regex': u'', # multi + u'exclude': u'', # multi + u'fork': True, # bool + u'group': u'', + u'hard_quota': u'', + u'hardlinked_archive': False, # bool + u'include_ext': u'', # multi + u'include_glob': u'', # multi + u'include_regex': u'', # multi + u'include': u'', # multi + u'keep': [7, 6, 4], # multi # int + u'librsync': True, # bool + u'max_children': 5, # int + u'max_file_size': u'', + u'max_hardlinks': 10000, # int + u'max_status_children': 5, # int + u'max_storage_subdirs': 30000, # int + u'min_file_size': u'', + u'mode': u'', + u'monitor_browse_cache': False, # bool + u'network_timeout': 7200, # int + u'nobackup': u'', + u'notify_failure_arg': u'', # multi + u'notify_failure_script': u'', # file + u'notify_success_arg': u'', # multi + u'notify_success_changes_only': False, # bool + u'notify_success_script': u'', # file + u'notify_success_warnings_only': False, # bool + u'password': u'', + u'password_check': True, # bool + u'path_length_warn': True, # bool + u'pidfile': u'', + u'port': 4971, # int + u'protocol': False, # int + u'ratelimit': False, # int + u'read_all_blockdevs': False, # bool + u'read_all_fifos': False, # bool + u'read_blockdev': u'', + u'read_fifo': u'', + u'restore_client': u'', # multi + u'scan_problem_raises_error': False, # bool + u'server_script_arg': u'', # multi + u'server_script_notify': False, # bool + u'server_script_post_arg': u'', # multi + u'server_script_post_notify': False, # bool + u'server_script_post_run_on_fail': False, # bool + u'server_script_post': u'', # file + u'server_script_pre_arg': u'', # multi + u'server_script_pre_notify': False, # bool + u'server_script_pre': u'', # file + u'server_script': u'', # file + u'soft_quota': u'', + u'split_vss': False, # bool + u'ssl_cert_ca': u'', # file + u'ssl_cert': u'', # file + u'ssl_ciphers': u'', + u'ssl_compression': u'zlib5', + u'ssl_dhfile': u'', # file + u'ssl_key_password': u'', + u'ssl_key': u'', # file + u'ssl_peer_cn': u'', + u'status_address': u'', # 127.0.0.1 / ::1 + u'status_port': 4972, # int + u'stdout': True, # bool + u'strip_vss': False, # bool + u'syslog': False, # bool + u'timer_arg': u'', # multi + u'timer_script': u'', # file + u'timestamp_format': u'', + u'umask': u'0022', # mode + u'user': u'', + u'version_warn': True, # bool + u'vss_drives': u'', + u'working_dir_recovery_method': u'', + } + placeholders = { + u'.': u"path or glob", + u'atime': u"0|1", + u'autoupgrade_dir': u"path", + u'ca_burp_ca': u"path", + u'ca_conf': u"path", + u'ca_name': u"name", + u'ca_server_name': u"name", + u'client_can_delete': u"0|1", + u'client_can_force_backup': u"0|1", + u'client_can_list': u"0|1", + u'client_can_restore': u"0|1", + u'client_can_verify': u"0|1", + u'client_lockdir': u"path", + u'clientconfdir': u"path", + u'compression': u"gzip[0-9]", + u'cross_all_filesystems': u"0|1", + u'cross_filesystem': u"path", + u'daemon': u"0|1", + u'dedup_group': u"string", + u'directory_tree': u"0|1", + u'directory': u"path", + u'exclude_comp': u"extension", + u'exclude_ext': u"extension", + u'exclude_fs': u"fstype", + u'exclude_regex': u"regular expression", + u'exclude': u"path", + u'fork': u"0|1", + u'group': u"groupname", + u'hard_quota': u"b/Kb/Mb/Gb", + u'hardlinked_archive': u"0|1", + u'include_ext': u"extension", + u'include_regex': u"regular expression", + u'include': u"path", + u'include_glob': u"glob", + u'keep': u"number", + u'librsync': u"0|1", + u'lockfile': u"path", + u'manual_delete': u"path", + u'max_children': u"number", + u'max_file_size': u"b/Kb/Mb/Gb", + u'max_hardlinks': u"number", + u'max_status_children': u"number", + u'max_storage_subdirs': u"number", + u'min_file_size': u"b/Kb/Mb/Gb", + u'network_timeout': u"s", + u'nobackup': u"file name", + u'notify_failure_arg': u"string", + u'notify_failure_script': u"path", + u'notify_success_arg': u"string", + u'notify_success_changes_only': u"0|1", + u'notify_success_script': u"path", + u'notify_success_warnings_only': u"0|1", + u'password': u"password", + u'password_check': u"0|1", + u'pidfile': u"path", + u'port': u"port number", + u'ratelimit': u"Mb/s", + u'read_all_blockdevs': u"0|1", + u'read_all_fifos': u"0|1", + u'read_blockdev': u"path", + u'read_fifo': u"path", + u'restore_client': u"client", + u'resume_partial': u"0|1", + u'scan_problem_raises_error': u"0|1", + u'server_script_arg': u"path", + u'server_script_notify': u"0|1", + u'server_script_post_arg': u"string", + u'server_script_post_notify': u"0|1", + u'server_script_post_run_on_fail': u"0|1", + u'server_script_post': u"path", + u'server_script_pre_arg': u"string", + u'server_script_pre_notify': u"0|1", + u'server_script_pre': u"path", + u'server_script': u"path", + u'soft_quota': u"b/Kb/Mb/Gb", + u'ssl_cert_ca': u"path", + u'ssl_cert_password': u"password", + u'ssl_cert': u"path", + u'ssl_ciphers': u"cipher list", + u'ssl_dhfile': u"path", + u'ssl_key_password': u"password", + u'ssl_key': u"path", + u'ssl_peer_cn': u"string", + u'status_port': u"port number", + u'stdout': u"0|1", + u'strip_vss': u"0|1", + u'suplit_vss': u"0|1", + u'syslog': u"0|1", + u'timer_arg': u"string", + u'timer_script': u"path", + u'timestamp_format': u"strftime format", + u'umask': u"umask", + u'user': u"username", + u'version_warn': u"0|1", + u'vss_drives': u"list of drive letters", + u'working_dir_recovery_method': u"resume|use|delete", + } + values = { + u'compression': [u'gzip{0}'.format(x) for x in range(0, 10)], + u'mode': [u'client', u'server'], + u'ssl_compression': [u'zlib{0}'.format(x) for x in range(0, 10)], + u'status_address': [u'127.0.0.1', u'::1'], # 127.0.0.1 / ::1 + u'working_dir_recovery_method': [u'use', u'delete', u'resume'], + } + files = [ + u'ca_burp_ca', + u'ca_conf', + u'notify_failure_script', + u'notify_success_script', + u'server_script_post', + u'server_script_pre', + u'server_script', + u'ssl_cert_ca', + u'ssl_cert', + u'ssl_dhfile', + u'ssl_key', + u'timer_script', + ] + multi_srv = [ + u'exclude_comp', + u'exclude_ext', + u'exclude_fs', + u'exclude_regex', + u'exclude', + u'include_ext', + u'include_glob', + u'include_regex', + u'include', + u'keep', + u'notify_failure_arg', + u'notify_success_arg', + u'restore_client', + u'server_script_arg', + u'server_script_post_arg', + u'server_script_pre_arg', + u'timer_arg', + ] + boolean_srv = [ + u'atime', + u'client_can_delete', + u'client_can_diff', + u'client_can_force_backup', + u'client_can_list', + u'client_can_restore', + u'client_can_verify', + u'cross_all_filesystems', + u'daemon', + u'directory_tree', + u'fork', + u'hardlinked_archive', + u'librsync', + u'monitor_browse_cache', + u'notify_success_changes_only', + u'notify_success_warnings_only', + u'password_check', + u'path_length_warn', + u'read_all_blockdevs', + u'read_all_fifos', + u'scan_problem_raises_error', + u'server_script_notify', + u'server_script_post_notify', + u'server_script_post_run_on_fail', + u'server_script_pre_notify', + u'split_vss', + u'stdout', + u'strip_vss', + u'syslog', + u'version_warn', + ] + integer_srv = [ + u'max_children', + u'max_hardlinks', + u'max_status_children', + u'max_storage_subdirs', + u'network_timeout', + u'port', + u'protocol', + u'ratelimit', + u'status_port', + ] + string_srv = [ + u'address', + u'ca_burp_ca', + u'ca_conf', + u'ca_name', + u'ca_server_name', + u'client_lockdir', + u'compression', + u'dedup_group', + u'directory', + u'group', + u'hard_quota', + u'mode', + u'notify_failure_script', + u'notify_success_script', + u'pidfile', + u'server_script_post', + u'server_script_pre', + u'server_script', + u'soft_quota', + u'ssl_cert_ca', + u'ssl_cert', + u'ssl_ciphers', + u'ssl_compression', + u'ssl_dhfile', + u'ssl_key_password', + u'ssl_key', + u'status_address', + u'timestamp_format', + u'umask', + u'user', + u'working_dir_recovery_method', + u'min_file_size', + u'max_file_size', + u'cross_filesystem', + u'nobackup', + u'read_fifo', + u'read_blockdev', + u'vss_drives', + ] + fields_cli = [ + u'atime', + u'client_can_delete', + u'client_can_force_backup', + u'client_can_list', + u'client_can_restore', + u'client_can_verify', + u'compression', + u'cross_all_filesystems', + u'cross_filesystem', + u'dedup_group', + u'directory_tree', + u'directory', + u'exclude_comp', + u'exclude_ext', + u'exclude_fs', + u'exclude_regex', + u'exclude', + u'hard_quota', + u'include_ext', + u'include_regex', + u'include', + u'keep', + u'librsync', + u'max_file_size', + u'min_file_size', + u'nobackup', + u'notify_failure_arg', + u'notify_failure_script' + u'notify_success_arg', + u'notify_success_script', + u'notify_success_warnings_only', + u'password_check', + u'password', + u'path_length_warn', + u'protocol', + u'read_all_blockdevs', + u'read_all_fifos', + u'read_blockdev', + u'read_fifo', + u'restore_client', + u'scan_problem_raises_error', + u'server_script_arg', + u'server_script_notify', + u'server_script_post_arg', + u'server_script_post_notify', + u'server_script_post_run_on_fail', + u'server_script_post', + u'server_script_pre_arg', + u'server_script_pre_notify', + u'server_script_pre', + u'server_script', + u'soft_quota', + u'split_vss', + u'ssl_peer_cn', + u'strip_vss', + u'syslog' + u'timer_arg', + u'timer_script', + u'timestamp_format', + u'version_warn', + u'vss_drives', + u'working_dir_recovery_method', + ] + string_cli = list(set(string_srv) & set(fields_cli)) + string_cli += [u'ssl_peer_cn', u'password'] + boolean_cli = list(set(boolean_srv) & set(fields_cli)) + integer_cli = list(set(integer_srv) & set(fields_cli)) + multi_cli = list(set(multi_srv) & set(fields_cli)) + doc = { + u'.': u"Read additional configuration files. On Windows, the glob is" + " unimplemented - you will need to specify an actual file.", + u'address': u"Defines the main TCP address that the server listens" + " on. The default is either '::' or '0.0.0.0', dependent" + " upon compile time options.", + u'atime': u"This allows you to control whether the client uses" + " O_NOATIME when opening files and directories. The" + " default is 0, which enables O_NOATIME. This means that" + " the client can read files and directories without" + " updating the access times. However, this is only" + " possible if you are running as root, or are the owner of" + " the file or directory. If this is not the case (perhaps" + " you only have group or world access to the files), you" + " will get errors until you set atime=1. With atime=1, the" + " access times will be updated on the files and" + " directories that get backed up.", + u'autoupgrade_dir': u"Path to autoupgrade directory from which" + " upgrades are downloaded. The option can be" + " left unset in order not to autoupgrade" + " clients. Please see docs/autoupgrade.txt in" + " the source package for more help with this" + " option.", + u'ca_burp_ca': u"Path to the burp_ca script when using the ca_conf" + " option.", + u'ca_conf': u"Path to certificate authority configuration file. The" + " CA configuration file will usually be" + " /etc/burp/CA.cnf. The CA directory indicated by CA.cnf" + " will usually be /etc/burp/CA. If ca_conf is set and" + " the CA directory does not exist, the server will" + " create, populate it, and the paths indicated by" + " ssl_cert_ca, ssl_cert, ssl_key and ssl_dhfile will be" + " overwritten. For more detailed information on this and" + " the other ca_* options, please see docs/burp_ca.txt.", + u'ca_name': u"Name of the CA that the server will generate when" + " using the ca_conf option.", + u'ca_server_name': u"The name that the server will put into its own" + " SSL certficates when using the ca_conf option.", + u'client_can_delete': u"Turn this off to prevent clients from" + " deleting backups with the '-a D' option. The" + " default is that clients can delete backups." + " Restore clients can override this setting.", + u'client_can_force_backup': u"Turn this off to prevent clients from" + " forcing backups with the '-a b'" + " option. Timed backups will still work." + " The default is that clients can force" + " backups.", + u'client_can_list': u"Turn this off to prevent clients from listing" + " backups with the '-a l' option. The default is" + " that clients can list backups. Restore clients" + " can override this setting.", + u'client_can_restore': u"Turn this off to prevent clients from" + " initiating restores with the '-a r' option." + " The default is that clients can initiate" + " restores. Restore clients can override this" + " setting.", + u'client_can_verify': u"Turn this off to prevent clients from" + " initiating a verify job with the '-a v'" + " option. The default is that clients can" + " initiate a verify job. Restore clients can" + " override this setting.", + u'client_lockdir': u"Path to the directory in which to keep" + " per-client lock files. By default, this is set" + " to the path given by the 'directory' option.", + u'clientconfdir': u"Path to the directory that contains client" + " configuration files.", + u'compression': u"Choose the level of gzip compression for files" + " stored in backups. Setting 0 or gzip0 turns" + " compression off. The default is gzip9. This option" + " can be overridden by the client configuration" + " files in clientconfdir on the server.", + u'cross_all_filesystems': u"Allow backups to cross all filesystem" + " mountpoints.", + u'cross_filesystem': u"Allow backups to cross a particular" + " filesystem mountpoint.", + u'daemon': u"Whether to daemonise. The default is 1.", + u'dedup_group': u"Enables you to group clients together for file" + " deduplication purposes. For example, you might" + " want to set 'dedup_group=xp' for each Windows XP" + " client, and then run the bedup program on a cron" + " job every other day with the option '-g xp'.", + u'directory_tree': u"When turned on (which is the default) and the" + " client is on version 1.3.6 or greater, the" + " structure of the storage directory will mimic" + " that of the original filesystem on the client.", + u'directory': u"Path to the directory in which to store backups.", + u'exclude_comp': u"Extensions to exclude from compression. Case" + " insensitive. You can have multiple exclude" + " compression lines. For example, set 'gz' to" + " exclude gzipped files from compression.", + u'exclude_ext': u"Extensions to exclude from the backup. Case" + " insensitive. You can have multiple exclude" + " extension lines. For example, set 'vdi' to exclude" + " VirtualBox disk images.", + u'exclude_fs': u"File systems to exclude from the backup. Case" + " insensitive. You can have multiple exclude file" + " system lines. For example, set 'tmpfs' to exclude" + " tmpfs. Burp has an internal mapping of file system" + " names to file system IDs. If you know the file" + " system ID, you can use that instead. For example," + " 'exclude_fs = 0x01021994' will also exclude tmpfs.", + u'exclude_regex': u"Exclude paths that match the regular expression.", + u'exclude': u"Path to exclude from the backup. You can have multiple" + " exclude lines. Use forward slashes '/', not" + " backslashes '\\' as path delimiters.", + u'fork': u"Whether to fork children. The default is 1.", + u'group': u"Run as a particular group. This can be overridden by the" + " client configuration files in clientconfdir on the" + " server.", + u'hard_quota': u"Do not back up the client if the estimated size of" + " all files is greater than the specified size." + " Example: 'hard_quota = 100Gb'. Set to 0 (the" + " default) to have no limit.", + u'hardlinked_archive': u"On the server, defines whether to keep" + " hardlinked files in the backups, or whether" + " to generate reverse deltas and delete the" + " original files. Can be set to either 0" + " (off) or 1 (on). Disadvantage: More disk" + " space will be used Advantage: Restores will" + " be faster, and since no reverse deltas need" + " to be generated, the time and effort the" + " server needs at the end of a backup is" + " reduced.", + u'include_ext': u"Extensions to include in the backup. Case" + " insensitive. Nothing else will be included in the" + " backup. You can have multiple include extension" + " lines. For example, set 'txt' to include files" + " that end in '.txt'. You need to specify an" + " 'include' line so that burp knows where to start" + " looking.", + u'include_regex': u"Not implemented.", + u'include': u"Path to include in the backup. You can have multiple" + " include lines. Use forward slashes '/', not" + " backslashes '\\' as path delimiters.", + u'include_glob': u"Include paths that match the glob expression. For" + " example, '/home/*/Documents' will include" + " '/home/user1/Documents' and" + " '/home/user2/Documents' if directories 'user1'" + " and 'user2' exist in '/home'. The Windows" + " implementation currently limit the expression to" + " contain only one '*'.", + u'keep': u"Number of backups to keep. This can be overridden by the" + " client configuration files in clientconfdir on the" + " server. Specify multiple 'keep' entries on separate lines" + " in order to keep multiple periods of backups. For" + " example, assuming that you are doing a backup a day," + " keep=7 keep=4 keep=6 (on separate lines) will keep 7" + " daily backups, 4 weekly backups (7x4=28), and 6 multiples" + " of 4 weeks (7x4x6=168) - roughly 6 monthly backups." + " Effectively, you will be guaranteed to be able to restore" + " up to 168 days ago, with the number of available backups" + " exponentially decreasing as you go back in time to that" + " point. In this example, every 7th backup will be" + " hardlinked to allow burp to safely delete intermediate" + " backups when necessary. You can have as many 'keep' lines" + " as you like, as long as they don't exceed 52560000 when" + " multiplied together. That is, a backup every minute for" + " 100 years.", + u'librsync': u"When set to 0, delta differencing will not take" + " place. That is, when a file changes, the server will" + " request the whole new file. The default is 1. This" + " option can be overridden by the client configuration" + " files in clientconfdir on the server.", + u'lockfile': u"Path to the lockfile that ensures that two server" + " processes cannot run simultaneously.", + u'manual_delete': u"If a path is given, the server will move" + " directories to be deleted into the directory" + " specified by the path, but will not actually" + " delete them. The path must be on the same file" + " system as the backup storage. The idea is that a" + " busy server may be configured to run the" + " deletions outside of the backup timebands, when" + " the server is less busy, via a cron job. The" + " default is unset, which means that the server" + " will automatically delete the directories at the" + " end of a backup. This option can be overridden" + " by the client configuration files in" + " clientconfdir on the server.", + u'max_children': u"Defines the number of child processes to fork" + " (the number of clients that can simultaneously" + " connect. The default is 5.", + u'max_file_size': u"Do not back up files that are greater than the" + " specified size. Example: 'max_file_size = 10Mb'." + " Set to 0 (the default) to have no limit.", + u'max_hardlinks': u"On the server, the number of times that a single" + " file can be hardlinked. The bedup program also" + " obeys this setting. The default is 10000.", + u'max_status_children': u"Defines the number of status child" + " processes to fork (the number of status" + " clients that can simultaneously connect." + " The default is 5.", + u'max_storage_subdirs': u"Defines the number of subdirectories in" + " the data storage areas. The maximum number" + " of subdirectories that ext3 allows is" + " 32000. If you do not set this option, it" + " defaults to 30000.", + u'min_file_size': u"Do not back up files that are less than the" + " specified size. Example: 'min_file_size = 10Mb'." + " Set to 0 (the default) to have no limit.", + u'mode': u"Required to run in server mode.", + u'monitor_browse_cache': u"Whether or not the server should cache" + " the directory tree when a monitor client" + " is browsing.
Advantage: browsing is" + " faster.
Disadvantage: more memory is" + " used.", + u'network_timeout': u"Set the network timeout in seconds. If no data" + " is sent or received over a period of this" + " length, burp will give up. The default is 7200" + " seconds (2 hours).", + u'nobackup': u"If this file system entry exists, the directory" + " containing it will not be backed up.", + u'notify_failure_arg': u"The same as notify_success_arg, but for" + " backups that failed.", + u'notify_failure_script': u"The same as notify_success_script, but" + " for backups that failed.", + u'notify_success_arg': u"A user-definable argument to the notify" + " success script. You can have many of these." + " The notify_success_arg options can be" + " overridden by the client configuration" + " files in clientconfdir on the server.", + u'notify_success_script': u"Path to the script to run when a backup" + " succeeds. User arguments are appended" + " after the first five reserved arguments." + " An example notify script is provided." + " The notify_success_script option can be" + " overridden by the client configuration" + " files in clientconfdir on the server.", + u'notify_success_warnings_only': u"Set to 1 to send success" + " notifications when there were" + " warnings. If this and" + " notify_success_changes_only are" + " not turned on, success" + " notifications are always sent.", + u'password': u"Defines the password to send to the server.", + u'password_check': u"Allows you to turn client password checking on" + " or off. The default is on. SSL certificates" + " will still be checked if you turn passwords" + " off. This option can be overridden by the" + " client configuration files in clientconfdir on" + " the server.", + u'path_length_warn': u"When this is on, which is the default, a" + " warning will be issued when the client sends" + " a path that is too long to replicate in the" + " storage area tree structure. The file will" + " still be saved in a numbered file outside of" + " the tree structure, regardless of the setting" + " of this option. This option can be overridden" + " by the client configuration files in" + " clientconfdir on the server.", + u'pidfile': u"Synonym for lockfile.", + u'port': u"Defines the main TCP port that the server listens on.", + u'protocol': u"Choose which style of backups and restores to use. 0" + " (the default) automatically decides based on the" + " server version and which protocol is set on the" + " server side. 1 forces protocol1 style (file level" + " granularity with a pseudo mirrored storage on the" + " server and optional rsync). 2 forces protocol2 style" + " (inline deduplication with variable length blocks)." + " If you choose a forced setting, it will be an error" + " if the server also chooses a forced setting.", + u'ratelimit': u"Set the network send rate limit, in Mb/s. If this" + " option is not given, burp will send data as fast as" + " it can.", + u'read_all_blockdevs': u"Open all block devices for reading and back" + " up the contents as if they were regular" + " files.", + u'read_all_fifos': u"Open all fifos for reading and back up the" + " contents as if they were regular files.", + u'read_blockdev': u"Do not back up the given block device itself," + " but open it for reading and back up the contents" + " as if it were a regular file.", + u'read_fifo': u"Do not back up the given fifo itself, but open it" + " for reading and back up the contents as if it were a" + " regular file.", + u'restore_client': u"A client that is permitted to list, verify," + " restore and delete files belonging to any other" + " client. You may specify multiple" + " restore_clients. If this is too permissive, you" + " may set a restore_client for individual" + " original clients in the individual" + " clientconfdir files. Note that restoring a" + " backup from a Windows computer onto a Linux" + " computer will currently leave the VSS headers" + " in place at the beginning of each file. This" + " will be addressed in a future version of burp.", + u'resume_partial': u"Turn this on to enable 'resume partial' code." + " Requires 'working_dir_recovery_method=resume'." + " When resuming an interrupted transfer of a" + " single file, it attempts to use previously" + " transferred blocks of that file in order to be" + " more efficient. However, situations have been" + " reported where the file on the server side just" + " gets bigger forever, so this feature now" + " defaults to being turned off.", + u'scan_problem_raises_error': u"When enabled, this causes problems" + " in the phase1 scan (such as an" + " 'include' being missing) to be" + " treated as fatal errors. The default" + " is off.", + u'server_script_arg': u"Goes with server_script and overrides" + " server_script_pre_arg and" + " server_script_post_arg.", + u'server_script_notify': u"Turn on to send a notification emails" + " when the server pre and post scripts" + " return non-zero. The output of the script" + " will be included it the email. The" + " default is off. Requires the" + " notify_failure options to be set.", + u'server_script_post_arg': u"A user-definable argument to the server" + " post script. You can have many of these.", + u'server_script_post_notify': u"Turn on to send a notification email" + " when the server post script returns" + " non-zero. The output of the script" + " will be included in the email. The" + " default is off. Requires the" + " notify_failure options to be set.", + u'server_script_post_run_on_fail': u"If this is set to 1," + " server_script_post will always" + " be run. The default is 0, which" + " means that if the task asked" + " for by the client fails," + " server_script_post will not be" + " run.", + u'server_script_post': u"Path to a script to run on the server" + " before the client disconnects. The" + " arguments to it are 'post', '(client" + " command)', 'reserved3' to 'reserved5', and" + " then arguments defined by" + " server_script_post_arg. This command and" + " related options can be overriddden by the" + " client configuration files in clientconfdir" + " on the server.", + u'server_script_pre_arg': u"A user-definable argument to the server" + " pre script. You can have many of these.", + u'server_script_pre_notify': u"Turn on to send a notification email" + " when the server pre script returns" + " non-zero. The output of the script" + " will be included in the email. The" + " default is off. Most people will not" + " want this turned on because clients" + " usually contact the server at 20" + " minute intervals and this could cause" + " a lot of emails to be generated." + " Requires the notify_failure options" + " to be set.", + u'server_script_pre': u"Path to a script to run on the server after" + " each successfully authenticated connection" + " but before any work is carried out. The" + " arguments to it are 'pre', '(client" + " command)', 'reserved3' to 'reserved5', and" + " then arguments defined by" + " server_script_pre_arg. If the script returns" + " non-zero, the task asked for by the client" + " will not be run. This command and related" + " options can be overriddden by the client" + " configuration files in clientconfdir on the" + " server.", + u'server_script': u"You can use this to save space in your config" + " file when you want to run the same server script" + " twice. It overrides server_script_pre and" + " server_script_post. This command and related" + " options can be overriddden by the client" + " configuration files in clientconfdir on the" + " server.", + u'soft_quota': u"A warning will be issued when the estimated size of" + " all files is greater than the specified size and" + " smaller than hard_quota. Example: 'soft_quota =" + " 95Gb'. Set to 0 (the default) to have no warning.", + u'split_vss': u"When backing up Windows computers with burp protocol" + " 1, this option allows you to save the VSS header" + " data separate from the file data. The default is" + " off, which means that the VSS header data is saved" + " prepended to the file data.", + u'ssl_cert_ca': u"The path to the SSL CA certificate. This file will" + " probably be the same on both the server and the" + " client. The file should contain just the" + " certificate in PEM format. For more information on" + " this, and the other ssl_* options, please see" + " " + " docs/burp_ca.txt.", + u'ssl_cert_password': u"Synonym for ssl_key_password.", + u'ssl_cert': u"The path to the server SSL certificate. It works for" + " me when the file contains the concatenation of the" + " certificate and private key in PEM format.", + u'ssl_ciphers': u"Allowed SSL ciphers. See openssl ciphers for" + " details.", + u'ssl_compression': u"Choose the level of zlib compression over SSL." + " Setting 0 or zlib0 turnsSSL compression off." + " Setting non-zero gives zlib5 compression (it" + " is not currently possible for openssl to set" + " any other level). The default is 5. 'gzip' is" + " a synonym of 'zlib'.is a synonym of 'zlib'.", + u'ssl_dhfile': u"Path to Diffie-Hellman parameter file. To generate" + " one with openssl, use a command like this: openssl" + " dhparam -out dhfile.pem -5 1024", + u'ssl_key_password': u"The SSL key password.", + u'ssl_key': u"The path to the server SSL private key in PEM format.", + u'status_address': u"Defines the main TCP address that the server" + " listens on for status requests. The default is" + " either '::1' or '127.0.0.1', dependent upon" + " compile time options.", + u'status_port': u"Defines the TCP port that the server listens on" + " for status requests.", + u'stdout': u"Log to stdout. Defaults to on.", + u'strip_vss': u"When backing up Windows computers with burp protocol" + " 1, this option allows you to prevent the VSS header" + " data being backed up. The default is off. To restore" + " a backup that has no VSS information on Windows, you" + " need to give the client the '-x' command line option.", + u'syslog': u"Log to syslog. Defaults to off.", + u'timer_arg': u"A user-definable argument to the timer script. You" + " can have many of these. The timer_arg options can be" + " overridden by the client configuration files in" + " clientconfdir on the server.", + u'timer_script': u"Path to the script to run when a client connects" + " with the timed backup option. If the script" + " exits with code 0, a backup will run. The first" + " two arguments are the client name and the path" + " to the 'current' storage directory. The next" + " three arguments are reserved, and user arguments" + " are appended after that. An example timer script" + " is provided. The timer_script option can be" + " overridden by the client configuration files in" + " clientconfdir on the server.", + u'timestamp_format': u"This allows you to tweak the format of the" + " timestamps of individual backups. See 'man" + " strftime' to see available substitutions." + " If this option is unset, burp uses" + " \"%Y-%m-%d %H:%M:%S\".", + u'umask': u"Set the file creation umask. Default is 0022.", + u'user': u"Run as a particular user. This can be overridden by the" + " client configuration files in clientconfdir on the server.", + u'version_warn': u"When this is on, which is the default, a warning" + " will be issued when the client version does not" + " match the server version. This option can be" + " overridden by the client configuration files in" + " clientconfdir on the server.", + u'vss_drives': u"When backing up Windows computers, this option" + " allows you to specify which drives have VSS" + " snapshots taken of them. If you omit this option," + " burp will automatically decide based on the" + " 'include' options. If you want no drives to have" + " snapshots taken of them, you can specify '0'.", + u'working_dir_recovery_method': u"This option tells the server what" + " to do when it finds the working" + " directory of an interrupted backup" + " (perhaps somebody pulled the plug" + " on the server, or something). This" + " can be overridden by the client" + " configurations files in" + " clientconfdir on the server." + " Options are...
  • delete:" + " Just delete the old working" + " directory.
  • use: Convert" + " the working directory into a" + " complete backup.
  • resume:" + " Simply continue the previous" + " backup from the point at which it" + " left off, at file granularity." + " NOTE: If the client has changed" + " its include/exclude configuration" + " since the backup was interrupted," + " the recovery method will" + " automatically switch to 'use'." + "
", + } diff --git a/burpui/misc/parser/interface.py b/burpui/misc/parser/interface.py index 2a1732e9..87de2911 100644 --- a/burpui/misc/parser/interface.py +++ b/burpui/misc/parser/interface.py @@ -20,16 +20,6 @@ class BUIparser(object): logger = logging.getLogger('burp-ui') - def __init__(self, backend=None): - """:func:`burpui.misc.parser.interface.BUIparser.__init__` instanciate - the parser. - - :param backend: The application backend - :type backend: :class:`burpui.misc.backend.BUIbackend` - """ - self.backend = backend - self.conf = backend.burpconfsrv - @abstractmethod def read_server_conf(self, conf=None): """:func:`burpui.misc.parser.interface.BUIparser.read_server_conf` is @@ -136,7 +126,7 @@ class BUIparser(object): ) # pragma: no cover @abstractmethod - def store_conf(self, data, conf=None, mode='srv'): + def store_conf(self, data, conf=None, client=None, mode='srv'): """:func:`burpui.misc.parser.interface.BUIparser.store_conf` is used to store the configuration from the web-ui into the actual configuration files. @@ -148,6 +138,9 @@ class BUIparser(object): :param conf: Force the file path (for file inclusions for instance) :type conf: str + :param client: Client name + :type client: str + :param mode: We actually use the same method for clients and server files :type mode: str @@ -164,7 +157,7 @@ class BUIparser(object): ) # pragma: no cover @abstractmethod - def path_expander(self, pattern=None, client=None): + def path_expander(self, pattern=None, source=None, client=None): """:func:`burpui.misc.parser.interface.BUIparser.path_expander` is used to expand path of file inclusions glob the user can set in the setting panel. @@ -172,6 +165,9 @@ class BUIparser(object): :param pattern: The glob/path to expand :type pattern: str + :param source: What file we are working in + :type source: str + :param client: The client name when working on client files :type client: str @@ -193,12 +189,19 @@ class BUIparser(object): ) # pragma: no cover @abstractmethod - def remove_client(self, client=None): + def remove_client(self, client=None, delcert=False, revoke=False): """:func:`burpui.misc.parser.interface.BUIparser.remove_client` is used to delete a client from burp's configuration. :param client: The name of the client to remove :type client: str + + :param delcert: Whether to delete the associated certificate + :type delcert: bool + + :param revoke: Whether to revoke the associated certificate + :type revoke: bool + :returns: A list of notifications to return to the UI (success or failure) """ diff --git a/burpui/misc/parser/utils.py b/burpui/misc/parser/utils.py new file mode 100644 index 00000000..1fd1ceba --- /dev/null +++ b/burpui/misc/parser/utils.py @@ -0,0 +1,547 @@ +# -*- coding: utf8 -*- +""" +.. module:: burpui.misc.parser.utils + :platform: Unix + :synopsis: Burp-UI configuration file parser utilities. + +.. moduleauthor:: Ziirish +""" +import os + +from collections import OrderedDict +from glob import glob +from six import iteritems + + +class Option(object): + """Object representing an option + + :param name: Option name + :type name: str + + :param value: Option value + :type value: str + """ + type = None + delim = '=' + _dirty = False + + def __init__(self, name, value=None): + self.name = name + self.value = value + self._dirty = True + + def update(self, value): + self._dirty = True + self.value = value + + def clean(self): + self._dirty = False + + def parse(self): + return self.value + + def dump(self): + return "{} {} {}".format(self.name, self.delim, self.value) + + def __repr__(self): + return "{} -> {}".format(self.name, self.parse()) + + def __str__(self): + return self.dump() + + +class OptionStr(Option): + """Option type String + + Example: + server = toto + """ + type = 'string' + + +class OptionInt(Option): + """Option type Integer + + Example: + port = 1234 + """ + type = 'integer' + + def parse(self): + return int(self.value) + + +class OptionBool(Option): + """Option type Boolean + + Example: + hardlinked_archive = 1 + """ + type = 'boolean' + + def parse(self): + try: + return int(self.value) == 1 + except ValueError: + return False + + +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 + if value: + self.value = [value] + else: + self.value = [] + + def append(self, value): + self._dirty = True + self.value.append(value) + 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 dump(self): + ret = u'' + for val in self.value: + ret += '{} {} {}\n'.format(self.name, self.delim, val) + return ret.rstrip('\n') + + +class OptionInc(Option): + """Option type Include + + Example: + . incexc/windows + """ + type = 'include' + delim = "" + + def __init__(self, parser, name, value=None, root=None, mode='srv'): + """ + :param parser: Parser instance + :type parser: :class:`burpui.misc.parser.burp1.Parser` + """ + super(OptionInc, self).__init__(name, value) + self.parser = parser + self.mode = mode + self.extended = [] + self._dirty = True + if root: + self.root = os.path.dirname(root) + else: + self.root = None + + def _path_absolute(self, path): + absolute = path + if not path.startswith('/'): + if self.root: + absolute = os.path.join(self.root, path) + elif self.mode == 'srv': + absolute = os.path.join(self.parser.root, path) + else: + absolute = os.path.join(self.parser.clientconfdir, path) + return absolute + + def extend(self): + if not self._dirty and self.extended: + return self.extended + paths = [] + root = self._path_absolute(self.value) + for path in glob(root): + if self.parser._is_secure_path(path) and os.path.isfile(path) and \ + not path.endswith('~') and not path.endswith('.back'): + paths.append(path) + self.clean() + self.extended = paths + return paths + + def parse(self): + return self.extend() + + def dump(self): + if self.extend() and not self.parser.backend.enforce: + return '. {}'.format(self.name) + # if the include did not match anything, we can safely remove it + return '' + + +class File(dict): + """Object representing a configuration file + + :param parser: Parser object + :type parser: :class:`burpui.misc.parser.doc.Doc` + """ + + def __init__(self, parser, name=None, mode='srv'): + """ + :param parser: Parser object + :type parser: :class:`burpui.misc.parser.doc.Doc` + + :param name: File name + :type name: str + + :param mode: Configuration type + :type mode: str + """ + self._dirty = False + self.parser = parser + self.mode = mode + self.name = name + self.options = OrderedDict() + self.types = { + 'boolean': OrderedDict(), + 'integer': OrderedDict(), + 'include': OrderedDict(), + 'multi': OrderedDict(), + 'string': OrderedDict(), + } + + def clean(self): + self._dirty = False + for _, opt in iteritems(self.options): + opt.clean() + + def get_name(self): + return self.name + + def set_name(self, name): + self.name = name + + def flatten(self, typ, listed=True, parse=True): + self._refresh_types() + if parse: + self.clean() + if listed: + return [ + { + 'name': key, + 'value': val.parse() if parse else val + } + for key, val in iteritems(self.types[typ]) + ] + ret = OrderedDict() + for key, val in iteritems(self.types[typ]): + ret[key] = val.parse() if parse else val + return ret + + @property + def dirty(self): + self._dirty = self._dirty or \ + any([x._dirty for _, x in iteritems(self.options)]) + return self._dirty + + @property + def boolean(self): + return self.flatten('boolean') + + @property + def integer(self): + return self.flatten('integer') + + @property + def include(self): + return self.flatten('include') + + @property + def multi(self): + return self.flatten('multi') + + @property + def string(self): + return self.flatten('string') + + def _refresh_types(self): + if self._dirty: + for key in self.types.viewkeys(): + self.types[key] = OrderedDict() + + for key, opt in iteritems(self.options): + self.types[opt.type][key] = opt + + self._dirty = False + + def _options_for_type(self, typ): + return getattr(self.parser, '{}_{}'.format(typ, self.mode), []) + + def get(self, key, default=None): + try: + return self.options[key] + except KeyError: + return default + + def __getitem__(self, key): + return self.options[key] + + def __setitem__(self, key, value): + self._dirty = True + if key in self._options_for_type('boolean'): + opt = OptionBool(key, value) + 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.append(value) + elif key == u'.': + key = value + opt = OptionInc( + self.parser, + key, + value, + root=self.name, + mode=self.mode + ) + else: + opt = OptionStr(key, value) + self.options[key] = opt + self.types[opt.type][key] = opt + + def __repr__(self): + self._refresh_types() + ret = u'' + for key, opts in iteritems(self.types): + ret += '{} =>\n'.format(key) + for _, opt in iteritems(opts): + ret += '\t' + repr(opt) + '\n' + return ret.rstrip('\n') + + def __str__(self): + ret = u'' + for key, val in iteritems(self.options): + tmp = str(val) + if tmp: + ret += tmp + '\n' + return ret.rstrip('\n') + + def __len__(self): + return len(self.options) + + def __delitem__(self, key): + self._dirty = True + del self.options[key] + + def clear(self): + self._dirty = True + return self.options.clear() + + def copy(self): + return self.options.copy() + + def has_key(self, k): + return k in self.options + + def update(self, *args, **kwargs): + self._dirty = True + return self.options.update(*args, **kwargs) + + def keys(self): + return self.options.keys() + + def values(self): + return self.options.values() + + def items(self): + return self.options.items() + + def pop(self, *args): + self._dirty = True + return self.options.pop(*args) + + def __cmp__(self, dict): + return cmp(self.options, dict) + + def __contains__(self, item): + return item in self.options + + def __iter__(self): + return iter(self.options) + + def __unicode__(self): + return unicode(self.__repr__()) + + +class Config(File): + """Object representing a configuration + + A config is like a virtual file so we can reuse some methods + + :param parser: Parser object + :type parser: :class:`burpui.misc.parser.doc.Doc` + """ + + def __init__(self, path=None, parsed=None, parser=None, mode='srv'): + """ + :param parser: Parser object + :type parser: :class:`burpui.misc.parser.doc.Doc` + + :param mode: Configuration type + :type mode: str + """ + super(Config, self).__init__(parser, mode) + # we need an OrderedDict since the order of the configuration matters + self.files = OrderedDict() + self.default = path + self.name = path + self._dirty = True + if path: + if not parsed: + self.files[path] = File(parser, path, mode=mode) + else: + self.files[path] = parsed + self.files.get(path).set_name(path) + + def set_default(self, path): + self.default = path + self.name = path + if not self.parser: + self.parser = self.get_default().parser + if not self.mode: + self.mode = self.get_default().mode + + def get_default(self, exc=False): + if self.default: + return self.get_file(self.default) + if exc: + raise ValueError('No default configuration found') + return {} + + def add_file(self, parsed=None, path=None): + idx = path or self.default + self.files[idx] = parsed or File(self.parser, mode=self.mode) + self.files[idx].set_name(idx) + self._dirty = True + return self.files[idx] + + def get_file(self, path): + return self.files.get(path, File(self.parser, mode=self.mode)) + + def del_file(self, path): + self._dirty = True + del self.files[path] + + def list_files(self): + return self.files.keys() + + def _refresh(self): + if self._dirty or \ + any([x.dirty + for _, x in iteritems(self.files)]): + + # cleanup "caches" + self.options = OrderedDict() + for key in self.types.viewkeys(): + self.types[key] = OrderedDict() + + # now update caches with new values + for _, fil in iteritems(self.files): + self.options.update(fil.options) + fil.clean() + + for key, val in iteritems(self.options): + self.types[val.type][key] = val + + self._dirty = False + + def _get(self, key, default=None, raw=False): + self._refresh() + try: + obj = self.options[key] + return obj if raw else obj.parse() + except KeyError: + return default + + def get_raw(self, key, default=None): + return self._get(key, default, True) + + def get(self, key, default=None): + return self._get(key, default) + + def __getitem__(self, key): + self._refresh() + return self.options[key] + + def __setitem__(self, key, value): + self.get_default(True)[key] = value + self._dirty = True + + def __repr__(self): + self._refresh() + ret = u'' + for key, fil in iteritems(self.files): + ret += '>' * 5 + key + '<' * 5 + '\n' + ret += repr(fil) + '\n' + return ret.rstrip('\n') + + def __str__(self): + self._refresh() + return super(Config, self).__str__() + + def __len__(self): + self._refresh() + return len(self.options) + + def __delitem__(self, key): + del self.get_default(True)[key] + self._dirty = True + + def clear(self): + self._dirty = True + return self.files.clear() + + def copy(self): + return self.files.copy() + + def has_key(self, k): + self._refresh() + return k in self.options + + def update(self, *args, **kwargs): + self._dirty = True + return self.get_default(True).update(*args, **kwargs) + + def keys(self): + self._refresh() + return self.options.keys() + + def values(self): + self._refresh() + return self.options.values() + + def items(self): + self._refresh() + return self.options.items() + + def pop(self, *args): + self._dirty = True + return self.get_default(True).pop(*args) + + def __cmp__(self, dict): + self._refresh() + return cmp(self.options, dict) + + def __contains__(self, item): + self._refresh() + return item in self.options + + def __iter__(self): + self._refresh() + return iter(self.options) + + def __unicode__(self): + return unicode(repr(self)) diff --git a/burpui/routes.py b/burpui/routes.py index 3ce56822..99421223 100644 --- a/burpui/routes.py +++ b/burpui/routes.py @@ -1,21 +1,16 @@ # -*- coding: utf8 -*- import math -import sys -from flask import request, render_template, redirect, url_for, abort, flash, Blueprint, session +from flask import request, render_template, redirect, url_for, abort, flash, Blueprint as FlaskBlueprint, session from flask_login import login_user, login_required, logout_user, current_user +from ._compat import quote from .forms import LoginForm from .exceptions import BUIserverException from .utils import human_readable as _hr -if sys.version_info >= (3, 0): - from urllib.parse import quote -else: - from urllib import quote - -class BPWrapper(Blueprint): +class Blueprint(FlaskBlueprint): bui = None __url__ = None __doc__ = None @@ -27,7 +22,7 @@ class BPWrapper(Blueprint): """ self.bui = bui -view = BPWrapper('view', __name__, template_folder='templates') +view = Blueprint('view', 'burpui', template_folder='templates') """ @@ -96,6 +91,8 @@ def settings(server=None, conf=None): if not conf: try: conf = quote(request.args.get('conf'), safe='') + if conf: + return redirect(url_for('.settings', server=server, conf=conf)) except: pass server = server or request.args.get('serverName') @@ -116,6 +113,8 @@ def cli_settings(server=None, client=None, conf=None): if not conf: try: conf = quote(request.args.get('conf'), safe='') + if conf: + return redirect(url_for('.cli_settings', server=server, client=client, conf=conf)) except: pass client = client or request.args.get('client') diff --git a/burpui/server.py b/burpui/server.py index 3b428628..bcfc79d6 100644 --- a/burpui/server.py +++ b/burpui/server.py @@ -54,7 +54,7 @@ class BUIServer(Flask): # We cannot override the Flask's logger so we use our own self.builogger = logging.getLogger('burp-ui') self.builogger.disabled = True - super(BUIServer, self).__init__(__name__) + super(BUIServer, self).__init__('burpui') def enable_logger(self, enable=True): """Enable or disable the logger""" diff --git a/burpui/templates/js/settings.js b/burpui/templates/js/settings.js index 38d290d4..f4c56e62 100644 --- a/burpui/templates/js/settings.js +++ b/burpui/templates/js/settings.js @@ -315,9 +315,9 @@ app.controller('ConfigCtrl', function($scope, $http) { $scope.expandPath = function(index) { path = $scope.includes[index]; {% if client -%} - api = '{{ url_for("api.path_expander", client=client, server=server) }}'; + api = '{{ url_for("api.path_expander", client=client, server=server, source=conf) }}'; {% else -%} - api = '{{ url_for("api.path_expander", server=server) }}'; + api = '{{ url_for("api.path_expander", server=server, source=conf) }}'; {% endif -%} $scope.inc_invalid = {}; $.ajax({ diff --git a/docs/usage.rst b/docs/usage.rst index 41233cf6..b85c3f6d 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -174,6 +174,9 @@ application: # Note: you can have several paths separated by comas. # Example: /etc/burp,/etc/burp.d includes: /etc/burp + # if files already included in config do not respect the above restriction, we + # prune them + enforce: false # enable certificates revocation revoke: false # remember_cookie duration in days diff --git a/share/burpui/etc/buiagent.sample.cfg b/share/burpui/etc/buiagent.sample.cfg index 4a8722fe..3f543023 100644 --- a/share/burpui/etc/buiagent.sample.cfg +++ b/share/burpui/etc/buiagent.sample.cfg @@ -25,6 +25,9 @@ password: password # Note: you can have several paths separated by comas. # Example: /etc/burp,/etc/burp.d includes: /etc/burp +# if files already included in config do not respect the above restriction, we +# prune them +enforce: false # enable certificates revocation revoke: false diff --git a/share/burpui/etc/burpui.sample.cfg b/share/burpui/etc/burpui.sample.cfg index e7f14e60..6b8fb429 100644 --- a/share/burpui/etc/burpui.sample.cfg +++ b/share/burpui/etc/burpui.sample.cfg @@ -56,6 +56,9 @@ redis: localhost:6379 # Note: you can have several paths separated by comas. # Example: /etc/burp,/etc/burp.d includes: /etc/burp +# if files already included in config do not respect the above restriction, we +# prune them +enforce: false # enable certificates revocation revoke: false # remember_cookie duration in days