/mandos/release

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2011-10-09 16:38:18 UTC
  • mfrom: (237.7.61 trunk)
  • Revision ID: teddy@recompile.se-20111009163818-ihb35bqq0iat18k2
MergeĀ fromĀ trunk.

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@fukt.bsnet.se>.
 
31
# Contact the authors at <mandos@recompile.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
65
66
 
66
67
import dbus
67
68
import dbus.service
159
160
                            " after %i retries, exiting.",
160
161
                            self.rename_count)
161
162
            raise AvahiServiceError("Too many renames")
162
 
        self.name = unicode(self.server.GetAlternativeServiceName(self.name))
 
163
        self.name = unicode(self.server
 
164
                            .GetAlternativeServiceName(self.name))
163
165
        logger.info("Changing Zeroconf service name to %r ...",
164
166
                    self.name)
165
167
        syslogger.setFormatter(logging.Formatter
264
266
        self.server_state_changed(self.server.GetState())
265
267
 
266
268
 
 
269
def _timedelta_to_milliseconds(td):
 
270
    "Convert a datetime.timedelta() to milliseconds"
 
271
    return ((td.days * 24 * 60 * 60 * 1000)
 
272
            + (td.seconds * 1000)
 
273
            + (td.microseconds // 1000))
 
274
        
267
275
class Client(object):
268
276
    """A representation of a client host served by this server.
269
277
    
297
305
    secret:     bytestring; sent verbatim (over TLS) to client
298
306
    timeout:    datetime.timedelta(); How long from last_checked_ok
299
307
                                      until this client is disabled
 
308
    extended_timeout:   extra long timeout when password has been sent
300
309
    runtime_expansions: Allowed attributes for runtime expansion.
 
310
    expires:    datetime.datetime(); time (UTC) when a client will be
 
311
                disabled, or None
301
312
    """
302
313
    
303
314
    runtime_expansions = ("approval_delay", "approval_duration",
305
316
                          "host", "interval", "last_checked_ok",
306
317
                          "last_enabled", "name", "timeout")
307
318
    
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
 
    
315
319
    def timeout_milliseconds(self):
316
320
        "Return the 'timeout' attribute in milliseconds"
317
 
        return self._timedelta_to_milliseconds(self.timeout)
 
321
        return _timedelta_to_milliseconds(self.timeout)
 
322
    
 
323
    def extended_timeout_milliseconds(self):
 
324
        "Return the 'extended_timeout' attribute in milliseconds"
 
325
        return _timedelta_to_milliseconds(self.extended_timeout)
318
326
    
319
327
    def interval_milliseconds(self):
320
328
        "Return the 'interval' attribute in milliseconds"
321
 
        return self._timedelta_to_milliseconds(self.interval)
322
 
 
 
329
        return _timedelta_to_milliseconds(self.interval)
 
330
    
323
331
    def approval_delay_milliseconds(self):
324
 
        return self._timedelta_to_milliseconds(self.approval_delay)
 
332
        return _timedelta_to_milliseconds(self.approval_delay)
325
333
    
326
334
    def __init__(self, name = None, disable_hook=None, config=None):
327
335
        """Note: the 'checker' key in 'config' sets the
354
362
        self.last_enabled = None
355
363
        self.last_checked_ok = None
356
364
        self.timeout = string_to_delta(config["timeout"])
 
365
        self.extended_timeout = string_to_delta(config
 
366
                                                ["extended_timeout"])
357
367
        self.interval = string_to_delta(config["interval"])
358
368
        self.disable_hook = disable_hook
359
369
        self.checker = None
360
370
        self.checker_initiator_tag = None
361
371
        self.disable_initiator_tag = None
 
372
        self.expires = None
362
373
        self.checker_callback_tag = None
363
374
        self.checker_command = config["checker"]
364
375
        self.current_checker_command = None
371
382
            config["approval_delay"])
372
383
        self.approval_duration = string_to_delta(
373
384
            config["approval_duration"])
374
 
        self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
 
385
        self.changedstate = (multiprocessing_manager
 
386
                             .Condition(multiprocessing_manager
 
387
                                        .Lock()))
375
388
    
376
389
    def send_changedstate(self):
377
390
        self.changedstate.acquire()
378
391
        self.changedstate.notify_all()
379
392
        self.changedstate.release()
380
 
        
 
393
    
381
394
    def enable(self):
382
395
        """Start this client's checker and timeout hooks"""
383
396
        if getattr(self, "enabled", False):
384
397
            # Already enabled
385
398
            return
386
399
        self.send_changedstate()
387
 
        self.last_enabled = datetime.datetime.utcnow()
388
400
        # Schedule a new checker to be started an 'interval' from now,
389
401
        # and every interval from then on.
390
402
        self.checker_initiator_tag = (gobject.timeout_add
391
403
                                      (self.interval_milliseconds(),
392
404
                                       self.start_checker))
393
405
        # Schedule a disable() when 'timeout' has passed
 
406
        self.expires = datetime.datetime.utcnow() + self.timeout
394
407
        self.disable_initiator_tag = (gobject.timeout_add
395
408
                                   (self.timeout_milliseconds(),
396
409
                                    self.disable))
397
410
        self.enabled = True
 
411
        self.last_enabled = datetime.datetime.utcnow()
398
412
        # Also start a new checker *right now*.
399
413
        self.start_checker()
400
414
    
409
423
        if getattr(self, "disable_initiator_tag", False):
410
424
            gobject.source_remove(self.disable_initiator_tag)
411
425
            self.disable_initiator_tag = None
 
426
        self.expires = None
412
427
        if getattr(self, "checker_initiator_tag", False):
413
428
            gobject.source_remove(self.checker_initiator_tag)
414
429
            self.checker_initiator_tag = None
440
455
            logger.warning("Checker for %(name)s crashed?",
441
456
                           vars(self))
442
457
    
443
 
    def checked_ok(self):
 
458
    def checked_ok(self, timeout=None):
444
459
        """Bump up the timeout for this client.
445
460
        
446
461
        This should only be called when the client has been seen,
447
462
        alive and well.
448
463
        """
 
464
        if timeout is None:
 
465
            timeout = self.timeout
449
466
        self.last_checked_ok = datetime.datetime.utcnow()
450
467
        gobject.source_remove(self.disable_initiator_tag)
451
468
        self.disable_initiator_tag = (gobject.timeout_add
452
 
                                      (self.timeout_milliseconds(),
453
 
                                       self.disable))
 
469
                                      (_timedelta_to_milliseconds
 
470
                                       (timeout), self.disable))
 
471
        self.expires = datetime.datetime.utcnow() + timeout
454
472
    
455
473
    def need_approval(self):
456
474
        self.last_approval_request = datetime.datetime.utcnow()
496
514
                                       'replace')))
