/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 mandos

  • Committer: Teddy Hogeborn
  • Date: 2009-12-27 03:35:58 UTC
  • Revision ID: teddy@fukt.bsnet.se-20091227033558-y57wv1u4cls3i8kq
TODO file changes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
55
55
import logging
56
56
import logging.handlers
57
57
import pwd
58
 
from contextlib import closing
 
58
import contextlib
59
59
import struct
60
60
import fcntl
61
61
import functools
 
62
import cPickle as pickle
62
63
 
63
64
import dbus
64
65
import dbus.service
67
68
from dbus.mainloop.glib import DBusGMainLoop
68
69
import ctypes
69
70
import ctypes.util
 
71
import xml.dom.minidom
 
72
import inspect
70
73
 
71
74
try:
72
75
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
77
80
        SO_BINDTODEVICE = None
78
81
 
79
82
 
80
 
version = "1.0.11"
 
83
version = "1.0.14"
81
84
 
82
85
logger = logging.Logger(u'mandos')
83
86
syslogger = (logging.handlers.SysLogHandler
174
177
                                    self.server.EntryGroupNew()),
175
178
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
176
179
            self.group.connect_to_signal('StateChanged',
177
 
                                         self.entry_group_state_changed)
 
180
                                         self
 
181
                                         .entry_group_state_changed)
178
182
        logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
179
183
                     self.name, self.type)
