/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: 2007-12-11 23:40:35 UTC
  • Revision ID: belorn@braxen-20071211234035-m1nsu41vuzkak69h
Python based server
Added client configfile

Show diffs side-by-side

added added

removed removed

Lines of Context:
10
10
import gnutls.connection
11
11
import gnutls.errors
12
12
import ConfigParser
13
 
import sys
14
 
import re
15
 
import os
16
 
import signal
17
13
 
18
14
class Client(object):
19
 
    def __init__(self, name=None, options=None, dn=None,
20
 
                 password=None, passfile=None, fqdn=None,
21
 
                 timeout=None, interval=-1):
 
15
    def __init__(self, name=None, dn=None, password=None,
 
16
                 passfile=None, fqdn=None, timeout=None,
 
17
                 interval=-1):
22
18
        self.name = name
23
19
        self.dn = dn
24
20
        if password:
29
25
            print "No Password or Passfile in client config file"
30
26
            # raise RuntimeError XXX
31
27
            self.password = "gazonk"
32
 
        self.fqdn = fqdn                # string
33
 
        self.created = datetime.datetime.now()
34
 
        self.last_seen = None           # datetime.datetime()
 
28
        self.fqdn = fqdn
 
29
        # self.created = ...
 
30
        self.last_seen = None
35
31
        if timeout is None:
36
 
            timeout = options.timeout
37
 
        self.timeout = timeout          # datetime.timedelta()
 
32
            timeout = self.server.options.timeout
 
33
        self.timeout = timeout
38
34
        if interval == -1:
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()
 
35
            interval = self.server.options.interval
 
36
        self.interval = interval
 
37
 
 
38
def server_bind(self):
 
39
    if self.options.interface:
 
40
        if not hasattr(socket, "SO_BINDTODEVICE"):
 
41
            # From /usr/include/asm-i486/socket.h
 
42
            socket.SO_BINDTODEVICE = 25
59
43
        try:
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)
 
44
            self.socket.setsockopt(socket.SOL_SOCKET,
 
45
                                   socket.SO_BINDTODEVICE,
 
46
                                   self.options.interface)
 
47
        except socket.error, error:
 
48
            if error[0] == errno.EPERM:
 
49
                print "Warning: Denied permission to bind to interface", \
 
50
                      self.options.interface
 
51
            else:
 
52
                raise error
 
53
    return super(type(self), self).server_bind()
 
54
 
 
55
 
 
56
def init_with_options(self, *args, **kwargs):
 
57
    if "options" in kwargs:
 
58
        self.options = kwargs["options"]
 
59
        del kwargs["options"]
 
60
    if "clients" in kwargs:
 
61
        self.clients = kwargs["clients"]
 
62
        del kwargs["clients"]
 
63
    if "credentials" in kwargs:
 
64
        self.credentials = kwargs["credentials"]
 
65
        del kwargs["credentials"]
 
66
    return super(type(self), self).__init__(*args, **kwargs)
128
67
 
129
68
 
130
69
class udp_handler(SocketServer.DatagramRequestHandler, object):
134
73
 
135
74
 
136
75
class IPv6_UDPServer(SocketServer.UDPServer, object):
137
 
    __metaclass__ = server_metaclass
 
76
    __init__ = init_with_options
 
77
    address_family = socket.AF_INET6
 
78
    allow_reuse_address = True
 
79
    server_bind = server_bind
138
80
    def verify_request(self, request, client_address):
139
81
        print "UDP request came"
140
82
        return request[0] == "Marco"
166
108
            # Log maybe? XXX
167
109
        session.bye()
168
110
 
169
 
 
170
111
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
171
 
    __metaclass__ = server_metaclass
 
112
    __init__ = init_with_options
 
113
    address_family = socket.AF_INET6
 
114
    allow_reuse_address = True
172
115
    request_queue_size = 1024
 
116
    server_bind = server_bind
173
117
 
174
118
 
175
119
in6addr_any = "::"
176
120
 
177
 
