/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-01-30 17:28:18 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080130172818-fnrq2xmtg1wa8wo2
* client.cpp (main): Get t_old early since it is used on error exits.

* server.py (Client.checker): New attribute.
  (Client.check_action, Client.start_checker, Client.stop_checker,
  Client.fileno, Client.next_stop, Client.still_valid,
  Client.it_is_time_to_check): New methods.
  (Client.handle_request): Alias to "check_action".
  (main): Stop all started checkers on exit.  Exit cleanly on keyboard
  interrupt.

Show diffs side-by-side

added added

removed removed

Lines of Context:
11
11
import gnutls.errors
12
12
import ConfigParser
13
13
import sys
 
14
import re
 
15
import os
 
16
import signal
14
17
 
15
18
class Client(object):
16
 
    def __init__(self, name=None, dn=None, password=None,
17
 
                 passfile=None, fqdn=None, timeout=None,
18
 
                 interval=-1):
 
19
    def __init__(self, name=None, options=None, dn=None,
 
20
                 password=None, passfile=None, fqdn=None,
 
21
                 timeout=None, interval=-1):
19
22
        self.name = name
20
23
        self.dn = dn
21
24
        if password:
26
29
            print "No Password or Passfile in client config file"
27
30
            # raise RuntimeError XXX
28
31
            self.password = "gazonk"
29
 
        self.fqdn = fqdn
 
32
        self.fqdn = fqdn                # string
30
33
        self.created = datetime.datetime.now()
31
 
        self.last_seen = None
 
34
        self.last_seen = None           # datetime.datetime()
32
35
        if timeout is None:
33
 
            timeout = self.server.options.timeout
34
 
        self.timeout = timeout
 
36
            timeout = options.timeout
 
37
        self.timeout = timeout          # datetime.timedelta()
35
38
        if interval == -1:
36
 
            interval = self.server.options.interval
37
 
        self.interval = interval
38
 
        self.next_check = datetime.datetime.now()
39
 
 
40
 
def server_bind(self):
41
 
    if self.options.interface:
42
 
        if not hasattr(socket, "SO_BINDTODEVICE"):
43
 
            # From /usr/include/asm-i486/socket.h
44
 
            socket.SO_BINDTODEVICE = 25
 
39
            interval = options.interval
 
40
        self.interval = interval        # datetime.timedelta()
 
41
        self.next_check = datetime.datetime.now() # datetime.datetime()
 
42
        self.checker = None             # or a subprocess.Popen()
 
43
    def check_action(self, now=None):
 
44
        """The checker said something and might have completed.
 
45
        Check if is has, and take appropriate actions."""
 
46
        if self.checker.poll() is None:
 
47
            # False alarm, no result yet
 
48
            #self.checker.read()
 
49
            return
 
50
        if now is None:
 
51
            now = datetime.datetime.now()
 
52
        if self.checker.returncode == 0:
 
53
            self.last_seen = now
 
54
        while self.next_check <= now:
 
55
            self.next_check += self.interval
 
56
    handle_request = check_action
 
57
    def start_checker(self):
 
58
        self.stop_checker()
45
59
        try:
46
 
            self.socket.setsockopt(socket.SOL_SOCKET,
47
 
                                   socket.SO_BINDTODEVICE,
48
 
                                   self.options.interface)
49
 
        except socket.error, error:
50
 
            if error[0] == errno.EPERM:
51
 
                print "Warning: Denied permission to bind to interface", \
52
 
                      self.options.interface
53
 
            else:
54
 
                raise error
55
 
    return super(type(self), self).server_bind()
56
 
 
57
 
 
58
 
def init_with_options(self, *args, **kwargs):
59
 
    if "options" in kwargs:
60
 
        self.options = kwargs["options"]
61
 
        del kwargs["options"]
62
 
    if "clients" in kwargs:
63
 
        self.clients = kwargs["clients"]
64
 
        del kwargs["clients"]
65
 
    if "credentials" in kwargs:
66
 
        self.credentials = kwargs["credentials"]
67
 
        del kwargs["credentials"]
68
 
    return super(type(self), self).__init__(*args, **kwargs)
 
60
            self.checker = subprocess.Popen("sleep 1; fping -q -- %s"
 
61
                                            % re.escape(self.fqdn),
 
62
                                            stdout=subprocess.PIPE,
 
63
                                            close_fds=True,
 
64
                                            shell=True, cwd="/")
 
65
        except subprocess.OSError, e:
 
66
            print "Failed to start subprocess:", e
 
67
    def stop_checker(self):
 
68
        if self.checker is None:
 
69
            return
 
70
        os.kill(self.checker.pid, signal.SIGTERM)
 
71
        if self.checker.poll() is None:
 
72
            os.kill(self.checker.pid, signal.SIGKILL)
 
73
        self.checker = None
 