497
515
                    for attr in
498
516
                    self.runtime_expansions)
499
 
 
 
517
                
500
518
                try:
501
519
                    command = self.checker_command % escaped_attrs
502
520
                except TypeError as error:
548
566
                raise
549
567
        self.checker = None
550
568
 
 
569
 
551
570
def dbus_service_property(dbus_interface, signature="v",
552
571
                          access="readwrite", byte_arrays=False):
553
572
    """Decorators for marking methods of a DBusObjectWithProperties to
599
618
 
600
619
class DBusObjectWithProperties(dbus.service.Object):
601
620
    """A D-Bus object with properties.
602
 
 
 
621
    
603
622
    Classes inheriting from this can use the dbus_service_property
604
623
    decorator to expose methods as D-Bus properties.  It exposes the
605
624
    standard Get(), Set(), and GetAll() methods on the D-Bus.
612
631
    def _get_all_dbus_properties(self):
613
632
        """Returns a generator of (name, attribute) pairs
614
633
        """
615
 
        return ((prop._dbus_name, prop)
 
634
        return ((prop.__get__(self)._dbus_name, prop.__get__(self))
 
635
                for cls in self.__class__.__mro__
616
636
                for name, prop in
617
 
                inspect.getmembers(self, self._is_dbus_property))
 
637
                inspect.getmembers(cls, self._is_dbus_property))
618
638
    
619
639
    def _get_dbus_property(self, interface_name, property_name):
620
640
        """Returns a bound method if one exists which is a D-Bus
621
641
        property with the specified name and interface.
622
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
        for cls in  self.__class__.__mro__:
 
644
            for name, value in (inspect.getmembers
 
645
                                (cls, self._is_dbus_property)):
 
646
                if (value._dbus_name == property_name
 
647
                    and value._dbus_interface == interface_name):
 
648
                    return value.__get__(self)
 
649
        
633
650
        # No such property
634
651
        raise DBusPropertyNotFound(self.dbus_object_path + ":"
635
652
                                   + interface_name + "."
669
686
    def GetAll(self, interface_name):
670
687
        """Standard D-Bus property GetAll() method, see D-Bus
671
688
        standard.
672
 
 
 
689
        
673
690
        Note: Will not include properties with access="write".
674
691
        """
675
692
        all = {}
737
754
        return xmlstring
738
755
 
739
756
 
 
757
def datetime_to_dbus (dt, variant_level=0):
 
758
    """Convert a UTC datetime.datetime() to a D-Bus type."""
 
759
    if dt is None:
 
760
        return dbus.String("", variant_level = variant_level)
 
761
    return dbus.String(dt.isoformat(),
 
762
                       variant_level=variant_level)
 
763
 
 
764
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
 
765
                                  .__metaclass__):
 
766
    """Applied to an empty subclass of a D-Bus object, this metaclass
 
767
    will add additional D-Bus attributes matching a certain pattern.
 
