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