bzr branch
http://bzr.recompile.se/loggerhead/mandos/trunk
| 
3
by Björn Påhlsson
 Python based server  | 
1  | 
#!/usr/bin/python
 | 
2  | 
||
3  | 
import SocketServer  | 
|
4  | 
import socket  | 
|
5  | 
import select  | 
|
6  | 
from optparse import OptionParser  | 
|
7  | 
import datetime  | 
|
8  | 
import errno  | 
|
9  | 
import gnutls.crypto  | 
|
10  | 
import gnutls.connection  | 
|
11  | 
import gnutls.errors  | 
|
12  | 
import ConfigParser  | 
|
| 
4
by Teddy Hogeborn
 * server.py (Client.created, Client.next_check): New.  | 
13  | 
import sys  | 
| 
3
by Björn Påhlsson
 Python based server  | 
14  | 
|
| 
5
by Teddy Hogeborn
 * server.py (server_metaclass): New.  | 
15  | 
|
| 
3
by Björn Påhlsson
 Python based server  | 
16  | 
class Client(object):  | 
| 
5
by Teddy Hogeborn
 * server.py (server_metaclass): New.  | 
17  | 
def __init__(self, name=None, options=None, dn=None,  | 
18  | 
password=None, passfile=None, fqdn=None,  | 
|
19  | 
timeout=None, interval=-1):  | 
|
| 
3
by Björn Påhlsson
 Python based server  | 
20  | 
self.name = name  | 
21  | 
self.dn = dn  | 
|
22  | 
if password:  | 
|
23  | 
self.password = password  | 
|
24  | 
elif passfile:  | 
|
25  | 
self.password = open(passfile).readall()  | 
|
26  | 
else:  | 
|
27  | 
print "No Password or Passfile in client config file"  | 
|
28  | 
            # raise RuntimeError XXX
 | 
|
29  | 
self.password = "gazonk"  | 
|
30  | 
self.fqdn = fqdn  | 
|
| 
4
by Teddy Hogeborn
 * server.py (Client.created, Client.next_check): New.  | 
31  | 
self.created = datetime.datetime.now()  | 
| 
3
by Björn Påhlsson
 Python based server  | 
32  | 
self.last_seen = None  | 
33  | 
if timeout is None:  | 
|
| 
5
by Teddy Hogeborn
 * server.py (server_metaclass): New.  | 
34  | 
timeout = options.timeout  | 
| 
3
by Björn Påhlsson
 Python based server  | 
35  | 
self.timeout = timeout  | 
36  | 
if interval == -1:  | 
|
| 
5
by Teddy Hogeborn
 * server.py (server_metaclass): New.  | 
37  | 
interval = options.interval  | 
| 
3
by Björn Påhlsson
 Python based server  | 
38  | 
self.interval = interval  | 
| 
4
by Teddy Hogeborn
 * server.py (Client.created, Client.next_check): New.  | 
39  | 
self.next_check = datetime.datetime.now()  | 
| 
3
by Björn Påhlsson
 Python based server  | 
40  | 
|
| 
5
by Teddy Hogeborn
 * server.py (server_metaclass): New.  | 
41  | 
|
42  | 
class server_metaclass(type):  | 
|
43  | 
    "Common behavior for the UDP and TCP server classes"
 | 
|
44  | 
def __new__(cls, name, bases, attrs):  | 
|
45  | 
attrs["address_family"] = socket.AF_INET6  | 
|
46  | 
attrs["allow_reuse_address"] = True  | 
|
47  | 
def server_bind(self):  | 
|
48  | 
if self.options.interface:  | 
|
49  | 
if not hasattr(socket, "SO_BINDTODEVICE"):  | 
|
50  | 
                    # From /usr/include/asm-i486/socket.h
 | 
