/mandos/trunk

To get this branch, use:
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
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
126
def string_to_delta(interval):
127
    """Parse a string and return a datetime.timedelta
128
129
    >>> string_to_delta('7d')
130
    datetime.timedelta(7)
131
    >>> string_to_delta('60s')
132
    datetime.timedelta(0, 60)
133
    >>> string_to_delta('60m')
134
    datetime.timedelta(0, 3600)
135
    >>> string_to_delta('24h')
136
    datetime.timedelta(1)
137
    >>> string_to_delta(u'1w')
138
    datetime.timedelta(7)
139
    """
140
    try:
141
        suffix=unicode(interval[-1])
142
        value=int(interval[:-1])
143
        if suffix == u"d":
144
            delta = datetime.timedelta(value)
145
        elif suffix == u"s":
146
            delta = datetime.timedelta(0, value)
147
        elif suffix == u"m":
148
            delta = datetime.timedelta(0, 0, 0, 0, value)
149
        elif suffix == u"h":
150
            delta = datetime.timedelta(0, 0, 0, 0, 0, value)
151
        elif suffix == u"w":
152
            delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
153
        else:
154
            raise ValueError
155
    except (ValueError, IndexError):
156
        raise ValueError
157
    return delta
158
8 by Teddy Hogeborn
* Makefile (client_debug): Bug fix; add quotes and / to CERT_ROOT.
159
3 by Björn Påhlsson
Python based server
160
def main():
161
    parser = OptionParser()
162
    parser.add_option("-i", "--interface", type="string",
163
                      default="eth0", metavar="IF",
164
                      help="Interface to bind to")
165
    parser.add_option("--cert", type="string", default="cert.pem",
166
                      metavar="FILE",
167
                      help="Public key certificate to use")
168
    parser.add_option("--key", type="string", default="key.pem",
169
                      metavar="FILE",
170
                      help="Private key to use")
171
    parser.add_option("--ca", type="string", default="ca.pem",
172
                      metavar="FILE",
173
                      help="Certificate Authority certificate to use")
174
    parser.add_option("--crl", type="string", default="crl.pem",
175
                      metavar="FILE",
176
                      help="Certificate Revokation List to use")
177
    parser.add_option("-p", "--port", type="int", default=49001,
178
                      help="Port number to receive requests on")
179
    parser.add_option("--dh", type="int", metavar="BITS",
180
                      help="DH group to use")
181
    parser.add_option("-t", "--timeout", type="string", # Parsed later
182
                      default="15m",
183
                      help="Amount of downtime allowed for clients")
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
184
    parser.add_option("--interval", type="string", # Parsed later
185
                      default="5m",
186
                      help="How often to check that a client is up")
187
    parser.add_option("--check", action="store_true", default=False,
188
                      help="Run self-test")
3 by Björn Påhlsson
Python based server
189
    (options, args) = parser.parse_args()
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
190
191
    if options.check:
192
        import doctest
193
        doctest.testmod()
194
        sys.exit()
3 by Björn Påhlsson
Python based server
195
    
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
196
    # Parse the time arguments
3 by Björn Påhlsson
Python based server
197
    try:
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
198
        options.timeout = string_to_delta(options.timeout)
199
    except ValueError:
3 by Björn Påhlsson
Python based server
200
        parser.error("option --timeout: Unparseable time")
201
    
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
202
    try:
203
        options.interval = string_to_delta(options.interval)
204
    except ValueError:
205
        parser.error("option --interval: Unparseable time")
206
    
3 by Björn Påhlsson
Python based server
207
    cert = gnutls.crypto.X509Certificate(open(options.cert).read())
208
    key = gnutls.crypto.X509PrivateKey(open(options.key).read())
209
    ca = gnutls.crypto.X509Certificate(open(options.ca).read())
210
    crl = gnutls.crypto.X509CRL(open(options.crl).read())
211
    cred = gnutls.connection.X509Credentials(cert, key, [ca], [crl])
212
    
213
    # Parse config file
214
    defaults = {}
215
    client_config_object = ConfigParser.SafeConfigParser(defaults)
216
    client_config_object.read("mandos-clients.conf")
5 by Teddy Hogeborn
* server.py (server_metaclass): New.
217
    clients = [Client(name=section, options=options,
3 by Björn Påhlsson
Python based server
218
                      **(dict(client_config_object.items(section))))
219
               for section in client_config_object.sections()]
220
    
221
    udp_server = IPv6_UDPServer((in6addr_any, options.port),
222
                                udp_handler,
223
                                options=options)
224
    
225
    tcp_server = IPv6_TCPServer((in6addr_any, options.port),
226
                                tcp_handler,
227
                                options=options,
228
                                clients=clients,
229
                                credentials=cred)
230
    
231
    while True:
232
        in_, out, err = select.select((udp_server,
233
                                       tcp_server), (), ())
234
        for server in in_:
235
            server.handle_request()
236
237
238
if __name__ == "__main__":
239
    main()
240