/mandos/release

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2008-11-09 06:40:29 UTC
  • mto: (24.1.113 mandos) (237.2.1 mandos)
  • mto: This revision was merged to the branch mainline in revision 238.
  • Revision ID: teddy@fukt.bsnet.se-20081109064029-df71jpoce308cq3v
First steps of a D-Bus interface to the server.

* mandos: Also import "dbus.service".
  (Client): Inherit from "dbus.service.Object", which is a new-style
            class, so inheriting from "object" is no longer necessary.
  (Client.interface): New temporary variable which only exists during
                     class definition.

  (Client.getName, Client.getFingerprint): New D-Bus getter methods.
  (Client.setSecret): New D-Bus setter method.
  (Client._set_timeout): Emit D-Bus signal "TimeoutChanged".
  (Client.getTimeout): New D-Bus getter method.
  (Client.TimeoutChanged): New D-Bus signal.
  (Client._set_interval): Emit D-Bus signal "IntervalChanged".
  (Client.getInterval): New D-Bus getter method.
  (Client.intervalChanged): New D-Bus signal.
  (Client.__init__): Also call "dbus.service.Object.__init__".
  (Client.started): New boolean attribute.
  (Client.start, Client.stop): Update "self.started", and emit D-Bus
                               signal "StateChanged".
  (Client.StateChanged): New D-Bus signal.
  (Client.stop): Use "self.started" instead of misusing "self.secret".
                 Also simplify code by using "getattr" instead of
                 "hasattr".
  (Client.checker_callback): Emit D-Bus signal "CheckerCompleted".
  (Client.CheckerCompleted): New D-Bus signal.
  (Client.bumpTimeout): D-Bus method name for "bump_timeout".
  (Client.start_checker): Emit D-Bus signal "CheckerStarted".
  (Client.CheckerStarted): New D-Bus signal.
  (Client.checkerIsRunning): New D-Bus method.
  (Client.StopChecker): D-Bus method name for "stop_checker".
  (Client.still_valid): First check "self.started".
  (Client.stillValid): D-Bus method name for "still_valid".

Show diffs side-by-side

added added

removed removed

Lines of Context:
30
30
# Contact the authors at <mandos@fukt.bsnet.se>.
31
31
32
32
 
33
 
from __future__ import division
 
33
from __future__ import division, with_statement, absolute_import
34
34
 
35
35
import SocketServer
36
36
import socket
55
55
import logging
56
56
import logging.handlers
57
57
import pwd
 
58
from contextlib import closing
58
59
 
59
60
import dbus
 
61
import dbus.service
60
62
import gobject
61
63
import avahi
62
64
from dbus.mainloop.glib import DBusGMainLoop
63
65
import ctypes
64
66
import ctypes.util
65
67
 
66
 
version = "1.0"
 
68
version = "1.0.2"
67
69
 
68
70
logger = logging.Logger('mandos')
69
71
syslogger = logging.handlers.SysLogHandler\
170
172
# End of Avahi example code
171
173
 
172
174
 
173
 
class Client(object):
 
175
class Client(dbus.service.Object):
174
176
    """A representation of a client host served by this server.
175
177
    Attributes:
176
178
    name:      string; from the config file, used in log messages
179
181
    secret:    bytestring; sent verbatim (over TLS) to client
180
182
    host:      string; available for use by the checker command
181
183
    created:   datetime.datetime(); object creation, not client host
 
184
    started:   bool()
182
185
    last_checked_ok: datetime.datetime() or None if not yet checked OK
183
186
    timeout:   datetime.timedelta(); How long from last_checked_ok
184
187
                                     until this client is invalid
200
203
    _timeout_milliseconds: Used when calling gobject.timeout_add()
201
204
    _interval_milliseconds: - '' -
202
205
    """
 
206
    interface = u"org.mandos_system.Mandos.Clients"
 
207
    
 
208
    @dbus.service.method(interface, out_signature="s")
 
209
    def getName(self):
 
210
        "D-Bus getter method"
 
211
        return self.name
 
212
    
 
213
    @dbus.service.method(interface, out_signature="s")
 