180
184
        self.group.AddService(
239
243
    enabled:    bool()
240
244
    last_checked_ok: datetime.datetime(); (UTC) or None
241
245
    timeout:    datetime.timedelta(); How long from last_checked_ok
242
 
                                      until this client is invalid
 
246
                                      until this client is disabled
243
247
    interval:   datetime.timedelta(); How often to start a new checker
244
248
    disable_hook:  If set, called by disable() as disable_hook(self)
245
249
    checker:    subprocess.Popen(); a running checker process used
246
250
                                    to see if the client lives.
247
251
                                    'None' if no process is running.
248
252
    checker_initiator_tag: a gobject event source tag, or None
249
 
    disable_initiator_tag:    - '' -
 
253
    disable_initiator_tag: - '' -
250
254
    checker_callback_tag:  - '' -
251
255
    checker_command: string; External command which is run to check if
252
256
                     client lives.  %() expansions are done at
256
260
    """
257
261
    
258
262
    @staticmethod
259
 
    def _datetime_to_milliseconds(dt):
260
 
        "Convert a datetime.datetime() to milliseconds"
261
 
        return ((dt.days * 24 * 60 * 60 * 1000)
262
 
                + (dt.seconds * 1000)
263
 
                + (dt.microseconds // 1000))
 
263
    def _timedelta_to_milliseconds(td):
 
264
        "Convert a datetime.timedelta() to milliseconds"
 
265
        return ((td.days * 24 * 60 * 60 * 1000)
 
266
                + (td.seconds * 1000)
 
267
                + (td.microseconds // 1000))
264
268
    
265
269
    def timeout_milliseconds(self):
266
270
        "Return the 'timeout' attribute in milliseconds"
267
 
        return self._datetime_to_milliseconds(self.timeout)
 
271
        return self._timedelta_to_milliseconds(self.timeout)
268
272
    
269
273
    def interval_milliseconds(self):
270
274
        "Return the 'interval' attribute in milliseconds"
271
 
        return self._datetime_to_milliseconds(self.interval)
 
275
        return self._timedelta_to_milliseconds(self.interval)
272
276
    
273
277
    def __init__(self, name = None, disable_hook=None, config=None):
274
278
        """Note: the 'checker' key in 'config' sets the
287
291
        if u"secret" in config:
288
292
            self.secret = config[u"secret"].decode(u"base64")
289
293
        elif u"secfile" in config:
290
 
            with closing(open(os.path.expanduser
291
 
                              (os.path.expandvars
292
 
                               (config[u"secfile"])))) as secfile:
 
294
            with open(os.path.expanduser(os.path.expandvars
 
295
                                         (config[u"secfile"])),
 
296
                      "rb") as secfile:
293
297
                self.secret = secfile.read()
294
298
        else:
295
299
            raise TypeError(u"No secret or secfile for client %s"
321
325
        self.checker_initiator_tag = (gobject.timeout_add
322
326
                                      (self.interval_milliseconds(),
323
327
                                       self.start_checker))
324
 
        # Also start a new checker *right now*.
325
 
        self.start_checker()
326
328
        # Schedule a disable() when 'timeout' has passed
327
329
        self.disable_initiator_tag = (gobject.timeout_add
328
330
                                   (self.timeout_milliseconds(),
329
331
                                    self.disable))
330
332
        self.enabled = True
 
333
        # Also start a new checker *right now*.
 
334
        self.start_checker()
331
335
    
332
 
    def disable(self):
 
336
    def disable(self, quiet=True):
333
337
        """Disable this client."""
334
338
        if not getattr(self, "enabled", False):
335
339
            return False
336
 
        logger.info(u"Disabling client %s", self.name)
 
340
        if not quiet:
 
341
            logger.info(u"Disabling client %s", self.name)
337
342
        if getattr(self, u"disable_initiator_tag", False):
338
343
            gobject.source_remove(self.disable_initiator_tag)
339
344
            self.disable_initiator_tag = None
391
396
        # client would inevitably timeout, since no checker would get
392
397
        # a chance to run to completion.  If we instead leave running
393
398
        # checkers alone, the checker would have to take more time
394
 
        # than 'timeout' for the client to be declared invalid, which
395
 
        # is as it should be.
 
399
        # than 'timeout' for the client to be disabled, which is as it
 
400
        # should be.
396
401
        
397
402
        # If a checker exists, make sure it is not a zombie
398
 
        if self.checker is not None:
 
403
        try:
399
404
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
 
405
        except (AttributeError, OSError), error:
 
406
            if (isinstance(error, OSError)
 
407
                and error.errno != errno.ECHILD):
 
408
                raise error
 
409
        else:
400
410
            if pid:
401
411
                logger.warning(u"Checker was a zombie")
402
412
                gobject.source_remove(self.checker_callback_tag)
458
468
        logger.debug(u"Stopping checker for %(name)s", vars(self))
459
469
        try:
460
470
            os.kill(self.checker.pid, signal.SIGTERM)
461
 
            #os.sleep(0.5)
 
471
            #time.sleep(0.5)
462
472
            #if self.checker.poll() is None:
463
473
            #    os.kill(self.checker.pid, signal.SIGKILL)
464
474
        except OSError, error:
465
475
            if error.errno != errno.ESRCH: # No such process
466
476
                raise
467
477
        self.checker = None
468
 
    
469
 
    def still_valid(self):
470
 
        """Has the timeout not yet passed for this client?"""
471
 
        if not getattr(self, u"enabled", False):
472
 
            return False
473
 
        now = datetime.datetime.utcnow()
474
 
        if self.last_checked_ok is None:
475
 
            return now < (self.created + self.timeout)
476
 
        else:
477
 
            return now < (self.last_checked_ok + self.timeout)
478
 
 
479
 
 
480
 
class ClientDBus(Client, dbus.service.Object):
 
478
 
 
479
 
 
480
def dbus_service_property(dbus_interface, signature=u"v",
 
481
                          access=u"readwrite", byte_arrays=False):
 
482
    """Decorators for marking methods of a DBusObjectWithProperties to
 
483
    become properties on the D-Bus.
 
484
    
 
485
    The decorated method will be called with no arguments by "Get"
 
486
    and with one argument by "Set".
 
487
    
 
488
    The parameters, where they are supported, are the same as
 
489
    dbus.service.method, except there is only "signature", since the
 
490
    type from Get() and the type sent to Set() is the same.
 
491
    """
 
492
    # Encoding deeply encoded byte arrays is not supported yet by the
 
493
    # "Set" method, so we fail early here:
 
494
    if byte_arrays and signature != u"ay":
 
495
        raise ValueError(u"Byte arrays not supported for non-'ay'"
 
496
                         u" signature %r" % signature)
 
497
    def decorator(func):
 
498
        func._dbus_is_property = True
 
499
        func._dbus_interface = dbus_interface
 
500
        func._dbus_signature = signature
 
501
        func._dbus_access = access
 
502
        func._dbus_name = func.__name__
 
503
        if func._dbus_name.endswith(u"_dbus_property"):
 
504
            func._dbus_name = func._dbus_name[:-14]
 
505
        func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
 
506
        return func
 
507
    return decorator
 
508
 
 
509
 
 
510
class DBusPropertyException(dbus.exceptions.DBusException):
 
511
    """A base class for D-Bus property-related exceptions
 
512
    """
 
513
    def __unicode__(self):
 
514
        return unicode(str(self))
 
515
 
 
516
 
 
517
class DBusPropertyAccessException(DBusPropertyException):
 
518
    """A property's access permissions disallows an operation.
 
519
    """
 
520
    pass
 
521
 
 
522
 
 
523
class DBusPropertyNotFound(DBusPropertyException):
 
524
    """An attempt was made to access a non-existing property.
 
525
    """
 
526
    pass
 
527
 
 
528
 
 
529
class DBusObjectWithProperties(dbus.service.Object):
 
530
    """A D-Bus object with properties.
 
531
 
 
532
    Classes inheriting from this can use the dbus_service_property
 
533
    decorator to expose methods as D-Bus properties.  It exposes the
 
534
    standard Get(), Set(), and GetAll() methods on the D-Bus.
 
535
    """
 
536
    
 
537
    @staticmethod
 
538
    def _is_dbus_property(obj):
 
539
        return getattr(obj, u"_dbus_is_property", False)
 
540
    
 
541
    def _get_all_dbus_properties(self):
 
542
        """Returns a generator of (name, attribute) pairs
 
543
        """
 
544
        return ((prop._dbus_name, prop)
 
545
                for name, prop in
 
546
                inspect.getmembers(self, self._is_dbus_property))
 
547
    
 
548
    def _get_dbus_property(self, interface_name, property_name):
 
549
        """Returns a bound method if one exists which is a D-Bus
 
550
        property with the specified name and interface.
 
551
        """
 
552
        for name in (property_name,
 
553
                     property_name + u"_dbus_property"):
 
554
            prop = getattr(self, name, None)
 
555
            if (prop is None
 
556
                or not self._is_dbus_property(prop)
 
557
                or prop._dbus_name != property_name
 
558
                or (interface_name and prop._dbus_interface
 
559
                    and interface_name != prop._dbus_interface)):
 
560
                continue
 
561
            return prop
 
562
        # No such property
 
563
        raise DBusPropertyNotFound(self.dbus_object_path + u":"
 
564
                                   + interface_name + u"."
 
565
                                   + property_name)
 
566
    
 
567
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
 
568
                         out_signature=u"v")
 
569
    def Get(self, interface_name, property_name):
 
570
        """Standard D-Bus property Get() method, see D-Bus standard.
 
571
        """
 
572
        prop = self._get_dbus_property(interface_name, property_name)
 
573
        if prop._dbus_access == u"write":
 
574
            raise DBusPropertyAccessException(property_name)
 
575
        value = prop()
 
576
        if not hasattr(value, u"variant_level"):
 
577
            return value
 
578
        return type(value)(value, variant_level=value.variant_level+1)
 
579
    
 
580
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
 
581
    def Set(self, interface_name, property_name, value):
 
582
        """Standard D-Bus property Set() method, see D-Bus standard.
 
583
        """
 
584
        prop = self._get_dbus_property(interface_name, property_name)
 
585
        if prop._dbus_access == u"read":
 
586
            raise DBusPropertyAccessException(property_name)
 
587
        if prop._dbus_get_args_options[u"byte_arrays"]:
 
588
            # The byte_arrays option is not supported yet on
 
589
            # signatures other than "ay".
 
590
            if prop._dbus_signature != u"ay":
 
591
                raise ValueError
 
592
            value = dbus.ByteArray(''.join(unichr(byte)
 
593
                                           for byte in value))
 
594
        prop(value)
 
595
    
 
596
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
 
597
                         out_signature=u"a{sv}")
 
