registry.py 8.12 KB
Newer Older
1
#!/usr/bin/env python
2
import argparse, math, random, select, smtplib, sqlite3, string, struct, socket, time
3
from email.mime.text import MIMEText
Guillaume Bury's avatar
Guillaume Bury committed
4 5
from functools import wraps
from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
6
from OpenSSL import crypto
7
import traceback
8

9 10 11
IPV6_V6ONLY = 26
SOL_IPV6 = 41

Guillaume Bury's avatar
Guillaume Bury committed
12 13 14 15 16
class RequestHandler(SimpleXMLRPCRequestHandler):

    def _dispatch(self, method, params):
        return self.server._dispatch(method, (self,) + params)

17 18 19 20 21 22 23 24 25 26 27 28
class SimpleXMLRPCServer4(SimpleXMLRPCServer):

    allow_reuse_address = True

class SimpleXMLRPCServer6(SimpleXMLRPCServer4):

    address_family = socket.AF_INET6

    def server_bind(self):
        self.socket.setsockopt(SOL_IPV6, IPV6_V6ONLY, 1)
        SimpleXMLRPCServer4.server_bind(self)

29 30 31
class main(object):

    def __init__(self):
Guillaume Bury's avatar
Guillaume Bury committed
32 33
        self.cert_duration = 365 * 86400

34 35 36 37
        # Command line parsing
        parser = argparse.ArgumentParser(
                description='Peer discovery http server for vifibnet')
        _ = parser.add_argument
38
        _('port', type=int, help='Port of the host server')
39 40 41 42 43 44
        _('--db', required=True,
                help='Path to database file')
        _('--ca', required=True,
                help='Path to ca.crt file')
        _('--key', required=True,
                help='Path to certificate key')
45 46 47
        _('--mailhost', required=True,
                help='SMTP server mail host')
        self.config = parser.parse_args()
48 49

        # Database initializing
50
        self.db = sqlite3.connect(self.config.db, isolation_level=None)
51 52 53 54
        self.db.execute("""CREATE TABLE IF NOT EXISTS peers (
                        prefix text primary key not null,
                        ip text not null,
                        port integer not null,
55 56
                        proto text not null,
                        date integer default (strftime('%s','now')))""")
57 58 59
        self.db.execute("""CREATE TABLE IF NOT EXISTS tokens (
                        token text primary key not null,
                        email text not null,
60
                        prefix_len integer not null,
61
                        date integer not null)""")
62 63 64 65 66 67
        try:
            self.db.execute("""CREATE TABLE vifib (
                               prefix text primary key not null,
                               email text,
                               cert text)""")
        except sqlite3.OperationalError, e:
Guillaume Bury's avatar
Guillaume Bury committed
68
            if e.args[0] != 'table vifib already exists':
69 70 71 72
                raise RuntimeError
        else:
            self.db.execute("INSERT INTO vifib VALUES ('',null,null)")

73 74

        # Loading certificates
75
        with open(self.config.ca) as f:
76
            self.ca = crypto.load_certificate(crypto.FILETYPE_PEM, f.read())
77
        with open(self.config.key) as f:
78
            self.key = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read())
Guillaume Bury's avatar
Guillaume Bury committed
79
        # Get vifib network prefix
80
        self.network = bin(self.ca.get_serial_number())[3:]
81
        print "Network prefix : %s/%u" % (self.network, len(self.network))
82 83

        # Starting server
84 85 86 87 88 89 90 91 92 93 94 95 96
        server4 = SimpleXMLRPCServer4(('0.0.0.0', self.config.port), requestHandler=RequestHandler, allow_none=True)
        server4.register_instance(self)
        server6 = SimpleXMLRPCServer6(('::', self.config.port), requestHandler=RequestHandler, allow_none=True)
        server6.register_instance(self)
        while True:
            try:
                r, w, e = select.select([server4, server6], [], [])
            except (OSError, select.error) as e:
                if e.args[0] != errno.EINTR:
                    raise
            else:
                for r in r:
                    r._handle_request_noblock()
97

Guillaume Bury's avatar
Guillaume Bury committed
98
    def requestToken(self, handler, email):
99 100 101 102 103
        while True:
            # Generating token
            token = ''.join(random.sample(string.ascii_lowercase, 8))
            # Updating database
            try:
104
                self.db.execute("INSERT INTO tokens VALUES (?,?,?,?)", (token, email, 16, int(time.time())))
105 106 107 108 109
                break
            except sqlite3.IntegrityError, e:
                pass

        # Creating and sending email
110
        s = smtplib.SMTP(self.config.mailhost)
111 112 113 114 115 116 117 118
        me = 'postmaster@vifibnet.com'
        msg = MIMEText('Hello world !\nYour token : %s' % (token,))
        msg['Subject'] = '[Vifibnet] Token Request'
        msg['From'] = me
        msg['To'] = email
        s.sendmail(me, email, msg.as_string())
        s.quit()

