Commit 268b2fa7 authored by Benjamin "Ziirish" SANS's avatar Benjamin "Ziirish" SANS
Browse files

new backend 'multi' for #14

parent bfdf3d7e
......@@ -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):
......
......@@ -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__)
......
......@@ -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]
......
......@@ -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)
......
......@@ -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):
......
# -*- 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
......@@ -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/<name>/<int:backup>', 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/<server>/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/<server>/render-live-template', methods=['GET'])
@app.route('/api/render-live-template/<name>')
@app.route('/api/<server>/render-live-template/<name>')
@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/<server>/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/<server>/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/<name>/<int:backup>', methods=['GET'])
@app.route('/api/<server>/client-tree.json/<name>/<int:backup>', 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/<server>/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/<name>')
@app.route('/api/<server>/client-stat.json/<name>')
@app.route('/api/client-stat.json/<name>/<int:backup>')
@app.route('/api/<server>/client-stat.json/<name>/<int:backup>')
@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,</