/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 at bsnet
  • Date: 2011-07-31 13:05:34 UTC
  • Revision ID: teddy@fukt.bsnet.se-20110731130534-v4d9hsvek80ms9uo
* plugins.d/mandos-client.c (avahi_loop_with_timeout): Fix warning.
  (main): Disallow "--retry" arguments < 0.  Allow brackets [] around
          IPv6 addresses, as recommended by RFC 5952.  Bug fix: When
          using --connect, really use retry_interval, not 1 second.
* plugins.d/mandos-client.xml (DESCRIPTION): Add retry info.
  (--retry): Remove repeated word.

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
160
159
                            " after %i retries, exiting.",
161
160
                            self.rename_count)
162
161
            raise AvahiServiceError("Too many renames")
163
 
        self.name = unicode(self.server
164
 
                            .GetAlternativeServiceName(self.name))
 
162
        self.name = unicode(self.server.GetAlternativeServiceName(self.name))
165
163
        logger.info("Changing Zeroconf service name to %r ...",
166
164
                    self.name)
167
165
        syslogger.setFormatter(logging.Formatter
266
264
        self.server_state_changed(self.server.GetState())
267
265
 
268
266
 
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
 
        
275
267
class Client(object):
276
268
    """A representation of a client host served by this server.
277
269
    
305
297
    secret:     bytestring; sent verbatim (over TLS) to client
306
298
    timeout:    datetime.timedelta(); How long from last_checked_ok
307
299
                                      until this client is disabled
308
 
    extended_timeout:   extra long timeout when password has been sent
309
300
    runtime_expansions: Allowed attributes for runtime expansion.
310
 
    expires:    datetime.datetime(); time (UTC) when a client will be
311
 
                disabled, or None
312
301
    """
313
302
    
314
303
    runtime_expansions = ("approval_delay", "approval_duration",
316
305
                          "host", "interval", "last_checked_ok",
317
306
                          "last_enabled", "name", "timeout")
318
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
    
319
315
    def timeout_milliseconds(self):
320
316
        "Return the 'timeout' attribute in milliseconds"
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)
 
317
        return self._timedelta_to_milliseconds(self.timeout)
326
318
    
327
319
    def interval_milliseconds(self):
328
320
        "Return the 'interval' attribute in milliseconds"
329
 
        return _timedelta_to_milliseconds(self.interval)
330
 
    
 
321
        return self._timedelta_to_milliseconds(self.interval)
 
322
 
331
323
    def approval_delay_milliseconds(self):
332
 
        return _timedelta_to_milliseconds(self.approval_delay)
 
324
        return self._timedelta_to_milliseconds(self.approval_delay)
333
325
    
334
326
    def __init__(self, name = None, disable_hook=None, config=None):
335
327
        """Note: the 'checker' key in 'config' sets the
362
354
        self.last_enabled = None
363
355
        self.last_checked_ok = None
364
356
        self.timeout = string_to_delta(config["timeout"])
365
 
        self.extended_timeout = string_to_delta(config
366
 
                                                ["extended_timeout"])
367
357
        self.interval = string_to_delta(config["interval"])
368
358
        self.disable_hook = disable_hook
369
359
        self.checker = None
370
360
        self.checker_initiator_tag = None
371
361
        self.disable_initiator_tag = None
372
 
        self.expires = None
373
362
        self.checker_callback_tag = None
374
363
        self.checker_command = config["checker"]
375
364
        self.current_checker_command = None
382
371
            config["approval_delay"])
383
372
        self.approval_duration = string_to_delta(
384
373
            config["approval_duration"])
385
 
        self.changedstate = (multiprocessing_manager
386
 
                             .Condition(multiprocessing_manager
387
 
                                        .Lock()))
 
374
        self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
388
375
    
389
376
    def send_changedstate(self):
390
377
        self.changedstate.acquire()
391
378
        self.changedstate.notify_all()
392
379
        self.changedstate.release()
393
 
    
 
380
        
394
381
    def enable(self):
395
382
        """Start this client's checker and timeout hooks"""
