/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: Björn Påhlsson
  • Date: 2011-06-23 22:27:15 UTC
  • mto: This revision was merged to the branch mainline in revision 485.
  • Revision ID: belorn@fukt.bsnet.se-20110623222715-q5wro9ma9iyjl367
* Makefile (CFLAGS): Added "-lrt" to include real time library.
* plugins.d/mandos-client.c: use scandir(3) instead of readdir(3)
                             Prefix all debug output with "Mandos plugin " + program_invocation_short_name
                             Retry servers that failed to provide password.
                             New option --retry SECONDS that sets the interval between rechecking.
                             --retry also controls how often it retries a server when using --connect.
* plugins.d/splashy.c:  Prefix all debug output with "Mandos plugin " + program_invocation_short_name
* plugins.d/usplash.c: --||--
* plugins.d/askpass-fifo.c: --||--
* plugins.d/password-prompt.c: --||--
* plugins.d/plymouth.c: --||--
* mandos: Lower logger level from warning to info on failed client requests because client was disabled or unknown fingerprint.
* plugins.d/plymouth.c (get_pid): bug fix. Was not calling free on direntries. 

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
83
82
        SO_BINDTODEVICE = None
84
83
 
85
84
 
86
 
version = "1.4.1"
 
85
version = "1.3.0"
87
86
 
88
87
#logger = logging.getLogger('mandos')
89
88
logger = logging.Logger('mandos')
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
 
        if self.disable_initiator_tag is not None:
468
 
            gobject.source_remove(self.disable_initiator_tag)
469
 
        if getattr(self, "enabled", False):
470
 
            self.disable_initiator_tag = (gobject.timeout_add
471
 
                                          (_timedelta_to_milliseconds
472
 
                                           (timeout), self.disable))
473
 
            self.expires = datetime.datetime.utcnow() + timeout
 
450
        gobject.source_remove(self.disable_initiator_tag)
 
451
        self.disable_initiator_tag = (gobject.timeout_add
 
452
                                      (self.timeout_milliseconds(),
 
453
                                       self.disable))
474
454
    
475
455
    def need_approval(self):
476
456
        self.last_approval_request = datetime.datetime.utcnow()
516
496
                                       'replace')))
517
497
                    for attr in
518
498
                    self.runtime_expansions)
519
 
                
 
499
 
520
500
                try:
521
501
                    command = self.checker_command % escaped_attrs
522
502
                except TypeError as error:
568
548
                raise
569
549
        self.checker = None
570
550
 
571
 
 
572
551
def dbus_service_property(dbus_interface, signature="v",
573
552
                          access="readwrite", byte_arrays=False):
574
553
    """Decorators for marking methods of a DBusObjectWithProperties to
620
599
 
621
600
class DBusObjectWithProperties(dbus.service.Object):
622
601
    """A D-Bus object with properties.
623
 
    
 
602
 
624
603
    Classes inheriting from this can use the dbus_service_property
625
604
    decorator to expose methods as D-Bus properties.  It exposes the
626
605
    standard Get(), Set(), and GetAll() methods on the D-Bus.
633
612
    def _get_all_dbus_properties(self):
634
613
        """Returns a generator of (name, attribute) pairs
635
614
        """
636
 
        return ((prop.__get__(self)._dbus_name, prop.__get__(self))
637
 
                for cls in self.__class__.__mro__
 
615
        return ((prop._dbus_name, prop)
638
616
                for name, prop in
639
 
                inspect.getmembers(cls, self._is_dbus_property))
 
617
                inspect.getmembers(self, self._is_dbus_property))
640
618
    
641
619
    def _get_dbus_property(self, interface_name, property_name):
642
620
        """Returns a bound method if one exists which is a D-Bus
643
621
        property with the specified name and interface.
644
622
        """
645
 
        for cls in  self.__class__.__mro__:
646
 
            for name, value in (inspect.getmembers
647
 
                                (cls, self._is_dbus_property)):
648
 
                if (value._dbus_name == property_name
649
 
                    and value._dbus_interface == interface_name):
650
 
                    return value.__get__(self)
651
 
        
 
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
652
633
        # No such property
