/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: 2011-08-08 21:12:37 UTC
  • Revision ID: teddy@fukt.bsnet.se-20110808211237-jejsz5brjytrjot8
* Makefile (DOCS): Added "intro.8mandos".
  (intro.8mandos, intro.8mandos.xhtml): New.
* README: Replaced text with link, reference and short summary.
* intro.xml: New.
* mandos-clients.conf.xml (SEE ALSO): Added "intro(8mandos)".
* mandos-ctl.xml (SEE ALSO): - '' -
* mandos-keygen.xml (SEE ALSO): - '' -
* mandos-monitor.xml (SEE ALSO): - '' -
* mandos.conf.xml (SEE ALSO): - '' -
* mandos.xml (SEE ALSO): - '' -
* plugin-runner.xml (SEE ALSO): - '' -
* plugins.d/askpass-fifo.xml (SEE ALSO): - '' -
* plugins.d/mandos-client.xml (SEE ALSO): - '' -
* plugins.d/password-prompt.xml (SEE ALSO): - '' -
* plugins.d/plymouth.xml (SEE ALSO): - '' -
* plugins.d/splashy.xml (SEE ALSO): - '' -
* plugins.d/usplash.xml (SEE ALSO): - '' -

Show diffs side-by-side

added added

removed removed

Lines of Context:
28
28
# along with this program.  If not, see
29
29
# <http://www.gnu.org/licenses/>.
30
30
31
 
# Contact the authors at <mandos@recompile.se>.
 
31
# Contact the authors at <mandos@fukt.bsnet.se>.
32
32
33
33
 