396
383
        if getattr(self, "enabled", False):
397
384
            # Already enabled
398
385
            return
399
386
        self.send_changedstate()
 
387
        self.last_enabled = datetime.datetime.utcnow()
400
388
        # Schedule a new checker to be started an 'interval' from now,
401
389
        # and every interval from then on.
402
390
        self.checker_initiator_tag = (gobject.timeout_add
403
391
                                      (self.interval_milliseconds(),
404
392
                                       self.start_checker))
405
393
        # Schedule a disable() when 'timeout' has passed
406
 
        self.expires = datetime.datetime.utcnow() + self.timeout
407
394
        self.disable_initiator_tag = (gobject.timeout_add
408
395
                                   (self.timeout_milliseconds(),
409
396
                                    self.disable))
410
397
        self.enabled = True
411
 
        self.last_enabled = datetime.datetime.utcnow()
412
398
        # Also start a new checker *right now*.
413
399
        self.start_checker()
414
400
    
423
409
        if getattr(self, "disable_initiator_tag", False):
424
410
            gobject.source_remove(self.disable_initiator_tag)
425
411
            self.disable_initiator_tag = None
426
 
        self.expires = None
427
412
        if getattr(self, "checker_initiator_tag", False):
428
413
            gobject.source_remove(self.checker_initiator_tag)
429
414
            self.checker_initiator_tag = None
455
440
            logger.warning("Checker for %(name)s crashed?",
456
441
                           vars(self))
457
442
    
458
 
    def checked_ok(self, timeout=None):
 
443
    def checked_ok(self):
459
444
        """Bump up the timeout for this client.
460
445
        
461
446
        This should only be called when the client has been seen,
462
447
        alive and well.
463
448
        """
464
 
        if timeout is None:
465
 
            timeout = self.timeout
466
449
        self.last_checked_ok = datetime.datetime.utcnow()
467
450
        gobject.source_remove(self.disable_initiator_tag)
468
 
        self.expires = datetime.datetime.utcnow() + timeout
469
451
        self.disable_initiator_tag = (gobject.timeout_add
470
 
                                      (_timedelta_to_milliseconds
471
 
                                       (timeout), self.disable))
 
452
                                      (self.timeout_milliseconds(),
 
453
                                       self.disable))
472
454
    
473
455
    def need_approval(self):
474
456
        self.last_approval_request = datetime.datetime.utcnow()
514
496
                                       'replace')))
515
497
                    for attr in
516
498
                    self.runtime_expansions)
517
 
                
 
499
 
518
500
                try:
519
501
                    command = self.checker_command % escaped_attrs
520
502
                except TypeError as error:
566
548
                raise
567
549
        self.checker = None
568
550
 
569
 
 
570
551
def dbus_service_property(dbus_interface, signature="v",
571
552
                          access="readwrite", byte_arrays=False):
572
553
    """Decorators for marking methods of a DBusObjectWithProperties to
618
599
 
619
600
class DBusObjectWithProperties(dbus.service.Object):
620
601
    """A D-Bus object with properties.
621
 
    
 
602
 
622
603
    Classes inheriting from this can use the dbus_service_property
623
604
    decorator to expose methods as D-Bus properties.  It exposes the
624
605
    standard Get(), Set(), and GetAll() methods on the D-Bus.
631
612
    def _get_all_dbus_properties(self):
632
613
        """Returns a generator of (name, attribute) pairs
633
614
        """
634
 
        return ((prop.__get__(self)._dbus_name, prop.__get__(self))
635
 
                for cls in self.__class__.__mro__
 
615
        return ((prop._dbus_name, prop)
636
616
                for name, prop in
637
 
                inspect.getmembers(cls, self._is_dbus_property))
 
617
                inspect.getmembers(self, self._is_dbus_property))
638
618
    
639
619
    def _get_dbus_property(self, interface_name, property_name):