653
634
        raise DBusPropertyNotFound(self.dbus_object_path + ":"
654
635
                                   + interface_name + "."
688
669
    def GetAll(self, interface_name):
689
670
        """Standard D-Bus property GetAll() method, see D-Bus
690
671
        standard.
691
 
        
 
672
 
692
673
        Note: Will not include properties with access="write".
693
674
        """
694
675
        all = {}
756
737
        return xmlstring
757
738
 
758
739
 
759
 
def datetime_to_dbus (dt, variant_level=0):
760
 
    """Convert a UTC datetime.datetime() to a D-Bus type."""
761
 
    if dt is None:
762
 
        return dbus.String("", variant_level = variant_level)
763
 
    return dbus.String(dt.isoformat(),
764
 
                       variant_level=variant_level)
765
 
 
766
 
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
767
 
                                  .__metaclass__):
768
 
    """Applied to an empty subclass of a D-Bus object, this metaclass
769
 
    will add additional D-Bus attributes matching a certain pattern.
770
 
    """
771
 
    def __new__(mcs, name, bases, attr):
772
 
        # Go through all the base classes which could have D-Bus
773
 
        # methods, signals, or properties in them
774
 
        for base in (b for b in bases
775
 
                     if issubclass(b, dbus.service.Object)):
776
 
            # Go though all attributes of the base class
777
 
            for attrname, attribute in inspect.getmembers(base):
778
 
                # Ignore non-D-Bus attributes, and D-Bus attributes
779
 
                # with the wrong interface name
780
 
                if (not hasattr(attribute, "_dbus_interface")
781
 
                    or not attribute._dbus_interface
782
 
                    .startswith("se.recompile.Mandos")):
783
 
                    continue
784
 
                # Create an alternate D-Bus interface name based on
785
 
                # the current name
786
 
                alt_interface = (attribute._dbus_interface
787
 
                                 .replace("se.recompile.Mandos",
788
 
                                          "se.bsnet.fukt.Mandos"))
789
 
                # Is this a D-Bus signal?
790
 
                if getattr(attribute, "_dbus_is_signal", False):
791
 
                    # Extract the original non-method function by
792
 
                    # black magic
793
 
                    nonmethod_func = (dict(
794
 
                            zip(attribute.func_code.co_freevars,
795
 
                                attribute.__closure__))["func"]
796
 
                                      .cell_contents)
797
 
                    # Create a new, but exactly alike, function
798
 
                    # object, and decorate it to be a new D-Bus signal
799
 
                    # with the alternate D-Bus interface name
800
 
                    new_function = (dbus.service.signal
801
 
                                    (alt_interface,
802
 
                                     attribute._dbus_signature)
803
 
                                    (types.FunctionType(
804
 
                                nonmethod_func.func_code,
805
 
                                nonmethod_func.func_globals,
806
 
                                nonmethod_func.func_name,
807
 
                                nonmethod_func.func_defaults,
808
 
                                nonmethod_func.func_closure)))
809
 
                    # Define a creator of a function to call both the
810
 
                    # old and new functions, so both the old and new
811
 
                    # signals gets sent when the function is called
812
 
                    def fixscope(func1, func2):
813
 
                        """This function is a scope container to pass
814
 
                        func1 and func2 to the "call_both" function
815
 
                        outside of its arguments"""
816
 
                        def call_both(*args, **kwargs):
817
 
                            """This function will emit two D-Bus
818
 
                            signals by calling func1 and func2"""
819
 
                            func1(*args, **kwargs)
820
 
                            func2(*args, **kwargs)
821
 
                        return call_both
822
 
                    # Create the "call_both" function and add it to
823
 
                    # the class
824
 
                    attr[attrname] = fixscope(attribute,
825
 
                                              new_function)
826
 
                # Is this a D-Bus method?
827
 
                elif getattr(attribute, "_dbus_is_method", False):
828
 
                    # Create a new, but exactly alike, function
829
 
                    # object.  Decorate it to be a new D-Bus method
830
 
                    # with the alternate D-Bus interface name.  Add it
831
 
                    # to the class.
832
 
                    attr[attrname] = (dbus.service.method
833
 
                                      (alt_interface,
834
 
                                       attribute._dbus_in_signature,
835
 
                                       attribute._dbus_out_signature)
836
 
                                      (types.FunctionType
837
 
                                       (attribute.func_code,
838
 
                                        attribute.func_globals,
839
 
                                        attribute.func_name,
840
 
                                        attribute.func_defaults,
841
 
                                        attribute.func_closure)))