598
    def GetAll(self, interface_name):
 
599
        """Standard D-Bus property GetAll() method, see D-Bus
 
600
        standard.
 
601
 
 
602
        Note: Will not include properties with access="write".
 
603
        """
 
604
        all = {}
 
605
        for name, prop in self._get_all_dbus_properties():
 
606
            if (interface_name
 
607
                and interface_name != prop._dbus_interface):
 
608
                # Interface non-empty but did not match
 
609
                continue
 
610
            # Ignore write-only properties
 
611
            if prop._dbus_access == u"write":
 
612
                continue
 
613
            value = prop()
 
614
            if not hasattr(value, u"variant_level"):
 
615
                all[name] = value
 
616
                continue
 
617
            all[name] = type(value)(value, variant_level=
 
618
                                    value.variant_level+1)
 
619
        return dbus.Dictionary(all, signature=u"sv")
 
620
    
 
621
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
 
622
                         out_signature=u"s",
 
623
                         path_keyword='object_path',
 
624
                         connection_keyword='connection')
 
625
    def Introspect(self, object_path, connection):
 
626
        """Standard D-Bus method, overloaded to insert property tags.
 
627
        """
 
628
        xmlstring = dbus.service.Object.Introspect(self, object_path,
 
629
                                                   connection)
 
630
        try:
 
631
            document = xml.dom.minidom.parseString(xmlstring)
 
632
            def make_tag(document, name, prop):
 
633
                e = document.createElement(u"property")
 
634
                e.setAttribute(u"name", name)
 
635
                e.setAttribute(u"type", prop._dbus_signature)
 
636
                e.setAttribute(u"access", prop._dbus_access)
 
637
                return e
 
638
            for if_tag in document.getElementsByTagName(u"interface"):
 
639
                for tag in (make_tag(document, name, prop)
 
640
                            for name, prop
 
641
                            in self._get_all_dbus_properties()
 
642
                            if prop._dbus_interface
 
643
                            == if_tag.getAttribute(u"name")):
 
644
                    if_tag.appendChild(tag)
 
645
                # Add the names to the return values for the
 
646
                # "org.freedesktop.DBus.Properties" methods
 
647
                if (if_tag.getAttribute(u"name")
 
648
                    == u"org.freedesktop.DBus.Properties"):
 
649
                    for cn in if_tag.getElementsByTagName(u"method"):
 
650
                        if cn.getAttribute(u"name") == u"Get":
 
651
                            for arg in cn.getElementsByTagName(u"arg"):
 
652
                                if (arg.getAttribute(u"direction")
 
653
                                    == u"out"):
 
654
                                    arg.setAttribute(u"name", u"value")
 
655
                        elif cn.getAttribute(u"name") == u"GetAll":
 
656
                            for arg in cn.getElementsByTagName(u"arg"):
 
657
                                if (arg.getAttribute(u"direction")
 
658
                                    == u"out"):
 
659
                                    arg.setAttribute(u"name", u"props")
 
660
            xmlstring = document.toxml(u"utf-8")
 
661
            document.unlink()
 
662
        except (AttributeError, xml.dom.DOMException,
 
663
                xml.parsers.expat.ExpatError), error:
 
664
            logger.error(u"Failed to override Introspection method",
 
665
                         error)
 
666
        return xmlstring
 
