/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: Teddy Hogeborn
  • Date: 2008-06-21 00:53:32 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080621005332-s4scjdpevuso4lsd
* server.py: Bug fix: Do "from __future__ import division".
  (Client.__init__): Bug fix: parse interval string from config file.
  (Client.check_action): Take no arguments.  Print some debugging
  output.  Reset "checker" to None.
  (Client.start_checker): Sleep 10 seconds before pinging to alleviate
  debugging.
  (Client.next_stop): Bug fix: check if "last_seen" and/or "checker"
  is None.
  (Client.still_valid): Bug fix: check if "last_seen" is None.
  (Client.handle): When finding the right password to send, use a list
  comprehension and an index lookup instead of a generator expression to
  a dict.
  (IPv6_TCPServer.request_queue_size): Removed.
  (in6addr_any): Moved inside "main".
  (main): Changed "clients" to be a Set instead of a list.  Bug fix:
  Exit when/if all clients are removed.  Call "select" with all client
  checkers and a suitable timeout.  Add some debugging output.  Start
  new checkers when needed and delete clients which have timed out.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#!/usr/bin/python
2
2
 
 
3
from __future__ import division
 
4
 
3
5
import SocketServer
4
6
import socket
5
7
import select
14
16
import re
15
17
import os
16
18
import signal
 
19
from sets import Set
 
20
import subprocess
17
21
 
