Fixing config
[re6stnet.git] / registry.py
1 #!/usr/bin/env python
2 import argparse, math, random, select, smtplib, sqlite3, string, struct, socket, time
3 from email.mime.text import MIMEText
4 from functools import wraps
5 from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
6 from OpenSSL import crypto
7 import traceback
8
9 # To generate server ca and key with correct serial
10 # openssl req -nodes -new -x509 -key ca.key -set_serial 0x120010db80042 -days 365 -out ca.crt
11
12 IPV6_V6ONLY = 26
13 SOL_IPV6 = 41
14
15 class RequestHandler(SimpleXMLRPCRequestHandler):
16
17 def _dispatch(self, method, params):
18 return self.server._dispatch(method, (self,) + params)
19
20 class SimpleXMLRPCServer4(SimpleXMLRPCServer):
21
22 allow_reuse_address = True
23
24 class SimpleXMLRPCServer6(SimpleXMLRPCServer4):
25
26 address_family = socket.AF_INET6
27
28 def server_bind(self):
29 self.socket.setsockopt(SOL_IPV6, IPV6_V6ONLY, 1)
30 SimpleXMLRPCServer4.server_bind(self)
31
32 class main(object):
33
34 def __init__(self):
35 self.cert_duration = 365 * 86400
36
37 # Command line parsing
38 parser = argparse.ArgumentParser(
39 description='Peer discovery http server for vifibnet')
40 _ = parser.add_argument
41 _('port', type=int, help='Port of the host server')
42 _('--db', required=True,
43 help='Path to database file')
44 _('--ca', required=True,
45 help='Path to ca.crt file')
46 _('--key', required=True,
47 help='Path to certificate key')
48 _('--mailhost', required=True,
49 help='SMTP server mail host')
50 self.config = parser.parse_args()
51
52 # Database initializing
53 self.db = sqlite3.connect(self.config.db, isolation_level=None)
54 self.db.execute("""CREATE TABLE IF NOT EXISTS peers (
55 prefix text primary key not null,
56 ip text not null,
57 port integer not null,
58 proto text not null,
59 date integer default (strftime('%s','now')))""")
60 self.db.execute("""CREATE TABLE IF NOT EXISTS tokens (
61 token text primary key not null,
62 email text not null,
63 prefix_len integer not null,
64 date integer not null)""")
65 try:
66 self.db.execute("""CREATE TABLE vifib (
67 prefix text primary key not null,
68 email text,
69 cert text)""")
70 except sqlite3.OperationalError, e:
71 if e.args[0] != 'table vifib already exists':
72 raise RuntimeError
73 else:
74 self.db.execute("INSERT INTO vifib VALUES ('',null,null)")
75
76
77 # Loading certificates
78 with open(self.config.ca) as f:
79 self.ca = crypto.load_certificate(crypto.FILETYPE_PEM, f.read())
80 with open(self.config.key) as f:
81 self.key = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read())
82 # Get vifib network prefix
83 self.network = bin(self.ca.get_serial_number())[3:]
84 print "Network prefix : %s/%u" % (self.network, len(self.network))
85
86 # Starting server
87 server4 = SimpleXMLRPCServer4(('0.0.0.0', self.config.port), requestHandler=RequestHandler, allow_none=True)
88 server4.register_instance(self)
89 server6 = SimpleXMLRPCServer6(('::', self.config.port), requestHandler=RequestHandler, allow_none=True)
90 server6.register_instance(self)
91 while True:
92 try:
93 r, w, e = select.select([server4, server6], [], [])
94 except (OSError, select.error) as e:
95 if e.args[0] != errno.EINTR:
96 raise
97 else:
98 for r in r:
99 r._handle_request_noblock()
100
101 def requestToken(self, handler, email):
102 while True:
103 # Generating token
104 token = ''.join(random.sample(string.ascii_lowercase, 8))
105 # Updating database
106 try:
107 self.db.execute("INSERT INTO tokens VALUES (?,?,?,?)", (token, email, 16, int(time.time())))
108 break
109 except sqlite3.IntegrityError, e:
110 pass
111
112 # Creating and sending email
113 s = smtplib.SMTP(self.config.mailhost)
114 me = 'postmaster@vifibnet.com'
115 msg = MIMEText('Hello world !\nYour token : %s' % (token,))
116 msg['Subject'] = '[Vifibnet] Token Request'
117 msg['From'] = me
118 msg['To'] = email
119 s.sendmail(me, email, msg.as_string())
120 s.quit()
121
122 def _getPrefix(self, prefix_len):
123 assert 0 < prefix_len <= 128 - len(self.network)
124 for prefix, in self.db.execute("""SELECT prefix FROM vifib WHERE length(prefix) <= ? AND cert is null
125 ORDER BY length(prefix) DESC""", (prefix_len,)):
126 while len(prefix) < prefix_len:
127 self.db.execute("UPDATE vifib SET prefix = ? WHERE prefix = ?", (prefix + '1', prefix))
128 prefix += '0'
129 self.db.execute("INSERT INTO vifib VALUES (?,null,null)", (prefix,))
130 return prefix
131 raise RuntimeError # TODO: raise better exception
132
133 def requestCertificate(self, handler, token, cert_req):
134 try:
135 req = crypto.load_certificate_request(crypto.FILETYPE_PEM, cert_req)
136 with self.db:
137 try:
138 token, email, prefix_len, _ = self.db.execute("SELECT * FROM tokens WHERE token = ?", (token,)).next()
139 except StopIteration:
140 # TODO: return nice error message
141 raise
142 self.db.execute("DELETE FROM tokens WHERE token = ?", (token,))
143
144 # Get a new prefix
145 prefix = self._getPrefix(prefix_len)
146
147 # Create certificate
148 cert = crypto.X509()
149 #cert.set_serial_number(serial)
150 cert.gmtime_adj_notBefore(0)
151 cert.gmtime_adj_notAfter(self.cert_duration)
152 cert.set_issuer(self.ca.get_subject())
153 subject = req.get_subject()
154 subject.CN = "%u/%u" % (int(prefix, 2), prefix_len)
155 cert.set_subject(subject)
156 cert.set_pubkey(req.get_pubkey())
157 cert.sign(self.key, 'sha1')
158 cert = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
159
160 # Insert certificate into db
161 self.db.execute("UPDATE vifib SET email = ?, cert = ? WHERE prefix = ?", (email, cert, prefix) )
162
163 return cert
164 except:
165 traceback.print_exc()
166 raise
167
168 def getCa(self, handler):
169 return crypto.dump_certificate(crypto.FILETYPE_PEM, self.ca)
170
171 def getBootstrapPeer(self, handler):
172 # TODO: Insert a flag column for bootstrap ready servers in peers
173 # ( servers which shouldn't go down or change ip and port as opposed to servers owned by particulars )
174 # that way, we also ascertain that the server sent is not the new node....
175 ip, port, proto = self.db.execute("SELECT ip, port, proto FROM peers ORDER BY random() LIMIT 1").next()
176 print "Sending bootstrap peer ( %s, %s, %s)" % (ip, port, proto)
177 return ip, port, proto
178
179 def declare(self, handler, address):
180 client_address, ip, port, proto = address
181 #client_address, _ = handler.client_address
182 client_ip1, client_ip2 = struct.unpack('>QQ', socket.inet_pton(socket.AF_INET6, client_address))
183 client_ip = bin(client_ip1)[2:].rjust(64, '0') + bin(client_ip2)[2:].rjust(64, '0')
184 if client_ip.startswith(self.network):
185 prefix = client_ip[len(self.network):]
186 prefix, = self.db.execute("SELECT prefix FROM vifib WHERE prefix <= ? ORDER BY prefix DESC LIMIT 1", (prefix,)).next()
187 self.db.execute("INSERT OR REPLACE INTO peers (prefix, ip, port, proto) VALUES (?,?,?,?)", (prefix, ip, port, proto))
188 return True
189 else:
190 # TODO: use log + DO NOT PRINT BINARY IP
191 print "Unauthorized connection from %s which does not start with %s" % (client_ip, self.network)
192 return False
193
194 def getPeerList(self, handler, n, address):
195 assert 0 < n < 1000
196 print "declaring new node"
197 if not self.declare(handler, address):
198 # TODO: do something intelligent
199 raise RuntimeError
200 print "sending peers"
201 return self.db.execute("SELECT ip, port, proto FROM peers ORDER BY random() LIMIT ?", (n,)).fetchall()
202
203 if __name__ == "__main__":
204 main()