667
 
 
668
 
 
669
class ClientDBus(Client, DBusObjectWithProperties):
481
670
    """A Client class using D-Bus
482
671
    
483
672
    Attributes:
494
683
        self.dbus_object_path = (dbus.ObjectPath
495
684
                                 (u"/clients/"
496
685
                                  + self.name.replace(u".", u"_")))
497
 
        dbus.service.Object.__init__(self, self.bus,
498
 
                                     self.dbus_object_path)
 
686
        DBusObjectWithProperties.__init__(self, self.bus,
 
687
                                          self.dbus_object_path)
499
688
    
500
689
    @staticmethod
501
690
    def _datetime_to_dbus(dt, variant_level=0):
516
705
                                       variant_level=1))
517
706
        return r
518
707
    
519
 
    def disable(self, signal = True):
 
708
    def disable(self, quiet = False):
520
709
        oldstate = getattr(self, u"enabled", False)
521
 
        r = Client.disable(self)
522
 
        if signal and oldstate != self.enabled:
 
710
        r = Client.disable(self, quiet=quiet)
 
711
        if not quiet and oldstate != self.enabled:
523
712
            # Emit D-Bus signal
524
713
            self.PropertyChanged(dbus.String(u"enabled"),
525
714
                                 dbus.Boolean(False, variant_level=1))
530
719
            self.remove_from_connection()
531
720
        except LookupError:
532
721
            pass
533
 
        if hasattr(dbus.service.Object, u"__del__"):
534
 
            dbus.service.Object.__del__(self, *args, **kwargs)
 
722
        if hasattr(DBusObjectWithProperties, u"__del__"):
 
723
            DBusObjectWithProperties.__del__(self, *args, **kwargs)
535
724
        Client.__del__(self, *args, **kwargs)
536
725
    
537
726
    def checker_callback(self, pid, condition, command,
611
800
        "D-Bus signal"
612
801
        pass
613
802
    
614
 
    # GetAllProperties - method
615
 
    @dbus.service.method(_interface, out_signature=u"a{sv}")
616
 
    def GetAllProperties(self):
617
 
        "D-Bus method"
618
 
        return dbus.Dictionary({
619
 
                dbus.String(u"name"):
620
 
                    dbus.String(self.name, variant_level=1),
621
 
                dbus.String(u"fingerprint"):
622
 
                    dbus.String(self.fingerprint, variant_level=1),
623
 
                dbus.String(u"host"):
624
 
                    dbus.String(self.host, variant_level=1),
625
 
                dbus.String(u"created"):
626
 
                    self._datetime_to_dbus(self.created,
627
 
                                           variant_level=1),
628
 
                dbus.String(u"last_enabled"):
629
 
                    (self._datetime_to_dbus(self.last_enabled,
630
 
                                            variant_level=1)
631
 
                     if self.last_enabled is not None
632
 
                     else dbus.Boolean(False, variant_level=1)),
633
 
                dbus.String(u"enabled"):
634
 
                    dbus.Boolean(self.enabled, variant_level=1),
635
 
                dbus.String(u"last_checked_ok"):
636
 
                    (self._datetime_to_dbus(self.last_checked_ok,
637
 
                                            variant_level=1)
638
 
                     if self.last_checked_ok is not None
639
 
                     else dbus.Boolean (False, variant_level=1)),
640
 
                dbus.String(u"timeout"):
641
 
                    dbus.UInt64(self.timeout_milliseconds(),
642
 
                                variant_level=1),
643
 
                dbus.String(u"interval"):
644
 
                    dbus.UInt64(self.interval_milliseconds(),
645
 
                                variant_level=1),
646
 
                dbus.String(u"checker"):
647
 
                    dbus.String(self.checker_command,
648
 
                                variant_level=1),
649
 
                dbus.String(u"checker_running"):
650
 
                    dbus.Boolean(self.checker is not None,
651
 
                                 variant_level=1),
652
 
                dbus.String(u"object_path"):
653
 
                    dbus.ObjectPath(self.dbus_object_path,
654
 
                                    variant_level=1)
655
 
                }, signature=u"sv")
656
 
    
657
 
    # IsStillValid - method
658
 
    @dbus.service.method(_interface, out_signature=u"b")
659
 
    def IsStillValid(self):
660
 
        return self.still_valid()
661
 
    
662
803
    # PropertyChanged - signal
663
804
    @dbus.service.signal(_interface, signature=u"sv")
664
805
    def PropertyChanged(self, property, value):
665
806
        "D-Bus signal"
666
807
        pass
667
808
    
668
 
    # ReceivedSecret - signal
 
809
    # GotSecret - signal
669
810
    @dbus.service.signal(_interface)
670
 
    def ReceivedSecret(self):
 
811
    def GotSecret(self):
671
812
        "D-Bus signal"
672
813
        pass
673
814
    
677
818
        "D-Bus signal"
678
819
        pass
679
820
    
680
 
    # SetChecker - method
681
 
    @dbus.service.method(_interface, in_signature=u"s")
682
 
    def SetChecker(self, checker):
683
 
        "D-Bus setter method"
684
 
        self.checker_command = checker
685
 
        # Emit D-Bus signal
686
 
        self.PropertyChanged(dbus.String(u"checker"),
687
 
                             dbus.String(self.checker_command,
688
 
                                         variant_level=1))
689
 
    
690
 
    # SetHost - method
691
 
    @dbus.service.method(_interface, in_signature=u"s")
692
 
    def SetHost(self, host):
693
 
        "D-Bus setter method"
694
 
        self.host = host
695
 
        # Emit D-Bus signal
696
 
        self.PropertyChanged(dbus.String(u"host"),
697
 
                             dbus.String(self.host, variant_level=1))
698
 
    
699
 
    # SetInterval - method
700
 
    @dbus.service.method(_interface, in_signature=u"t")
701
 
    def SetInterval(self, milliseconds):
702
 
        self.interval = datetime.timedelta(0, 0, 0, milliseconds)
703
 
        # Emit D-Bus signal
704
 
        self.PropertyChanged(dbus.String(u"interval"),
705
 
                             (dbus.UInt64(self.interval_milliseconds(),
706
 
                                          variant_level=1)))
707
 
    
708
 
    # SetSecret - method
709
 
    @dbus.service.method(_interface, in_signature=u"ay",
710
 
                         byte_arrays=True)
711
 
    def SetSecret(self, secret):
712
 
        "D-Bus setter method"
713
 
        self.secret = str(secret)
714
 
    
715
 
    # SetTimeout - method
716
 
    @dbus.service.method(_interface, in_signature=u"t")
717
 
    def SetTimeout(self, milliseconds):
718
 
        self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
719
 
        # Emit D-Bus signal
720
 
        self.PropertyChanged(dbus.String(u"timeout"),
721
 
                             (dbus.UInt64(self.timeout_milliseconds(),
722
 
                                          variant_level=1)))
723
 
    
724
821
    # Enable - method
725
822
    @dbus.service.method(_interface)
726
823
    def Enable(self):
744
841
    def StopChecker(self):
745
842
        self.stop_checker()
746
843
    
 
844
    # name - property
 
845
    @dbus_service_property(_interface, signature=u"s", access=u"read")
 
846
    def name_dbus_property(self):
 
847
        return dbus.String(self.name)
 
848
    
 
849
    # fingerprint - property
 
850
    @dbus_service_property(_interface, signature=u"s", access=u"read")
 
851
    def fingerprint_dbus_property(self):
 
852
        return dbus.String(self.fingerprint)
 
853
    
 
854
    # host - property
 
855
    @dbus_service_property(_interface, signature=u"s",
 
856
                           access=u"readwrite")
 
857
    def host_dbus_property(self, value=None):
 
858
        if value is None:       # get
 
859
            return dbus.String(self.host)
 
860
        self.host = value
 
861
        # Emit D-Bus signal
 
862
        self.PropertyChanged(dbus.String(u"host"),
 
863
                             dbus.String(value, variant_level=1))
 
864
    
 
865
    # created - property
 
866
    @dbus_service_property(_interface, signature=u"s", access=u"read")
 
867
    def created_dbus_property(self):
 
868
        return dbus.String(self._datetime_to_dbus(self.created))
 
869
    
 
870
    # last_enabled - property
 
871
    @dbus_service_property(_interface, signature=u"s", access=u"read")
 
872
    def last_enabled_dbus_property(self):
 
873
        if self.last_enabled is None:
 
874
            return dbus.String(u"")
 
875
        return dbus.String(self._datetime_to_dbus(self.last_enabled))
 
876
    
 
877
    # enabled - property
 
878
    @dbus_service_property(_interface, signature=u"b",
 
879
                           access=u"readwrite")
 
880
    def enabled_dbus_property(self, value=None):
 
881
        if value is None:       # get
 
882
            return dbus.Boolean(self.enabled)
 
883
        if value:
 
884
            self.enable()
 
885
        else:
 
886
            self.disable()
 
887
    
 
888
    # last_checked_ok - property
 
889
    @dbus_service_property(_interface, signature=u"s",
 
890
                           access=u"readwrite")
 
891
    def last_checked_ok_dbus_property(self, value=None):
 
892
        if value is not None:
 
893
            self.checked_ok()
 
894
            return
 
895
        if self.last_checked_ok is None:
 
896
            return dbus.String(u"")
 
897
        return dbus.String(self._datetime_to_dbus(self
 
898
                                                  .last_checked_ok))
 
899
    
 
900
    # timeout - property
 
901
    @dbus_service_property(_interface, signature=u"t",
 
902
                           access=u"readwrite")
 
903
    def timeout_dbus_property(self, value=None):
 
904
        if value is None:       # get
 
905
            return dbus.UInt64(self.timeout_milliseconds())
 
906
        self.timeout = datetime.timedelta(0, 0, 0, value)
 
907
        # Emit D-Bus signal
 
908
        self.PropertyChanged(dbus.String(u"timeout"),
 
909
                             dbus.UInt64(value, variant_level=1))
 
910
        if getattr(self, u"disable_initiator_tag", None) is None:
 
911
            return
 
912
        # Reschedule timeout
 
913
        gobject.source_remove(self.disable_initiator_tag)
 
914
        self.disable_initiator_tag = None
 
915
        time_to_die = (self.
 
916
                       _timedelta_to_milliseconds((self
 
917
                                                   .last_checked_ok
 
918
                                                   + self.timeout)
 
919
                                                  - datetime.datetime
 
920
                                                  .utcnow()))
 
921
        if time_to_die <= 0:
 
922
            # The timeout has passed
 
923
            self.disable()
 
924
        else:
 
925
            self.disable_initiator_tag = (gobject.timeout_add
 
926
                                          (time_to_die, self.disable))
 
927
    
 
928
    # interval - property
 
929
    @dbus_service_property(_interface, signature=u"t",
 
930
                           access=u"readwrite")
 
931
    def interval_dbus_property(self, value=None):
 
932
        if value is None:       # get
 
933
            return dbus.UInt64(self.interval_milliseconds())
 
934
        self.interval = datetime.timedelta(0, 0, 0, value)
 
935
        # Emit D-Bus signal
 
936
        self.PropertyChanged(dbus.String(u"interval"),
 
937
                             dbus.UInt64(value, variant_level=1))
 
938
        if getattr(self, u"checker_initiator_tag", None) is None:
 
939
            return
 
940
        # Reschedule checker run
 
941
        gobject.source_remove(self.checker_initiator_tag)
 
942
        self.checker_initiator_tag = (gobject.timeout_add
 
943
                                      (value, self.start_checker))
 
944
        self.start_checker()    # Start one now, too
 
945
 
 
946
    # checker - property
 
947
    @dbus_service_property(_interface, signature=u"s",
 
948
                           access=u"readwrite")
 
949
    def checker_dbus_property(self, value=None):
 
950
        if value is None:       # get
 
951
            return dbus.String(self.checker_command)
 
952
        self.checker_command = value
 
953
        # Emit D-Bus signal
 
954
        self.PropertyChanged(dbus.String(u"checker"),
 
955
                             dbus.String(self.checker_command,
 
956
                                         variant_level=1))
 
957
    
 
958
    # checker_running - property
 
959
    @dbus_service_property(_interface, signature=u"b",
 
960
                           access=u"readwrite")
 
961
    def checker_running_dbus_property(self, value=None):
 
962
        if value is None:       # get
 
963
            return dbus.Boolean(self.checker is not None)
 
964
        if value:
 
965
            self.start_checker()
 
966
        else:
 
967
            self.stop_checker()
 
968
    
 
969
    # object_path - property
 
970
    @dbus_service_property(_interface, signature=u"o", access=u"read")
 
971
    def object_path_dbus_property(self):
 
972
        return self.dbus_object_path # is already a dbus.ObjectPath
 
973
    
 
974
    # secret = property
 
975
    @dbus_service_property(_interface, signature=u"ay",
 
976
                           access=u"write", byte_arrays=True)
 
977
    def secret_dbus_property(self, value):
 
978
        self.secret = str(value)
 
979
    
747
980
    del _interface
748
981
 
749
982
 
756
989
    def handle(self):
757
990
        logger.info(u"TCP connection from: %s",
758
991
                    unicode(self.client_address))
759
 
        logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
 
992
        logger.debug(u"IPC Pipe FD: %d", self.server.child_pipe[1])
760
993
        # Open IPC pipe to parent process
761
 
        with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
 
994
        with contextlib.nested(os.fdopen(self.server.child_pipe[1],
 
995
                                         u"w", 1),
 
996
                               os.fdopen(self.server.parent_pipe[0],
 
997
                                         u"r", 0)) as (ipc,
 
998
                                                       ipc_return):
762
999
            session = (gnutls.connection
763
1000
                       .ClientSession(self.request,
764
1001
                                      gnutls.connection
799
1036
                return
800
1037
            logger.debug(u"Handshake succeeded")
801
1038
            try:
802
 
                fpr = self.fingerprint(self.peer_certificate(session))
803
 
            except (TypeError, gnutls.errors.GNUTLSError), error:
804
 
                logger.warning(u"Bad certificate: %s", error)
805
 
                session.bye()
806
 
                return
807
 
            logger.debug(u"Fingerprint: %s", fpr)
808
 
            
809
 
            for c in self.server.clients:
810
 
                if c.fingerprint == fpr:
811
 
                    client = c
812
 
                    break
813
 
            else:
814
 
                ipc.write(u"NOTFOUND %s %s\n"
815
 
                          % (fpr, unicode(self.client_address)))
816
 
                session.bye()
817
 
                return
818
 
            # Have to check if client.still_valid(), since it is
819
 
            # possible that the client timed out while establishing
820
 
            # the GnuTLS session.
821
 
            if not client.still_valid():
822
 
                ipc.write(u"INVALID %s\n" % client.name)
823
 
                session.bye()
824
 
                return
825
 
            ipc.write(u"SENDING %s\n" % client.name)
826
 
            sent_size = 0
827
 
            while sent_size < len(client.secret):
828
 
                sent = session.send(client.secret[sent_size:])
829
 
                logger.debug(u"Sent: %d, remaining: %d",
830
 
                             sent, len(client.secret)
831
 
                             - (sent_size + sent))
832
 
                sent_size += sent
833
 
            session.bye()
 
1039
                try:
 
1040
                    fpr = self.fingerprint(self.peer_certificate
 
1041
                                           (session))
 
1042
                except (TypeError, gnutls.errors.GNUTLSError), error:
 
1043
                    logger.warning(u"Bad certificate: %s", error)
 
1044
                    return
 
1045
                logger.debug(u"Fingerprint: %s", fpr)
 
1046
 
 
1047
                for c in self.server.clients:
 
1048
                    if c.fingerprint == fpr:
 
1049
                        client = c
 
1050
                        break
 
1051
                else:
 
1052
                    ipc.write(u"NOTFOUND %s %s\n"
 
1053
                              % (fpr, unicode(self.client_address)))
 
1054
                    return
 
1055
                # Have to check if client.enabled, since it is
 
1056
                # possible that the client was disabled since the
 
1057
                # GnuTLS session was established.
 
1058
                ipc.write(u"GETATTR enabled %s\n" % fpr)
 
1059
                enabled = pickle.load(ipc_return)
 
1060
                if not enabled:
 
1061
                    ipc.write(u"DISABLED %s\n" % client.name)
 
1062
                    return
 
1063
                ipc.write(u"SENDING %s\n" % client.name)
 
1064
                sent_size = 0
 
1065
                while sent_size < len(client.secret):
 
1066
                    sent = session.send(client.secret[sent_size:])
 
1067
                    logger.debug(u"Sent: %d, remaining: %d",
 
1068
                                 sent, len(client.secret)
 
1069
                                 - (sent_size + sent))
 
1070
                    sent_size += sent
 
1071
            finally:
 
1072
                session.bye()
834
1073
    
835
1074
    @staticmethod
836
1075
    def peer_certificate(session):
896
1135
        return hex_fpr
897
1136
 
898
1137
 
899
 
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
900
 
    """Like socketserver.ForkingMixIn, but also pass a pipe."""
 
1138
class ForkingMixInWithPipes(socketserver.ForkingMixIn, object):
 
1139
    """Like socketserver.ForkingMixIn, but also pass a pipe pair."""
901
1140
    def process_request(self, request, client_address):
902
1141
        """Overrides and wraps the original process_request().