|
51  | 
socket.SO_BINDTODEVICE = 25  | 
|
52  | 
try:  | 
|
53  | 
self.socket.setsockopt(socket.SOL_SOCKET,  | 
|
54  | 
socket.SO_BINDTODEVICE,  | 
|
55  | 
self.options.interface)  | 
|
56  | 
except socket.error, error:  | 
|
57  | 
if error[0] == errno.EPERM:  | 
|
58  | 
print "Warning: No permission to bind to interface", \  | 
|
59  | 
self.options.interface  | 
|
60  | 
else:  | 
|
61  | 
raise error  | 
|
62  | 
return super(type(self), self).server_bind()  | 
|
63  | 
attrs["server_bind"] = server_bind  | 
|
64  | 
def init(self, *args, **kwargs):  | 
|
65  | 
if "options" in kwargs:  | 
|
66  | 
self.options = kwargs["options"]  | 
|
67  | 
del kwargs["options"]  | 
|
68  | 
if "clients" in kwargs:  | 
|
69  | 
self.clients = kwargs["clients"]  | 
|
70  | 
del kwargs["clients"]  | 
|
71  | 
if "credentials" in kwargs:  | 
|
72  | 
self.credentials = kwargs["credentials"]  | 
|
73  | 
del kwargs["credentials"]  | 
|
74  | 
return super(type(self), self).__init__(*args, **kwargs)  | 
|
75  | 
attrs["__init__"] = init  | 
|
76  | 
return type.__new__(cls, name, bases, attrs)  | 
|
| 
3
by Björn Påhlsson
 Python based server  | 
77  | 
|
78  | 
||
79  | 
class udp_handler(SocketServer.DatagramRequestHandler, object):  | 
|
80  | 
def handle(self):  | 
|
81  | 
self.wfile.write("Polo")  | 
|
82  | 
print "UDP request answered"  | 
|
83  | 
||
84  | 
||
85  | 
class IPv6_UDPServer(SocketServer.UDPServer, object):  | 
|
| 
5
by Teddy Hogeborn
 * server.py (server_metaclass): New.  | 
86  | 
__metaclass__ = server_metaclass  | 
| 
3
by Björn Påhlsson
 Python based server  | 
87  | 
def verify_request(self, request, client_address):  | 
88  | 
print "UDP request came"  | 
|
89  | 
return request[0] == "Marco"  | 
|
90  | 
||
91  | 
||
92  | 
class tcp_handler(SocketServer.BaseRequestHandler, object):  | 
|
93  | 
def handle(self):  | 
|
94  | 
print "TCP request came"  | 
|
95  | 
print "Request:", self.request  | 
|
96  | 
print "Client Address:", self.client_address  | 
|
97  | 
print "Server:", self.server  | 
|
98  | 
session = gnutls.connection.ServerSession(self.request,  | 
|
99  | 
self.server.credentials)  | 
|
100  | 
session.handshake()  | 
|
101  | 
if session.peer_certificate:  | 
|
102  | 
print "DN:", session.peer_certificate.subject  | 
|
103  | 
try:  | 
|
104  | 
session.verify_peer()  | 
|
105  | 
except gnutls.errors.CertificateError, error:  | 
|
106  | 
print "Verify failed", error  | 
|
107  | 
session.bye()  | 
|
108  | 
            return
 | 
|
109  | 
try:  | 
|
110  | 
session.send(dict((client.dn, client.password)  | 
|
111  | 
for client in self.server.clients)  | 
|
112  | 
[session.peer_certificate.subject])  | 
|
113  | 
except KeyError:  | 
|
114  | 
session.send("gazonk")  | 
|
115  | 
            # Log maybe? XXX
 | 
|
116  | 
session.bye()  | 
|
117  | 
||
| 
5
by Teddy Hogeborn
 * server.py (server_metaclass): New.  | 
118  | 
|
| 
3
by Björn Påhlsson
 Python based server  | 
119  | 
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):  | 
| 
5
by Teddy Hogeborn
 * server.py (server_metaclass): New.  | 