842
 
                # Is this a D-Bus property?
843
 
                elif getattr(attribute, "_dbus_is_property", False):
844
 
                    # Create a new, but exactly alike, function
845
 
                    # object, and decorate it to be a new D-Bus
846
 
                    # property with the alternate D-Bus interface
847
 
                    # name.  Add it to the class.
848
 
                    attr[attrname] = (dbus_service_property
849
 
                                      (alt_interface,
850
 
                                       attribute._dbus_signature,
851
 
                                       attribute._dbus_access,
852
 
                                       attribute
853
 
                                       ._dbus_get_args_options
854
 
                                       ["byte_arrays"])
855
 
                                      (types.FunctionType
856
 
                                       (attribute.func_code,
857
 
                                        attribute.func_globals,
858
 
                                        attribute.func_name,
859
 
                                        attribute.func_defaults,
860
 
                                        attribute.func_closure)))
861
 
        return type.__new__(mcs, name, bases, attr)
862
 
 
863
740
class ClientDBus(Client, DBusObjectWithProperties):
864
741
    """A Client class using D-Bus
865
742
    
887
764
        DBusObjectWithProperties.__init__(self, self.bus,
888
765
                                          self.dbus_object_path)
889
766
        
890
 
    def notifychangeproperty(transform_func,
891
 
                             dbus_name, type_func=lambda x: x,
892
 
                             variant_level=1):
893
 
        """ Modify a variable so that it's a property which announces
894
 
        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)
895
778
 
896
 
        transform_fun: Function that takes a value and a variant_level
897
 
                       and transforms it to a D-Bus type.
898
 
        dbus_name: D-Bus name of the variable
899
 
        type_func: Function that transform the value before sending it
900
 
                   to the D-Bus.  Default: no transform
901
 
        variant_level: D-Bus variant level.  Default: 1