Guillaume Bury's avatar
Guillaume Bury committed
119 120
    def _getPrefix(self, prefix_len):
        assert 0 < prefix_len <= 128 - len(self.network)
121
        for prefix, in self.db.execute("""SELECT prefix FROM vifib WHERE length(prefix) <= ? AND cert is null
Guillaume Bury's avatar
Guillaume Bury committed
122 123 124 125 126 127
                                         ORDER BY length(prefix) DESC""", (prefix_len,)):
            while len(prefix) < prefix_len:
                self.db.execute("UPDATE vifib SET prefix = ? WHERE prefix = ?", (prefix + '1', prefix))
                prefix += '0'
                self.db.execute("INSERT INTO vifib VALUES (?,null,null)", (prefix,))
            return prefix
128
        raise RuntimeError # TODO: raise better exception
Guillaume Bury's avatar
Guillaume Bury committed
129

Guillaume Bury's avatar
Guillaume Bury committed
130
    def requestCertificate(self, handler, token, cert_req):
131
      try:
132
        req = crypto.load_certificate_request(crypto.FILETYPE_PEM, cert_req)
Guillaume Bury's avatar
Guillaume Bury committed
133 134 135 136
        with self.db:
            try:
                token, email, prefix_len, _ = self.db.execute("SELECT * FROM tokens WHERE token = ?", (token,)).next()
            except StopIteration:
137
                # TODO: return nice error message
Guillaume Bury's avatar
Guillaume Bury committed
138
                raise
139 140
            self.db.execute("DELETE FROM tokens WHERE token = ?", (token,))

Guillaume Bury's avatar
Guillaume Bury committed
141 142 143 144
            # Get a new prefix
            prefix = self._getPrefix(prefix_len)

            # Create certificate
145 146
            cert = crypto.X509()
            #cert.set_serial_number(serial)
147
            cert.gmtime_adj_notBefore(0)
Guillaume Bury's avatar
Guillaume Bury committed
148
            cert.gmtime_adj_notAfter(self.cert_duration)
149
            cert.set_issuer(self.ca.get_subject())
Guillaume Bury's avatar
Guillaume Bury committed
150 151 152
            subject = req.get_subject()
            subject.serialNumber = "%u/%u" % (int(prefix, 2), prefix_len)
            cert.set_subject(subject)
153 154 155 156 157
            cert.set_pubkey(req.get_pubkey())
            cert.sign(self.key, 'sha1')
            cert = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)

            # Insert certificate into db
158
            self.db.execute("UPDATE vifib SET email = ?, cert = ? WHERE prefix = ?", (email, cert, prefix) )
159

Guillaume Bury's avatar
Guillaume Bury committed
160
        return cert
161 162 163
      except:
        traceback.print_exc()
        raise
164

Guillaume Bury's avatar
Guillaume Bury committed
165
    def getCa(self, handler):
166 167
        return crypto.dump_certificate(crypto.FILETYPE_PEM, self.ca)

168 169
    def getBootstrapPeer(self, handler):
        # TODO: Insert a flag column for bootstrap ready servers in peers 
170 171
        # ( servers which shouldn't go down or change ip and port as opposed to servers owned by particulars )
        # that way, we also ascertain that the server sent is not the new node....
172 173
        return self.db.execute("SELECT ip, port proto FROM peers ORDER BY random() LIMIT 1").next()

Guillaume Bury's avatar
Guillaume Bury committed
174
    def declare(self, handler, address):
175 176
        client_address, ip, port, proto = address
        #client_address, _ = handler.client_address
177 178 179 180 181
        client_ip1, client_ip2 = struct.unpack('>QQ', socket.inet_pton(socket.AF_INET6, client_address))
        client_ip = bin(client_ip1)[2:].rjust(64, '0') + bin(client_ip2)[2:].rjust(64, '0')
        if client_ip.startswith(self.network):
            prefix = client_ip[len(self.network):]
            prefix, = self.db.execute("SELECT prefix FROM vifib WHERE prefix <= ? ORDER BY prefix DESC LIMIT 1", (prefix,)).next()
Guillaume Bury's avatar
Guillaume Bury committed
182 183 184
            self.db.execute("INSERT OR REPLACE INTO peers VALUES (?,?,?,?)", (prefix, ip, port, proto))
            return True
        else:
185 186
            # TODO: use log + DO NOT PRINT BINARY IP
            print "Unauthorized connection from %s which does not start with %s" % (client_ip, self.network)
Guillaume Bury's avatar
Guillaume Bury committed
187
            return False
Guillaume Bury's avatar
Guillaume Bury committed
188 189

    def getPeerList(self, handler, n, address):
190
        assert 0 < n < 1000
191
        print "declaring new node"
Guillaume Bury's avatar
Guillaume Bury committed
192 193 194
        if not self.declare(handler, address):
            # TODO: do something intelligent
            raise RuntimeError
195
        print "sending peers"
196 197
        return self.db.execute("SELECT ip, port, proto FROM peers ORDER BY random() LIMIT ?", (n,)).fetchall()

198 199
if __name__ == "__main__":
    main()