768
    """
 
769
    def __new__(mcs, name, bases, attr):
 
770
        # Go through all the base classes which could have D-Bus
 
771
        # methods, signals, or properties in them
 
772
        for base in (b for b in bases
 
773
                     if issubclass(b, dbus.service.Object)):
 
774
            # Go though all attributes of the base class
 
775
            for attrname, attribute in inspect.getmembers(base):
 
776
                # Ignore non-D-Bus attributes, and D-Bus attributes
 
777
                # with the wrong interface name
 
778
                if (not hasattr(attribute, "_dbus_interface")
 
779
                    or not attribute._dbus_interface
 
780
                    .startswith("se.recompile.Mandos")):
 
781
                    continue
 
782
                # Create an alternate D-Bus interface name based on
 
783
                # the current name
 
784
                alt_interface = (attribute._dbus_interface
 
785
                                 .replace("se.recompile.Mandos",
 
786
                                          "se.bsnet.fukt.Mandos"))
 
787
                # Is this a D-Bus signal?
 
788
                if getattr(attribute, "_dbus_is_signal", False):
 
789
                    # Extract the original non-method function by
 
790
                    # black magic
 
791
                    nonmethod_func = (dict(
 
792
                            zip(attribute.func_code.co_freevars,
 
793
                                attribute.__closure__))["func"]
 
794
                                      .cell_contents)
 
795
                    # Create a new, but exactly alike, function
 
796
                    # object, and decorate it to be a new D-Bus signal
 
797
                    # with the alternate D-Bus interface name
 
798
                    new_function = (dbus.service.signal
 
799
                                    (alt_interface,
 
800
                                     attribute._dbus_signature)
 
801
                                    (types.FunctionType(
 
802
                                nonmethod_func.func_code,
 
803
                                nonmethod_func.func_globals,
 
804
                                nonmethod_func.func_name,
 
805
                                nonmethod_func.func_defaults,
 
806
                                nonmethod_func.func_closure)))
 
807
                    # Define a creator of a function to call both the
 
808
                    # old and new functions, so both the old and new
 
809
                    # signals gets sent when the function is called
 
810
                    def fixscope(func1, func2):
 
811
                        """This function is a scope container to pass
 
812
                        func1 and func2 to the "call_both" function
 
813
                        outside of its arguments"""
 
814
                        def call_both(*args, **kwargs):
 
815
                            """This function will emit two D-Bus
 
816
                            signals by calling func1 and func2"""
 
817
                            func1(*args, **kwargs)
 
818
                            func2(*args, **kwargs)
 
819
                        return call_both
 
820
                    # Create the "call_both" function and add it to
 
821
                    # the class
 
822
                    attr[attrname] = fixscope(attribute,
 
823
                                              new_function)
 
824
                # Is this a D-Bus method?
 
825
                elif getattr(attribute, "_dbus_is_method", False):
 
826
                    # Create a new, but exactly alike, function
 
827
                    # object.  Decorate it to be a new D-Bus method
 
828
                    # with the alternate D-Bus interface name.  Add it
 
829
                    # to the class.
 
830
                    attr[attrname] = (dbus.service.method
 
831
                                      (alt_interface,
 
832
                                       attribute._dbus_in_signature,
 
833
                                       attribute._dbus_out_signature)
 
834
                                      (types.FunctionType
 
835
                                       (attribute.func_code,
 
836
                                        attribute.func_globals,
 
837
                                        attribute.func_name,
 
838
                                        attribute.func_defaults,
 
839
                                        attribute.func_closure)))
 
840
                # Is this a D-Bus property?
 
841
                elif getattr(attribute, "_dbus_is_property", False):
 
842
                    # Create a new, but exactly alike, function
 
843
                    # object, and decorate it to be a new D-Bus
 
844
                    # property with the alternate D-Bus interface
 
845
                    # name.  Add it to the class.
 
846
                    attr[attrname] = (dbus_service_property
 
847
                                      (alt_interface,
 
848
                                       attribute._dbus_signature,
 
849
                                       attribute._dbus_access,
 
850
                                       attribute
 
851
                                       ._dbus_get_args_options
 
852
                                       ["byte_arrays"])
 
853
                                      (types.FunctionType
 
854
                                       (attribute.func_code,
 
855
                                        attribute.func_globals,
 
856
                                        attribute.func_name,
 
857
                                        attribute.func_defaults,
 
858
                                        attribute.func_closure)))
 
859
        return type.__new__(mcs, name, bases, attr)
 
860
 
740
861
class ClientDBus(Client, DBusObjectWithProperties):
741
862
    """A Client class using D-Bus
742
863
    
764
885
        DBusObjectWithProperties.__init__(self, self.bus,
765
886
                                          self.dbus_object_path)
766
887
        
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)
 
888
    def notifychangeproperty(transform_func,
 
889
                             dbus_name, type_func=lambda x: x,
 
890
                             variant_level=1):
 
891
        """ Modify a variable so that it's a property which announces
 
892
        its changes to DBus.
778
893
 
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
 
894
        transform_fun: Function that takes a value and transforms it
 
895
                       to a D-Bus type.
 
896
        dbus_name: D-Bus name of the variable
 
897
        type_func: Function that transform the value before sending it
 
898
                   to the D-Bus.  Default: no transform
 
899
        variant_level: D-Bus variant level.  Default: 1
 
900
        """
 