902
 
        """
903
 
        attrname = "_{0}".format(dbus_name)
904
 
        def setter(self, value):
905
 
            if hasattr(self, "dbus_object_path"):
906
 
                if (not hasattr(self, attrname) or
907
 
                    type_func(getattr(self, attrname, None))
908
 
                    != type_func(value)):
909
 
                    dbus_value = transform_func(type_func(value),
910
 
                                                variant_level
911
 
                                                =variant_level)
912
 
                    self.PropertyChanged(dbus.String(dbus_name),
913
 
                                         dbus_value)
914
 
            setattr(self, attrname, value)
915
 
        
916
 
        return property(lambda self: getattr(self, attrname), setter)
917
 
    
918
 
    
919
 
    expires = notifychangeproperty(datetime_to_dbus, "Expires")
920
 
    approvals_pending = notifychangeproperty(dbus.Boolean,
921
 
                                             "ApprovalPending",
922
 
                                             type_func = bool)
923
 
    enabled = notifychangeproperty(dbus.Boolean, "Enabled")
924
 
    last_enabled = notifychangeproperty(datetime_to_dbus,
925
 
                                        "LastEnabled")
926
 
    checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
927
 
                                   type_func = lambda checker:
928
 
                                       checker is not None)
929
 
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
930
 
                                           "LastCheckedOK")
931
 
    last_approval_request = notifychangeproperty(
932
 
        datetime_to_dbus, "LastApprovalRequest")
933
 
    approved_by_default = notifychangeproperty(dbus.Boolean,
934
 
                                               "ApprovedByDefault")
935
 
    approval_delay = notifychangeproperty(dbus.UInt16,
936
 
                                          "ApprovalDelay",
937
 
                                          type_func =
938
 
                                          _timedelta_to_milliseconds)
939
 
    approval_duration = notifychangeproperty(
940
 
        dbus.UInt16, "ApprovalDuration",
941
 
        type_func = _timedelta_to_milliseconds)
942
 
    host = notifychangeproperty(dbus.String, "Host")
943
 
    timeout = notifychangeproperty(dbus.UInt16, "Timeout",
944
 
                                   type_func =
945
 
                                   _timedelta_to_milliseconds)
946
 
    extended_timeout = notifychangeproperty(
947
 
        dbus.UInt16, "ExtendedTimeout",
948
 
        type_func = _timedelta_to_milliseconds)
949
 
    interval = notifychangeproperty(dbus.UInt16,
950
 
                                    "Interval",
951
 
                                    type_func =
952
 
                                    _timedelta_to_milliseconds)
953
 
    checker_command = notifychangeproperty(dbus.String, "Checker")
954
 
    
955
 
    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
956
810
    
957
811
    def __del__(self, *args, **kwargs):
958
812
        try:
967
821
                         *args, **kwargs):
968
822
        self.checker_callback_tag = None
969
823
        self.checker = None
 
824
        # Emit D-Bus signal
 
825
        self.PropertyChanged(dbus.String("CheckerRunning"),
 
826
                             dbus.Boolean(False, variant_level=1))
970
827
        if os.WIFEXITED(condition):
971
828
            exitstatus = os.WEXITSTATUS(condition)
972
829
            # Emit D-Bus signal
982
839
        return Client.checker_callback(self, pid, condition, command,
983
840
                                       *args, **kwargs)
984
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
    
985
859
    def start_checker(self, *args, **kwargs):
986
860
        old_checker = self.checker
987
861
        if self.checker is not None:
994
868
            and old_checker_pid != self.checker.pid):
995
869
            # Emit D-Bus signal
996
870
            self.CheckerStarted(self.current_checker_command)
 
871
            self.PropertyChanged(
 
872
                dbus.String("CheckerRunning"),
 
873
                dbus.Boolean(True, variant_level=1))
997
874
        return r
998
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
 
999
885
    def _reset_approved(self):
1000
886
        self._approved = None
1001
887
        return False
1003
889
    def approve(self, value=True):
1004
890
        self.send_changedstate()
1005
891
        self._approved = value
1006
 
        gobject.timeout_add(_timedelta_to_milliseconds
 
892
        gobject.timeout_add(self._timedelta_to_milliseconds
1007
893
                            (self.approval_duration),
1008
894
                            self._reset_approved)
1009
895
    
1010
896
    
1011
897
    ## D-Bus methods, signals & properties
1012
 
    _interface = "se.recompile.Mandos.Client"
 
898
    _interface = "se.bsnet.fukt.Mandos.Client"
1013
899
    
1014
900
    ## Signals
1015
901
    
1101
987
        if value is None:       # get
1102
988
            return dbus.Boolean(self.approved_by_default)
1103
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))
1104
993
    
1105
994
    # ApprovalDelay - property
1106
995
    @dbus_service_property(_interface, signature="t",
1109
998
        if value is None:       # get
1110
999
            return dbus.UInt64(self.approval_delay_milliseconds())
1111
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))
1112
1004
    
1113
1005
    # ApprovalDuration - property
1114
1006
    @dbus_service_property(_interface, signature="t",
1115
1007
                           access="readwrite")
1116
1008
    def ApprovalDuration_dbus_property(self, value=None):
1117
1009
        if value is None:       # get
1118
 
            return dbus.UInt64(_timedelta_to_milliseconds(
 
1010
            return dbus.UInt64(self._timedelta_to_milliseconds(
1119
1011
                    self.approval_duration))
1120
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))
1121
1016
    
1122
1017
    # Name - property
1123
1018
    @dbus_service_property(_interface, signature="s", access="read")
1136
1031
        if value is None:       # get
1137
1032
            return dbus.String(self.host)
1138
1033
        self.host = value
 
1034
        # Emit D-Bus signal
 
1035
        self.PropertyChanged(dbus.String("Host"),
 
1036
                             dbus.String(value, variant_level=1))
1139
1037
    
1140
1038
    # Created - property
1141
1039
    @dbus_service_property(_interface, signature="s", access="read")
1142
1040
    def Created_dbus_property(self):
1143
 
        return dbus.String(datetime_to_dbus(self.created))
 
1041
        return dbus.String(self._datetime_to_dbus(self.created))
1144
1042
    
1145
1043
    # LastEnabled - property
1146
1044
    @dbus_service_property(_interface, signature="s", access="read")
1147
1045
    def LastEnabled_dbus_property(self):
1148
 
        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))
1149
1049
    
1150
1050
    # Enabled - property
1151
1051
    @dbus_service_property(_interface, signature="b",
1165
1065
        if value is not None:
1166
1066
            self.checked_ok()
1167
1067
            return
1168
 
        return datetime_to_dbus(self.last_checked_ok)
1169
 
    
1170
 
    # Expires - property
1171
 
    @dbus_service_property(_interface, signature="s", access="read")
1172
 
    def Expires_dbus_property(self):
1173
 
        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))
1174
1072
    
1175
1073
    # LastApprovalRequest - property
1176
1074
    @dbus_service_property(_interface, signature="s", access="read")
1177
1075
    def LastApprovalRequest_dbus_property(self):
1178
 
        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))
1179
1081
    
1180
1082
    # Timeout - property
1181
1083
    @dbus_service_property(_interface, signature="t",
1184
1086
        if value is None:       # get
1185
1087
            return dbus.UInt64(self.timeout_milliseconds())
1186
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))
1187
1092
        if getattr(self, "disable_initiator_tag", None) is None:
1188
1093
            return
1189
1094
        # Reschedule timeout
1190
1095
        gobject.source_remove(self.disable_initiator_tag)
1191
1096
        self.disable_initiator_tag = None
1192
 
        self.expires = None
1193
 
        time_to_die = _timedelta_to_milliseconds((self
1194
 
                                                  .last_checked_ok
1195
 
                                                  + self.timeout)
1196
 
                                                 - datetime.datetime
1197
 
                                                 .utcnow())
 
1097
        time_to_die = (self.
 
1098
                       _timedelta_to_milliseconds((self
 
1099
                                                   .last_checked_ok
 
1100
                                                   + self.timeout)
 
1101
                                                  - datetime.datetime
 
1102
                                                  .utcnow()))
1198
1103
        if time_to_die <= 0:
1199
1104
            # The timeout has passed
1200
1105
            self.disable()
1201
1106
        else:
1202
 
            self.expires = (datetime.datetime.utcnow()
1203
 
                            + datetime.timedelta(milliseconds =
1204
 
                                                 time_to_die))
1205
1107
            self.disable_initiator_tag = (gobject.timeout_add
1206
1108
                                          (time_to_die, self.disable))
1207
1109
    
1208
 
    # ExtendedTimeout - property
1209
 
    @dbus_service_property(_interface, signature="t",
1210
 
                           access="readwrite")
1211
 
    def ExtendedTimeout_dbus_property(self, value=None):
1212
 
        if value is None:       # get
1213
 
            return dbus.UInt64(self.extended_timeout_milliseconds())
1214
 
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1215
 
    
1216
1110
    # Interval - property
1217
1111
    @dbus_service_property(_interface, signature="t",
1218
1112
                           access="readwrite")
1220
1114
        if value is None:       # get
1221
1115
            return dbus.UInt64(self.interval_milliseconds())
1222
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))
1223
1120
        if getattr(self, "checker_initiator_tag", None) is None:
1224
1121
            return
1225
1122
        # Reschedule checker run
1227
1124
        self.checker_initiator_tag = (gobject.timeout_add
1228
1125
                                      (value, self.start_checker))
1229
1126
        self.start_checker()    # Start one now, too
1230
 
    
 
1127
 
1231
1128
    # Checker - property
1232
1129
    @dbus_service_property(_interface, signature="s",
1233
1130
                           access="readwrite")
1235
1132
        if value is None:       # get
1236
1133
            return dbus.String(self.checker_command)
1237
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))
1238
1139
    
1239
1140
    # CheckerRunning - property
1240
1141
    @dbus_service_property(_interface, signature="b",
1267
1168
        self._pipe.send(('init', fpr, address))
1268
1169
        if not self._pipe.recv():
1269
1170
            raise KeyError()
1270
 
    
 
1171
 
1271
1172
    def __getattribute__(self, name):
1272
1173
        if(name == '_pipe'):
1273
1174
            return super(ProxyClient, self).__getattribute__(name)
1280
1181
                self._pipe.send(('funcall', name, args, kwargs))
1281
1182
                return self._pipe.recv()[1]
1282
1183
            return func
1283
 
    
 
1184
 
1284
1185
    def __setattr__(self, name, value):
1285
1186
        if(name == '_pipe'):
1286
1187
            return super(ProxyClient, self).__setattr__(name, value)
1287
1188
        self._pipe.send(('setattr', name, value))
1288
1189
 
1289
 
class ClientDBusTransitional(ClientDBus):
1290
 
    __metaclass__ = AlternateDBusNamesMetaclass
1291
1190
 
1292
1191
class ClientHandler(socketserver.BaseRequestHandler, object):
1293
1192
    """A class to handle client connections.