903
1142
        
904
1143
        This function creates a new pipe in self.pipe
905
1144
        """
906
 
        self.pipe = os.pipe()
907
 
        super(ForkingMixInWithPipe,
 
1145
        self.child_pipe = os.pipe() # Child writes here
 
1146
        self.parent_pipe = os.pipe() # Parent writes here
 
1147
        super(ForkingMixInWithPipes,
908
1148
              self).process_request(request, client_address)
909
 
        os.close(self.pipe[1])  # close write end
910
 
        self.add_pipe(self.pipe[0])
911
 
    def add_pipe(self, pipe):
 
1149
        # Close unused ends for parent
 
1150
        os.close(self.parent_pipe[0]) # close read end
 
1151
        os.close(self.child_pipe[1])  # close write end
 
1152
        self.add_pipe_fds(self.child_pipe[0], self.parent_pipe[1])
 
1153
    def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
912
1154
        """Dummy function; override as necessary"""
913
 
        os.close(pipe)
914
 
 
915
 
 
916
 
class IPv6_TCPServer(ForkingMixInWithPipe,
 
1155
        os.close(child_pipe_fd)
 
1156
        os.close(parent_pipe_fd)
 
1157
 
 
1158
 
 
1159
class IPv6_TCPServer(ForkingMixInWithPipes,
917
1160
                     socketserver.TCPServer, object):
918
1161
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
919
1162
    
983
1226
        clients:        set of Client objects
984
1227
        gnutls_priority GnuTLS priority string
985
1228
        use_dbus:       Boolean; to emit D-Bus signals or not
986
 
        clients:        set of Client objects
987
 
        gnutls_priority GnuTLS priority string
988
 
        use_dbus:       Boolean; to emit D-Bus signals or not
989
1229
    
990
1230
    Assumes a gobject.MainLoop event loop.
991
1231
    """