18
22
class Client(object):
19
23
    def __init__(self, name=None, options=None, dn=None,
37
41
        self.timeout = timeout          # datetime.timedelta()
38
42
        if interval == -1:
39
43
            interval = options.interval
 
44
        else:
 
45
            interval = string_to_delta(interval)
40
46
        self.interval = interval        # datetime.timedelta()
41
47
        self.next_check = datetime.datetime.now() # datetime.datetime()
 
48
        # Note: next_check may be in the past if checker is not None
42
49
        self.checker = None             # or a subprocess.Popen()
43
 
    def check_action(self, now=None):
 
50
    def check_action(self):
44
51
        """The checker said something and might have completed.
45
52
        Check if is has, and take appropriate actions."""
46
53
        if self.checker.poll() is None:
47
54
            # False alarm, no result yet
48
55
            #self.checker.read()
 
56
            #print "Checker for %(name)s said nothing?" % vars(self)
49
57
            return
50
 
        if now is None:
51
 
            now = datetime.datetime.now()
 
58
        now = datetime.datetime.now()
52
59
        if self.checker.returncode == 0:
 
60
            print "Checker for %(name)s succeeded" % vars(self)
53
61
            self.last_seen = now
 
62
        else:
 
63
            print "Checker for %(name)s failed" % vars(self)
54
64
        while self.next_check <= now:
55
65
            self.next_check += self.interval
 
66
        self.checker = None
56
67
    handle_request = check_action
57
68
    def start_checker(self):
58
69
        self.stop_checker()
59
70
        try:
60
 
            self.checker = subprocess.Popen("sleep 1; fping -q -- %s"
 
71
            self.checker = subprocess.Popen("sleep 10; fping -q -- %s"
61
72
                                            % re.escape(self.fqdn),
62
73
                                            stdout=subprocess.PIPE,
63
74
                                            close_fds=True,
77
88
            return None
78
89
        return self.checker.stdout.fileno()
79
90
    def next_stop(self):
80
 
        """The time when something must be done about this client"""
81
 
        return min(self.last_seen + self.timeout, self.next_check)
 
91
        """The time when something must be done about this client
 
92
        May be in the past."""
 
93
        if self.last_seen is None:
 
94
            # This client has never been seen
 
95
            next_timeout = self.created + self.timeout
 
96
        else:
 
97
            next_timeout = self.last_seen + self.timeout
 
98
        if self.checker is None:
 
99
            return min(next_timeout, self.next_check)
 
100
        else:
 
101
            return next_timeout
82
102
    def still_valid(self, now=None):
83
103
        """Has this client's timeout not passed?"""
84
104
        if now is None:
85
105
            now = datetime.datetime.now()
86
 
        return now < (self.last_seen + timeout)
 
106
        if self.last_seen is None:
 
107
            return now < (self.created + self.timeout)
 
108
        else:
 
109
            return now < (self.last_seen + self.timeout)
87
110
    def it_is_time_to_check(self, now=None):
88
111
        if now is None:
89
112
            now = datetime.datetime.now()
158
181
            session.bye()
159
182
            return
160
183
        try:
161
 
            session.send(dict((client.dn, client.password)
162
 
                              for client in self.server.clients)
163
 
                         [session.peer_certificate.subject])
164
 
        except KeyError:
 
184
            session.send([client.password
 
185
                          for client in self.server.clients
 
186
                          if (client.dn ==
 
187
                              session.peer_certificate.subject)][0])
 
188
        except IndexError:
165
189
            session.send("gazonk")
166
190
            # Log maybe? XXX
167
191
        session.bye()
169
193
 
170
194
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
171
195
    __metaclass__ = server_metaclass
172
 
    request_queue_size = 1024
173
 
 
174
 
 
175
 
in6addr_any = "::"
 
196
 
176
197
 
177
198
def string_to_delta(interval):
178
199
    """Parse a string and return a datetime.timedelta
265
286
    defaults = {}
266
287
    client_config_object = ConfigParser.SafeConfigParser(defaults)
267
288
    client_config_object.read("mandos-clients.conf")
268
 
    clients = [Client(name=section, options=options,
269
 
                      **(dict(client_config_object.items(section))))
270
 
               for section in client_config_object.sections()]
 
289
    clients = Set(Client(name=section, options=options,
 
290
                         **(dict(client_config_object\
 
291
                                 .items(section))))
 
292
                  for section in client_config_object.sections())
271
293
    
 
294
    in6addr_any = "::"
272
295
    udp_server = IPv6_UDPServer((in6addr_any, options.port),
273
296
                                udp_handler,
274
297
                                options=options)
280
303
                                credentials=cred)
281
304
    
282
305
    while True:
 
306
        if not clients:
 
307
            break
283
308
        try:
284
 
            input, out, err = select.select((udp_server,
285
 
                                             tcp_server), (), ())
286
 
            if not input:
287
 
                pass
288
 
            else:
 
309
            next_stop = min(client.next_stop() for client in clients)
 
310
            now = datetime.datetime.now()
 
311
            if next_stop > now:
 
312
                delay = next_stop - now
 
313
                delay_seconds = (delay.days * 24 * 60 * 60
 
314
                                 + delay.seconds
 
315
                                 + delay.microseconds / 1000000)
 
316
                clients_with_checkers = tuple(client for client in
 
317
                                              clients
 
318
                                              if client.checker
 
319
                                              is not None)
 
320
                input_checks = (udp_server, tcp_server) \
 
321
                               + clients_with_checkers
 
322
                print "Waiting for network",
 
323
                if clients_with_checkers:
 
324
                    print "and checkers for:",
 
325
                    for client in clients_with_checkers:
 
326
                        print client.name,
 
327
                print
 
328
                input, out, err = select.select(input_checks, (), (),
 
329
                                                delay_seconds)
289
330
                for obj in input:
290
331
                    obj.handle_request()
 
332
            # start new checkers
 
333
            for client in clients:
 
334
                if client.it_is_time_to_check(now=now) and \
 
335
                       client.checker is None:
 
336
                    print "Starting checker for client %(name)s" \
 
337
                          % vars(client)
 
338
                    client.start_checker()
 
339
            # delete timed-out clients
 
340
            for client in clients.copy():
 
341
                if not client.still_valid(now=now):
 
342
                    # log xxx
 
343
                    print "Removing client %(name)s" % vars(client)
 
344
                    clients.remove(client)
291
345
        except KeyboardInterrupt:
292
346
            break
293
347