640
620
        """Returns a bound method if one exists which is a D-Bus
641
621
        property with the specified name and interface.
642
622
        """
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
 
        
 
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
650
633
        # No such property
651
634
        raise DBusPropertyNotFound(self.dbus_object_path + ":"
652
635
                                   + interface_name + "."
686
669
    def GetAll(self, interface_name):
687
670
        """Standard D-Bus property GetAll() method, see D-Bus
688
671
        standard.
689
 
        
 
672
 
690
673
        Note: Will not include properties with access="write".
691
674
        """
692
675
        all = {}
754
737
        return xmlstring
755
738
 
756
739
 
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
 
 
861
740
class ClientDBus(Client, DBusObjectWithProperties):
862
741
    """A Client class using D-Bus
863
742
    
885
764
        DBusObjectWithProperties.__init__(self, self.bus,
886
765
                                          self.dbus_object_path)
887
766
        
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.
 
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)
893
778
 
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
 
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
953
810
    
954
811
    def __del__(self, *args, **kwargs):
955
812
        try:
964
821
                         *args, **kwargs):
965
822
        self.checker_callback_tag = None
966
823
        self.checker = None
 
824
        # Emit D-Bus signal
 
825
        self.PropertyChanged(dbus.String("CheckerRunning"),
 
826
                             dbus.Boolean(False, variant_level=1))
967
827
        if os.WIFEXITED(condition):
968
828
            exitstatus = os.WEXITSTATUS(condition)
969
829
            # Emit D-Bus signal
979
839
        return Client.checker_callback(self, pid, condition, command,
980
840
                                       *args, **kwargs)
981
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
    
982
859
    def start_checker(self, *args, **kwargs):
983
860
        old_checker = self.checker
984
861
        if self.checker is not None:
991
868
            and old_checker_pid != self.checker.pid):
992
869
            # Emit D-Bus signal
993
870
            self.CheckerStarted(self.current_checker_command)
 
871
            self.PropertyChanged(
 
872
                dbus.String("CheckerRunning"),
 
873
                dbus.Boolean(True, variant_level=1))
994
874
        return r
995
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
 
996
885
    def _reset_approved(self):
997
886
        self._approved = None
998
887
        return False
1000
889
    def approve(self, value=True):
1001
890
        self.send_changedstate()
1002
891
        self._approved = value
1003
 
        gobject.timeout_add(_timedelta_to_milliseconds
 
892
        gobject.timeout_add(self._timedelta_to_milliseconds
1004
893
                            (self.approval_duration),
1005
894
                            self._reset_approved)
1006
895
    
1007
896
    
1008
897
    ## D-Bus methods, signals & properties
1009
 
    _interface = "se.recompile.Mandos.Client"
 
898
    _interface = "se.bsnet.fukt.Mandos.Client"
1010
899
    
1011
900
    ## Signals
1012
901
    
1098
987
        if value is None:       # get
1099
988
            return dbus.Boolean(self.approved_by_default)
1100
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))
1101
993
    
1102
994
    # ApprovalDelay - property
1103
995
    @dbus_service_property(_interface, signature="t",
1106
998
        if value is None:       # get
1107
999
            return dbus.UInt64(self.approval_delay_milliseconds())
1108
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))
1109
1004
    
1110
1005
    # ApprovalDuration - property
1111
1006
    @dbus_service_property(_interface, signature="t",
1112
1007
                           access="readwrite")
1113
1008
    def ApprovalDuration_dbus_property(self, value=None):
1114
1009
        if value is None:       # get
1115
 
            return dbus.UInt64(_timedelta_to_milliseconds(
 
1010
            return dbus.UInt64(self._timedelta_to_milliseconds(
1116
1011
                    self.approval_duration))
1117
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))
1118
1016
    
1119
1017
    # Name - property
1120
1018
    @dbus_service_property(_interface, signature="s", access="read")
1133
1031
        if value is None:       # get
1134
1032
            return dbus.String(self.host)
1135
1033
        self.host = value
 
1034
        # Emit D-Bus signal
 
1035
        self.PropertyChanged(dbus.String("Host"),
 
1036
                             dbus.String(value, variant_level=1))
1136
1037
    
1137
1038
    # Created - property
1138
1039
    @dbus_service_property(_interface, signature="s", access="read")
1139
1040
    def Created_dbus_property(self):
1140
 
        return dbus.String(datetime_to_dbus(self.created))
 
1041
        return dbus.String(self._datetime_to_dbus(self.created))
1141
1042
    
1142
1043
    # LastEnabled - property
1143
1044
    @dbus_service_property(_interface, signature="s", access="read")
1144
1045
    def LastEnabled_dbus_property(self):
1145
 
        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))
1146
1049
    
1147
1050
    # Enabled - property
1148
1051
    @dbus_service_property(_interface, signature="b",
1162
1065
        if value is not None:
1163
1066
            self.checked_ok()
1164
1067
            return
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)
 
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))
1171
1072
    
1172
1073
    # LastApprovalRequest - property
1173
1074
    @dbus_service_property(_interface, signature="s", access="read")
1174
1075
    def LastApprovalRequest_dbus_property(self):
1175
 
        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))
1176
1081
    
1177
1082
    # Timeout - property
1178
1083
    @dbus_service_property(_interface, signature="t",
1181
1086
        if value is None:       # get
1182
1087
            return dbus.UInt64(self.timeout_milliseconds())
1183
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))
1184
1092
        if getattr(self, "disable_initiator_tag", None) is None:
1185
1093
            return
1186
1094
        # Reschedule timeout
1187
1095
        gobject.source_remove(self.disable_initiator_tag)
1188
1096
        self.disable_initiator_tag = None
1189
 
        self.expires = None
1190
1097
        time_to_die = (self.
1191
1098
                       _timedelta_to_milliseconds((self
1192
1099
                                                   .last_checked_ok
1197
1104
            # The timeout has passed
1198
1105
            self.disable()
1199
1106
        else:
1200
 
            self.expires = (datetime.datetime.utcnow()
1201
 
                            + datetime.timedelta(milliseconds =
1202
 
                                                 time_to_die))
1203
1107
            self.disable_initiator_tag = (gobject.timeout_add
1204
1108
                                          (time_to_die, self.disable))
1205
1109
    
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
 
    
1214
1110
    # Interval - property
1215
1111
    @dbus_service_property(_interface, signature="t",
1216
1112
                           access="readwrite")
1218
1114
        if value is None:       # get
1219
1115
            return dbus.UInt64(self.interval_milliseconds())
1220
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))
1221
1120
        if getattr(self, "checker_initiator_tag", None) is None:
1222
1121
            return
1223
1122
        # Reschedule checker run
1225
1124
        self.checker_initiator_tag = (gobject.timeout_add
1226
1125
                                      (value, self.start_checker))
1227
1126
        self.start_checker()    # Start one now, too
1228
 
    
 
1127
 
1229
1128
    # Checker - property
1230
1129
    @dbus_service_property(_interface, signature="s",
1231
1130
                           access="readwrite")
1233
1132
        if value is None:       # get
1234
1133
            return dbus.String(self.checker_command)
1235
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))
1236
1139
    
1237
1140
    # CheckerRunning - property
1238
1141
    @dbus_service_property(_interface, signature="b",
1265
1168
        self._pipe.send(('init', fpr, address))
1266
1169
        if not self._pipe.recv():
1267
1170
            raise KeyError()
1268
 
    
 
1171
 
1269
1172
    def __getattribute__(self, name):
1270
1173
        if(name == '_pipe'):
1271
1174
            return super(ProxyClient, self).__getattribute__(name)
1278
1181
                self._pipe.send(('funcall', name, args, kwargs))
1279
1182
                return self._pipe.recv()[1]
1280
1183
            return func
1281
 
    
 
1184
 
1282
1185
    def __setattr__(self, name, value):
1283
1186
        if(name == '_pipe'):
1284
1187
            return super(ProxyClient, self).__setattr__(name, value)
1285
1188
        self._pipe.send(('setattr', name, value))
1286
1189
 
1287
 
class ClientDBusTransitional(ClientDBus):
1288
 
    __metaclass__ = AlternateDBusNamesMetaclass
1289
1190
 
1290
1191
class ClientHandler(socketserver.BaseRequestHandler, object):
1291
1192
    """A class to handle client connections.
1299
1200
                        unicode(self.client_address))