1007
1247
            return socketserver.TCPServer.server_activate(self)
1008
1248
    def enable(self):
1009
1249
        self.enabled = True
1010
 
    def add_pipe(self, pipe):
 
1250
    def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
1011
1251
        # Call "handle_ipc" for both data and EOF events
1012
 
        gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1013
 
                             self.handle_ipc)
1014
 
    def handle_ipc(self, source, condition, file_objects={}):
 
1252
        gobject.io_add_watch(child_pipe_fd,
 
1253
                             gobject.IO_IN | gobject.IO_HUP,
 
1254
                             functools.partial(self.handle_ipc,
 
1255
                                               reply_fd
 
1256
                                               =parent_pipe_fd))
 
1257
    def handle_ipc(self, source, condition, reply_fd=None,
 
1258
                   file_objects={}):
1015
1259
        condition_names = {
1016
1260
            gobject.IO_IN: u"IN",   # There is data to read.
1017
1261
            gobject.IO_OUT: u"OUT", # Data can be written (without
1029
1273
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1030
1274
                     conditions_string)
1031
1275
        
1032
 
        # Turn the pipe file descriptor into a Python file object
 
1276
        # Turn the pipe file descriptors into Python file objects
1033
1277
        if source not in file_objects:
1034
1278
            file_objects[source] = os.fdopen(source, u"r", 1)
 