901
        attrname = "_{0}".format(dbus_name)
 
902
        def setter(self, value):
 
903
            if hasattr(self, "dbus_object_path"):
 
904
                if (not hasattr(self, attrname) or
 
905
                    type_func(getattr(self, attrname, None))
 
906
                    != type_func(value)):
 
907
                    dbus_value = transform_func(type_func(value),
 
908
                                                variant_level)
 
909
                    self.PropertyChanged(dbus.String(dbus_name),
 
910
                                         dbus_value)
 
911
            setattr(self, attrname, value)
 
912
        
 
913
        return property(lambda self: getattr(self, attrname), setter)
 
914
    
 
915
    
 
916
    expires = notifychangeproperty(datetime_to_dbus, "Expires")
 
917
    approvals_pending = notifychangeproperty(dbus.Boolean,
 
918
                                             "ApprovalPending",
 
919
                                             type_func = bool)
 
920
    enabled = notifychangeproperty(dbus.Boolean, "Enabled")
 
921
    last_enabled = notifychangeproperty(datetime_to_dbus,
 
922
                                        "LastEnabled")
 
923
    checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
 
924
                                   type_func = lambda checker:
 
925
                                       checker is not None)
 
926
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
 
927
                                           "LastCheckedOK")
 
928
    last_approval_request = notifychangeproperty(
 
929
        datetime_to_dbus, "LastApprovalRequest")
 
930
    approved_by_default = notifychangeproperty(dbus.Boolean,
 
931
                                               "ApprovedByDefault")
 
932
    approval_delay = notifychangeproperty(dbus.UInt16,
 
933
                                          "ApprovalDelay",
 
934
                                          type_func =
 
935
                                          _timedelta_to_milliseconds)
 
936
    approval_duration = notifychangeproperty(
 
937
        dbus.UInt16, "ApprovalDuration",
 
938
        type_func = _timedelta_to_milliseconds)
 
939
    host = notifychangeproperty(dbus.String, "Host")
 
940
    timeout = notifychangeproperty(dbus.UInt16, "Timeout",
 
941
                                   type_func =
 
942
                                   _timedelta_to_milliseconds)
 
943
    extended_timeout = notifychangeproperty(
 
944
        dbus.UInt16, "ExtendedTimeout",
 
945
        type_func = _timedelta_to_milliseconds)
 
946
    interval = notifychangeproperty(dbus.UInt16,
 
947
                                    "Interval",
 
948
                                    type_func =
 
949
                                    _timedelta_to_milliseconds)
 
950
    checker_command = notifychangeproperty(dbus.String, "Checker")
 
951
    
 
952
    del notifychangeproperty
810
953
    
811
954
    def __del__(self, *args, **kwargs):
812
955
        try:
821
964
                         *args, **kwargs):
822
965
        self.checker_callback_tag = None
823
966
        self.checker = None
824
 
        # Emit D-Bus signal
825
 
        self.PropertyChanged(dbus.String("CheckerRunning"),
826
 
                             dbus.Boolean(False, variant_level=1))
827
967
        if os.WIFEXITED(condition):
828
968
            exitstatus = os.WEXITSTATUS(condition)
829
969
            # Emit D-Bus signal
839
979
        return Client.checker_callback(self, pid, condition, command,
840
980
                                       *args, **kwargs)
841
981
    
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
 
    
859
982
    def start_checker(self, *args, **kwargs):
860
983
        old_checker = self.checker
861
984
        if self.checker is not None:
868
991
            and old_checker_pid != self.checker.pid):
869
992
            # Emit D-Bus signal
870
993
            self.CheckerStarted(self.current_checker_command)
871
 
            self.PropertyChanged(
872
 
                dbus.String("CheckerRunning"),
873
 
                dbus.Boolean(True, variant_level=1))
874
994
        return r
875
995
    
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
 
 
885
996
    def _reset_approved(self):
886
997
        self._approved = None
887
998
        return False
889
1000
    def approve(self, value=True):
890
1001
        self.send_changedstate()
891
1002
        self._approved = value
892
 
        gobject.timeout_add(self._timedelta_to_milliseconds
 
1003
        gobject.timeout_add(_timedelta_to_milliseconds
893
1004
                            (self.approval_duration),
894
1005
                            self._reset_approved)
895
1006
    
896
1007
    
897
1008
    ## D-Bus methods, signals & properties
898
 
    _interface = "se.bsnet.fukt.Mandos.Client"
 
1009
    _interface = "se.recompile.Mandos.Client"
899
1010
    
900
1011
    ## Signals
901
1012
    
987
1098
        if value is None:       # get
988
1099
            return dbus.Boolean(self.approved_by_default)
989
1100
        self.approved_by_default = bool(value)
990
 
        # Emit D-Bus signal
991
 
        self.PropertyChanged(dbus.String("ApprovedByDefault"),
992
 
                             dbus.Boolean(value, variant_level=1))
993
1101
    
994
1102
    # ApprovalDelay - property
995
1103
    @dbus_service_property(_interface, signature="t",
998
1106
        if value is None:       # get
999
1107
            return dbus.UInt64(self.approval_delay_milliseconds())
1000
1108
        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))