1300
1201
            logger.debug("Pipe FD: %d",
1301
1202
                         self.server.child_pipe.fileno())
1302
 
            
 
1203
 
1303
1204
            session = (gnutls.connection
1304
1205
                       .ClientSession(self.request,
1305
1206
                                      gnutls.connection
1306
1207
                                      .X509Credentials()))
1307
 
            
 
1208
 
1308
1209
            # Note: gnutls.connection.X509Credentials is really a
1309
1210
            # generic GnuTLS certificate credentials object so long as
1310
1211
            # no X.509 keys are added to it.  Therefore, we can use it
1311
1212
            # here despite using OpenPGP certificates.
1312
 
            
 
1213
 
1313
1214
            #priority = ':'.join(("NONE", "+VERS-TLS1.1",
1314
1215
            #                      "+AES-256-CBC", "+SHA1",
1315
1216
            #                      "+COMP-NULL", "+CTYPE-OPENPGP",
1321
1222
            (gnutls.library.functions
1322
1223
             .gnutls_priority_set_direct(session._c_object,
1323
1224
                                         priority, None))
1324
 
            
 
1225
 
1325
1226
            # Start communication using the Mandos protocol
1326
1227
            # Get protocol number
1327
1228
            line = self.request.makefile().readline()
1332
1233
            except (ValueError, IndexError, RuntimeError) as error:
