|
@@ -57,24 +57,26 @@ def normalize_ispname(isp):
|
|
|
|
|
|
|
|
|
class BatcaveHttpRequestHandler(BaseHTTPRequestHandler):
|
|
|
+ """Handles a single HTTP request to the BATCAVE."""
|
|
|
|
|
|
- def __init__(self, request, client_address, server):
|
|
|
+ def __init__(self, request, client_address, sockserver):
|
|
|
self.logger = logging.getLogger('API')
|
|
|
- BaseHTTPRequestHandler.__init__(self, request, client_address, server)
|
|
|
+ BaseHTTPRequestHandler.__init__(
|
|
|
+ self, request, client_address, sockserver)
|
|
|
|
|
|
def parse_url_pathquery(self):
|
|
|
"""Extracts the query parameters from the request path."""
|
|
|
url = re.match(r'^/(?P<path>.*?)(\?(?P<query>.+))?$', self.path.strip())
|
|
|
if url is None:
|
|
|
logging.warn('Failed to parse URL \'' + str(self.path) + '\'.')
|
|
|
- return ( None, None )
|
|
|
+ return (None, None)
|
|
|
|
|
|
path = url.group('path')
|
|
|
query = {}
|
|
|
if not url.group('query') is None:
|
|
|
- return ( path, query )
|
|
|
for match in REGEX_QUERYPARAM.finditer(url.group('query')):
|
|
|
query[match.group('key')] = match.group('value')
|
|
|
+ return (path, query)
|
|
|
|
|
|
def do_GET(self):
|
|
|
"""Handles all HTTP GET requests."""
|
|
@@ -143,13 +145,17 @@ class BatcaveHttpRequestHandler(BaseHTTPRequestHandler):
|
|
|
self.send_error(404, 'The URL \'{0}\' was not found here.'.format(path))
|
|
|
|
|
|
def send_nocache_headers(self):
|
|
|
- """Sets HTTP headers indicating that this response shall not be cached."""
|
|
|
+ """
|
|
|
+ Sets HTTP headers indicating that this response shall not be cached.
|
|
|
+ """
|
|
|
|
|
|
self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate')
|
|
|
self.send_header('Pragma', 'no-cache')
|
|
|
self.send_header('Expires', '0')
|
|
|
|
|
|
- def send_headers(self, content_type='text/html; charset=utf-8', nocache=True):
|
|
|
+ def send_headers(self,
|
|
|
+ content_type='text/html; charset=utf-8',
|
|
|
+ nocache=True):
|
|
|
"""Send HTTP 200 Response header with the given Content-Type.
|
|
|
Optionally send no-caching headers, too."""
|
|
|
|
|
@@ -161,13 +167,20 @@ class BatcaveHttpRequestHandler(BaseHTTPRequestHandler):
|
|
|
|
|
|
def parse_post_params(self):
|
|
|
ctype, pdict = cgi.parse_header(self.headers.getheader('content-type'))
|
|
|
+
|
|
|
if ctype == 'multipart/form-data':
|
|
|
postvars = cgi.parse_multipart(self.rfile, pdict)
|
|
|
+
|
|
|
elif ctype == 'application/x-www-form-urlencoded':
|
|
|
length = int(self.headers.getheader('content-length'))
|
|
|
- postvars = cgi.parse_qs(self.rfile.read(length), keep_blank_values=1)
|
|
|
+ postvars = cgi.parse_qs(
|
|
|
+ self.rfile.read(length),
|
|
|
+ keep_blank_values=1,
|
|
|
+ )
|
|
|
+
|
|
|
else:
|
|
|
postvars = {}
|
|
|
+
|
|
|
return postvars
|
|
|
|
|
|
def respond_index(self, query):
|
|
@@ -217,7 +230,10 @@ class BatcaveHttpRequestHandler(BaseHTTPRequestHandler):
|
|
|
nodeid = node['node_id']
|
|
|
nodename = node['hostname'] if 'hostname' in node else '<?>'
|
|
|
|
|
|
- self.wfile.write('<tr><td><a href="/node/' + nodeid + '.json">' + nodeid + '</a></td><td>' + nodename + '</td></tr>')
|
|
|
+ self.wfile.write('<tr>\n')
|
|
|
+ self.wfile.write(' <td><a href="/node/{0}.json">{0}</a></td>\n'.format(nodeid))
|
|
|
+ self.wfile.write(' <td>{0}</td>\n'.format(nodename))
|
|
|
+ self.wfile.write('</tr>\n')
|
|
|
|
|
|
self.wfile.write('</tbody>\n')
|
|
|
self.wfile.write('</table>\n')
|
|
@@ -253,12 +269,20 @@ class BatcaveHttpRequestHandler(BaseHTTPRequestHandler):
|
|
|
|
|
|
self.send_headers('text/plain')
|
|
|
for nodeid in ids:
|
|
|
- node = self.server.storage.find_node(nodeid) if not ':' in nodeid else self.server.storage.find_node_by_mac(nodeid)
|
|
|
- nodename = node['hostname'] if (not node is None) and 'hostname' in node else nodeid
|
|
|
+ node = None
|
|
|
+ if not ':' in nodeid:
|
|
|
+ node = self.server.storage.find_node(nodeid)
|
|
|
+ else:
|
|
|
+ node = self.server.storage.find_node_by_mac(nodeid)
|
|
|
+ nodename = node.get('hostname', nodeid) if node is not None else nodeid
|
|
|
self.wfile.write('{0}={1}\n'.format(nodeid, nodename))
|
|
|
|
|
|
def respond_nodedetail(self, nodeid, field):
|
|
|
- """Return a field from the given node - a string is returned as text, all other as JSON."""
|
|
|
+ """
|
|
|
+ Return a field from the given node.
|
|
|
+ String and integers are returned as text/plain,
|
|
|
+ all other as JSON.
|
|
|
+ """
|
|
|
|
|
|
node = self.server.storage.find_node(nodeid)
|
|
|
if node is None:
|
|
@@ -271,24 +295,30 @@ class BatcaveHttpRequestHandler(BaseHTTPRequestHandler):
|
|
|
field = field[0:-6]
|
|
|
|
|
|
if not field in node:
|
|
|
- self.send_error(404, 'The node \'' + nodeid + '\' does not have a field named \'' + str(field) + '\'.')
|
|
|
+ self.send_error(
|
|
|
+ 404,
|
|
|
+ 'The node \'{0}\' does not have a field named \'{1}\'.'.format(
|
|
|
+ nodeid, field
|
|
|
+ )
|
|
|
+ )
|
|
|
return
|
|
|
|
|
|
value = node[field]
|
|
|
if return_count:
|
|
|
value = len(value)
|
|
|
|
|
|
- self.send_headers('text/plain' if isinstance(value, basestring) or isinstance(value, int) else 'text/json')
|
|
|
- self.wfile.write(value if isinstance(value, basestring) else json.dumps(value))
|
|
|
+ no_json = isinstance(value, basestring) or isinstance(value, int)
|
|
|
+ self.send_headers('text/plain' if no_json else 'text/json')
|
|
|
+ self.wfile.write(value if no_json else json.dumps(value))
|
|
|
|
|
|
def respond_vpn(self, query):
|
|
|
storage = self.server.storage
|
|
|
- peername = query['peer'] if 'peer' in query else None
|
|
|
- key = query['key'] if 'key' in query else None
|
|
|
- action = query['action'] if 'action' in query else None
|
|
|
- remote = query['remote'] if 'remote' in query else None
|
|
|
- gw = query['gw'] if 'gw' in query else None
|
|
|
- ts = query['ts'] if 'ts' in query else time.time()
|
|
|
+ peername = query.get('peer')
|
|
|
+ key = query.get('key')
|
|
|
+ action = query.get('action')
|
|
|
+ remote = query.get('remote')
|
|
|
+ gateway = query.get('gw')
|
|
|
+ timestamp = query.get('ts', time.time())
|
|
|
|
|
|
if action == 'list':
|
|
|
self.respond_vpnlist()
|
|
@@ -299,7 +329,12 @@ class BatcaveHttpRequestHandler(BaseHTTPRequestHandler):
|
|
|
self.send_error(400, 'Invalid action.')
|
|
|
return
|
|
|
|
|
|
- check = {'peername': peername, 'key': key, 'remote': remote, 'gw': gw}
|
|
|
+ check = {
|
|
|
+ 'peername': peername,
|
|
|
+ 'key': key,
|
|
|
+ 'remote': remote,
|
|
|
+ 'gw': gateway,
|
|
|
+ }
|
|
|
for k, val in check.items():
|
|
|
if val is None or len(val.strip()) == 0:
|
|
|
self.logger.error('VPN {0}: no or empty {1}'.format(action, k))
|
|
@@ -343,8 +378,10 @@ class BatcaveHttpRequestHandler(BaseHTTPRequestHandler):
|
|
|
self.wfile.write('table tbody tr.online { background-color: #CFC; }\n')
|
|
|
self.wfile.write('table tbody tr.offline { background-color: #FCC; }\n')
|
|
|
self.wfile.write('</style>\n')
|
|
|
- self.wfile.write('<table>\n<thead>\n')
|
|
|
+ self.wfile.write('<table>\n')
|
|
|
+
|
|
|
gateways = self.server.storage.get_vpn_gateways()
|
|
|
+ self.wfile.write('<thead>\n')
|
|
|
self.wfile.write('<tr><th rowspan="2">names (key)</th><th colspan="' + str(len(gateways)) + '">active</th><th colspan="' + str(len(gateways)) + '">last</th></tr>\n')
|
|
|
self.wfile.write('<tr><th>' + '</th><th>'.join(gateways) + '</th><th>' + '</th><th>'.join(gateways) + '</th></tr>\n')
|
|
|
self.wfile.write('</thead>\n')
|
|
@@ -414,7 +451,13 @@ class BatcaveHttpRequestHandler(BaseHTTPRequestHandler):
|
|
|
item_isps.add('unknown')
|
|
|
|
|
|
elif len(item_isps) > 1:
|
|
|
- self.logger.warn('VPN key \'{0}\' has {1} active IPs which resolved to {2} ISPs: \'{3}\''.format(key, len(ips), len(item_isps), '\', \''.join(item_isps)))
|
|
|
+ self.logger.warn(
|
|
|
+ 'VPN key \'%s\' has %d active IPs which resolved to %d ISPs: \'%s\'',
|
|
|
+ item['key'],
|
|
|
+ len(remotes),
|
|
|
+ len(item_isps),
|
|
|
+ '\', \''.join(item_isps)
|
|
|
+ )
|
|
|
|
|
|
for isp in item_isps:
|
|
|
if not isp in isps:
|