1004
1109
    
1005
1110
    # ApprovalDuration - property
1006
1111
    @dbus_service_property(_interface, signature="t",
1007
1112
                           access="readwrite")
1008
1113
    def ApprovalDuration_dbus_property(self, value=None):
1009
1114
        if value is None:       # get
1010
 
            return dbus.UInt64(self._timedelta_to_milliseconds(
 
1115
            return dbus.UInt64(_timedelta_to_milliseconds(
1011
1116
                    self.approval_duration))
1012
1117
        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))
1016
1118
    
1017
1119
    # Name - property
1018
1120
    @dbus_service_property(_interface, signature="s", access="read")
1031
1133
        if value is None:       # get
1032
1134
            return dbus.String(self.host)
1033
1135
        self.host = value
1034
 
        # Emit D-Bus signal
1035
 
        self.PropertyChanged(dbus.String("Host"),
1036
 
                             dbus.String(value, variant_level=1))
1037
1136
    
1038
1137
    # Created - property
1039
1138
    @dbus_service_property(_interface, signature="s", access="read")
1040
1139
    def Created_dbus_property(self):
1041
 
        return dbus.String(self._datetime_to_dbus(self.created))
 
1140
        return dbus.String(datetime_to_dbus(self.created))
1042
1141
    
1043
1142
    # LastEnabled - property
1044
1143
    @dbus_service_property(_interface, signature="s", access="read")
1045
1144
    def LastEnabled_dbus_property(self):
1046
 
        if self.last_enabled is None:
1047
 
            return dbus.String("")
1048
 
        return dbus.String(self._datetime_to_dbus(self.last_enabled))
 
1145
        return datetime_to_dbus(self.last_enabled)
1049
1146
    
1050
1147
    # Enabled - property
1051
1148
    @dbus_service_property(_interface, signature="b",
1065
1162
        if value is not None:
1066
1163
            self.checked_ok()
1067
1164
            return
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))
 
1165
        return datetime_to_dbus(self.last_checked_ok)
 
1166
    
 
1167
    # Expires - property
 
1168
    @dbus_service_property(_interface, signature="s", access="read")
 
1169
    def Expires_dbus_property(self):
 
1170
        return datetime_to_dbus(self.expires)
1072
1171
    
1073
1172
    # LastApprovalRequest - property
1074
1173
    @dbus_service_property(_interface, signature="s", access="read")
1075
1174
    def LastApprovalRequest_dbus_property(self):
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))
 
1175
        return datetime_to_dbus(self.last_approval_request)
1081
1176
    
1082
1177
    # Timeout - property
1083
1178
    @dbus_service_property(_interface, signature="t",
1086
1181
        if value is None:       # get
1087
1182
            return dbus.UInt64(self.timeout_milliseconds())
1088
1183
        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))
1092
1184
        if getattr(self, "disable_initiator_tag", None) is None:
1093
1185
            return
1094
1186
        # Reschedule timeout
1095
1187
        gobject.source_remove(self.disable_initiator_tag)
1096
1188
        self.disable_initiator_tag = None
 
1189
        self.expires = None
1097
1190
        time_to_die = (self.
1098
1191
                       _timedelta_to_milliseconds((self
1099
1192
                                                   .last_checked_ok
1104
1197
            # The timeout has passed
1105
1198
            self.disable()
1106
1199
        else:
 
1200
            self.expires = (datetime.datetime.utcnow()
 
1201
                            + datetime.timedelta(milliseconds =
 
1202
                                                 time_to_die))
1107
1203
            self.disable_initiator_tag = (gobject.timeout_add
1108
1204
                                          (time_to_die, self.disable))
1109
1205
    
 
1206
    # ExtendedTimeout - property
 
1207
    @dbus_service_property(_interface, signature="t",
 
1208
                           access="readwrite")
 
1209
    def ExtendedTimeout_dbus_property(self, value=None):
 
1210
        if value is None:       # get
 
1211
            return dbus.UInt64(self.extended_timeout_milliseconds())
 
1212
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
 
1213
    
1110
1214
    # Interval - property
1111
1215
    @dbus_service_property(_interface, signature="t",
1112
1216
                           access="readwrite")
1114
1218
        if value is None:       # get
1115
1219
            return dbus.UInt64(self.interval_milliseconds())
1116
1220
        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))
1120
1221
        if getattr(self, "checker_initiator_tag", None) is None:
1121
1222
            return
1122
1223
        # Reschedule checker run
1124
1225
        self.checker_initiator_tag = (gobject.timeout_add
1125
1226
                                      (value, self.start_checker))
1126
1227
        self.start_checker()    # Start one now, too
1127
 
 
 
1228
    
1128
1229
    # Checker - property
1129
1230
    @dbus_service_property(_interface, signature="s",
1130
1231
                           access="readwrite")
1132
1233
        if value is None:       # get
1133
1234
            return dbus.String(self.checker_command)
1134
1235
        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))
