/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to server.py

  • Committer: Björn Påhlsson
  • Date: 2008-01-18 21:18:26 UTC
  • mto: This revision was merged to the branch mainline in revision 6.
  • Revision ID: belorn@legolas-20080118211826-5rbwo54l4bwim5x2
Client:
        [Working version in initrd for booting]
        Added #ifdef DEBUG statements through out the program
        Added support to keep bouth tcp and udp up at the same time
        Catching several more error return codes that was unchecked.
        Starts the Network interface during startup.
        Added support for entering password on console
        Added error handling, like looping until a password has been received.
        Added cleanup handling so console state is always restored
                
removed:
        Old server.cpp [see next version]
        Test certificates

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
13
 
import sys
14
 
 
15
 
 
16
 
class Client(object):
17
 
    def __init__(self, name=None, options=None, dn=None,
18
 
                 password=None, passfile=None, fqdn=None,
19
 
                 timeout=None, interval=-1):
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
31
 
        self.created = datetime.datetime.now()
32
 
        self.last_seen = None
33
 
        if timeout is None:
34
 
            timeout = options.timeout
35
 
        self.timeout = timeout
36
 
        if interval == -1:
37
 
            interval = options.interval
38
 
        self.interval = interval
39
 
        self.next_check = datetime.datetime.now()
40
 
 
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)
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):
86
 
    __metaclass__ = server_metaclass
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
 
 
118
 
 
119
 
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
120
 
    __metaclass__ = server_metaclass
121
 
    request_queue_size = 1024
122
 
 
123
 
 
124
 
in6addr_any = "::"
125
 
 
126
 
cred = None
127
 
 
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
 
 
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")
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")
190
 
    (options, args) = parser.parse_args()
191
 
 
192
 
    if options.check:
193
 
        import doctest
194
 
        doctest.testmod()
195
 
        sys.exit()
196
 
    
197
 
    # Parse the time arguments
198
 
    try:
199
 
        options.timeout = string_to_delta(options.timeout)
200
 
    except ValueError:
201
 
        parser.error("option --timeout: Unparseable time")
202
 
    
203
 
    try:
204
 
        options.interval = string_to_delta(options.interval)
205
 
    except ValueError:
206
 
        parser.error("option --interval: Unparseable time")
207
 
    
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")
218
 
    clients = [Client(name=section, options=options,
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