From 268b2fa756e234110b1eb9f299ba58bbf102c0e9 Mon Sep 17 00:00:00 2001 From: ziirish Date: Sat, 13 Sep 2014 14:33:59 +0200 Subject: [PATCH] new backend 'multi' for #14 --- bin/burp-ui | 2 + burpui/__init__.py | 2 +- burpui/misc/auth/ldap.py | 4 +- burpui/misc/backend/burp1.py | 18 +- burpui/misc/backend/interface.py | 16 +- burpui/misc/backend/multi.py | 249 ++++++++++++++++++++ burpui/routes.py | 180 +++++++++----- burpui/server.py | 27 ++- burpui/templates/backup-report.html | 2 +- burpui/templates/gerard.js | 26 +- burpui/templates/js/backup-report.js | 2 +- burpui/templates/js/client-browse.js | 4 +- burpui/templates/js/client-report.js | 2 +- burpui/templates/js/client.js | 4 +- burpui/templates/js/clients-report.js | 2 +- burpui/templates/js/{home.js => clients.js} | 4 +- burpui/templates/js/live-monitor.js | 6 +- burpui/templates/js/servers.js | 33 +++ burpui/templates/servers.html | 26 ++ burpui/templates/sidebar.html | 10 +- burpui/templates/small_topbar.html | 10 +- burpui/templates/topbar.html | 13 +- share/burpui/etc/burpui.cfg | 11 +- 23 files changed, 534 insertions(+), 119 deletions(-) create mode 100644 burpui/misc/backend/multi.py rename burpui/templates/js/{home.js => clients.js} (84%) create mode 100644 burpui/templates/js/servers.js create mode 100644 burpui/templates/servers.html diff --git a/bin/burp-ui b/bin/burp-ui index fcabd5cd..d457d9b5 100755 --- a/bin/burp-ui +++ b/bin/burp-ui @@ -20,6 +20,8 @@ if __name__ == '__main__': (options, args) = parser.parse_args() d = options.log app.config['DEBUG'] = d + if d: + app.config['TESTING'] = True if options.config: if os.path.isfile(options.config): diff --git a/burpui/__init__.py b/burpui/__init__.py index 024f7eca..c2e4c235 100644 --- a/burpui/__init__.py +++ b/burpui/__init__.py @@ -10,7 +10,7 @@ __license__ = 'BSD 3-clause' from flask import Flask from flask.ext.login import LoginManager -from burpui.server import Server as BurpUI +from burpui.server import BUIServer as BurpUI # First, we setup the app app = Flask(__name__) diff --git a/burpui/misc/auth/ldap.py b/burpui/misc/auth/ldap.py index 7b680cdf..94b8f5b0 100644 --- a/burpui/misc/auth/ldap.py +++ b/burpui/misc/auth/ldap.py @@ -57,8 +57,8 @@ class LdapLoader: query = 'uid={0}'.format(uid) self.app.logger.info('query: %s | base: %s', query, self.base) r = self.ldap.search(query, base_dn=self.base) - except: - self.app.logger.info('Ooops, LDAP lookup failed') + except Exception, e: + self.app.logger.error('Ooops, LDAP lookup failed: %s', str(e)) return None return r[0]['uid'][0] diff --git a/burpui/misc/backend/burp1.py b/burpui/misc/backend/burp1.py index 3b8f8aec..f97179d6 100644 --- a/burpui/misc/backend/burp1.py +++ b/burpui/misc/backend/burp1.py @@ -145,7 +145,7 @@ class Burp(BUIbackend): Utilities functions """ - def status(self, query='\n'): + def status(self, query='\n', agent=None): """ status connects to the burp status port, ask the given 'question' and parses the output in an array @@ -178,7 +178,7 @@ class Burp(BUIbackend): self.logger('error', 'Cannot contact burp server at %s:%s', self.host, self.port) raise BUIserverException('Cannot contact burp server at {0}:{1}'.format(self.host, self.port)) - def parse_backup_log(self, f, n, c=None): + def parse_backup_log(self, f, n, c=None, agent=None): """ parse_backup_log parses the log.gz of a given backup and returns a dict containing different stats used to render the charts in the reporting view @@ -269,7 +269,7 @@ class Burp(BUIbackend): break return backup - def get_counters(self, name=None): + def get_counters(self, name=None, agent=None): """ get_counters parses the stats of the live status for a given client and returns a dict @@ -310,7 +310,7 @@ class Burp(BUIbackend): r['timeleft'] = -1 return r - def is_backup_running(self, name=None): + def is_backup_running(self, name=None, agent=None): """ is_backup_running returns True if the given client is currently running a backup @@ -327,7 +327,7 @@ class Burp(BUIbackend): return True return False - def is_one_backup_running(self): + def is_one_backup_running(self, agent=None): """ is_one_backup_running returns a list of clients name that are currently running a backup @@ -343,7 +343,7 @@ class Burp(BUIbackend): self.running = r return r - def get_all_clients(self): + def get_all_clients(self, agent=None): """ get_all_clients returns a list of dict representing each clients with their name, state and last backup date @@ -370,7 +370,7 @@ class Burp(BUIbackend): j.append(c) return j - def get_client(self, name=None): + def get_client(self, name=None, agent=None): """ get_client returns a list of dict representing the backups (with its number and date) of a given client @@ -399,7 +399,7 @@ class Burp(BUIbackend): r.reverse() return r - def get_tree(self, name=None, backup=None, root=None): + def get_tree(self, name=None, backup=None, root=None, agent=None): """ get_tree returns a list of dict representing files/dir (with their attr) within a given path @@ -442,7 +442,7 @@ class Burp(BUIbackend): r.append(t) return r - def restore_files(self, name=None, backup=None, files=None): + def restore_files(self, name=None, backup=None, files=None, agent=None): if not name or not backup or not files: return None flist = json.loads(files) diff --git a/burpui/misc/backend/interface.py b/burpui/misc/backend/interface.py index 535a48c1..7f6d4668 100644 --- a/burpui/misc/backend/interface.py +++ b/burpui/misc/backend/interface.py @@ -6,28 +6,28 @@ class BUIbackend: self.host = host self.port = port - def status(self, query='\n'): + def status(self, query='\n', agent=None): raise NotImplementedError("Sorry, the current Backend does not implement this method!") - def parse_backup_log(self, f, n, c=None): + def parse_backup_log(self, f, n, c=None, agent=None): raise NotImplementedError("Sorry, the current Backend does not implement this method!") - def get_counters(self, name=None): + def get_counters(self, name=None, agent=None): raise NotImplementedError("Sorry, the current Backend does not implement this method!") - def is_backup_running(self, name=None): + def is_backup_running(self, name=None, agent=None): raise NotImplementedError("Sorry, the current Backend does not implement this method!") - def is_one_backup_running(self): + def is_one_backup_running(self, agent=None): raise NotImplementedError("Sorry, the current Backend does not implement this method!") - def get_all_clients(self): + def get_all_clients(self, agent=None): raise NotImplementedError("Sorry, the current Backend does not implement this method!") - def get_client(self, name=None): + def get_client(self, name=None, agent=None): raise NotImplementedError("Sorry, the current Backend does not implement this method!") - def get_tree(self, name=None, backup=None, root=None): + def get_tree(self, name=None, backup=None, root=None, agent=None): raise NotImplementedError("Sorry, the current Backend does not implement this method!") class BUIserverException(Exception): diff --git a/burpui/misc/backend/multi.py b/burpui/misc/backend/multi.py new file mode 100644 index 00000000..4007628c --- /dev/null +++ b/burpui/misc/backend/multi.py @@ -0,0 +1,249 @@ +# -*- coding: utf8 -*- +import re + +import socket +import sys +import json +import struct +import ConfigParser + +from burpui.misc.backend.interface import BUIbackend, BUIserverException + +class Burp(BUIbackend): + + def __init__(self, app=None, conf=None): + self.app = app + self.servers = {} + self.servers_status = {} + if conf: + config = ConfigParser.ConfigParser() + with open(conf) as fp: + config.readfp(fp) + for sec in config.sections(): + r = re.match('^Agent:(.+)$', sec) + if r: + try: + host = config.get(sec, 'host') + port = config.getint(sec, 'port') + password = config.get(sec, 'password') + ssl = config.getboolean(sec, 'ssl') + except Exception, e: + self.app.logger.error(str(e)) + self.servers[r.group(1)] = NClient(app, host, port, password, ssl) + + self.app.logger.debug(self.servers) + for key, serv in self.servers.iteritems(): + self.servers_status[key] = {'clients': [], 'alive': serv.connected} + if not serv.connected: + continue + for c in serv.get_all_clients(key): + self.servers_status[key]['clients'].append(c['name']) + + """ + Utilities functions + """ + + def status(self, query='\n', agent=None): + """ + status connects to the burp status port, ask the given 'question' and + parses the output in an array + """ + return self.servers[agent].status(query) + + def parse_backup_log(self, f, n, c=None, agent=None): + """ + parse_backup_log parses the log.gz of a given backup and returns a dict + containing different stats used to render the charts in the reporting view + """ + return self.servers[agent].parse_backup_log(f, n, c) + + def get_counters(self, name=None, agent=None): + """ + get_counters parses the stats of the live status for a given client and + returns a dict + """ + return self.servers[agent].get_counters(name) + + def is_backup_running(self, name=None, agent=None): + """ + is_backup_running returns True if the given client is currently running a + backup + """ + return self.servers[agent].is_backup_running(name) + + def is_one_backup_running(self, agent=None): + """ + is_one_backup_running returns a list of clients name that are currently + running a backup + """ + return self.servers[agent].is_one_backup_running() + + def get_all_clients(self, agent=None): + """ + get_all_clients returns a list of dict representing each clients with their + name, state and last backup date + """ + return self.servers[agent].get_all_clients() + + def get_client(self, name=None, agent=None): + """ + get_client returns a list of dict representing the backups (with its number + and date) of a given client + """ + return self.servers[agent].get_client(name) + + def get_tree(self, name=None, backup=None, root=None, agent=None): + """ + get_tree returns a list of dict representing files/dir (with their attr) + within a given path + """ + return self.servers[agent].get_tree(name, backup, root) + + def restore_files(self, name=None, backup=None, files=None, agent=None): + pass + +class NClient(BUIbackend): + + def __init__(self, app=None, host=None, port=None, password=None, ssl=None): + self.host = host + self.port = port + self.password = password + self.ssl = ssl + self.nok = False + self.connected = False + self.app = app + self.conn() + + def conn(self): + try: + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.connect((self.host, self.port)) + self.connected = True + self.app.logger.debug('OK, connected to agent %s:%s', self.host, self.port) + except Exception, e: + self.connected = False + self.app.logger.error('Could not connect to %s:%s => %s', self.host, self.port, str(e)) + + def send_command(self, data=None): + if not data: + return + old_data = data + try: + data['password'] = self.password + raw = json.dumps(data) + length = len(raw) + self.sock.sendall(struct.pack('!Q', length)) + self.sock.sendall(raw) + self.app.logger.debug("Sending: %s", raw) + res = self.sock.recv(10) + self.app.logger.debug("recv: '%s'", res) + if 'OK Result:' != res: + self.app.logger.debug('Ooops, unsuccessful!') + self.nok = True + return + self.app.logger.debug("Data sent successfully") + self.nok = False + except Exception, e: + self.app.logger.error(str(e)) + self.conn() + self.nok = True + self.send_command(old_data) + + def get_result(self): + if self.nok: + return None + self.app.logger.debug('What now?') + lengthbuf = self.sock.recv(8) + length, = struct.unpack('!Q', lengthbuf) + return self.recvall(length) + + def recvall(self, length): + buf = b'' + bsize = 1024 + while length: + newbuf = self.sock.recv(1024) + if not newbuf: return None + buf += newbuf + length -= len(newbuf) + self.app.logger.debug('result: %s', buf) + return buf + + """ + Utilities functions + """ + + def status(self, query='\n', agent=None): + """ + status connects to the burp status port, ask the given 'question' and + parses the output in an array + """ + data = {'func': 'status', 'args': {'query': query}} + self.send_command(data) + return json.loads(self.get_result()) + + def parse_backup_log(self, f, n, c=None, agent=None): + """ + parse_backup_log parses the log.gz of a given backup and returns a dict + containing different stats used to render the charts in the reporting view + """ + data = {'func': 'parse_backup_log', 'args': {'f': f, 'n': n, 'c': c}} + self.send_command(data) + return json.loads(self.get_result()) + + def get_counters(self, name=None, agent=None): + """ + get_counters parses the stats of the live status for a given client and + returns a dict + """ + data = {'func': 'get_counters', 'args': {'name': name}} + self.send_command(data) + return json.loads(self.get_result()) + + def is_backup_running(self, name=None, agent=None): + """ + is_backup_running returns True if the given client is currently running a + backup + """ + data = {'func': 'is_backup_running', 'args': {'name': name}} + self.send_command(data) + return json.loads(self.get_result()) + + def is_one_backup_running(self, agent=None): + """ + is_one_backup_running returns a list of clients name that are currently + running a backup + """ + data = {'func': 'is_one_backup_running', 'args': None} + self.send_command(data) + return json.loads(self.get_result()) + + def get_all_clients(self, agent=None): + """ + get_all_clients returns a list of dict representing each clients with their + name, state and last backup date + """ + data = {'func': 'get_all_clients', 'args': None} + self.send_command(data) + return json.loads(self.get_result()) + + def get_client(self, name=None, agent=None): + """ + get_client returns a list of dict representing the backups (with its number + and date) of a given clientm + """ + data = {'func': 'get_client', 'args': {'name': name}} + self.send_command(data) + return json.loads(self.get_result()) + + def get_tree(self, name=None, backup=None, root=None, agent=None): + """ + get_tree returns a list of dict representing files/dir (with their attr) + within a given path + """ + data = {'func': 'get_tree', 'args': {'name': name, 'backup': backup, 'root': root}} + self.send_command(data) + return json.loads(self.get_result()) + + def restore_files(self, name=None, backup=None, files=None, agent=None): + return None + diff --git a/burpui/routes.py b/burpui/routes.py index 56891bbc..c99b6c52 100644 --- a/burpui/routes.py +++ b/burpui/routes.py @@ -15,15 +15,6 @@ def load_user(userid): return bui.uhandler.user(userid) return None -@app.route('/test/download') -def test_download(): - try: - resp = send_file('/tmp/monfichierr', as_attachment=True) - resp.set_cookie('fileDownload', 'true') - return resp - except Exception, e: - abort(500) - @app.route('/api/restore//', methods=['POST']) @login_required def restore(name=None, backup=None): @@ -47,25 +38,32 @@ The whole API returns JSON-formated data """ @app.route('/api/running-clients.json') +@app.route('/api//running-clients.json') @login_required -def running_clients(): +def running_clients(server=None): """ API: running_clients :returns: a list of running clients """ - r = bui.cli.is_one_backup_running() + if not server: + server = request.args.get('server') + r = bui.cli.is_one_backup_running(agent=server) return jsonify(results=r) @app.route('/api/render-live-template', methods=['GET']) +@app.route('/api//render-live-template', methods=['GET']) @app.route('/api/render-live-template/') +@app.route('/api//render-live-template/') @login_required -def render_live_tpl(name=None): +def render_live_tpl(server=None, name=None): """ API: render_live_tpl :param name: the client name if any. You can also use the GET parameter 'name' to achieve the same thing :returns: HTML that should be included directly into the page """ + if not server: + server = request.args.get('server') c = request.args.get('name') if not name and not c: abort(500) @@ -74,136 +72,166 @@ def render_live_tpl(name=None): if name not in bui.cli.running: abort(404) try: - counters = bui.cli.get_counters(name) + counters = bui.cli.get_counters(name, agent=server) except BUIserverException: counters = [] - return render_template('live-monitor-template.html', cname=name, counters=counters) + return render_template('live-monitor-template.html', cname=name, counters=counters, server=server) + +@app.route('/api/servers.json') +@login_required +def servers_json(): + r = [] + for serv in bui.cli.servers_status: + r.append({'name': serv, 'clients': len(bui.cli.servers_status[serv]['clients']), 'connected': bui.cli.servers_status[serv]['alive']}) + return jsonify(results=r) @app.route('/api/live.json') +@app.route('/api//live.json') @login_required -def live(): +def live(server=None): """ API: live :returns: the live status of the server """ + if not server: + server = request.args.get('server') r = [] - for c in bui.cli.is_one_backup_running(): + for c in bui.cli.is_one_backup_running(agent=server): s = {} s['client'] = c try: - s['status'] = bui.cli.get_counters(c) + s['status'] = bui.cli.get_counters(c, agent=server) except BUIserverException: s['status'] = [] r.append(s) return jsonify(results=r) @app.route('/api/running.json') +@app.route('/api//running.json') @login_required -def backup_running(): +def backup_running(server=None): """ API: backup_running :returns: true if at least one backup is running """ - j = bui.cli.is_one_backup_running() + if not server: + server = request.args.get('server') + j = bui.cli.is_one_backup_running(agent=server) r = len(j) > 0 return jsonify(results=r) @app.route('/api/client-tree.json//', methods=['GET']) +@app.route('/api//client-tree.json//', methods=['GET']) @login_required -def client_tree(name=None, backup=None): +def client_tree(server=None, name=None, backup=None): """ WebService: return a specific client files tree :param name: the client name (mandatory) :param backup: the backup number (mandatory) """ + if not server: + server = request.args.get('server') j = [] if not name or not backup: return jsonify(results=j) root = request.args.get('root') try: - j = bui.cli.get_tree(name, backup, root) + j = bui.cli.get_tree(name, backup, root, agent=server) except BUIserverException, e: err = [[2, str(e)]] return jsonify(notif=err) return jsonify(results=j) @app.route('/api/clients-report.json') +@app.route('/api//clients-report.json') @login_required -def clients_report_json(): +def clients_report_json(server=None): """ WebService: return a JSON with global stats """ + if not server: + server = request.args.get('server') j = [] try: - clients = bui.cli.get_all_clients() + clients = bui.cli.get_all_clients(agent=server) except BUIserverException, e: err = [[2, str(e)]] return jsonify(notif=err) cl = [] ba = [] for c in clients: - client = bui.cli.get_client(c['name']) + client = bui.cli.get_client(c['name'], agent=server) if not client: continue - f = bui.cli.status('c:{0}:b:{1}:f:log.gz\n'.format(c['name'], client[-1]['number'])) - cl.append( { 'name': c['name'], 'stats': bui.cli.parse_backup_log(f, client[-1]['number']) } ) + f = bui.cli.status('c:{0}:b:{1}:f:log.gz\n'.format(c['name'], client[-1]['number']), agent=server) + cl.append( { 'name': c['name'], 'stats': bui.cli.parse_backup_log(f, client[-1]['number'], agent=server) } ) for b in client: - f = bui.cli.status('c:{0}:b:{1}:f:log.gz\n'.format(c['name'], b['number'])) - ba.append(bui.cli.parse_backup_log(f, b['number'], c['name'])) + f = bui.cli.status('c:{0}:b:{1}:f:log.gz\n'.format(c['name'], b['number']), agent=server) + ba.append(bui.cli.parse_backup_log(f, b['number'], c['name']), agent=server) j.append( { 'clients': cl, 'backups': sorted(ba, key=lambda k: k['end']) } ) return jsonify(results=j) @app.route('/api/client-stat.json/') +@app.route('/api//client-stat.json/') @app.route('/api/client-stat.json//') +@app.route('/api//client-stat.json//') @login_required -def client_stat_json(name=None, backup=None): +def client_stat_json(server=None, name=None, backup=None): """ WebService: return a specific client detailed report """ + if not server: + server = request.args.get('server') j = [] if not name: err = [[1, 'No client defined']] return jsonify(notif=err) if backup: try: - f = bui.cli.status('c:{0}:b:{1}:f:log.gz\n'.format(name, backup)) + f = bui.cli.status('c:{0}:b:{1}:f:log.gz\n'.format(name, backup), agent=server) except BUIserverException, e: err = [[2, str(e)]] return jsonify(notif=err) - j = bui.cli.parse_backup_log(f, backup) + j = bui.cli.parse_backup_log(f, backup, agent=server) else: try: - cl = bui.cli.get_client(name) + cl = bui.cli.get_client(name, agent=server) except BUIserverException, e: err = [[2, str(e)]] return jsonify(notif=err) for c in cl: - f = bui.cli.status('c:{0}:b:{1}:f:log.gz\n'.format(name, c['number'])) - j.append(bui.cli.parse_backup_log(f, c['number'])) + f = bui.cli.status('c:{0}:b:{1}:f:log.gz\n'.format(name, c['number']), agent=server) + j.append(bui.cli.parse_backup_log(f, c['number']), agent=server) return jsonify(results=j) @app.route('/api/client.json/') +@app.route('/api//client.json/') @login_required -def client_json(name=None): +def client_json(server=None, name=None): """ WebService: return a specific client backups overview """ + if not server: + server = request.args.get('server') try: - j = bui.cli.get_client(name) + j = bui.cli.get_client(name, agent=server) except BUIserverException, e: err = [[2, str(e)]] return jsonify(notif=err) return jsonify(results=j) @app.route('/api/clients.json') +@app.route('/api//clients.json') @login_required -def clients(): +def clients_json(server=None): """ WebService: return a JSON listing all clients """ + if not server: + server = request.args.get('server') try: - j = bui.cli.get_all_clients() + j = bui.cli.get_all_clients(agent=server) except BUIserverException, e: err = [[2, str(e)]] return jsonify(notif=err) @@ -240,63 +268,83 @@ And here is the main site """ @app.route('/live-monitor') +@app.route('//live-monitor') @app.route('/live-monitor/') +@app.route('//live-monitor/') @login_required -def live_monitor(name=None): +def live_monitor(server=None, name=None): """ Live status monitor view """ + if not server: + server = request.get.args('server') if not bui.cli.running: flash('Sorry, there are no running backups', 'warning') return redirect(url_for('home')) - return render_template('live-monitor.html', live=True, cname=name) + return render_template('live-monitor.html', live=True, cname=name, server=server) @app.route('/client-browse/', methods=['GET']) +@app.route('//client-browse/', methods=['GET']) @app.route('/client-browse//') +@app.route('//client-browse//') @login_required -def client_browse(name=None, backup=None): +def client_browse(server=None, name=None, backup=None): """ Browse a specific backup of a specific client """ + if not server: + server = request.args.get('server') bkp = request.args.get('backup') if bkp and not backup: - return redirect(url_for('client_browse', name=name, backup=bkp)) - return render_template('client-browse.html', tree=True, backup=True, overview=True, cname=name, nbackup=backup) + return redirect(url_for('client_browse', name=name, backup=bkp, server=server)) + return render_template('client-browse.html', tree=True, backup=True, overview=True, cname=name, nbackup=backup, server=server) @app.route('/client-report/') +@app.route('//client-report/') @login_required -def client_report(name=None): +def client_report(server=None, name=None): """ Specific client report """ - l = bui.cli.get_client(name) + if not server: + server = request.args.get('server') + l = bui.cli.get_client(name, agent=server) if len(l) == 1: - return redirect(url_for('backup_report', name=name, backup=l[0]['number'])) - return render_template('client-report.html', client=True, report=True, cname=name) + return redirect(url_for('backup_report', name=name, backup=l[0]['number']), server=server) + return render_template('client-report.html', client=True, report=True, cname=name, server=server) @app.route('/clients-report') +@app.route('//clients-report') @login_required -def clients_report(): +def clients_report(server=None): """ Global report """ - return render_template('clients-report.html', clients=True, report=True) + if not server: + server = request.args.get('server') + return render_template('clients-report.html', clients=True, report=True, server=server) @app.route('/backup-report/', methods=['GET']) +@app.route('//backup-report/', methods=['GET']) @app.route('/backup-report//', methods=['GET']) +@app.route('//backup-report//', methods=['GET']) @login_required -def backup_report(name=None, backup=None): +def backup_report(server=None, name=None, backup=None): """ Backup specific report """ if not backup: backup = request.args.get('backup') - return render_template('backup-report.html', client=True, backup=True, report=True, cname=name, nbackup=backup) + if not server: + server = request.args.get('server') + return render_template('backup-report.html', client=True, backup=True, report=True, cname=name, nbackup=backup, server=server) @app.route('/client', methods=['GET']) +@app.route('//client', methods=['GET']) @app.route('/client/') +@app.route('//client/') @login_required -def client(name=None): +def client(server=None, name=None): """ Specific client overview """ @@ -304,9 +352,24 @@ def client(name=None): c = name else: c = request.args.get('name') - if bui.cli.is_backup_running(c): - return redirect(url_for('live_monitor', name=name)) - return render_template('client.html', client=True, overview=True, cname=c) + if not server: + server = request.args.get('server') + if bui.cli.is_backup_running(c, agent=server): + return redirect(url_for('live_monitor', name=name, server=server)) + return render_template('client.html', client=True, overview=True, cname=c, server=server) + +@app.route('/clients', methods=['GET']) +@app.route('//clients', methods=['GET']) +@login_required +def clients(server=None): + if not server: + server = request.args.get('server') + return render_template('clients.html', clients=True, overview=True, server=server) + +@app.route('/servers', methods=['GET']) +@login_required +def servers(): + return render_template('servers.html', servers=True, overview=True) @app.route('/login', methods=['POST', 'GET']) def login(): @@ -317,6 +380,8 @@ def login(): login_user(user, remember=form.remember.data) flash('Logged in successfully', 'success') return redirect(request.args.get("next") or url_for('home')) + else: + flash('Wrong username or password', 'danger') return render_template('login.html', form=form, login=True) @app.route('/logout') @@ -331,4 +396,7 @@ def home(): """ Home page """ - return render_template('clients.html', clients=True, overview=True) + if bui.standalone: + return redirect(url_for('clients')) + else: + return redirect(url_for('servers')) diff --git a/burpui/server.py b/burpui/server.py index cd317337..639d495b 100644 --- a/burpui/server.py +++ b/burpui/server.py @@ -6,18 +6,19 @@ g_port = 5000 g_bind = '::' g_refresh = 15 g_ssl = False +g_standalone = True g_sslcert = '' g_sslkey = '' g_version = 1 g_auth = 'basic' -class Server: +class BUIServer: def __init__(self, app=None): self.init = False self.app = app def setup(self, conf=None): - global g_refresh, g_port, g_bind, g_ssl, g_sslcert, g_sslkey, g_version, g_auth + global g_refresh, g_port, g_bind, g_ssl, g_sslcert, g_sslkey, g_version, g_auth, g_standalone self.sslcontext = None if not conf: conf = self.app.config['CFG'] @@ -27,7 +28,8 @@ class Server: config = ConfigParser.ConfigParser({'port': g_port,'bind': g_bind, 'refresh': g_refresh, 'ssl': g_ssl, 'sslcert': g_sslcert, - 'sslkey': g_sslkey, 'version': g_version, 'auth': g_auth}) + 'sslkey': g_sslkey, 'version': g_version, 'auth': g_auth, + 'standalone': g_standalone}) with open(conf) as fp: config.readfp(fp) try: @@ -39,6 +41,11 @@ class Server: except ValueError: self.app.logger.error("Wrong value for 'ssl' key! Assuming 'false'") self.ssl = False + try: + self.standalone = config.getboolean('Global', 'standalone') + except ValueError: + self.app.logger.error("Wrong value for 'standalone' key! Assuming 'True'") + self.standalone = True self.sslcert = config.get('Global', 'sslcert') self.sslkey = config.get('Global', 'sslkey') self.auth = config.get('Global', 'auth') @@ -53,21 +60,28 @@ class Server: else: # I know that's ugly, but hey, I need it! self.app.login_manager._login_disabled = True - except ConfigParser.NoOptionError: - self.app.logger.error("Missing option") + except ConfigParser.NoOptionError, e: + self.app.logger.error(str(e)) self.app.config['REFRESH'] = config.getint('UI', 'refresh') + self.app.config['STANDALONE'] = self.standalone + self.app.logger.info('burp version: %d', self.vers) self.app.logger.info('listen port: %d', self.port) self.app.logger.info('bind addr: %s', self.bind) self.app.logger.info('use ssl: %s', self.ssl) + self.app.logger.info('standalone: %s', self.standalone) self.app.logger.info('sslcert: %s', self.sslcert) self.app.logger.info('sslkey: %s', self.sslkey) self.app.logger.info('refresh: %d', self.app.config['REFRESH']) + if self.standalone: + module = 'burpui.misc.backend.burp{0}'.format(self.vers) + else: + module = 'burpui.misc.backend.multi' try: - mod = __import__('burpui.misc.backend.burp{0}'.format(self.vers), fromlist=['Burp']) + mod = __import__(module, fromlist=['Burp']) Client = mod.Burp self.cli = Client(self.app, conf=conf) except Exception, e: @@ -87,6 +101,7 @@ class Server: self.sslcontext.use_certificate_file(self.sslcert) if self.sslcontext: + self.app.config['SSL'] = True self.app.run(host=self.bind, port=self.port, debug=debug, ssl_context=self.sslcontext) else: self.app.run(host=self.bind, port=self.port, debug=debug) diff --git a/burpui/templates/backup-report.html b/burpui/templates/backup-report.html index 33c5c4b5..fe358a32 100644 --- a/burpui/templates/backup-report.html +++ b/burpui/templates/backup-report.html @@ -5,7 +5,7 @@ {% include "small_topbar.html" %}
diff --git a/burpui/templates/gerard.js b/burpui/templates/gerard.js index 28fa6f58..6cf85dd6 100644 --- a/burpui/templates/gerard.js +++ b/burpui/templates/gerard.js @@ -34,8 +34,9 @@ var notif = function(type, message) { }; {% if not login -%} + {% if not (servers and overview) -%} var _check_running = function() { - url = '{{ url_for("backup_running") }}'; + url = '{{ url_for("backup_running", server=server) }}'; $.getJSON(url, function(data) { if (data.results) { $('#toblink').addClass('blink'); @@ -44,6 +45,11 @@ var _check_running = function() { } }); }; + {% else -%} +var _check_running = function() { + return false; +}; + {% endif -%} {% endif -%} /*** @@ -54,7 +60,7 @@ var _clients_bh = new Bloodhound({ queryTokenizer: Bloodhound.tokenizers.whitespace, limit: 10, prefetch: { - url: '{{ url_for("clients") }}', + url: '{{ url_for("clients_json", server=server) }}', filter: function(list) { if (list.results) { return list.results; @@ -75,9 +81,12 @@ $('#input-client').typeahead(null, { source: _clients_bh.ttAdapter() }); +{% if servers and overview -%} +{% include "js/servers.js" %} +{% endif -%} {% if clients and overview -%} -{% include "js/home.js" %} +{% include "js/clients.js" %} {% endif -%} {% if clients and report -%} @@ -148,6 +157,9 @@ $(function() { {% if not login -%} _check_running(); {% endif -%} + {% if servers and overview -%} + _servers(); + {% endif -%} }); /*** @@ -156,7 +168,7 @@ $(function() { var search = $('input[id="input-client"]'); search.keypress(function(e) { if (e.which == 13) { - window.location = '{{ url_for("client") }}?name='+search.val(); + window.location = '{{ url_for("client", server=server) }}?name='+search.val(); } }); @@ -182,6 +194,9 @@ $(function() { {% if live -%} _live(); {% endif -%} + {% if servers and overview -%} + _servers(); + {% endif -%} {% if not report and not login -%} /*** @@ -197,6 +212,9 @@ $(function() { {% if live -%} _live(); {% endif -%} + {% if servers and overview -%} + _servers(); + {% endif -%} return; }, {{ config.REFRESH * 1000 }}); {% endif -%} diff --git a/burpui/templates/js/backup-report.js b/burpui/templates/js/backup-report.js index c8803185..f8d5a5d5 100644 --- a/burpui/templates/js/backup-report.js +++ b/burpui/templates/js/backup-report.js @@ -34,7 +34,7 @@ var _client = function() { chart_unified.yAxis.tickFormat(d3.format(',.0f')); } - url = '{{ url_for("client_stat_json", name=cname, backup=nbackup) }}'; + url = '{{ url_for("client_stat_json", name=cname, backup=nbackup, server=server) }}'; $.getJSON(url, function(d) { var _fields = [ 'dir', 'files', 'hardlink', 'softlink' ]; j = d.results; diff --git a/burpui/templates/js/client-browse.js b/burpui/templates/js/client-browse.js index 0040abd5..99ef7277 100644 --- a/burpui/templates/js/client-browse.js +++ b/burpui/templates/js/client-browse.js @@ -56,7 +56,7 @@ }, source: function() { r = []; - $.getJSON('{{ url_for("client_tree", name=cname, backup=nbackup) }}', function(data) { + $.getJSON('{{ url_for("client_tree", name=cname, backup=nbackup, server=server) }}', function(data) { if (!data.results) { if (data.notif) { $.each(data.notif, function(i, n) { @@ -84,7 +84,7 @@ r = []; p = node.key; if (p !== "/") p += '/'; - $.getJSON('{{ url_for("client_tree", name=cname, backup=nbackup) }}?root='+p, function(data) { + $.getJSON('{{ url_for("client_tree", name=cname, backup=nbackup, server=server) }}?root='+p, function(data) { if (!data.results) { if (data.notif) { $.each(data.notif, function(i, n) { diff --git a/burpui/templates/js/client-report.js b/burpui/templates/js/client-report.js index 27befdf3..984bf168 100644 --- a/burpui/templates/js/client-report.js +++ b/burpui/templates/js/client-report.js @@ -73,7 +73,7 @@ var _client = function() { _chart_stats.bars.forceY([0]); } - url = '{{ url_for("client_stat_json", name=cname) }}'; + url = '{{ url_for("client_stat_json", name=cname, server=server) }}'; $.getJSON(url, function(d) { var _fields = [ 'dir', 'files', 'hardlink', 'softlink', 'files_enc', 'meta', 'meta_enc', 'special', 'efs', 'vssheader', 'vssheader_enc', 'vssfooter', 'vssfooter_enc' ]; var stats = true; diff --git a/burpui/templates/js/client.js b/burpui/templates/js/client.js index d0c16f60..962dd227 100644 --- a/burpui/templates/js/client.js +++ b/burpui/templates/js/client.js @@ -22,7 +22,7 @@ * The JSON is then parsed into a table */ var _client = function() { - url = '{{ url_for("client_json", name=cname) }}'; + url = '{{ url_for("client_json", name=cname, server=server) }}'; $.getJSON(url, function(data) { $('#table-client >tbody:last').empty(); $('#client-alert').hide(); @@ -41,7 +41,7 @@ var _client = function() { $('#client-alert').show(); } else { $.each(data.results.reverse(), function(j, c) { - $('#table-client> tbody:last').append(''+pad(c.number, 7)+''+c.date+''); + $('#table-client> tbody:last').append(''+pad(c.number, 7)+''+c.date+''); }); } }); diff --git a/burpui/templates/js/clients-report.js b/burpui/templates/js/clients-report.js index 6527dcef..edb0d2eb 100644 --- a/burpui/templates/js/clients-report.js +++ b/burpui/templates/js/clients-report.js @@ -35,7 +35,7 @@ var _clients = function() { _charts_obj.push({ 'key': 'chart_'+j, 'obj': tmp, 'data': [] }); }); } - url = '{{ url_for("clients_report_json") }}'; + url = '{{ url_for("clients_report_json", server=server) }}'; $.getJSON(url, function(d) { rep = []; size = []; diff --git a/burpui/templates/js/home.js b/burpui/templates/js/clients.js similarity index 84% rename from burpui/templates/js/home.js rename to burpui/templates/js/clients.js index 82982cad..01e01db9 100644 --- a/burpui/templates/js/home.js +++ b/burpui/templates/js/clients.js @@ -33,7 +33,7 @@ var __status = { * The JSON is then parsed into a table */ var _clients = function() { - url = '{{ url_for("clients") }}'; + url = '{{ url_for("clients_json", server=server) }}'; $.getJSON(url, function(data) { $('#table-clients > tbody:last').empty(); if (!data.results) { @@ -49,7 +49,7 @@ var _clients = function() { if (__status[c.state] != undefined) { clas = ' '+__status[c.state]; } - $('#table-clients > tbody:last').append(''+c.name+''+c.state+''+c.last+''); + $('#table-clients > tbody:last').append(''+c.name+''+c.state+''+c.last+''); }); }); }; diff --git a/burpui/templates/js/live-monitor.js b/burpui/templates/js/live-monitor.js index a75e880a..63bba1ea 100644 --- a/burpui/templates/js/live-monitor.js +++ b/burpui/templates/js/live-monitor.js @@ -1,13 +1,13 @@ _live = function() { - url = '{{ url_for("running_clients") }}'; + url = '{{ url_for("running_clients", server=server) }}'; html = '' $.getJSON(url, function(data) { if (!data.results || data.results.length === 0) { - document.location = '{{ url_for("home") }}'; + document.location = '{{ url_for("home", server=server) }}'; } $.each(data.results, function(i, c) { - u = '{{ url_for("render_live_tpl") }}?name='+c; + u = '{{ url_for("render_live_tpl", server=server) }}?name='+c; $.get(u, function(d) { html += d; }); diff --git a/burpui/templates/js/servers.js b/burpui/templates/js/servers.js new file mode 100644 index 00000000..fcea3bfc --- /dev/null +++ b/burpui/templates/js/servers.js @@ -0,0 +1,33 @@ + +/*** + * Here is the 'servers' part + * It is available on the global clients view + */ + +/*** + * _servers: function that retrieve up-to-date informations from the burp server + * The JSON is then parsed into a table + */ +var _servers = function() { + url = '{{ url_for("servers_json") }}'; + $.getJSON(url, function(data) { + $('#table-servers > tbody:last').empty(); + if (!data.results) { + if (data.notif) { + $.each(data.notif, function(i, n) { + notif(n[0], n[1]); + }); + } + return; + } + $.each(data.results, function(j, c) { + cl = ''; + glyph = 'glyphicon-ok'; + if (!c.connected) { + cl = ' danger'; + glyph = 'glyphicon-remove'; + } + $('#table-servers > tbody:last').append(''+c.name+''+c.clients+''); + }); + }); +}; diff --git a/burpui/templates/servers.html b/burpui/templates/servers.html new file mode 100644 index 00000000..e649c744 --- /dev/null +++ b/burpui/templates/servers.html @@ -0,0 +1,26 @@ +{% extends "layout.html" %} +{% block body %} + {% include "notifications.html" %} +
+ {% include "small_topbar.html" %} + +
+

Servers

+ +
+ + + + + + + + + + +
NameClientsStatus
+
+
+{% endblock %} diff --git a/burpui/templates/sidebar.html b/burpui/templates/sidebar.html index 95359101..9ceda7f0 100644 --- a/burpui/templates/sidebar.html +++ b/burpui/templates/sidebar.html @@ -2,9 +2,9 @@