1333
1234
                logger.error("Unknown protocol version: %s", error)
1334
1235
                return
1335
 
            
 
1236
 
1336
1237
            # Start GnuTLS connection
1337
1238
            try:
1338
1239
                session.handshake()
1342
1243
                # established.  Just abandon the request.
1343
1244
                return
1344
1245
            logger.debug("Handshake succeeded")
1345
 
            
 
1246
 
1346
1247
            approval_required = False
1347
1248
            try:
1348
1249
                try:
1353
1254
                    logger.warning("Bad certificate: %s", error)
1354
1255
                    return
1355
1256
                logger.debug("Fingerprint: %s", fpr)
1356
 
                
 
1257
 
1357
1258
                try:
1358
1259
                    client = ProxyClient(child_pipe, fpr,
1359
1260
                                         self.client_address)
1371
1272
                                       client.name)
1372
1273
                        if self.server.use_dbus:
1373
1274
                            # Emit D-Bus signal
1374
 
                            client.Rejected("Disabled")
 
1275
                            client.Rejected("Disabled")                    
1375
1276
                        return
1376
1277
                    
1377
1278
                    if client._approved or not client.approval_delay:
1394
1295
                        return
1395
1296
                    
1396
1297
                    #wait until timeout or approved
1397
 
                    #x = float(client
1398
 
                    #          ._timedelta_to_milliseconds(delay))
 
1298
                    #x = float(client._timedelta_to_milliseconds(delay))
1399
1299
                    time = datetime.datetime.now()
1400
1300
                    client.changedstate.acquire()
1401
 
                    (client.changedstate.wait
1402
 
                     (float(client._timedelta_to_milliseconds(delay)
1403
 
                            / 1000)))
 
1301
                    client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1404
1302
                    client.changedstate.release()
1405
1303
                    time2 = datetime.datetime.now()
1406
1304
                    if (time2 - time) >= delay:
1428
1326
                                 sent, len(client.secret)
1429
1327
                                 - (sent_size + sent))
1430
1328
                    sent_size += sent
1431
 
                
 
1329
 
1432
1330
                logger.info("Sending secret to %s", client.name)
1433
1331
                # bump the timeout as if seen
1434
 
                client.checked_ok(client.extended_timeout)
 