120  | 
__metaclass__ = server_metaclass  | 
| 
3
by Björn Påhlsson
 Python based server  | 
121  | 
request_queue_size = 1024  | 
122  | 
||
123  | 
||
124  | 
in6addr_any = "::"  | 
|
125  | 
||
126  | 
cred = None  | 
|
127  | 
||
| 
4
by Teddy Hogeborn
 * server.py (Client.created, Client.next_check): New.  | 
128  | 
def string_to_delta(interval):  | 
129  | 
"""Parse a string and return a datetime.timedelta  | 
|
130  | 
||
131  | 
    >>> string_to_delta('7d')
 | 
|
132  | 
    datetime.timedelta(7)
 | 
|
133  | 
    >>> string_to_delta('60s')
 | 
|
134  | 
    datetime.timedelta(0, 60)
 | 
|
135  | 
    >>> string_to_delta('60m')
 | 
|
136  | 
    datetime.timedelta(0, 3600)
 | 
|
137  | 
    >>> string_to_delta('24h')
 | 
|
138  | 
    datetime.timedelta(1)
 | 
|
139  | 
    >>> string_to_delta(u'1w')
 | 
|
140  | 
    datetime.timedelta(7)
 | 
|
141  | 
    """
 | 
|
142  | 
try:  | 
|
143  | 
suffix=unicode(interval[-1])  | 
|
144  | 
value=int(interval[:-1])  | 
|
145  | 
if suffix == u"d":  | 
|
146  | 
delta = datetime.timedelta(value)  | 
|
147  | 
elif suffix == u"s":  | 
|
148  | 
delta = datetime.timedelta(0, value)  | 
|
149  | 
elif suffix == u"m":  | 
|
150  | 
delta = datetime.timedelta(0, 0, 0, 0, value)  | 
|
151  | 
elif suffix == u"h":  | 
|
152  | 
delta = datetime.timedelta(0, 0, 0, 0, 0, value)  | 
|
153  | 
elif suffix == u"w":  | 
|
154  | 
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)  | 
|
155  | 
else:  | 
|
156  | 
raise ValueError  | 
|
157  | 
except (ValueError, IndexError):  | 
|
158  | 
raise ValueError  | 
|
159  | 
return delta  | 
|
160  | 
||
| 
3
by Björn Påhlsson
 Python based server  | 
161  | 
def main():  | 
162  | 
parser = OptionParser()  | 
|
163  | 
parser.add_option("-i", "--interface", type="string",  | 
|
164  | 
default="eth0", metavar="IF",  | 
|
165  | 
help="Interface to bind to")  | 
|
166  | 
parser.add_option("--cert", type="string", default="cert.pem",  | 
|
167  | 
metavar="FILE",  | 
|
168  | 
help="Public key certificate to use")  | 
|
169  | 
parser.add_option("--key", type="string", default="key.pem",  | 
|
170  | 
metavar="FILE",  | 
|
171  | 
help="Private key to use")  | 
|
172  | 
parser.add_option("--ca", type="string", default="ca.pem",  | 
|
173  | 
metavar="FILE",  | 
|
174  | 
help="Certificate Authority certificate to use")  | 
|
175  | 
parser.add_option("--crl", type="string", default="crl.pem",  | 
|
176  | 
metavar="FILE",  | 
|
177  | 
help="Certificate Revokation List to use")  | 
|
178  | 
parser.add_option("-p", "--port", type="int", default=49001,  | 
|
179  | 
help="Port number to receive requests on")  | 
|
180  | 
parser.add_option("--dh", type="int", metavar="BITS",  | 
|
181  | 
help="DH group to use")  | 
|
182  | 
parser.add_option("-t", "--timeout", type="string", # Parsed later  | 
|
183  | 
default="15m",  | 
|
184  | 
help="Amount of downtime allowed for clients")  | 
|
| 
4
by Teddy Hogeborn
 * server.py (Client.created, Client.next_check): New.  | 
