server.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. from __future__ import print_function
  4. from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
  5. from storage import Storage
  6. import json
  7. import logging
  8. import re
  9. import socket
  10. from SocketServer import ThreadingMixIn
  11. import time
  12. class BatcaveHttpRequestHandler(BaseHTTPRequestHandler):
  13. def __init__(self, request, client_address, server):
  14. self.logger = logging.getLogger('API')
  15. BaseHTTPRequestHandler.__init__(self, request, client_address, server)
  16. def parse_url_pathquery(self):
  17. url = re.match(r'^/(?P<path>.*?)(\?(?P<query>.+))?$', self.path.strip())
  18. if url is None:
  19. logging.warn('Failed to parse URL \'' + str(self.path) + '\'.')
  20. return ( None, None )
  21. path = url.group('path')
  22. query = {}
  23. if not url.group('query') is None:
  24. for m in re.finditer(r'(?P<key>.+?)=(?P<value>.+?)(&|$)', url.group('query')):
  25. query[m.group('key')] = m.group('value')
  26. return ( path, query )
  27. def do_GET(self):
  28. path, query = self.parse_url_pathquery()
  29. if path is None:
  30. self.send_error(400, 'Could not parse URL (' + str(self.path) + ')')
  31. return
  32. if path == '':
  33. self.respond_index(query)
  34. return
  35. if path == 'list':
  36. self.respond_list(query)
  37. return
  38. if path == 'vpn':
  39. self.respond_vpn(query)
  40. return
  41. m = re.match(r'node/([a-f0-9]{12})(?P<cmd>\.json|/[a-zA-Z0-9_\-]+)$', path)
  42. if m != None:
  43. cmd = m.group('cmd')
  44. if cmd == '.json':
  45. self.respond_node(m.group(1))
  46. else:
  47. self.respond_nodedetail(m.group(1), cmd[1:])
  48. return
  49. self.send_error(404, 'The URL \'{0}\' was not found here.'.format(path))
  50. def send_nocache_headers(self):
  51. self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate')
  52. self.send_header('Pragma', 'no-cache')
  53. self.send_header('Expires', '0')
  54. def send_headers(self, content_type='text/html; charset=utf-8', nocache=True):
  55. self.send_response(200)
  56. self.send_header('Content-Type', content_type)
  57. if nocache: self.send_nocache_headers()
  58. self.end_headers()
  59. def respond_index(self, query):
  60. storagy = self.server.storage
  61. self.send_headers()
  62. self.wfile.write('<!DOCTYPE html><html><head><title>BATCAVE</title></head>\n')
  63. self.wfile.write('<body>\n')
  64. self.wfile.write('<H1 title="Batman/Alfred Transmission Collection, Aggregation & Value Engine">BATCAVE</H1>\n')
  65. self.wfile.write('<p>Dies ist ein interner Hintergrund-Dienst. Er wird nur von anderen Diensten\n')
  66. self.wfile.write('angesprochen und sollte aus einer Mehrzahl von Gr&uuml;nden nicht &ouml;ffentlich\n')
  67. self.wfile.write('zug&auml;nglich sein.</p>\n')
  68. self.wfile.write('<H2>Status</H2>\n')
  69. self.wfile.write('Daten: <span id="datacount" class="value">')
  70. self.wfile.write(len(storage.data))
  71. self.wfile.write('</span>\n')
  72. self.wfile.write('<H2>API</H2>\n')
  73. self.wfile.write('<p>Grundsätzlich ist das Antwort-Format JSON und alle Daten sind Live-Daten (kein Cache) die ggf. etwas Bearbeitungs-Zeit erfordern.</p>')
  74. self.wfile.write('<dl>\n')
  75. self.wfile.write('<dt><a href="/nodes.json">nodes.json</a></dt><dd>zur Verwendung mit ffmap (MACs anonymisiert)</dd>\n')
  76. self.wfile.write('<dt><a href="/node/ff00ff00ff00.json">/node/&lt;id&gt;.json</a></dt><dd><u>alle</u> vorhandenen Information zu der gewünschten Node</dd>\n')
  77. self.wfile.write('</dl>\n')
  78. self.wfile.write('</body></html>')
  79. def respond_list(self, query):
  80. storage = self.server.storage
  81. self.send_headers()
  82. self.wfile.write('<!DOCTYPE html><html><head><title>BATCAVE</title></head>\n')
  83. self.wfile.write('<body>\n')
  84. self.wfile.write('<H1>BATCAVE - LIST</H1>\n')
  85. self.wfile.write('<table>\n')
  86. self.wfile.write('<thead><tr><th>ID</th><th>Name</th></tr></thead>\n')
  87. self.wfile.write('<tbody>\n')
  88. data = storage.data
  89. if 'sort' in query:
  90. if query['sort'] == 'name':
  91. sorteddata = sorted(data, key=lambda x: data[x]['hostname'].lower())
  92. data = sorteddata
  93. elif query['sort'] == 'id':
  94. sorteddata = sorted(data)
  95. data = sorteddata
  96. for nodeid in data:
  97. nodename = storage.data[nodeid]['hostname'] if 'hostname' in storage.data[nodeid] else '&lt;?&gt;'
  98. self.wfile.write('<tr><td><a href="/node/' + nodeid + '.json">' + nodeid + '</a></td><td>' + nodename + '</td></tr>')
  99. self.wfile.write('</tbody>\n')
  100. self.wfile.write('</table>\n')
  101. def respond_node(self, nodeid):
  102. storage = self.server.storage
  103. if nodeid == 'ff00ff00ff00':
  104. self.send_headers('text/json')
  105. self.wfile.write(json.dumps({
  106. 'name': 'API-Example',
  107. 'nodeid': nodeid,
  108. 'META': 'Dies ist ein minimaler Beispiel-Datensatz. Herzlichen Glückwunsch, du hast das Prinzip der API kapiert.',
  109. }))
  110. return
  111. if not nodeid in storage.data:
  112. self.send_error(404, 'No node with id \'' + nodeid + '\' present.')
  113. return
  114. self.send_headers('text/json')
  115. self.wfile.write(json.dumps(storage.data[nodeid]))
  116. def respond_nodedetail(self, nodeid, field):
  117. storage = self.server.storage
  118. if not nodeid in storage.data:
  119. self.send_error(404, 'No node with id \'' + nodeid + '\' present.')
  120. return
  121. if not field in storage.data[nodeid]:
  122. self.send_error(404, 'The node \'' + nodeid + '\' does not have a field named \'' + str(field) + '\'.')
  123. return
  124. value = storage.data[nodeid][field]
  125. self.send_headers('text/plain' if isinstance(value, basestring) else 'text/json')
  126. self.wfile.write(value if isinstance(value, basestring) else json.dumps(value))
  127. def respond_vpn(self, query):
  128. storage = self.server.storage
  129. peername = query['peer'] if 'peer' in query else None
  130. key = query['key'] if 'key' in query else None
  131. action = query['action'] if 'action' in query else None
  132. remote = query['remote'] if 'remote' in query else None
  133. gw = query['gw'] if 'gw' in query else None
  134. if action == 'list':
  135. self.respond_vpnlist()
  136. return
  137. if action != 'establish' and action != 'disestablish':
  138. self.logger.error('VPN: unknown action \'{0}\''.format(action))
  139. self.send_error(400, 'Invalid action.')
  140. return
  141. for k,v in { 'peername': peername, 'key': key, 'remote': remote, 'gw': gw }.items():
  142. if v is None or len(v.strip()) == 0:
  143. self.logger.error('VPN {0}: no or empty {1}'.format(action, k))
  144. self.send_error(400, 'Missing value for ' + str(k))
  145. return
  146. if key is None or re.match(r'^[a-fA-F0-9]+$', key) is None:
  147. self.logger.error('VPN peer \'{0}\' {1}: bad key \'{2}\''.format(peername, action, key))
  148. self.send_error(400, 'Bad key.')
  149. return
  150. if not 'vpn' in storage.data: storage.data['vpn'] = {}
  151. if not key in storage.data['vpn']: storage.data['vpn'][key] = { 'active': {}, 'last': {} }
  152. item = storage.data['vpn'][key]
  153. if action == 'establish':
  154. item['active'][gw] = { 'establish': time.time(), 'peer': peername, 'remote': remote }
  155. elif action == 'disestablish':
  156. active = {}
  157. if gw in item['active']:
  158. active = item['active'][gw]
  159. del(item['active'][gw])
  160. active['disestablish'] = time.time()
  161. item['last'][gw] = active
  162. else:
  163. self.send_error(500, 'Unknown action not filtered (' + str(action) + ')')
  164. return
  165. self.send_headers('text/plain')
  166. self.wfile.write('OK')
  167. storage.save()
  168. def respond_vpnlist(self):
  169. storage = self.server.storage
  170. self.send_headers()
  171. self.wfile.write('<!DOCTYPE html>\n')
  172. self.wfile.write('<html><head><title>BATCAVE - VPN LIST</title></head>\n')
  173. self.wfile.write('<body>\n')
  174. self.wfile.write('<table>\n<thead><tr><th>key</th><th>active</th><th>last</th></tr></thead>\n')
  175. if 'vpn' in storage.data:
  176. for key in storage.data['vpn']:
  177. item = storage.data['vpn'][key]
  178. self.wfile.write('<tr><td>' + str(key) + '</td>')
  179. self.wfile.write('<td>' + json.dumps(item['active'] if 'active' in item else {}) + '</td>')
  180. self.wfile.write('<td>' + json.dumps(item['last'] if 'last' in item else {}) + '</td>')
  181. self.wfile.write('</tr>\n')
  182. self.wfile.write('</table>\n')
  183. self.wfile.write('</body>')
  184. self.wfile.write('</html>')
  185. class ApiServer(ThreadingMixIn, HTTPServer):
  186. def __init__(self, endpoint, storage):
  187. if ':' in endpoint[0]: self.address_family = socket.AF_INET6
  188. HTTPServer.__init__(self, endpoint, BatcaveHttpRequestHandler)
  189. self.storage = storage
  190. def __str__(self):
  191. return 'ApiServer on {0}'.format(self.server_address)
  192. if __name__ == '__main__':
  193. dummystorage = Storage()
  194. server = ApiServer(('0.0.0.0', 8888), dummystorage)
  195. print("Server:", str(server))
  196. server.serve_forever()