diff -r 60ead38a61af -r da291d9426ea rest.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rest.py Sat Nov 29 05:04:07 2014 +0200 @@ -0,0 +1,194 @@ +import ssl +import socket +import errno +import select +import asyncore +import base64 +import re +import json +import urllib +import irc +from datetime import datetime, timedelta +from configfile import Config + +g_credentials = None +g_portnumber = None +g_throttle = [] + +valid_repos = ['Torr_Samaho/zandronum', 'Torr_Samaho/zandronum-stable', + 'crimsondusk/zandronum-sandbox', 'crimsondusk/zandronum-sandbox-stable', + 'crimsondusk/testrepo'] + +def is_throttled (address): + i = 0 + + while i < len (g_throttle): + if g_throttle[i][1] <= datetime.utcnow(): + print 'Throttle of %s expired' % g_throttle[i][0][0] + item = g_throttle.pop (i) # expired + + if item[0][0] == address[0]: + return False # this address expired + + continue + + if g_throttle[i][0][0] == address[0]: + return True # is throttled + + i += 1 + + return False # not throttled + +def throttle (address): + tt = datetime.utcnow() + timedelta (0, 30) + + try: + # attempt to just update the item + g_throttle[g_throttle.index (address)][1] = tt + except ValueError: + # not in list + g_throttle.append ([address, tt]) + + Irc.broadcast ('Throttling %s:%s for 30 seconds' % address) + +def handle_rest_http (data, address): + global g_credentials + displayaddress = '%s:%s' % address + authrex = re.compile (r'^authorization: Basic (.+)$') + payloadrex = re.compile (r'^payload=(.+)$') + authenticated = False + payload = '' + + if not g_credentials: + g_credentials = base64.b64encode (Config.get_node ('rest').get_value ('credentials')) + + # Authenticate and find the payload + for line in data: + match = authrex.match (line) + + if match and match.group (1) == g_credentials) + authenticated = True + continue + + match = payloadrex.match (line) + if match: + payload = match.group (1) + + if not authenticated: + Irc.broadcast ('%s:%s failed to authenticate' % address) + throttle (address) + return + + jsonstring = urllib.unquote_plus (payload).decode ('utf-8') + + try: + jsondata = json.loads (jsonstring) + repodata = jsondata['repository'] + repopath = '%s/%s' % (repodata['owner'], repodata['name'] + + if repopath not in valid_repos: + raise ValueError ('unknown repository %s' % repopath) + + if 'commits' in jsondata: + for commit in jsondata['commits']: + Irc.broadcast ('%s: new commit %s' % (address, commit['node'])) + except Exception as e: + Irc.broadcast ('%s provided bad JSON: %s' % (address, e)) + + try: + with open ('rejected_json.txt', 'w') as fp: + fp.write (jsonstring) + Irc.broadcast ('bad json written to rejected_json.txt') + except: + Irc.broadcast ('failed to log json') + pass + + throttle (address) + return + +class RESTConnection (asyncore.dispatcher): + httpdata = '' + address=None + writebuffer = '' + + def __init__ (self, conn, address): + asyncore.dispatcher.__init__ (self, conn) + self.socket = ssl.wrap_socket (conn, server_side=True, keyfile='key.pem', + certfile='cert.pem', do_handshake_on_connect=False) + self.socket.setblocking (0) + self.address = address + + while True: + try: + self.socket.do_handshake() + break + except ssl.SSLError, err: + if err.args[0] == ssl.SSL_ERROR_WANT_READ: + select.select([self.socket], [], []) + elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE: + select.select([], [self.socket], []) + else: + Irc.broadcast ('%s:%s: SSL error: %s' % (self.address[0], self.address[1], err)) + throttle (self.address) + self.close() + return + + def write (self, msg): + self.writebuffer += msg + + def readable (self): + return True + + def writable (self): + return self.writebuffer + + def handle_close (self): + self.finish() + + def handle_read (self): + while 1: + try: + data = self.recv (4096) + except ssl.SSLError, err: + # EOF + self.finish() + return + + self.httpdata += data.replace ('\r', '') + + def finish (self): + handle_rest_http (self.httpdata.split ('\n'), self.address) + self.close() + + def handle_write (self): + self.send (self.writebuffer) + self.writebuffer='' + + def handle_error (self): + raise + +class RESTServer (asyncore.dispatcher): + def __init__ (self): + global g_portnumber + + if g_portnumber == None: + g_portnumber = Config.get_node ('rest').get_value ('port') + + asyncore.dispatcher.__init__ (self) + self.create_socket (socket.AF_INET, socket.SOCK_STREAM) + self.bind (('', g_portnumber)) + self.listen (5) + + def handle_accept (self): + sock, address = self.accept() + + if is_throttled (address): + # throttled + sock.close() + return + + Irc.broadcast ('REST connection from %s:%s' % address) + RESTConnection (sock, address) + + def handle_error (self): + return