1332
                client.checked_ok()
1435
1333
                if self.server.use_dbus:
1436
1334
                    # Emit D-Bus signal
1437
1335
                    client.GotSecret()
1522
1420
        multiprocessing.Process(target = self.sub_process_main,
1523
1421
                                args = (request, address)).start()
1524
1422
 
1525
 
 
1526
1423
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1527
1424
    """ adds a pipe to the MixIn """
1528
1425
    def process_request(self, request, client_address):
1531
1428
        This function creates a new pipe in self.pipe
1532
1429
        """
1533
1430
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
1534
 
        
 
1431
 
1535
1432
        super(MultiprocessingMixInWithPipe,
1536
1433
              self).process_request(request, client_address)
1537
1434
        self.child_pipe.close()
1538
1435
        self.add_pipe(parent_pipe)
1539
 
    
 
1436
 
1540
1437
    def add_pipe(self, parent_pipe):
1541
1438
        """Dummy function; override as necessary"""
1542
1439
        raise NotImplementedError
1543
1440
 
1544
 
 
1545
1441
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1546
1442
                     socketserver.TCPServer, object):
1547
1443
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1638
1534
        gobject.io_add_watch(parent_pipe.fileno(),
1639
1535
                             gobject.IO_IN | gobject.IO_HUP,
1640
1536
                             functools.partial(self.handle_ipc,
1641
 
                                               parent_pipe =
1642
 
                                               parent_pipe))
1643
 
    
 
1537
                                               parent_pipe = parent_pipe))
 
1538
        
1644
1539
    def handle_ipc(self, source, condition, parent_pipe=None,
1645
1540
                   client_object=None):
1646
1541
        condition_names = {
1678
1573
                            "dress: %s", fpr, address)
1679
1574
                if self.use_dbus:
1680
1575
                    # Emit D-Bus signal
1681
 
                    mandos_dbus_service.ClientNotFound(fpr,
1682
 
                                                       address[0])
 
1576
                    mandos_dbus_service.ClientNotFound(fpr, address[0])
1683
1577
                parent_pipe.send(False)
1684
1578
                return False
1685
1579
            
1686
1580
            gobject.io_add_watch(parent_pipe.fileno(),
1687
1581
                                 gobject.IO_IN | gobject.IO_HUP,
1688
1582
                                 functools.partial(self.handle_ipc,
1689
 
                                                   parent_pipe =
1690
 
                                                   parent_pipe,
1691
 
                                                   client_object =
1692
 
                                                   client))
 
1583
                                                   parent_pipe = parent_pipe,
 
1584
                                                   client_object = client))
1693
1585
            parent_pipe.send(True)
1694
 
            # remove the old hook in favor of the new above hook on
1695
 
            # same fileno
 
1586
            # remove the old hook in favor of the new above hook on same fileno
1696
1587
            return False
1697
1588
        if command == 'funcall':
1698
1589
            funcname = request[1]
1699
1590
            args = request[2]
1700
1591
            kwargs = request[3]
1701
1592
            
1702
 
            parent_pipe.send(('data', getattr(client_object,
1703
 
                                              funcname)(*args,
1704
 
                                                         **kwargs)))
1705
 
        
 
1593
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
 
1594
 
1706
1595
        if command == 'getattr':
1707
1596
            attrname = request[1]
1708
1597
            if callable(client_object.__getattribute__(attrname)):
1709
1598
                parent_pipe.send(('function',))
1710
1599
            else:
1711
 
                parent_pipe.send(('data', client_object
1712
 
                                  .__getattribute__(attrname)))
 
1600
                parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1713
1601
        
1714
1602
        if command == 'setattr':
1715
1603
            attrname = request[1]
1716
1604
            value = request[2]
1717
1605
            setattr(client_object, attrname, value)
1718
 
        
 
1606
 
1719
1607
        return True
1720
1608
 
1721
1609
 
1900
1788
    debuglevel = server_settings["debuglevel"]
1901
1789
    use_dbus = server_settings["use_dbus"]
1902
1790
    use_ipv6 = server_settings["use_ipv6"]
1903
 
    
 
1791
 
1904
1792
    if server_settings["servicename"] != "Mandos":
1905
1793
        syslogger.setFormatter(logging.Formatter
1906
1794
                               ('Mandos (%s) [%%(process)d]:'
1908
1796
                                % server_settings["servicename"]))
1909
1797
    
1910
1798
    # Parse config file with clients
1911
 
    client_defaults = { "timeout": "5m",
1912
 
                        "extended_timeout": "15m",
1913
 
                        "interval": "2m",
 
1799
    client_defaults = { "timeout": "1h",
 
1800
                        "interval": "5m",
1914
1801
                        "checker": "fping -q -- %%(host)s",
1915
1802
                        "host": "",
1916
1803
                        "approval_delay": "0s",
1967
1854
        level = getattr(logging, debuglevel.upper())
1968
1855
        syslogger.setLevel(level)
1969
1856
        console.setLevel(level)
1970
 
    
 
1857
 
1971
1858
    if debug:
1972
1859
        # Enable all possible GnuTLS debugging
1973
1860
        
2004
1891
    # End of Avahi example code
2005
1892
    if use_dbus:
2006
1893
        try:
2007
 
            bus_name = dbus.service.BusName("se.recompile.Mandos",
 
1894
            bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
2008
1895
                                            bus, do_not_queue=True)
2009
 
            old_bus_name = (dbus.service.BusName
2010
 
                            ("se.bsnet.fukt.Mandos", bus,
2011
 
                             do_not_queue=True))
2012
1896
        except dbus.exceptions.NameExistsException as e:
2013
1897
            logger.error(unicode(e) + ", disabling D-Bus")
2014
1898
            use_dbus = False
2027
1911
    
2028
1912
    client_class = Client
2029
1913
    if use_dbus:
2030
 
        client_class = functools.partial(ClientDBusTransitional,
2031
 
                                         bus = bus)
 
1914
        client_class = functools.partial(ClientDBus, bus = bus)
2032
1915
    def client_config_items(config, section):
2033
1916
        special_settings = {
2034
1917
            "approved_by_default":
2064
1947
        del pidfilename
2065
1948
        
2066
1949
        signal.signal(signal.SIGINT, signal.SIG_IGN)
2067
 
    
 
1950
 
2068
1951
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2069
1952
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2070
1953
    
2073
1956
            """A D-Bus proxy object"""
2074
1957
            def __init__(self):
2075
1958
                dbus.service.Object.__init__(self, bus, "/")
2076
 
            _interface = "se.recompile.Mandos"
 
1959
            _interface = "se.bsnet.fukt.Mandos"
2077
1960
            
2078
1961
            @dbus.service.signal(_interface, signature="o")
2079
1962
            def ClientAdded(self, objpath):
2121
2004
            
2122
2005
            del _interface
2123
2006
        
2124
 
        class MandosDBusServiceTransitional(MandosDBusService):
2125
 
            __metaclass__ = AlternateDBusNamesMetaclass
2126
 
        mandos_dbus_service = MandosDBusServiceTransitional()
 
2007
        mandos_dbus_service = MandosDBusService()
2127
2008
    
2128
2009
    def cleanup():
2129
2010
        "Cleanup function; run on exit"
2138
2019
            client.disable(quiet=True)
2139
2020
            if use_dbus:
2140
2021
                # Emit D-Bus signal
2141
 
                mandos_dbus_service.ClientRemoved(client
2142
 
                                                  .dbus_object_path,
 
2022
                mandos_dbus_service.ClientRemoved(client.dbus_object_path,
2143
2023
                                                  client.name)
2144
2024
    
2145
2025
    atexit.register(cleanup)
2194
2074
    # Must run before the D-Bus bus name gets deregistered
2195
2075
    cleanup()
2196
2076
 
2197
 
 
2198
2077
if __name__ == '__main__':
2199
2078
    main()