1139
1236
    
1140
1237
    # CheckerRunning - property
1141
1238
    @dbus_service_property(_interface, signature="b",
1168
1265
        self._pipe.send(('init', fpr, address))
1169
1266
        if not self._pipe.recv():
1170
1267
            raise KeyError()
1171
 
 
 
1268
    
1172
1269
    def __getattribute__(self, name):
1173
1270
        if(name == '_pipe'):
1174
1271
            return super(ProxyClient, self).__getattribute__(name)
1181
1278
                self._pipe.send(('funcall', name, args, kwargs))
1182
1279
                return self._pipe.recv()[1]
1183
1280
            return func
1184
 
 
 
1281
    
1185
1282
    def __setattr__(self, name, value):
1186
1283
        if(name == '_pipe'):
1187
1284
            return super(ProxyClient, self).__setattr__(name, value)
1188
1285
        self._pipe.send(('setattr', name, value))
1189
1286
 
 
1287
class ClientDBusTransitional(ClientDBus):
 
1288
    __metaclass__ = AlternateDBusNamesMetaclass
1190
1289
 
1191
1290
class ClientHandler(socketserver.BaseRequestHandler, object):
1192
1291
    """A class to handle client connections.
1200
1299
                        unicode(self.client_address))
1201
1300
            logger.debug("Pipe FD: %d",
1202
1301
                         self.server.child_pipe.fileno())
1203
 
 
 
1302
            
1204
1303
            session = (gnutls.connection
1205
1304
                       .ClientSession(self.request,
1206
1305
                                      gnutls.connection
1207
1306
                                      .X509Credentials()))
1208
 
 
 
1307
            
1209
1308
            # Note: gnutls.connection.X509Credentials is really a
1210
1309
            # generic GnuTLS certificate credentials object so long as
1211
1310
            # no X.509 keys are added to it.  Therefore, we can use it
1212
1311
            # here despite using OpenPGP certificates.
1213
 
 
 
1312
            
1214
1313
            #priority = ':'.join(("NONE", "+VERS-TLS1.1",
1215
1314
            #                      "+AES-256-CBC", "+SHA1",
1216
1315
            #                      "+COMP-NULL", "+CTYPE-OPENPGP",
1222
1321
            (gnutls.library.functions
1223
1322
             .gnutls_priority_set_direct(session._c_object,
1224
1323
                                         priority, None))
1225
 
 
 
1324
            
1226
1325
            # Start communication using the Mandos protocol
1227
1326
            # Get protocol number
1228
1327
            line = self.request.makefile().readline()
1233
1332
            except (ValueError, IndexError, RuntimeError) as error:
1234
1333
                logger.error("Unknown protocol version: %s", error)
1235
1334
                return
1236
 
 
 
1335
            
1237
1336
            # Start GnuTLS connection
1238
1337
            try:
1239
1338
                session.handshake()
1243
1342
                # established.  Just abandon the request.
1244
1343
                return
1245
1344
            logger.debug("Handshake succeeded")
1246
 
 
 
1345
            
1247
1346
            approval_required = False
1248
1347
            try:
1249
1348
                try:
1254
1353
                    logger.warning("Bad certificate: %s", error)
1255
1354
                    return
1256
1355
                logger.debug("Fingerprint: %s", fpr)
1257
 
 
 
1356
                
1258
1357
                try:
1259
1358
                    client = ProxyClient(child_pipe, fpr,
1260
1359
                                         self.client_address)
1272
1371
                                       client.name)
1273
1372
                        if self.server.use_dbus:
1274
1373
                            # Emit D-Bus signal
1275
 
                            client.Rejected("Disabled")                    
 
1374
                            client.Rejected("Disabled")
1276
1375
                        return
1277
1376
                    
1278
1377
                    if client._approved or not client.approval_delay:
1295
1394
                        return
1296
1395
                    
1297
1396
                    #wait until timeout or approved
1298
 
                    #x = float(client._timedelta_to_milliseconds(delay))
1299
1397
                    time = datetime.datetime.now()
1300
1398
                    client.changedstate.acquire()
1301
 
                    client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
 
1399
                    (client.changedstate.wait
 
1400
                     (float(client._timedelta_to_milliseconds(delay)
 
1401
                            / 1000)))
1302
1402
                    client.changedstate.release()
1303
1403
                    time2 = datetime.datetime.now()
1304
1404
                    if (time2 - time) >= delay:
1326
1426
                                 sent, len(client.secret)
1327
1427
                                 - (sent_size + sent))
1328
1428
                    sent_size += sent
1329
 
 
 
1429
                
1330
1430
                logger.info("Sending secret to %s", client.name)
1331
1431
                # bump the timeout as if seen
1332
 
                client.checked_ok()
 
1432
                client.checked_ok(client.extended_timeout)
1333
1433
                if self.server.use_dbus:
1334
1434
                    # Emit D-Bus signal
1335
1435
                    client.GotSecret()
1414
1514
        except:
1415
1515
            self.handle_error(request, address)
1416
1516
        self.close_request(request)
1417
 
            
 
1517
    
1418
1518
    def process_request(self, request, address):
1419
1519
        """Start a new process to process the request."""
1420
 
        multiprocessing.Process(target = self.sub_process_main,
1421
 
                                args = (request, address)).start()
 
1520
        proc = multiprocessing.Process(target = self.sub_process_main,
 
1521
                                       args = (request,
 
1522
                                               address))
 
1523
        proc.start()
 
1524
        return proc
 
1525
 
1422
1526
 
1423
1527
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1424
1528
    """ adds a pipe to the MixIn """
1428
1532
        This function creates a new pipe in self.pipe
1429
1533
        """
1430
1534
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
1431
 
 
1432
 
        super(MultiprocessingMixInWithPipe,
1433
 
              self).process_request(request, client_address)
 
1535
        
 
1536
        proc = MultiprocessingMixIn.process_request(self, request,
 
1537
                                                    client_address)
1434
1538
        self.child_pipe.close()
1435
 
        self.add_pipe(parent_pipe)
1436
 
 
1437
 
    def add_pipe(self, parent_pipe):
 
1539
        self.add_pipe(parent_pipe, proc)
 
1540
    
 
1541
    def add_pipe(self, parent_pipe, proc):
1438
1542
        """Dummy function; override as necessary"""
1439
1543
        raise NotImplementedError
1440
1544
 
 
1545
 
1441
1546
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1442
1547
                     socketserver.TCPServer, object):