74
    __del__ = stop_checker
 
75
    def fileno(self):
 
76
        if self.checker is None:
 
77
            return None
 
78
        return self.checker.stdout.fileno()
 
79
    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)
 
82
    def still_valid(self, now=None):
 
83
        """Has this client's timeout not passed?"""
 
84
        if now is None:
 
85
            now = datetime.datetime.now()
 
86
        return now < (self.last_seen + timeout)
 
87
    def it_is_time_to_check(self, now=None):
 
88
        if now is None:
 
89
            now = datetime.datetime.now()
 
90
        return self.next_check <= now
 
91
 
 
92
 
 
93
class server_metaclass(type):
 
94
    "Common behavior for the UDP and TCP server classes"
 
95
    def __new__(cls, name, bases, attrs):
 
96
        attrs["address_family"] = socket.AF_INET6
 
97
        attrs["allow_reuse_address"] = True
 
98
        def server_bind(self):
 
99
            if self.options.interface:
 
100
                if not hasattr(socket, "SO_BINDTODEVICE"):
 
101
                    # From /usr/include/asm-i486/socket.h
 
102
                    socket.SO_BINDTODEVICE = 25
 
103
                try:
 
104
                    self.socket.setsockopt(socket.SOL_SOCKET,
 
105
                                           socket.SO_BINDTODEVICE,
 
106
                                           self.options.interface)
 
107
                except socket.error, error:
 
108
                    if error[0] == errno.EPERM:
 
109
                        print "Warning: No permission to bind to interface", \
 
110
                              self.options.interface
 
111
                    else:
 
112
                        raise error
 
113
            return super(type(self), self).server_bind()
 
114
        attrs["server_bind"] = server_bind
 
115
        def init(self, *args, **kwargs):
 
116
            if "options" in kwargs:
 
117
                self.options = kwargs["options"]
 
118
                del kwargs["options"]
 
119
            if "clients" in kwargs:
 
120
                self.clients = kwargs["clients"]
 
121
                del kwargs["clients"]
 
122
            if "credentials" in kwargs:
 
123
                self.credentials = kwargs["credentials"]
 
124
                del kwargs["credentials"]
 
125
            return super(type(self), self).__init__(*args, **kwargs)
 
126
        attrs["__init__"] = init
 
127
        return type.__new__(cls, name, bases, attrs)
69
128
 
70
129
 
71
130
class udp_handler(SocketServer.DatagramRequestHandler, object):
75
134
 
76
135
 
77
136
class IPv6_UDPServer(SocketServer.UDPServer, object):
78
 
    __init__ = init_with_options
79
 
    address_family = socket.AF_INET6
80
 
    allow_reuse_address = True
81
 
    server_bind = server_bind
 
137
    __metaclass__ = server_metaclass
82
138
    def verify_request(self, request, client_address):
83
139
        print "UDP request came"
84
140
        return request[0] == "Marco"
110
166
            # Log maybe? XXX
111
167
        session.bye()
112
168
 
 
169
 
113
170
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
114
 
    __init__ = init_with_options
115
 
    address_family = socket.AF_INET6
116
 
    allow_reuse_address = True
 
171
    __metaclass__ = server_metaclass
117
172
    request_queue_size = 1024
118
 
    server_bind = server_bind
119
173
 
120
174
 
121
175
in6addr_any = "::"
122
176
 
123
 
cred = None
124
 
 
125
177
def string_to_delta(interval):
126
178
    """Parse a string and return a datetime.timedelta
127
179
 
155
207
        raise ValueError
156
208
    return delta
157
209
 
 
210
 
158
211
def main():
159
212
    parser = OptionParser()
160
213
    parser.add_option("-i", "--interface", type="string",
212
265
    defaults = {}
213
266
    client_config_object = ConfigParser.SafeConfigParser(defaults)
214
267
    client_config_object.read("mandos-clients.conf")
215
 
    clients = [Client(name=section,
 
268
    clients = [Client(name=section, options=options,
216
269
                      **(dict(client_config_object.items(section))))
217
270
               for section in client_config_object.sections()]
218
271
    
227
280
                                credentials=cred)
228
281
    
229
282
    while True:
230
 
        in_, out, err = select.select((udp_server,
231
 
                                       tcp_server), (), ())
232
 
        for server in in_:
233
 
            server.handle_request()
 
283
        try:
 
284
            input, out, err = select.select((udp_server,
 
285
                                             tcp_server), (), ())
 
286
            if not input:
 
287
                pass
 
288
            else:
 
289
                for obj in input:
 
290
                    obj.handle_request()
 
291
        except KeyboardInterrupt:
 
292
            break
 
293
    
 
294
    # Cleanup here
 
295
    for client in clients:
 
296
        client.stop_checker()
234
297
 
235
298
 
236
299
if __name__ == "__main__":