1279
        if reply_fd not in file_objects:
 
1280
            file_objects[reply_fd] = os.fdopen(reply_fd, u"w", 0)
1035
1281
        
1036
1282
        # Read a line from the file object
1037
1283
        cmdline = file_objects[source].readline()
1038
1284
        if not cmdline:             # Empty line means end of file
1039
 
            # close the IPC pipe
 
1285
            # close the IPC pipes
1040
1286
            file_objects[source].close()
1041
1287
            del file_objects[source]
 
1288
            file_objects[reply_fd].close()
 
1289
            del file_objects[reply_fd]
1042
1290
            
1043
1291
            # Stop calling this function
1044
1292
            return False
1049
1297
        cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1050
1298
        
1051
1299
        if cmd == u"NOTFOUND":
1052
 
            logger.warning(u"Client not found for fingerprint: %s",
1053
 
                           args)
 
1300
            fpr, address = args.split(None, 1)
 
1301
            logger.warning(u"Client not found for fingerprint: %s, ad"
 
1302
                           u"dress: %s", fpr, address)
1054
1303
            if self.use_dbus:
1055
1304
                # Emit D-Bus signal
1056
 
                mandos_dbus_service.ClientNotFound(args)
1057
 
        elif cmd == u"INVALID":
 
1305
                mandos_dbus_service.ClientNotFound(fpr, address)
 
1306
        elif cmd == u"DISABLED":
1058
1307
            for client in self.clients:
1059
1308
                if client.name == args:
1060
 
                    logger.warning(u"Client %s is invalid", args)
 
1309
                    logger.warning(u"Client %s is disabled", args)
1061
1310
                    if self.use_dbus:
1062
1311
                        # Emit D-Bus signal
1063
1312
                        client.Rejected()
1064
1313
                    break
1065
1314
            else:
1066
 
                logger.error(u"Unknown client %s is invalid", args)
 
1315
                logger.error(u"Unknown client %s is disabled", args)
1067
1316
        elif cmd == u"SENDING":
1068
1317
            for client in self.clients:
1069
1318
                if client.name == args:
1071
1320
                    client.checked_ok()
1072
1321
                    if self.use_dbus:
1073
1322
                        # Emit D-Bus signal
1074
 
                        client.ReceivedSecret()
 
1323
                        client.GotSecret()
1075
1324
                    break
1076
1325
            else:
1077
1326
                logger.error(u"Sending secret to unknown client %s",
1078
1327
                             args)
 
1328
        elif cmd == u"GETATTR":
 
1329
            attr_name, fpr = args.split(None, 1)
 
1330
            for client in self.clients:
 
1331
                if client.fingerprint == fpr:
 
1332
                    attr_value = getattr(client, attr_name, None)
 
1333
                    logger.debug("IPC reply: %r", attr_value)
 
1334
                    pickle.dump(attr_value, file_objects[reply_fd])
 
1335
                    break
 
1336
            else:
 
1337
                logger.error(u"Client %s on address %s requesting "
 
1338
                             u"attribute %s not found", fpr, address,
 
1339
                             attr_name)
 
1340
                pickle.dump(None, file_objects[reply_fd])
1079
1341
        else:
1080
1342
            logger.error(u"Unknown IPC command: %r", cmdline)
1081
1343
        
1115
1377
            elif suffix == u"w":
1116
1378
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1117
1379
            else:
1118
 
                raise ValueError
1119
 
        except (ValueError, IndexError):
1120
 
            raise ValueError
 
1380
                raise ValueError(u"Unknown suffix %r" % suffix)
 
1381
        except (ValueError, IndexError), e:
 
1382
            raise ValueError(e.message)
1121
1383
        timevalue += delta
1122
1384
    return timevalue
1123
1385
 
1136
1398
        def if_nametoindex(interface):
1137
1399
            "Get an interface index the hard way, i.e. using fcntl()"
1138
1400
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1139
 
            with closing(socket.socket()) as s:
 
1401
            with contextlib.closing(socket.socket()) as s:
1140
1402
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1141
1403
                                    struct.pack(str(u"16s16x"),
1142
1404
                                                interface))
1162
1424
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1163
1425
        if not stat.S_ISCHR(os.fstat(null).st_mode):
1164
1426
            raise OSError(errno.ENODEV,
1165
 
                          u"/dev/null not a character device")
 
1427
                          u"%s not a character device"
 
1428
                          % os.path.devnull)
1166
1429
        os.dup2(null, sys.stdin.fileno())
1167
1430
        os.dup2(null, sys.stdout.fileno())
1168
1431
        os.dup2(null, sys.stderr.fileno())
1172
1435
 