def string_to_delta(interval):
178
 
    """Parse a string and return a datetime.timedelta
179
 
 
180
 
    >>> string_to_delta('7d')
181
 
    datetime.timedelta(7)
182
 
    >>> string_to_delta('60s')
183
 
    datetime.timedelta(0, 60)
184
 
    >>> string_to_delta('60m')
185
 
    datetime.timedelta(0, 3600)
186
 
    >>> string_to_delta('24h')
187
 
    datetime.timedelta(1)
188
 
    >>> string_to_delta(u'1w')
189
 
    datetime.timedelta(7)
190
 
    """
191
 
    try:
192
 
        suffix=unicode(interval[-1])
193
 
        value=int(interval[:-1])
194
 
        if suffix == u"d":
195
 
            delta = datetime.timedelta(value)
196
 
        elif suffix == u"s":
197
 
            delta = datetime.timedelta(0, value)
198
 
        elif suffix == u"m":
199
 
            delta = datetime.timedelta(0, 0, 0, 0, value)
200
 
        elif suffix == u"h":
201
 
            delta = datetime.timedelta(0, 0, 0, 0, 0, value)
202
 
        elif suffix == u"w":
203
 
            delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
204
 
        else:
205
 
            raise ValueError
206
 
    except (ValueError, IndexError):
207
 
        raise ValueError
208
 
    return delta
209
 
 
 
121
cred = None
210
122
 
211
123
def main():
212
124
    parser = OptionParser()
232
144
    parser.add_option("-t", "--timeout", type="string", # Parsed later
233
145
                      default="15m",
234
146
                      help="Amount of downtime allowed for clients")
235
 
    parser.add_option("--interval", type="string", # Parsed later
236
 
                      default="5m",
237
 
                      help="How often to check that a client is up")
238
 
    parser.add_option("--check", action="store_true", default=False,
239
 
                      help="Run self-test")
240
147
    (options, args) = parser.parse_args()
241
 
 
242
 
    if options.check:
243
 
        import doctest
244
 
        doctest.testmod()
245
 
        sys.exit()
246
148
    
247
 
    # Parse the time arguments
 
149
    # Parse the time argument
248
150
    try:
249
 
        options.timeout = string_to_delta(options.timeout)
250
 
    except ValueError:
 
151
        suffix=options.timeout[-1]
 
152
        value=int(options.timeout[:-1])
 
153
        if suffix == "d":
 
154
            options.timeout = datetime.timedelta(value)
 
155
        elif suffix == "s":
 
156
            options.timeout = datetime.timedelta(0, value)
 
157
        elif suffix == "m":
 
158
            options.timeout = datetime.timedelta(0, 0, 0, 0, value)
 
159
        elif suffix == "h":
 
160
            options.timeout = datetime.timedelta(0, 0, 0, 0, 0, value)
 
161
        elif suffix == "w":
 
162
            options.timeout = datetime.timedelta(0, 0, 0, 0, 0, 0,
 
163
                                                 value)
 
164
        else:
 
165
            raise ValueError
 
166
    except (ValueError, IndexError):
251
167
        parser.error("option --timeout: Unparseable time")
252
168
    
253
 
    try:
254
 
        options.interval = string_to_delta(options.interval)
255
 
    except ValueError:
256
 
        parser.error("option --interval: Unparseable time")
257
 
    
258
169
    cert = gnutls.crypto.X509Certificate(open(options.cert).read())
259
170
    key = gnutls.crypto.X509PrivateKey(open(options.key).read())
260
171
    ca = gnutls.crypto.X509Certificate(open(options.ca).read())
265
176
    defaults = {}
266
177
    client_config_object = ConfigParser.SafeConfigParser(defaults)
267
178
    client_config_object.read("mandos-clients.conf")
268
 
    clients = [Client(name=section, options=options,
 
179
    clients = [Client(name=section,
269
180
                      **(dict(client_config_object.items(section))))
270
181
               for section in client_config_object.sections()]
271
182
    
280
191
                                credentials=cred)
281
192
    
282
193
    while True:
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()
 
194
        in_, out, err = select.select((udp_server,
 
195
                                       tcp_server), (), ())
 
196
        for server in in_:
 
197
            server.handle_request()
297
198
 
298
199
 
299
200
if __name__ == "__main__":