1301
1200
                        unicode(self.client_address))
1302
1201
            logger.debug("Pipe FD: %d",
1303
1202
                         self.server.child_pipe.fileno())
1304
 
            
 
1203
 
1305
1204
            session = (gnutls.connection
1306
1205
                       .ClientSession(self.request,
1307
1206
                                      gnutls.connection
1308
1207
                                      .X509Credentials()))
1309
 
            
 
1208
 
1310
1209
            # Note: gnutls.connection.X509Credentials is really a
1311
1210
            # generic GnuTLS certificate credentials object so long as
1312
1211
            # no X.509 keys are added to it.  Therefore, we can use it
1313
1212
            # here despite using OpenPGP certificates.
1314
 
            
 
1213
 
1315
1214
            #priority = ':'.join(("NONE", "+VERS-TLS1.1",
1316
1215
            #                      "+AES-256-CBC", "+SHA1",
1317
1216
            #                      "+COMP-NULL", "+CTYPE-OPENPGP",
1323
1222
            (gnutls.library.functions
1324
1223
             .gnutls_priority_set_direct(session._c_object,
1325
1224
                                         priority, None))
1326
 
            
 
1225
 
1327
1226
            # Start communication using the Mandos protocol
1328
1227
            # Get protocol number
1329
1228
            line = self.request.makefile().readline()