1173
1436
def main():
1174
1437
    
1175
 
    ######################################################################
 
1438
    ##################################################################
1176
1439
    # Parsing of options, both command line and config file
1177
1440
    
1178
1441
    parser = optparse.OptionParser(version = "%%prog %s" % version)
1335
1598
    bus = dbus.SystemBus()
1336
1599
    # End of Avahi example code
1337
1600
    if use_dbus:
1338
 
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
 
1601
        try:
 
1602
            bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
 
1603
                                            bus, do_not_queue=True)
 
1604
        except dbus.exceptions.NameExistsException, e:
 
1605
            logger.error(unicode(e) + u", disabling D-Bus")
 
1606
            use_dbus = False
 
1607
            server_settings[u"use_dbus"] = False
 
1608
            tcp_server.use_dbus = False
1339
1609
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1340
1610
    service = AvahiService(name = server_settings[u"servicename"],
1341
1611
                           servicetype = u"_mandos._tcp",
1367
1637
        daemon()
1368
1638
    
1369
1639
    try:
1370
 
        with closing(pidfile):
 
1640
        with pidfile:
1371
1641
            pid = os.getpid()
1372
1642
            pidfile.write(str(pid) + "\n")
1373
1643
        del pidfile
1379
1649
        pass
1380
1650
    del pidfilename
1381
1651
    
1382
 
    def cleanup():
1383
 
        "Cleanup function; run on exit"
1384
 
        service.cleanup()
1385
 
        
1386
 
        while tcp_server.clients:
1387
 
            client = tcp_server.clients.pop()
1388
 
            client.disable_hook = None
1389
 
            client.disable()
1390
 
    
1391
 
    atexit.register(cleanup)
1392
 
    
1393
1652
    if not debug:
1394
1653
        signal.signal(signal.SIGINT, signal.SIG_IGN)
1395
1654
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1402
1661
                dbus.service.Object.__init__(self, bus, u"/")
1403
1662
            _interface = u"se.bsnet.fukt.Mandos"
1404
1663
            
1405
 
            @dbus.service.signal(_interface, signature=u"oa{sv}")
1406
 
            def ClientAdded(self, objpath, properties):
 
1664
            @dbus.service.signal(_interface, signature=u"o")
 
1665
            def ClientAdded(self, objpath):
1407
1666
                "D-Bus signal"
1408
1667
                pass
1409
1668
            
1410
 
            @dbus.service.signal(_interface, signature=u"s")
1411
 
            def ClientNotFound(self, fingerprint):
 
1669
            @dbus.service.signal(_interface, signature=u"ss")
 
1670
            def ClientNotFound(self, fingerprint, address):
1412
1671
                "D-Bus signal"
1413
1672
                pass
1414
1673
            
1428
1687
            def GetAllClientsWithProperties(self):
1429
1688
                "D-Bus method"
1430
1689
                return dbus.Dictionary(
1431
 
                    ((c.dbus_object_path, c.GetAllProperties())
 
1690
                    ((c.dbus_object_path, c.GetAll(u""))
1432
1691
                     for c in tcp_server.clients),
1433
1692
                    signature=u"oa{sv}")
1434
1693
            
1440
1699
                        tcp_server.clients.remove(c)
1441
1700
                        c.remove_from_connection()
1442
1701
                        # Don't signal anything except ClientRemoved
1443
 
                        c.disable(signal=False)
 
1702
                        c.disable(quiet=True)
1444
1703
                        # Emit D-Bus signal
1445
1704
                        self.ClientRemoved(object_path, c.name)
1446
1705
                        return
1447
 
                raise KeyError
 
1706
                raise KeyError(object_path)
1448
1707
            
1449
1708
            del _interface
1450
1709
        
1451
1710
        mandos_dbus_service = MandosDBusService()
1452
1711
    
 
1712
    def cleanup():
 
1713
        "Cleanup function; run on exit"
 
1714
        service.cleanup()
 
1715
        
 
1716
        while tcp_server.clients:
 
1717
            client = tcp_server.clients.pop()
 
1718
            if use_dbus:
 
1719
                client.remove_from_connection()
 
1720
            client.disable_hook = None
 
1721
            # Don't signal anything except ClientRemoved
 
1722
            client.disable(quiet=True)
 
1723
            if use_dbus:
 
1724
                # Emit D-Bus signal
 
1725
                mandos_dbus_service.ClientRemoved(client.dbus_object_path,
 
1726
                                                  client.name)
 
1727
    
 
1728
    atexit.register(cleanup)
 
1729
    
1453
1730
    for client in tcp_server.clients:
1454
1731
        if use_dbus:
1455
1732
            # Emit D-Bus signal
1456
 
            mandos_dbus_service.ClientAdded(client.dbus_object_path,
1457
 
                                            client.GetAllProperties())
 
1733
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
1458
1734
        client.enable()
1459
1735
    
1460
1736
    tcp_server.enable()
1478
1754
            service.activate()
1479
1755
        except dbus.exceptions.DBusException, error:
1480
1756
            logger.critical(u"DBusException: %s", error)
 
1757
            cleanup()
1481
1758
            sys.exit(1)
1482
1759
        # End of Avahi example code
1483
1760
        
1490
1767
        main_loop.run()
1491
1768
    except AvahiError, error:
1492
1769
        logger.critical(u"AvahiError: %s", error)
 
1770
        cleanup()
1493
1771
        sys.exit(1)
1494
1772
    except KeyboardInterrupt:
1495
1773
        if debug:
1496
1774
            print >> sys.stderr
1497
1775
        logger.debug(u"Server received KeyboardInterrupt")
1498
1776
    logger.debug(u"Server exiting")
 
1777
    # Must run before the D-Bus bus name gets deregistered
 
1778
    cleanup()
1499
1779
 
1500
1780
if __name__ == '__main__':
1501
1781
    main()