1443
1548
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1527
1632
    def server_activate(self):
1528
1633
        if self.enabled:
1529
1634
            return socketserver.TCPServer.server_activate(self)
 
1635
    
1530
1636
    def enable(self):
1531
1637
        self.enabled = True
1532
 
    def add_pipe(self, parent_pipe):
 
1638
    
 
1639
    def add_pipe(self, parent_pipe, proc):
1533
1640
        # Call "handle_ipc" for both data and EOF events
1534
1641
        gobject.io_add_watch(parent_pipe.fileno(),
1535
1642
                             gobject.IO_IN | gobject.IO_HUP,
1536
1643
                             functools.partial(self.handle_ipc,
1537
 
                                               parent_pipe = parent_pipe))
1538
 
        
 
1644
                                               parent_pipe =
 
1645
                                               parent_pipe,
 
1646
                                               proc = proc))
 
1647
    
1539
1648
    def handle_ipc(self, source, condition, parent_pipe=None,
1540
 
                   client_object=None):
 
1649
                   proc = None, client_object=None):
1541
1650
        condition_names = {
1542
1651
            gobject.IO_IN: "IN",   # There is data to read.
1543
1652
            gobject.IO_OUT: "OUT", # Data can be written (without
1554
1663
                                       if cond & condition)
1555
1664
        # error or the other end of multiprocessing.Pipe has closed
1556
1665
        if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
 
1666
            proc.join()
1557
1667
            return False
1558
1668
        
1559
1669
        # Read a request from the child
1573
1683
                            "dress: %s", fpr, address)
1574
1684
                if self.use_dbus:
1575
1685
                    # Emit D-Bus signal
1576
 
                    mandos_dbus_service.ClientNotFound(fpr, address[0])
 
1686
                    mandos_dbus_service.ClientNotFound(fpr,
 
1687
                                                       address[0])
1577
1688
                parent_pipe.send(False)
1578
1689
                return False
1579
1690
            
1580
1691
            gobject.io_add_watch(parent_pipe.fileno(),
1581
1692
                                 gobject.IO_IN | gobject.IO_HUP,
1582
1693
                                 functools.partial(self.handle_ipc,
1583
 
                                                   parent_pipe = parent_pipe,
1584
 
                                                   client_object = client))
 
1694
                                                   parent_pipe =
 
1695
                                                   parent_pipe,
 
1696
                                                   proc = proc,
 
1697
                                                   client_object =
 
1698
                                                   client))
1585
1699
            parent_pipe.send(True)
1586
 
            # remove the old hook in favor of the new above hook on same fileno
 
1700
            # remove the old hook in favor of the new above hook on
 
1701
            # same fileno
1587
1702
            return False
1588
1703
        if command == 'funcall':
1589
1704
            funcname = request[1]
1590
1705
            args = request[2]
1591
1706
            kwargs = request[3]
1592
1707
            
1593
 
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1594
 
 
 
1708
            parent_pipe.send(('data', getattr(client_object,
 
1709
                                              funcname)(*args,
 
1710
                                                         **kwargs)))
 
1711
        
1595
1712
        if command == 'getattr':
1596
1713
            attrname = request[1]