1334
1233
            except (ValueError, IndexError, RuntimeError) as error:
1335
1234
                logger.error("Unknown protocol version: %s", error)
1336
1235
                return
1337
 
            
 
1236
 
1338
1237
            # Start GnuTLS connection
1339
1238
            try:
1340
1239
                session.handshake()
1344
1243
                # established.  Just abandon the request.
1345
1244
                return
1346
1245
            logger.debug("Handshake succeeded")
1347
 
            
 
1246
 
1348
1247
            approval_required = False
1349
1248
            try:
1350
1249
                try:
1355
1254
                    logger.warning("Bad certificate: %s", error)
1356
1255
                    return
1357
1256
                logger.debug("Fingerprint: %s", fpr)
1358
 
                
 
1257
 
1359
1258
                try:
1360
1259
                    client = ProxyClient(child_pipe, fpr,
1361
1260
                                         self.client_address)
1373
1272
                                       client.name)
1374
1273
                        if self.server.use_dbus:
1375
1274
                            # Emit D-Bus signal
1376
 
                            client.Rejected("Disabled")
 
1275
                            client.Rejected("Disabled")                    
1377
1276
                        return
1378
1277
                    
1379
1278
                    if client._approved or not client.approval_delay:
1396
1295
                        return
1397
1296
                    
1398
1297
                    #wait until timeout or approved
 
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
 
                # bump the timeout using extended_timeout
1434
 
                client.checked_ok(client.extended_timeout)
 
1331
                # bump the timeout as if seen
 
1332
                client.checked_ok()
1435
1333
                if self.server.use_dbus:
1436
1334
                    # Emit D-Bus signal
1437
1335
                    client.GotSecret()
1516
1414
        except:
1517
1415
            self.handle_error(request, address)
1518
1416
        self.close_request(request)
1519
 
    
 
1417
            
1520
1418
    def process_request(self, request, address):
1521
1419
        """Start a new process to process the request."""
1522
 
        proc = multiprocessing.Process(target = self.sub_process_main,
1523
 
                                       args = (request,
1524
 
                                               address))
1525
 
        proc.start()
1526
 
        return proc
1527
 
 
 
1420
        multiprocessing.Process(target = self.sub_process_main,
 
1421
                                args = (request, address)).start()
1528
1422
 
1529
1423
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1530
1424
    """ adds a pipe to the MixIn """
1534
1428
        This function creates a new pipe in self.pipe