185  | 
parser.add_option("--interval", type="string", # Parsed later  | 
186  | 
default="5m",  | 
|
187  | 
help="How often to check that a client is up")  | 
|
188  | 
parser.add_option("--check", action="store_true", default=False,  | 
|
189  | 
help="Run self-test")  | 
|
| 
3
by Björn Påhlsson
 Python based server  | 
190  | 
(options, args) = parser.parse_args()  | 
| 
4
by Teddy Hogeborn
 * server.py (Client.created, Client.next_check): New.  | 
191  | 
|
192  | 
if options.check:  | 
|
193  | 
import doctest  | 
|
194  | 
doctest.testmod()  | 
|
195  | 
sys.exit()  | 
|
| 
3
by Björn Påhlsson
 Python based server  | 
196  | 
|
| 
4
by Teddy Hogeborn
 * server.py (Client.created, Client.next_check): New.  | 
197  | 
    # Parse the time arguments
 | 
| 
3
by Björn Påhlsson
 Python based server  | 
198  | 
try:  | 
| 
4
by Teddy Hogeborn
 * server.py (Client.created, Client.next_check): New.  | 
199  | 
options.timeout = string_to_delta(options.timeout)  | 
200  | 
except ValueError:  | 
|
| 
3
by Björn Påhlsson
 Python based server  | 
201  | 
parser.error("option --timeout: Unparseable time")  | 
202  | 
||
| 
4
by Teddy Hogeborn
 * server.py (Client.created, Client.next_check): New.  | 
203  | 
try:  | 
204  | 
options.interval = string_to_delta(options.interval)  | 
|
205  | 
except ValueError:  | 
|
206  | 
parser.error("option --interval: Unparseable time")  | 
|
207  | 
||
| 
3
by Björn Påhlsson
 Python based server  | 
208  | 
cert = gnutls.crypto.X509Certificate(open(options.cert).read())  | 
209  | 
key = gnutls.crypto.X509PrivateKey(open(options.key).read())  | 
|
210  | 
ca = gnutls.crypto.X509Certificate(open(options.ca).read())  | 
|
211  | 
crl = gnutls.crypto.X509CRL(open(options.crl).read())  | 
|
212  | 
cred = gnutls.connection.X509Credentials(cert, key, [ca], [crl])  | 
|
213  | 
||
214  | 
    # Parse config file
 | 
|
215  | 
defaults = {}  | 
|
216  | 
client_config_object = ConfigParser.SafeConfigParser(defaults)  | 
|
217  | 
client_config_object.read("mandos-clients.conf")  | 
|
| 
5
by Teddy Hogeborn
 * server.py (server_metaclass): New.  | 
218  | 
clients = [Client(name=section, options=options,  | 
| 
3
by Björn Påhlsson
 Python based server  | 
219  | 
**(dict(client_config_object.items(section))))  | 
220  | 
for section in client_config_object.sections()]  | 
|
221  | 
||
222  | 
udp_server = IPv6_UDPServer((in6addr_any, options.port),  | 
|
223  | 
udp_handler,  | 
|
224  | 
options=options)  | 
|
225  | 
||
226  | 
tcp_server = IPv6_TCPServer((in6addr_any, options.port),  | 
|
227  | 
tcp_handler,  | 
|
228  | 
options=options,  | 
|
229  | 
clients=clients,  | 
|
230  | 
credentials=cred)  | 
|
231  | 
||
232  | 
while True:  | 
|
233  | 
in_, out, err = select.select((udp_server,  | 
|
234  | 
tcp_server), (), ())  | 
|
235  | 
for server in in_:  | 
|
236  | 
server.handle_request()  | 
|
237  | 
||
238  | 
||
239  | 
if __name__ == "__main__":  | 
|
240  | 
main()  | 
|
241  |