34
34
from __future__ import (division, absolute_import, print_function,
62
62
import functools
63
63
import cPickle as pickle
64
64
import multiprocessing
65
 
import types
66
65
 
67
66
import dbus
68
67
import dbus.service
265
264
        self.server_state_changed(self.server.GetState())
266
265
 
267
266
 
268
 
def _timedelta_to_milliseconds(td):
269
 
    "Convert a datetime.timedelta() to milliseconds"
270
 
    return ((td.days * 24 * 60 * 60 * 1000)
271
 
            + (td.seconds * 1000)
272
 
            + (td.microseconds // 1000))
273
 
        
274
267
class Client(object):
275
268
    """A representation of a client host served by this server.
276
269
    
304
297
    secret:     bytestring; sent verbatim (over TLS) to client
305
298
    timeout:    datetime.timedelta(); How long from last_checked_ok
306
299
                                      until this client is disabled
307
 
    extended_timeout:   extra long timeout when password has been sent
308
300
    runtime_expansions: Allowed attributes for runtime expansion.
309
 
    expires:    datetime.datetime(); time (UTC) when a client will be
310
 
                disabled, or None
311
301
    """
312
302
    
313
303
    runtime_expansions = ("approval_delay", "approval_duration",
315
305
                          "host", "interval", "last_checked_ok",
316
306
                          "last_enabled", "name", "timeout")
317
307
    
 
308
    @staticmethod
 
309
    def _timedelta_to_milliseconds(td):
 
310
        "Convert a datetime.timedelta() to milliseconds"
 
311
        return ((td.days * 24 * 60 * 60 * 1000)
 
312
                + (td.seconds * 1000)
 
313
                + (td.microseconds // 1000))
 
314
    
318
315
    def timeout_milliseconds(self):
319
316
        "Return the 'timeout' attribute in milliseconds"
320
 
        return _timedelta_to_milliseconds(self.timeout)
321
 
    
322
 
    def extended_timeout_milliseconds(self):
323
 
        "Return the 'extended_timeout' attribute in milliseconds"
324
 
        return _timedelta_to_milliseconds(self.extended_timeout)    
 
317
        return self._timedelta_to_milliseconds(self.timeout)
325
318
    
326
319
    def interval_milliseconds(self):
327
320
        "Return the 'interval' attribute in milliseconds"
328
 
        return _timedelta_to_milliseconds(self.interval)
329
 
    
 
321
        return self._timedelta_to_milliseconds(self.interval)
 
322
 
330
323
    def approval_delay_milliseconds(self):
331
 
        return _timedelta_to_milliseconds(self.approval_delay)
 
324
        return self._timedelta_to_milliseconds(self.approval_delay)
332
325
    
333
326
    def __init__(self, name = None, disable_hook=None, config=None):
334
327
        """Note: the 'checker' key in 'config' sets the
361
354
        self.last_enabled = None
362
355
        self.last_checked_ok = None
363
356
        self.timeout = string_to_delta(config["timeout"])
364
 
        self.extended_timeout = string_to_delta(config["extended_timeout"])
365
357
        self.interval = string_to_delta(config["interval"])
366
358
        self.disable_hook = disable_hook
367
359
        self.checker = None
368
360
        self.checker_initiator_tag = None
369
361
        self.disable_initiator_tag = None
370
 
        self.expires = None
371
362
        self.checker_callback_tag = None
372
363
        self.checker_command = config["checker"]
373
364
        self.current_checker_command = None
393
384
            # Already enabled
394
385
            return
395
386
        self.send_changedstate()
 
387
        self.last_enabled = datetime.datetime.utcnow()
396
388
        # Schedule a new checker to be started an 'interval' from now,
397
389
        # and every interval from then on.
398
390
        self.checker_initiator_tag = (gobject.timeout_add
399
391
                                      (self.interval_milliseconds(),
400
392
                                       self.start_checker))
401
393
        # Schedule a disable() when 'timeout' has passed
402
 
        self.expires = datetime.datetime.utcnow() + self.timeout
403
394
        self.disable_initiator_tag = (gobject.timeout_add
404
395
                                   (self.timeout_milliseconds(),
405
396
                                    self.disable))
406
397
        self.enabled = True
407
 
        self.last_enabled = datetime.datetime.utcnow()
408
398
        # Also start a new checker *right now*.
409
399
        self.start_checker()
410
400
    
419
409
        if getattr(self, "disable_initiator_tag", False):
420
410
            gobject.source_remove(self.disable_initiator_tag)
421
411
            self.disable_initiator_tag = None
422
 
        self.expires = None
423
412
        if getattr(self, "checker_initiator_tag", False):
424
413
            gobject.source_remove(self.checker_initiator_tag)
425
414
            self.checker_initiator_tag = None
451
440
            logger.warning("Checker for %(name)s crashed?",
452
441
                           vars(self))
453
442
    
454
 
    def checked_ok(self, timeout=None):
 
443
    def checked_ok(self):
455
444
        """Bump up the timeout for this client.
456
445
        
457
446
        This should only be called when the client has been seen,
458
447
        alive and well.
459
448
        """
460
 
        if timeout is None:
461
 
            timeout = self.timeout
462
449
        self.last_checked_ok = datetime.datetime.utcnow()
463
450
        gobject.source_remove(self.disable_initiator_tag)
464
 
        self.expires = datetime.datetime.utcnow() + timeout
465
451
        self.disable_initiator_tag = (gobject.timeout_add
466
 
                                      (_timedelta_to_milliseconds(timeout),
 
452
                                      (self.timeout_milliseconds(),
467
453
                                       self.disable))
468
454
    
469
455
    def need_approval(self):
510
496
                                       'replace')))
511
497
                    for attr in
512
498
                    self.runtime_expansions)
513
 
                
 
499
 
514
500
                try:
515
501
                    command = self.checker_command % escaped_attrs
516
502
                except TypeError as error:
562
548
                raise
563
549
        self.checker = None
564
550
 
565
 
 
566
551
def dbus_service_property(dbus_interface, signature="v",
567
552
                          access="readwrite", byte_arrays=False):
568
553
    """Decorators for marking methods of a DBusObjectWithProperties to
614
599
 
615
600
class DBusObjectWithProperties(dbus.service.Object):
616
601
    """A D-Bus object with properties.
617
 
    
 
602
 
618
603
    Classes inheriting from this can use the dbus_service_property
619
604
    decorator to expose methods as D-Bus properties.  It exposes the
620
605
    standard Get(), Set(), and GetAll() methods on the D-Bus.
627
612
    def _get_all_dbus_properties(self):
628
613
        """Returns a generator of (name, attribute) pairs
629
614
        """
630
 
        return ((prop.__get__(self)._dbus_name, prop.__get__(self))
631
 
                for cls in self.__class__.__mro__
632
 
                for name, prop in inspect.getmembers(cls, self._is_dbus_property))
 
615
        return ((prop._dbus_name, prop)
 
616
                for name, prop in
 
617
                inspect.getmembers(self, self._is_dbus_property))
633
618
    
634
619
    def _get_dbus_property(self, interface_name, property_name):
635
620
        """Returns a bound method if one exists which is a D-Bus
636
621
        property with the specified name and interface.
637
622
        """
638
 
        for cls in  self.__class__.__mro__:
639
 
            for name, value in inspect.getmembers(cls, self._is_dbus_property):
640
 
                if value._dbus_name == property_name and value._dbus_interface == interface_name:
641
 
                    return value.__get__(self)
642
 
        
 
623
        for name in (property_name,
 
624
                     property_name + "_dbus_property"):
 
625
            prop = getattr(self, name, None)
 
626
            if (prop is None
 
627
                or not self._is_dbus_property(prop)
 
628
                or prop._dbus_name != property_name
 
629
                or (interface_name and prop._dbus_interface
 
630
                    and interface_name != prop._dbus_interface)):
 
631
                continue
 
632
            return prop
643
633
        # No such property
644
634
        raise DBusPropertyNotFound(self.dbus_object_path + ":"
645
635
                                   + interface_name + "."
679
669
    def GetAll(self, interface_name):
680
670
        """Standard D-Bus property GetAll() method, see D-Bus
681
671
        standard.
682
 
        
 
672
 
683
673
        Note: Will not include properties with access="write".
684
674
        """
685
675
        all = {}
747
737
        return xmlstring
748
738
 
749
739
 
750
 
def datetime_to_dbus (dt, variant_level=0):
751
 
    """Convert a UTC datetime.datetime() to a D-Bus type."""
752
 
    if dt is None:
753
 
        return dbus.String("", variant_level = variant_level)
754
 
    return dbus.String(dt.isoformat(),
755
 
                       variant_level=variant_level)
756
 
 
757
 
class AlternateDBusNamesMetaclass(DBusObjectWithProperties.__metaclass__):
758
 
    """Applied to an empty subclass of a D-Bus object, this metaclass
759
 
    will add additional D-Bus attributes matching a certain pattern.
760
 
    """
761
 
    def __new__(mcs, name, bases, attr):
762
 
        # Go through all the base classes which could have D-Bus
763
 
        # methods, signals, or properties in them
764
 
        for base in (b for b in bases
765
 
                     if issubclass(b, dbus.service.Object)):
766
 
            # Go though all attributes of the base class
767
 
            for attrname, attribute in inspect.getmembers(base):
768
 
                # Ignore non-D-Bus attributes, and D-Bus attributes
769
 
                # with the wrong interface name
770
 
                if (not hasattr(attribute, "_dbus_interface")
771
 
                    or not attribute._dbus_interface
772
 
                    .startswith("se.recompile.Mandos")):
773
 
                    continue
774
 
                # Create an alternate D-Bus interface name based on
775
 
                # the current name
776
 
                alt_interface = (attribute._dbus_interface
777
 
                                 .replace("se.recompile.Mandos",
778
 
                                          "se.bsnet.fukt.Mandos"))
779
 
                # Is this a D-Bus signal?
780
 
                if getattr(attribute, "_dbus_is_signal", False):
781
 
                    # Extract the original non-method function by
782
 
                    # black magic
783
 
                    nonmethod_func = (dict(
784
 
                            zip(attribute.func_code.co_freevars,
785
 
                                attribute.__closure__))["func"]
786
 
                                      .cell_contents)
787
 
                    # Create a new, but exactly alike, function
788
 
                    # object, and decorate it to be a new D-Bus signal
789
 
                    # with the alternate D-Bus interface name
790
 
                    new_function = (dbus.service.signal
791
 
                                    (alt_interface,
792
 
                                     attribute._dbus_signature)
793
 
                                    (types.FunctionType(
794
 
                                nonmethod_func.func_code,
795
 
                                nonmethod_func.func_globals,
796
 
                                nonmethod_func.func_name,
797
 
                                nonmethod_func.func_defaults,
798
 
                                nonmethod_func.func_closure)))
799
 
                    # Define a creator of a function to call both the
800
 
                    # old and new functions, so both the old and new
801
 
                    # signals gets sent when the function is called
802
 
                    def fixscope(func1, func2):
803
 
                        """This function is a scope container to pass
804
 
                        func1 and func2 to the "call_both" function
805
 
                        outside of its arguments"""
806
 
                        def call_both(*args, **kwargs):
807
 
                            """This function will emit two D-Bus
808
 
                            signals by calling func1 and func2"""
809
 
                            func1(*args, **kwargs)
810
 
                            func2(*args, **kwargs)
811
 
                        return call_both
812
 
                    # Create the "call_both" function and add it to
813
 
                    # the class
814
 
                    attr[attrname] = fixscope(attribute,
815
 
                                              new_function)
816
 
                # Is this a D-Bus method?
817
 
                elif getattr(attribute, "_dbus_is_method", False):
818
 
                    # Create a new, but exactly alike, function
819
 
                    # object.  Decorate it to be a new D-Bus method
820
 
                    # with the alternate D-Bus interface name.  Add it
821
 
                    # to the class.
822
 
                    attr[attrname] = (dbus.service.method
823
 
                                      (alt_interface,
824
 
                                       attribute._dbus_in_signature,
825
 
                                       attribute._dbus_out_signature)
826
 
                                      (types.FunctionType
827
 
                                       (attribute.func_code,
828
 
                                        attribute.func_globals,
829
 
                                        attribute.func_name,
830
 
                                        attribute.func_defaults,
831
 
                                        attribute.func_closure)))
832
 
                # Is this a D-Bus property?
833
 
                elif getattr(attribute, "_dbus_is_property", False):
834
 
                    # Create a new, but exactly alike, function
835
 
                    # object, and decorate it to be a new D-Bus
836
 
                    # property with the alternate D-Bus interface
837
 
                    # name.  Add it to the class.
838
 
                    attr[attrname] = (dbus_service_property
839
 
                                      (alt_interface,
840
 
                                       attribute._dbus_signature,
841
 
                                       attribute._dbus_access,
842
 
                                       attribute
843
 
                                       ._dbus_get_args_options
844
 
                                       ["byte_arrays"])
845
 
                                      (types.FunctionType
846
 
                                       (attribute.func_code,
847
 
                                        attribute.func_globals,
848
 
                                        attribute.func_name,
849
 
                                        attribute.func_defaults,
850
 
                                        attribute.func_closure)))
851
 
        return type.__new__(mcs, name, bases, attr)
852
 
 
853
740
class ClientDBus(Client, DBusObjectWithProperties):
854
741
    """A Client class using D-Bus
855
742
    
877
764
        DBusObjectWithProperties.__init__(self, self.bus,
878
765
                                          self.dbus_object_path)
879
766
        
880
 
    def notifychangeproperty(transform_func,
881
 
                             dbus_name, type_func=lambda x: x,
882
 
                             variant_level=1):
883
 
        """ Modify a variable so that it's a property which announces
884
 
        its changes to DBus.
 
767
    def _get_approvals_pending(self):
 
768
        return self._approvals_pending
 
769
    def _set_approvals_pending(self, value):
 
770
        old_value = self._approvals_pending
 
771
        self._approvals_pending = value
 
772
        bval = bool(value)
 
773
        if (hasattr(self, "dbus_object_path")
 
774
            and bval is not bool(old_value)):
 
775
            dbus_bool = dbus.Boolean(bval, variant_level=1)
 
776
            self.PropertyChanged(dbus.String("ApprovalPending"),
 
777
                                 dbus_bool)
885
778
 
886
 
        transform_fun: Function that takes a value and transforms it
887
 
                       to a D-Bus type.
888
 
        dbus_name: D-Bus name of the variable
889
 
        type_func: Function that transform the value before sending it
890
 
                   to the D-Bus.  Default: no transform
891
 
        variant_level: D-Bus variant level.  Default: 1
892
 
        """
893
 
        real_value = [None,]
894
 
        def setter(self, value):
895
 
            old_value = real_value[0]
896
 
            real_value[0] = value
897
 
            if hasattr(self, "dbus_object_path"):
898
 
                if type_func(old_value) != type_func(real_value[0]):
899
 
                    dbus_value = transform_func(type_func(real_value[0]),
900
 
                                                variant_level)
901
 
                    self.PropertyChanged(dbus.String(dbus_name),
902
 
                                         dbus_value)
903
 
        
904
 
        return property(lambda self: real_value[0], setter)
905
 
    
906
 
    
907
 
    expires = notifychangeproperty(datetime_to_dbus, "Expires")
908
 
    approvals_pending = notifychangeproperty(dbus.Boolean,
909
 
                                             "ApprovalPending",
910
 
                                             type_func = bool)
911
 
    enabled = notifychangeproperty(dbus.Boolean, "Enabled")
912
 
    last_enabled = notifychangeproperty(datetime_to_dbus,
913
 
                                        "LastEnabled")
914
 
    checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
915
 
                                   type_func = lambda checker: checker is not None)
916
 
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
917
 
                                           "LastCheckedOK")
918
 
    last_approval_request = notifychangeproperty(datetime_to_dbus,
919
 
                                                 "LastApprovalRequest")
920
 
    approved_by_default = notifychangeproperty(dbus.Boolean,
921
 
                                               "ApprovedByDefault")
922
 
    approval_delay = notifychangeproperty(dbus.UInt16, "ApprovalDelay",
923
 
                                          type_func = _timedelta_to_milliseconds)
924
 
    approval_duration = notifychangeproperty(dbus.UInt16, "ApprovalDuration",
925
 
                                             type_func = _timedelta_to_milliseconds)
926
 
    host = notifychangeproperty(dbus.String, "Host")
927
 
    timeout = notifychangeproperty(dbus.UInt16, "Timeout",
928
 
                                   type_func = _timedelta_to_milliseconds)
929
 
    extended_timeout = notifychangeproperty(dbus.UInt16, "ExtendedTimeout",
930
 
                                            type_func = _timedelta_to_milliseconds)
931
 
    interval = notifychangeproperty(dbus.UInt16, "Interval",
932
 
                                    type_func = _timedelta_to_milliseconds)
933
 
    checker_command = notifychangeproperty(dbus.String, "Checker")
934
 
    
935
 
    del notifychangeproperty
 
779
    approvals_pending = property(_get_approvals_pending,
 
780
                                 _set_approvals_pending)
 
781
    del _get_approvals_pending, _set_approvals_pending
 
782
    
 
783
    @staticmethod
 
784
    def _datetime_to_dbus(dt, variant_level=0):
 
785
        """Convert a UTC datetime.datetime() to a D-Bus type."""
 
786
        return dbus.String(dt.isoformat(),
 
787
                           variant_level=variant_level)
 
788
    
 
789
    def enable(self):
 
790
        oldstate = getattr(self, "enabled", False)
 
791
        r = Client.enable(self)
 
792
        if oldstate != self.enabled:
 
793
            # Emit D-Bus signals
 
794
            self.PropertyChanged(dbus.String("Enabled"),
 
795
                                 dbus.Boolean(True, variant_level=1))
 
796
            self.PropertyChanged(
 
797
                dbus.String("LastEnabled"),
 
798
                self._datetime_to_dbus(self.last_enabled,
 
799
                                       variant_level=1))
 
800
        return r
 
801
    
 
802
    def disable(self, quiet = False):
 
803
        oldstate = getattr(self, "enabled", False)
 
804
        r = Client.disable(self, quiet=quiet)
 
805
        if not quiet and oldstate != self.enabled:
 
806
            # Emit D-Bus signal
 
807
            self.PropertyChanged(dbus.String("Enabled"),
 
808
                                 dbus.Boolean(False, variant_level=1))
 
809
        return r
936
810
    
937
811
    def __del__(self, *args, **kwargs):
938
812
        try:
947
821
                         *args, **kwargs):
948
822
        self.checker_callback_tag = None
949
823
        self.checker = None
 
824
        # Emit D-Bus signal
 
825
        self.PropertyChanged(dbus.String("CheckerRunning"),
 
826
                             dbus.Boolean(False, variant_level=1))
950
827
        if os.WIFEXITED(condition):
951
828
            exitstatus = os.WEXITSTATUS(condition)
952
829
            # Emit D-Bus signal
962
839
        return Client.checker_callback(self, pid, condition, command,
963
840
                                       *args, **kwargs)
964
841
    
 
842
    def checked_ok(self, *args, **kwargs):
 
843
        Client.checked_ok(self, *args, **kwargs)
 
844
        # Emit D-Bus signal
 
845
        self.PropertyChanged(
 
846
            dbus.String("LastCheckedOK"),
 
847
            (self._datetime_to_dbus(self.last_checked_ok,
 
848
                                    variant_level=1)))
 
849
    
 
850
    def need_approval(self, *args, **kwargs):
 
851
        r = Client.need_approval(self, *args, **kwargs)
 
852
        # Emit D-Bus signal
 
853
        self.PropertyChanged(
 
854
            dbus.String("LastApprovalRequest"),
 
855
            (self._datetime_to_dbus(self.last_approval_request,
 
856
                                    variant_level=1)))
 
857
        return r
 
858
    
965
859
    def start_checker(self, *args, **kwargs):
966
860
        old_checker = self.checker
967
861
        if self.checker is not None:
974
868
            and old_checker_pid != self.checker.pid):
975
869
            # Emit D-Bus signal
976
870
            self.CheckerStarted(self.current_checker_command)
 
871
            self.PropertyChanged(
 
872
                dbus.String("CheckerRunning"),
 
873
                dbus.Boolean(True, variant_level=1))
977
874
        return r
978
875
    
 
876
    def stop_checker(self, *args, **kwargs):
 
877
        old_checker = getattr(self, "checker", None)
 
878
        r = Client.stop_checker(self, *args, **kwargs)
 
879
        if (old_checker is not None
 
880
            and getattr(self, "checker", None) is None):
 
881
            self.PropertyChanged(dbus.String("CheckerRunning"),
 
882
                                 dbus.Boolean(False, variant_level=1))
 
883
        return r
 
884
 
979
885
    def _reset_approved(self):
980
886
        self._approved = None
981
887
        return False
983
889
    def approve(self, value=True):
984
890
        self.send_changedstate()
985
891
        self._approved = value
986
 
        gobject.timeout_add(_timedelta_to_milliseconds
 
892
        gobject.timeout_add(self._timedelta_to_milliseconds
987
893
                            (self.approval_duration),
988
894
                            self._reset_approved)
989
895
    
990
896
    
991
897
    ## D-Bus methods, signals & properties
992
 
    _interface = "se.recompile.Mandos.Client"
 
898
    _interface = "se.bsnet.fukt.Mandos.Client"
993
899
    
994
900
    ## Signals
995
901
    
1081
987
        if value is None:       # get
1082
988
            return dbus.Boolean(self.approved_by_default)
1083
989
        self.approved_by_default = bool(value)
 
990
        # Emit D-Bus signal
 
991
        self.PropertyChanged(dbus.String("ApprovedByDefault"),
 
992
                             dbus.Boolean(value, variant_level=1))
1084
993
    
1085
994
    # ApprovalDelay - property
1086
995
    @dbus_service_property(_interface, signature="t",
1089
998
        if value is None:       # get
1090
999
            return dbus.UInt64(self.approval_delay_milliseconds())
1091
1000
        self.approval_delay = datetime.timedelta(0, 0, 0, value)
 
1001
        # Emit D-Bus signal
 
1002
        self.PropertyChanged(dbus.String("ApprovalDelay"),
 
1003
                             dbus.UInt64(value, variant_level=1))
1092
1004
    
1093
1005
    # ApprovalDuration - property
1094
1006
    @dbus_service_property(_interface, signature="t",
1095
1007
                           access="readwrite")
1096
1008
    def ApprovalDuration_dbus_property(self, value=None):
1097
1009
        if value is None:       # get
1098
 
            return dbus.UInt64(_timedelta_to_milliseconds(
 
1010
            return dbus.UInt64(self._timedelta_to_milliseconds(
1099
1011
                    self.approval_duration))
1100
1012
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
 
1013
        # Emit D-Bus signal
 
1014
        self.PropertyChanged(dbus.String("ApprovalDuration"),
 
1015
                             dbus.UInt64(value, variant_level=1))
1101
1016
    
1102
1017
    # Name - property
1103
1018
    @dbus_service_property(_interface, signature="s", access="read")
1116
1031
        if value is None:       # get
1117
1032
            return dbus.String(self.host)
1118
1033
        self.host = value
 
1034
        # Emit D-Bus signal
 
1035
        self.PropertyChanged(dbus.String("Host"),
 
1036
                             dbus.String(value, variant_level=1))
1119
1037
    
1120
1038
    # Created - property
1121
1039
    @dbus_service_property(_interface, signature="s", access="read")
1122
1040
    def Created_dbus_property(self):
1123
 
        return dbus.String(datetime_to_dbus(self.created))
 
1041
        return dbus.String(self._datetime_to_dbus(self.created))
1124
1042
    
1125
1043
    # LastEnabled - property
1126
1044
    @dbus_service_property(_interface, signature="s", access="read")
1127
1045
    def LastEnabled_dbus_property(self):
1128
 
        return datetime_to_dbus(self.last_enabled)
 
1046
        if self.last_enabled is None:
 
1047
            return dbus.String("")
 
1048
        return dbus.String(self._datetime_to_dbus(self.last_enabled))
1129
1049
    
1130
1050
    # Enabled - property
1131
1051
    @dbus_service_property(_interface, signature="b",
1145
1065
        if value is not None:
1146
1066
            self.checked_ok()
1147
1067
            return
1148
 
        return datetime_to_dbus(self.last_checked_ok)
1149
 
    
1150
 
    # Expires - property
1151
 
    @dbus_service_property(_interface, signature="s", access="read")
1152
 
    def Expires_dbus_property(self):
1153
 
        return datetime_to_dbus(self.expires)
 
1068
        if self.last_checked_ok is None:
 
1069
            return dbus.String("")
 
1070
        return dbus.String(self._datetime_to_dbus(self
 
1071
                                                  .last_checked_ok))
1154
1072
    
1155
1073
    # LastApprovalRequest - property
1156
1074
    @dbus_service_property(_interface, signature="s", access="read")
1157
1075
    def LastApprovalRequest_dbus_property(self):
1158
 
        return datetime_to_dbus(self.last_approval_request)
 
1076
        if self.last_approval_request is None:
 
1077
            return dbus.String("")
 
1078
        return dbus.String(self.
 
1079
                           _datetime_to_dbus(self
 
1080
                                             .last_approval_request))
1159
1081
    
1160
1082
    # Timeout - property
1161
1083
    @dbus_service_property(_interface, signature="t",
1164
1086
        if value is None:       # get
1165
1087
            return dbus.UInt64(self.timeout_milliseconds())
1166
1088
        self.timeout = datetime.timedelta(0, 0, 0, value)
 
1089
        # Emit D-Bus signal
 
1090
        self.PropertyChanged(dbus.String("Timeout"),
 
1091
                             dbus.UInt64(value, variant_level=1))
1167
1092
        if getattr(self, "disable_initiator_tag", None) is None:
1168
1093
            return
1169
1094
        # Reschedule timeout
1170
1095
        gobject.source_remove(self.disable_initiator_tag)
1171
1096
        self.disable_initiator_tag = None
1172
 
        self.expires = None
1173
1097
        time_to_die = (self.
1174
1098
                       _timedelta_to_milliseconds((self
1175
1099
                                                   .last_checked_ok
1180
1104
            # The timeout has passed
1181
1105
            self.disable()
1182
1106
        else:
1183
 
            self.expires = (datetime.datetime.utcnow()
1184
 
                            + datetime.timedelta(milliseconds = time_to_die))
1185
1107
            self.disable_initiator_tag = (gobject.timeout_add
1186
1108
                                          (time_to_die, self.disable))
1187
1109
    
1188
 
    # ExtendedTimeout - property
1189
 
    @dbus_service_property(_interface, signature="t",
1190
 
                           access="readwrite")
1191
 
    def ExtendedTimeout_dbus_property(self, value=None):
1192
 
        if value is None:       # get
1193
 
            return dbus.UInt64(self.extended_timeout_milliseconds())
1194
 
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1195
 
    
1196
1110
    # Interval - property
1197
1111
    @dbus_service_property(_interface, signature="t",
1198
1112
                           access="readwrite")
1200
1114
        if value is None:       # get
1201
1115
            return dbus.UInt64(self.interval_milliseconds())
1202
1116
        self.interval = datetime.timedelta(0, 0, 0, value)
 
1117
        # Emit D-Bus signal
 
1118
        self.PropertyChanged(dbus.String("Interval"),
 
1119
                             dbus.UInt64(value, variant_level=1))
1203
1120
        if getattr(self, "checker_initiator_tag", None) is None:
1204
1121
            return
1205
1122
        # Reschedule checker run
1207
1124
        self.checker_initiator_tag = (gobject.timeout_add
1208
1125
                                      (value, self.start_checker))
1209
1126
        self.start_checker()    # Start one now, too
1210
 
    
 
1127
 
1211
1128
    # Checker - property
1212
1129
    @dbus_service_property(_interface, signature="s",
1213
1130
                           access="readwrite")
1215
1132
        if value is None:       # get
1216
1133
            return dbus.String(self.checker_command)
1217
1134
        self.checker_command = value
 
1135
        # Emit D-Bus signal
 
1136
        self.PropertyChanged(dbus.String("Checker"),
 
1137
                             dbus.String(self.checker_command,
 
1138
                                         variant_level=1))
1218
1139
    
1219
1140
    # CheckerRunning - property
1220
1141
    @dbus_service_property(_interface, signature="b",
1247
1168
        self._pipe.send(('init', fpr, address))
1248
1169
        if not self._pipe.recv():
1249
1170
            raise KeyError()
1250
 
    
 
1171
 
1251
1172
    def __getattribute__(self, name):
1252
1173
        if(name == '_pipe'):
1253
1174
            return super(ProxyClient, self).__getattribute__(name)
1260
1181
                self._pipe.send(('funcall', name, args, kwargs))
1261
1182
                return self._pipe.recv()[1]
1262
1183
            return func
1263
 
    
 
1184
 
1264
1185
    def __setattr__(self, name, value):
1265
1186
        if(name == '_pipe'):
1266
1187
            return super(ProxyClient, self).__setattr__(name, value)
1267
1188
        self._pipe.send(('setattr', name, value))
1268
1189
 
1269
 
class ClientDBusTransitional(ClientDBus):
1270
 
    __metaclass__ = AlternateDBusNamesMetaclass
1271
1190
 
1272
1191
class ClientHandler(socketserver.BaseRequestHandler, object):
1273
1192
    """A class to handle client connections.
1281
1200
                        unicode(self.client_address))
1282
1201
            logger.debug("Pipe FD: %d",
1283
1202
                         self.server.child_pipe.fileno())
1284
 
            
 
1203
 
1285
1204
            session = (gnutls.connection
1286
1205
                       .ClientSession(self.request,
1287
1206
                                      gnutls.connection
1288
1207
                                      .X509Credentials()))
1289
 
            
 
1208
 
1290
1209
            # Note: gnutls.connection.X509Credentials is really a
1291
1210
            # generic GnuTLS certificate credentials object so long as
1292
1211
            # no X.509 keys are added to it.  Therefore, we can use it
1293
1212
            # here despite using OpenPGP certificates.
1294
 
            
 
1213
 
1295
1214
            #priority = ':'.join(("NONE", "+VERS-TLS1.1",
1296
1215
            #                      "+AES-256-CBC", "+SHA1",
1297
1216
            #                      "+COMP-NULL", "+CTYPE-OPENPGP",
1303
1222
            (gnutls.library.functions
1304
1223
             .gnutls_priority_set_direct(session._c_object,
1305
1224
                                         priority, None))
1306
 
            
 
1225
 
1307
1226
            # Start communication using the Mandos protocol
1308
1227
            # Get protocol number
1309
1228
            line = self.request.makefile().readline()
1314
1233
            except (ValueError, IndexError, RuntimeError) as error:
1315
1234
                logger.error("Unknown protocol version: %s", error)
1316
1235
                return
1317
 
            
 
1236
 
1318
1237
            # Start GnuTLS connection
1319
1238
            try:
1320
1239
                session.handshake()
1324
1243
                # established.  Just abandon the request.
1325
1244
                return
1326
1245
            logger.debug("Handshake succeeded")
1327
 
            
 
1246
 
1328
1247
            approval_required = False
1329
1248
            try:
1330
1249
                try:
1335
1254
                    logger.warning("Bad certificate: %s", error)
1336
1255
                    return
1337
1256
                logger.debug("Fingerprint: %s", fpr)
1338
 
                
 
1257
 
1339
1258
                try:
1340
1259
                    client = ProxyClient(child_pipe, fpr,
1341
1260
                                         self.client_address)
1407
1326
                                 sent, len(client.secret)
1408
1327
                                 - (sent_size + sent))
1409
1328
                    sent_size += sent
1410
 
                
 
1329
 
1411
1330
                logger.info("Sending secret to %s", client.name)
1412
1331
                # bump the timeout as if seen
1413
 
                client.checked_ok(client.extended_timeout)
 
1332
                client.checked_ok()
1414
1333
                if self.server.use_dbus:
1415
1334
                    # Emit D-Bus signal
1416
1335
                    client.GotSecret()
1501
1420
        multiprocessing.Process(target = self.sub_process_main,
1502
1421
                                args = (request, address)).start()
1503
1422
 
1504
 
 
1505
1423
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1506
1424
    """ adds a pipe to the MixIn """
1507
1425
    def process_request(self, request, client_address):
1510
1428
        This function creates a new pipe in self.pipe
1511
1429
        """
1512
1430
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
1513
 
        
 
1431
 
1514
1432
        super(MultiprocessingMixInWithPipe,
1515
1433
              self).process_request(request, client_address)
1516
1434
        self.child_pipe.close()
1517
1435
        self.add_pipe(parent_pipe)
1518
 
    
 
1436
 
1519
1437
    def add_pipe(self, parent_pipe):
1520
1438
        """Dummy function; override as necessary"""
1521
1439
        raise NotImplementedError
1522
1440
 
1523
 
 
1524
1441
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1525
1442
                     socketserver.TCPServer, object):
1526
1443
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1674
1591
            kwargs = request[3]
1675
1592
            
1676
1593
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1677
 
        
 
1594
 
1678
1595
        if command == 'getattr':
1679
1596
            attrname = request[1]
1680
1597
            if callable(client_object.__getattribute__(attrname)):
1686
1603
            attrname = request[1]
1687
1604
            value = request[2]
1688
1605
            setattr(client_object, attrname, value)
1689
 
        
 
1606
 
1690
1607
        return True
1691
1608
 
1692
1609
 
1871
1788
    debuglevel = server_settings["debuglevel"]
1872
1789
    use_dbus = server_settings["use_dbus"]
1873
1790
    use_ipv6 = server_settings["use_ipv6"]
1874
 
    
 
1791
 
1875
1792
    if server_settings["servicename"] != "Mandos":
1876
1793
        syslogger.setFormatter(logging.Formatter
1877
1794
                               ('Mandos (%s) [%%(process)d]:'
1879
1796
                                % server_settings["servicename"]))
1880
1797
    
1881
1798
    # Parse config file with clients
1882
 
    client_defaults = { "timeout": "5m",
1883
 
                        "extended_timeout": "15m",
1884
 
                        "interval": "2m",
 
1799
    client_defaults = { "timeout": "1h",
 
1800
                        "interval": "5m",
1885
1801
                        "checker": "fping -q -- %%(host)s",
1886
1802
                        "host": "",
1887
1803
                        "approval_delay": "0s",
1938
1854
        level = getattr(logging, debuglevel.upper())
1939
1855
        syslogger.setLevel(level)
1940
1856
        console.setLevel(level)
1941
 
    
 
1857
 
1942
1858
    if debug:
1943
1859
        # Enable all possible GnuTLS debugging
1944
1860
        
1975
1891
    # End of Avahi example code
1976
1892
    if use_dbus:
1977
1893
        try:
1978
 
            bus_name = dbus.service.BusName("se.recompile.Mandos",
 
1894
            bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
1979
1895
                                            bus, do_not_queue=True)
1980
 
            old_bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
1981
 
                                                bus, do_not_queue=True)
1982
1896
        except dbus.exceptions.NameExistsException as e:
1983
1897
            logger.error(unicode(e) + ", disabling D-Bus")
1984
1898
            use_dbus = False
1997
1911
    
1998
1912
    client_class = Client
1999
1913
    if use_dbus:
2000
 
        client_class = functools.partial(ClientDBusTransitional, bus = bus)        
 
1914
        client_class = functools.partial(ClientDBus, bus = bus)
2001
1915
    def client_config_items(config, section):
2002
1916
        special_settings = {
2003
1917
            "approved_by_default":
2033
1947
        del pidfilename
2034
1948
        
2035
1949
        signal.signal(signal.SIGINT, signal.SIG_IGN)
2036
 
    
 
1950
 
2037
1951
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2038
1952
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2039
1953
    
2042
1956
            """A D-Bus proxy object"""
2043
1957
            def __init__(self):
2044
1958
                dbus.service.Object.__init__(self, bus, "/")
2045
 
            _interface = "se.recompile.Mandos"
 
1959
            _interface = "se.bsnet.fukt.Mandos"
2046
1960
            
2047
1961
            @dbus.service.signal(_interface, signature="o")
2048
1962
            def ClientAdded(self, objpath):
2090
2004
            
2091
2005
            del _interface
2092
2006
        
2093
 
        class MandosDBusServiceTransitional(MandosDBusService):
2094
 
            __metaclass__ = AlternateDBusNamesMetaclass
2095
 
        mandos_dbus_service = MandosDBusServiceTransitional()
 
2007
        mandos_dbus_service = MandosDBusService()
2096
2008
    
2097
2009
    def cleanup():
2098
2010
        "Cleanup function; run on exit"
2162
2074
    # Must run before the D-Bus bus name gets deregistered
2163
2075
    cleanup()
2164
2076
 
2165
 
 
2166
2077
if __name__ == '__main__':
2167
2078
    main()