1535
1429
        """
1536
1430
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
1537
 
        
1538
 
        proc = MultiprocessingMixIn.process_request(self, request,
1539
 
                                                    client_address)
 
1431
 
 
1432
        super(MultiprocessingMixInWithPipe,
 
1433
              self).process_request(request, client_address)
1540
1434
        self.child_pipe.close()
1541
 
        self.add_pipe(parent_pipe, proc)
1542
 
    
1543
 
    def add_pipe(self, parent_pipe, proc):
 
1435
        self.add_pipe(parent_pipe)
 
1436
 
 
1437
    def add_pipe(self, parent_pipe):
1544
1438
        """Dummy function; override as necessary"""
1545
1439
        raise NotImplementedError
1546
1440
 
1547
 
 
1548
1441
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1549
1442
                     socketserver.TCPServer, object):
1550
1443
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1634
1527
    def server_activate(self):
1635
1528
        if self.enabled:
1636
1529
            return socketserver.TCPServer.server_activate(self)
1637
 
    
1638
1530
    def enable(self):
1639
1531
        self.enabled = True
1640
 
    
1641
 
    def add_pipe(self, parent_pipe, proc):
 
1532
    def add_pipe(self, parent_pipe):
1642
1533
        # Call "handle_ipc" for both data and EOF events
1643
1534
        gobject.io_add_watch(parent_pipe.fileno(),
1644
1535
                             gobject.IO_IN | gobject.IO_HUP,
1645
1536
                             functools.partial(self.handle_ipc,
1646
 
                                               parent_pipe =
1647
 
                                               parent_pipe,
1648
 
                                               proc = proc))
1649
 
    
 
1537
                                               parent_pipe = parent_pipe))
 
1538
        
1650
1539
    def handle_ipc(self, source, condition, parent_pipe=None,
1651
 
                   proc = None, client_object=None):
 
1540
                   client_object=None):
1652
1541
        condition_names = {
1653
1542
            gobject.IO_IN: "IN",   # There is data to read.
1654
1543
            gobject.IO_OUT: "OUT", # Data can be written (without
1663
1552
                                       for cond, name in
1664
1553
                                       condition_names.iteritems()
1665
1554
                                       if cond & condition)
1666
 
        # error, or the other end of multiprocessing.Pipe has closed
 
1555
        # error or the other end of multiprocessing.Pipe has closed
1667
1556
        if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1668
 
            # Wait for other process to exit
1669
 
            proc.join()
1670
1557
            return False
1671
1558
        
1672
1559
        # Read a request from the child
1686
1573
                            "dress: %s", fpr, address)
1687
1574
                if self.use_dbus:
1688
1575
                    # Emit D-Bus signal
1689
 
                    mandos_dbus_service.ClientNotFound(fpr,
1690
 
                                                       address[0])
 
1576
                    mandos_dbus_service.ClientNotFound(fpr, address[0])
1691
1577
                parent_pipe.send(False)
1692
1578
                return False
1693
1579
            
1694
1580
            gobject.io_add_watch(parent_pipe.fileno(),
1695
1581
                                 gobject.IO_IN | gobject.IO_HUP,
1696
1582
                                 functools.partial(self.handle_ipc,
1697
 
                                                   parent_pipe =
1698
 
                                                   parent_pipe,
1699
 
                                                   proc = proc,
1700
 
                                                   client_object =
1701
 
                                                   client))
 
1583
                                                   parent_pipe = parent_pipe,
 
1584
                                                   client_object = client))
1702
1585
            parent_pipe.send(True)
1703
 
            # remove the old hook in favor of the new above hook on
1704
 
            # same fileno
 
1586
            # remove the old hook in favor of the new above hook on same fileno
1705
1587
            return False
1706
1588
        if command == 'funcall':
1707
1589
            funcname = request[1]
1708
1590
            args = request[2]
1709
1591
            kwargs = request[3]
1710
1592
            
1711
 
            parent_pipe.send(('data', getattr(client_object,
1712
 
                                              funcname)(*args,
1713
 
                                                         **kwargs)))
1714
 
        
 
1593
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
 
1594
 
1715
1595
        if command == 'getattr':
1716
1596
            attrname = request[1]
1717
1597
            if callable(client_object.__getattribute__(attrname)):
1718
1598
                parent_pipe.send(('function',))
1719
1599
            else:
1720
 
                parent_pipe.send(('data', client_object
1721
 
                                  .__getattribute__(attrname)))
 
1600
                parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1722
1601
        
1723
1602
        if command == 'setattr':
1724
1603
            attrname = request[1]
1725
1604
            value = request[2]
1726
1605
            setattr(client_object, attrname, value)
1727
 
        
 
1606
 
1728
1607
        return True
1729
1608
 
1730
1609
 
1909
1788
    debuglevel = server_settings["debuglevel"]
1910
1789
    use_dbus = server_settings["use_dbus"]
1911
1790
    use_ipv6 = server_settings["use_ipv6"]
1912
 
    
 
1791
 
1913
1792
    if server_settings["servicename"] != "Mandos":
1914
1793
        syslogger.setFormatter(logging.Formatter
1915
1794
                               ('Mandos (%s) [%%(process)d]:'
1917
1796
                                % server_settings["servicename"]))
1918
1797
    
1919
1798
    # Parse config file with clients
1920
 
    client_defaults = { "timeout": "5m",
1921
 
                        "extended_timeout": "15m",
1922
 
                        "interval": "2m",
 
1799
    client_defaults = { "timeout": "1h",
 
1800
                        "interval": "5m",
1923
1801
                        "checker": "fping -q -- %%(host)s",
1924
1802
                        "host": "",
1925
1803
                        "approval_delay": "0s",
1976
1854
        level = getattr(logging, debuglevel.upper())
1977
1855
        syslogger.setLevel(level)
1978
1856
        console.setLevel(level)
1979
 
    
 
1857
 
1980
1858
    if debug:
1981
1859
        # Enable all possible GnuTLS debugging
1982
1860
        
2013
1891
    # End of Avahi example code
2014
1892
    if use_dbus:
2015
1893
        try:
2016
 
            bus_name = dbus.service.BusName("se.recompile.Mandos",
 
1894
            bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
2017
1895
                                            bus, do_not_queue=True)
2018
 
            old_bus_name = (dbus.service.BusName
2019
 
                            ("se.bsnet.fukt.Mandos", bus,
2020
 
                             do_not_queue=True))
2021
1896
        except dbus.exceptions.NameExistsException as e:
2022
1897
            logger.error(unicode(e) + ", disabling D-Bus")
2023
1898
            use_dbus = False
2036
1911
    
2037
1912
    client_class = Client
2038
1913
    if use_dbus:
2039
 
        client_class = functools.partial(ClientDBusTransitional,
2040
 
                                         bus = bus)
 
1914
        client_class = functools.partial(ClientDBus, bus = bus)
2041
1915
    def client_config_items(config, section):
2042
1916
        special_settings = {
2043
1917
            "approved_by_default":
2073
1947
        del pidfilename
2074
1948
        
2075
1949
        signal.signal(signal.SIGINT, signal.SIG_IGN)
2076
 
    
 
1950
 
2077
1951
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2078
1952
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2079
1953
    
2082
1956
            """A D-Bus proxy object"""
2083
1957
            def __init__(self):
2084
1958
                dbus.service.Object.__init__(self, bus, "/")
2085
 
            _interface = "se.recompile.Mandos"
 
1959
            _interface = "se.bsnet.fukt.Mandos"
2086
1960
            
2087
1961
            @dbus.service.signal(_interface, signature="o")
2088
1962
            def ClientAdded(self, objpath):
2130
2004
            
2131
2005
            del _interface
2132
2006
        
2133
 
        class MandosDBusServiceTransitional(MandosDBusService):
2134
 
            __metaclass__ = AlternateDBusNamesMetaclass
2135
 
        mandos_dbus_service = MandosDBusServiceTransitional()
 
2007
        mandos_dbus_service = MandosDBusService()
2136
2008
    
2137
2009
    def cleanup():
2138
2010
        "Cleanup function; run on exit"
2139
2011
        service.cleanup()
2140
2012
        
2141
 
        multiprocessing.active_children()
2142
2013
        while tcp_server.clients:
2143
2014
            client = tcp_server.clients.pop()
2144
2015
            if use_dbus:
2148
2019
            client.disable(quiet=True)
2149
2020
            if use_dbus:
2150
2021
                # Emit D-Bus signal
2151
 
                mandos_dbus_service.ClientRemoved(client
2152
 
                                                  .dbus_object_path,
 
2022
                mandos_dbus_service.ClientRemoved(client.dbus_object_path,
2153
2023
                                                  client.name)
2154
2024
    
2155
2025
    atexit.register(cleanup)
2204
2074
    # Must run before the D-Bus bus name gets deregistered
2205
2075
    cleanup()
2206
2076
 
2207
 
 
2208
2077
if __name__ == '__main__':
2209
2078
    main()