214
    def getFingerprint(self):
 
215
        "D-Bus getter method"
 
216
        return self.fingerprint
 
217
    
 
218
    @dbus.service.method(interface, in_signature="ay",
 
219
                         byte_arrays=True)
 
220
    def setSecret(self, secret):
 
221
        "D-Bus setter method"
 
222
        self.secret = secret
 
223
    
203
224
    def _set_timeout(self, timeout):
204
 
        "Setter function for 'timeout' attribute"
 
225
        "Setter function for the 'timeout' attribute"
205
226
        self._timeout = timeout
206
227
        self._timeout_milliseconds = ((self.timeout.days
207
228
                                       * 24 * 60 * 60 * 1000)
208
229
                                      + (self.timeout.seconds * 1000)
209
230
                                      + (self.timeout.microseconds
210
231
                                         // 1000))
211
 
    timeout = property(lambda self: self._timeout,
212
 
                       _set_timeout)
 
232
        # Emit D-Bus signal
 
233
        self.TimeoutChanged(self._timeout_milliseconds)
 
234
    timeout = property(lambda self: self._timeout, _set_timeout)
213
235
    del _set_timeout
 
236
    
 
237
    @dbus.service.method(interface, out_signature="t")
 
238
    def getTimeout(self):
 
239
        "D-Bus getter method"
 
240
        return self._timeout_milliseconds
 
241
    
 
242
    @dbus.service.signal(interface, signature="t")
 
243
    def TimeoutChanged(self, t):
 
244
        "D-Bus signal"
 
245
        pass
 
246
    
214
247
    def _set_interval(self, interval):
215
 
        "Setter function for 'interval' attribute"
 
248
        "Setter function for the 'interval' attribute"
216
249
        self._interval = interval
217
250
        self._interval_milliseconds = ((self.interval.days
218
251
                                        * 24 * 60 * 60 * 1000)
220
253
                                          * 1000)
221
254
                                       + (self.interval.microseconds
222
255
                                          // 1000))
223
 
    interval = property(lambda self: self._interval,
224
 
                        _set_interval)
 
256
        # Emit D-Bus signal
 
257
        self.IntervalChanged(self._interval_milliseconds)
 
258
    interval = property(lambda self: self._interval, _set_interval)
225
259
    del _set_interval
 
260
    
 
261
    @dbus.service.method(interface, out_signature="t")
 
262
    def getInterval(self):
 
263
        "D-Bus getter method"
 
264
        return self._interval_milliseconds
 
265
    
 
266
    @dbus.service.signal(interface, signature="t")
 
267
    def IntervalChanged(self, t):
 
268
        "D-Bus signal"
 
269
        pass
 
270
    
226
271
    def __init__(self, name = None, stop_hook=None, config=None):
227
272
        """Note: the 'checker' key in 'config' sets the
228
273
        'checker_command' attribute and *not* the 'checker'
229
274
        attribute."""
 
275
        dbus.service.Object.__init__(self, bus,
 
276
                                     "/Mandos/Clients/%s"
 
277
                                     % name.replace(".", "_"))
230
278
        if config is None:
231
279
            config = {}
232
280
        self.name = name
240
288
        if "secret" in config:
241
289
            self.secret = config["secret"].decode(u"base64")
242
290
        elif "secfile" in config:
243
 
            secfile = open(os.path.expanduser(os.path.expandvars
244
 
                                              (config["secfile"])))
245
 
            self.secret = secfile.read()
246
 
            secfile.close()
 
291
            with closing(open(os.path.expanduser
 
292
                              (os.path.expandvars
 
293
                               (config["secfile"])))) \
 
294
                               as secfile:
 
295
                self.secret = secfile.read()
247
296
        else:
248
297
            raise TypeError(u"No secret or secfile for client %s"
249
298
                            % self.name)
250
299
        self.host = config.get("host", "")
251
300
        self.created = datetime.datetime.now()
 
301
        self.started = False
252
302
        self.last_checked_ok = None
253
303
        self.timeout = string_to_delta(config["timeout"])
254
304
        self.interval = string_to_delta(config["interval"])
258
308
        self.stop_initiator_tag = None
259
309
        self.checker_callback_tag = None
260
310
        self.check_command = config["checker"]
 
311
    
261
312
    def start(self):
262
313
        """Start this client's checker and timeout hooks"""
 
314
        self.started = True
263
315
        # Schedule a new checker to be started an 'interval' from now,
264
316
        # and every interval from then on.
265
317
        self.checker_initiator_tag = gobject.timeout_add\
271
323
        self.stop_initiator_tag = gobject.timeout_add\
272
324
                                  (self._timeout_milliseconds,
273
325
                                   self.stop)
 
326
        # Emit D-Bus signal
 
327
        self.StateChanged(True)
 
328
    
 
329
    @dbus.service.signal(interface, signature="b")
 
330
    def StateChanged(self, started):
 
331
        "D-Bus signal"
 
332
        pass
 
333
    
274
334
    def stop(self):
275
 
        """Stop this client.
276
 
        The possibility that a client might be restarted is left open,
277
 
        but not currently used."""
278
 
        # If this client doesn't have a secret, it is already stopped.
279
 
        if hasattr(self, "secret") and self.secret:
 
335
        """Stop this client."""
 
336
        if getattr(self, "started", False):
280
337
            logger.info(u"Stopping client %s", self.name)
281
 
            self.secret = None
282
338
        else:
283
339
            return False
284
340
        if getattr(self, "stop_initiator_tag", False):
290
346
        self.stop_checker()
291
347
        if self.stop_hook:
292
348
            self.stop_hook(self)
 
349
        # Emit D-Bus signal
 
350
        self.StateChanged(False)
293
351
        # Do not run this again if called by a gobject.timeout_add
294
352
        return False
 
353
    # D-Bus variant
 
354
    Stop = dbus.service.method(interface)(stop)
 
355
    
295
356
    def __del__(self):
296
357
        self.stop_hook = None
297
358
        self.stop()
 
359
    
298
360
    def checker_callback(self, pid, condition):
299
361
        """The checker has completed, so take appropriate actions."""
300
 
        now = datetime.datetime.now()
301
362
        self.checker_callback_tag = None
302
363
        self.checker = None
303
364
        if os.WIFEXITED(condition) \
304
365
               and (os.WEXITSTATUS(condition) == 0):
305
366
            logger.info(u"Checker for %(name)s succeeded",
306
367
                        vars(self))
307
 
            self.last_checked_ok = now
308
 
            gobject.source_remove(self.stop_initiator_tag)
309
 
            self.stop_initiator_tag = gobject.timeout_add\
310
 
                                      (self._timeout_milliseconds,
311
 
                                       self.stop)
 
368
            # Emit D-Bus signal
 
369
            self.CheckerCompleted(True)
 
370
            self.bump_timeout()
312
371
        elif not os.WIFEXITED(condition):
313
372
            logger.warning(u"Checker for %(name)s crashed?",
314
373
                           vars(self))
 
374
            # Emit D-Bus signal
 
375
            self.CheckerCompleted(False)
315
376
        else:
316
377
            logger.info(u"Checker for %(name)s failed",
317
378
                        vars(self))
 
379
            # Emit D-Bus signal
 
380
            self.CheckerCompleted(False)
 
381
    
 
382
    @dbus.service.signal(interface, signature="b")
 
383
    def CheckerCompleted(self, success):
 
384
        "D-Bus signal"
 
385
        pass
 
386
    
 
387
    def bump_timeout(self):
 
388
        """Bump up the timeout for this client.
 
389
        This should only be called when the client has been seen,
 
390
        alive and well.
 
391
        """
 
392
        self.last_checked_ok = datetime.datetime.now()
 
393
        gobject.source_remove(self.stop_initiator_tag)
 
394
        self.stop_initiator_tag = gobject.timeout_add\
 
395
            (self._timeout_milliseconds, self.stop)
 
396
    # D-Bus variant
 
397
    bumpTimeout = dbus.service.method(interface)(bump_timeout)
 
398
    
318
399
    def start_checker(self):
319
400
        """Start a new checker subprocess if one is not running.
320
401
        If a checker already exists, leave it running and do
355
436
                self.checker_callback_tag = gobject.child_watch_add\
356
437
                                            (self.checker.pid,
357
438
                                             self.checker_callback)
 
439
                # Emit D-Bus signal
 
440
                self.CheckerStarted(command)
358
441
            except OSError, error:
359
442
                logger.error(u"Failed to start subprocess: %s",
360
443
                             error)
361
444
        # Re-run this periodically if run by gobject.timeout_add
362
445
        return True
 
446
    
 
447
    @dbus.service.signal(interface, signature="s")
 
448
    def CheckerStarted(self, command):
 
449
        pass
 
450
    
 
451
    @dbus.service.method(interface, out_signature="b")
 
452
    def checkerIsRunning(self):
 
453
        "D-Bus getter method"
 
454
        return self.checker is not None
 
455
    
363
456
    def stop_checker(self):
364
457
        """Force the checker process, if any, to stop."""
365
458
        if self.checker_callback_tag:
377
470
            if error.errno != errno.ESRCH: # No such process
378
471
                raise
379
472
        self.checker = None
 
473
    # D-Bus variant
 
474
    StopChecker = dbus.service.method(interface)(stop_checker)
 
475
    
380
476
    def still_valid(self):
381
477
        """Has the timeout not yet passed for this client?"""
 
478
        if not self.started:
 
479
            return False
382
480
        now = datetime.datetime.now()
383
481
        if self.last_checked_ok is None:
384
482
            return now < (self.created + self.timeout)
385
483
        else:
386
484
            return now < (self.last_checked_ok + self.timeout)
 
485
    # D-Bus variant
 
486
    stillValid = dbus.service.method(interface, out_signature="b")\
 
487
        (still_valid)
 
488
    
 
489
    del interface
387
490
 
388
491
 
389
492
def peer_certificate(session):
447
550
    
448
551
    def handle(self):
449
552
        logger.info(u"TCP connection from: %s",
450
 
                     unicode(self.client_address))
 
553
                    unicode(self.client_address))
451
554
        session = gnutls.connection.ClientSession\
452
555
                  (self.request, gnutls.connection.X509Credentials())
453
556
        
468
571
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
469
572
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
470
573
        #                "+DHE-DSS"))
471
 
        priority = "NORMAL"             # Fallback default, since this
472
 
                                        # MUST be set.
473
 
        if self.server.settings["priority"]:
474
 
            priority = self.server.settings["priority"]
 
574
        # Use a fallback default, since this MUST be set.
 
575
        priority = self.server.settings.get("priority", "NORMAL")
475
576
        gnutls.library.functions.gnutls_priority_set_direct\
476
577
            (session._c_object, priority, None)
477
578
        
507
608
                           vars(client))
508
609
            session.bye()
509
610
            return
 
611
        ## This won't work here, since we're in a fork.
 
612
        # client.bump_timeout()
510
613
        sent_size = 0
511
614
        while sent_size < len(client.secret):
512
615
            sent = session.send(client.secret[sent_size:])
517
620
        session.bye()
518
621
 
519
622
 
520
 
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
 
623
class IPv6_TCPServer(SocketServer.ForkingMixIn,
 
624
                     SocketServer.TCPServer, object):
521
625
    """IPv6 TCP server.  Accepts 'None' as address and/or port.
522
626
    Attributes:
523
627
        settings:       Server settings
652
756
        def if_nametoindex(interface):
653
757
            "Get an interface index the hard way, i.e. using fcntl()"
654
758
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
655
 
            s = socket.socket()
656
 
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
657
 
                                struct.pack("16s16x", interface))
658
 
            s.close()
 
759
            with closing(socket.socket()) as s:
 
760
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
 
761
                                    struct.pack("16s16x", interface))
659
762
            interface_index = struct.unpack("I", ifreq[16:20])[0]
660
763
            return interface_index
661
764
    return if_nametoindex(interface)