1597
1714
            if callable(client_object.__getattribute__(attrname)):
1598
1715
                parent_pipe.send(('function',))
1599
1716
            else:
1600
 
                parent_pipe.send(('data', client_object.__getattribute__(attrname)))
 
1717
                parent_pipe.send(('data', client_object
 
1718
                                  .__getattribute__(attrname)))
1601
1719
        
1602
1720
        if command == 'setattr':
1603
1721
            attrname = request[1]
1604
1722
            value = request[2]
1605
1723
            setattr(client_object, attrname, value)
1606
 
 
 
1724
        
1607
1725
        return True
1608
1726
 
1609
1727
 
1788
1906
    debuglevel = server_settings["debuglevel"]
1789
1907
    use_dbus = server_settings["use_dbus"]
1790
1908
    use_ipv6 = server_settings["use_ipv6"]
1791
 
 
 
1909
    
1792
1910
    if server_settings["servicename"] != "Mandos":
1793
1911
        syslogger.setFormatter(logging.Formatter
1794
1912
                               ('Mandos (%s) [%%(process)d]:'
1796
1914
                                % server_settings["servicename"]))
1797
1915
    
1798
1916
    # Parse config file with clients
1799
 
    client_defaults = { "timeout": "1h",
1800
 
                        "interval": "5m",
 
1917
    client_defaults = { "timeout": "5m",
 
1918
                        "extended_timeout": "15m",
 
1919
                        "interval": "2m",
1801
1920
                        "checker": "fping -q -- %%(host)s",
1802
1921
                        "host": "",
1803
1922
                        "approval_delay": "0s",
1854
1973
        level = getattr(logging, debuglevel.upper())
1855
1974
        syslogger.setLevel(level)
1856
1975
        console.setLevel(level)
1857
 
 
 
1976
    
1858
1977
    if debug:
1859
1978
        # Enable all possible GnuTLS debugging
1860
1979
        
1891
2010
    # End of Avahi example code
1892
2011
    if use_dbus:
1893
2012
        try:
1894
 
            bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
 
2013
            bus_name = dbus.service.BusName("se.recompile.Mandos",
1895
2014
                                            bus, do_not_queue=True)
 
2015
            old_bus_name = (dbus.service.BusName
 
2016
                            ("se.bsnet.fukt.Mandos", bus,
 
2017
                             do_not_queue=True))
1896
2018
        except dbus.exceptions.NameExistsException as e:
1897
2019
            logger.error(unicode(e) + ", disabling D-Bus")
1898
2020
            use_dbus = False
1911
2033
    
1912
2034
    client_class = Client
1913
2035
    if use_dbus:
1914
 
        client_class = functools.partial(ClientDBus, bus = bus)
 
2036
        client_class = functools.partial(ClientDBusTransitional,
 
2037
                                         bus = bus)
1915
2038
    def client_config_items(config, section):
1916
2039
        special_settings = {
1917
2040
            "approved_by_default":
1947
2070
        del pidfilename
1948
2071
        
1949
2072
        signal.signal(signal.SIGINT, signal.SIG_IGN)
1950
 
 
 
2073
    
1951
2074
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1952
2075
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1953
2076
    
1956
2079
            """A D-Bus proxy object"""
1957
2080
            def __init__(self):
1958
2081
                dbus.service.Object.__init__(self, bus, "/")
1959
 
            _interface = "se.bsnet.fukt.Mandos"
 
2082
            _interface = "se.recompile.Mandos"
1960
2083
            
1961
2084
            @dbus.service.signal(_interface, signature="o")
1962
2085
            def ClientAdded(self, objpath):
2004
2127
            
2005
2128
            del _interface
2006
2129
        
2007
 
        mandos_dbus_service = MandosDBusService()
 
2130
        class MandosDBusServiceTransitional(MandosDBusService):
 
2131
            __metaclass__ = AlternateDBusNamesMetaclass
 
2132
        mandos_dbus_service = MandosDBusServiceTransitional()
2008
2133
    
2009
2134
    def cleanup():
2010
2135
        "Cleanup function; run on exit"
2011
2136
        service.cleanup()
2012
2137
        
 
2138
        multiprocessing.active_children()
2013
2139
        while tcp_server.clients:
2014
2140
            client = tcp_server.clients.pop()
2015
2141
            if use_dbus:
2019
2145
            client.disable(quiet=True)
2020
2146
            if use_dbus:
2021
2147
                # Emit D-Bus signal
2022
 
                mandos_dbus_service.ClientRemoved(client.dbus_object_path,
 
2148
                mandos_dbus_service.ClientRemoved(client
 
2149
                                                  .dbus_object_path,
2023
2150
                                                  client.name)
2024
2151
    
2025
2152
    atexit.register(cleanup)
2074
2201
    # Must run before the D-Bus bus name gets deregistered
2075
2202
    cleanup()
2076
2203
 
 
2204
 
2077
2205
if __name__ == '__main__':
2078
2206
    main()