/mandos/release

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Björn Påhlsson
  • Date: 2011-09-26 18:47:38 UTC
  • mto: (237.7.50 mandos)
  • mto: This revision was merged to the branch mainline in revision 286.
  • Revision ID: belorn@fukt.bsnet.se-20110926184738-ee8kz5vc9pb3393m
updated TODO

Show diffs side-by-side

added added

removed removed

Lines of Context:
264
264
        self.server_state_changed(self.server.GetState())
265
265
 
266
266
 
 
267
def _timedelta_to_milliseconds(td):
 
268
    "Convert a datetime.timedelta() to milliseconds"
 
269
    return ((td.days * 24 * 60 * 60 * 1000)
 
270
            + (td.seconds * 1000)
 
271
            + (td.microseconds // 1000))
 
272
        
267
273
class Client(object):
268
274
    """A representation of a client host served by this server.
269
275
    
297
303
    secret:     bytestring; sent verbatim (over TLS) to client
298
304
    timeout:    datetime.timedelta(); How long from last_checked_ok
299
305
                                      until this client is disabled
 
306
    extended_timeout:   extra long timeout when password has been sent
300
307
    runtime_expansions: Allowed attributes for runtime expansion.
 
308
    expires:    datetime.datetime(); time (UTC) when a client will be
 
309
                disabled, or None
301
310
    """
302
311
    
303
312
    runtime_expansions = ("approval_delay", "approval_duration",
304
313
                          "created", "enabled", "fingerprint",
305
314
                          "host", "interval", "last_checked_ok",
306
315
                          "last_enabled", "name", "timeout")
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
 
    
 
316
        
315
317
    def timeout_milliseconds(self):
316
318
        "Return the 'timeout' attribute in milliseconds"
317
 
        return self._timedelta_to_milliseconds(self.timeout)
 
319
        return _timedelta_to_milliseconds(self.timeout)
 
320
 
 
321
    def extended_timeout_milliseconds(self):
 
322
        "Return the 'extended_timeout' attribute in milliseconds"
 
323
        return _timedelta_to_milliseconds(self.extended_timeout)    
318
324
    
319
325
    def interval_milliseconds(self):
320
326
        "Return the 'interval' attribute in milliseconds"
321
 
        return self._timedelta_to_milliseconds(self.interval)
 
327
        return _timedelta_to_milliseconds(self.interval)
322
328
 
323
329
    def approval_delay_milliseconds(self):
324
 
        return self._timedelta_to_milliseconds(self.approval_delay)
 
330
        return _timedelta_to_milliseconds(self.approval_delay)
325
331
    
326
332
    def __init__(self, name = None, disable_hook=None, config=None):
327
333
        """Note: the 'checker' key in 'config' sets the
354
360
        self.last_enabled = None
355
361
        self.last_checked_ok = None
356
362
        self.timeout = string_to_delta(config["timeout"])
 
363
        self.extended_timeout = string_to_delta(config["extended_timeout"])
357
364
        self.interval = string_to_delta(config["interval"])
358
365
        self.disable_hook = disable_hook
359
366
        self.checker = None
360
367
        self.checker_initiator_tag = None
361
368
        self.disable_initiator_tag = None
 
369
        self.expires = None
362
370
        self.checker_callback_tag = None
363
371
        self.checker_command = config["checker"]
364
372
        self.current_checker_command = None
384
392
            # Already enabled
385
393
            return
386
394
        self.send_changedstate()
387
 
        self.last_enabled = datetime.datetime.utcnow()
388
395
        # Schedule a new checker to be started an 'interval' from now,
389
396
        # and every interval from then on.
390
397
        self.checker_initiator_tag = (gobject.timeout_add
391
398
                                      (self.interval_milliseconds(),
392
399
                                       self.start_checker))
393
400
        # Schedule a disable() when 'timeout' has passed
 
401
        self.expires = datetime.datetime.utcnow() + self.timeout
394
402
        self.disable_initiator_tag = (gobject.timeout_add
395
403
                                   (self.timeout_milliseconds(),
396
404
                                    self.disable))
397
405
        self.enabled = True
 
406
        self.last_enabled = datetime.datetime.utcnow()
398
407
        # Also start a new checker *right now*.
399
408
        self.start_checker()
400
409
    
409
418
        if getattr(self, "disable_initiator_tag", False):
410
419
            gobject.source_remove(self.disable_initiator_tag)
411
420
            self.disable_initiator_tag = None
 
421
        self.expires = None
412
422
        if getattr(self, "checker_initiator_tag", False):
413
423
            gobject.source_remove(self.checker_initiator_tag)
414
424
            self.checker_initiator_tag = None
440
450
            logger.warning("Checker for %(name)s crashed?",
441
451
                           vars(self))
442
452
    
443
 
    def checked_ok(self):
 
453
    def checked_ok(self, timeout=None):
444
454
        """Bump up the timeout for this client.
445
455
        
446
456
        This should only be called when the client has been seen,
447
457
        alive and well.
448
458
        """
 
459
        if timeout is None:
 
460
            timeout = self.timeout
449
461
        self.last_checked_ok = datetime.datetime.utcnow()
450
462
        gobject.source_remove(self.disable_initiator_tag)
 
463
        self.expires = datetime.datetime.utcnow() + timeout
451
464
        self.disable_initiator_tag = (gobject.timeout_add
452
 
                                      (self.timeout_milliseconds(),
 
465
                                      (_timedelta_to_milliseconds(timeout),
453
466
                                       self.disable))
454
467
    
455
468
    def need_approval(self):
737
750
        return xmlstring
738
751
 
739
752
 
 
753
def datetime_to_dbus (dt, variant_level=0):
 
754
    """Convert a UTC datetime.datetime() to a D-Bus type."""
 
755
    if dt is None:
 
756
        return dbus.String("", variant_level = variant_level)
 
757
    return dbus.String(dt.isoformat(),
 
758
                       variant_level=variant_level)
 
759
 
740
760
class ClientDBus(Client, DBusObjectWithProperties):
741
761
    """A Client class using D-Bus
742
762
    
764
784
        DBusObjectWithProperties.__init__(self, self.bus,
765
785
                                          self.dbus_object_path)
766
786
        
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)
778
 
 
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
 
787
    def notifychangeproperty(transform_func,
 
788
                             dbus_name, type_func=lambda x: x,
 
789
                             variant_level=1):
 
790
        """ Modify a variable so that its a property that announce its
 
791
        changes to DBus.
 
792
        transform_fun: Function that takes a value and transform it to
 
793
                       DBus type.
 
794
        dbus_name: DBus name of the variable
 
795
        type_func: Function that transform the value before sending it
 
796
                   to DBus
 
797
        variant_level: DBus variant level. default: 1
 
798
        """
 
799
        real_value = [None,]
 
800
        def setter(self, value):
 
801
            old_value = real_value[0]
 
802
            real_value[0] = value
 
803
            if hasattr(self, "dbus_object_path"):
 
804
                if type_func(old_value) != type_func(real_value[0]):
 
805
                    dbus_value = transform_func(type_func(real_value[0]),
 
806
                                                variant_level)
 
807
                    self.PropertyChanged(dbus.String(dbus_name),
 
808
                                         dbus_value)
 
809
 
 
810
        return property(lambda self: real_value[0], setter)
 
811
 
 
812
 
 
813
    expires = notifychangeproperty(datetime_to_dbus, "Expires")
 
814
    approvals_pending = notifychangeproperty(dbus.Boolean,
 
815
                                             "ApprovalPending",
 
816
                                             type_func = bool)
 
817
    enabled = notifychangeproperty(dbus.Boolean, "Enabled")
 
818
    last_enabled = notifychangeproperty(datetime_to_dbus,
 
819
                                        "LastEnabled")
 
820
    checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
 
821
                                   type_func = lambda checker: checker is not None)
 
822
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
 
823
                                           "LastCheckedOK")
 
824
    last_approval_request = notifychangeproperty(datetime_to_dbus,
 
825
                                                 "LastApprovalRequest")
 
826
    approved_by_default = notifychangeproperty(dbus.Boolean,
 
827
                                               "ApprovedByDefault")
 
828
    approval_delay = notifychangeproperty(dbus.UInt16, "ApprovalDelay",
 
829
                                          type_func = _timedelta_to_milliseconds)
 
830
    approval_duration = notifychangeproperty(dbus.UInt16, "ApprovalDuration",
 
831
                                             type_func = _timedelta_to_milliseconds)
 
832
    host = notifychangeproperty(dbus.String, "Host")
 
833
    timeout = notifychangeproperty(dbus.UInt16, "Timeout",
 
834
                                   type_func = _timedelta_to_milliseconds)
 
835
    extended_timeout = notifychangeproperty(dbus.UInt16, "ExtendedTimeout",
 
836
                                            type_func = _timedelta_to_milliseconds)
 
837
    interval = notifychangeproperty(dbus.UInt16, "Interval",
 
838
                                    type_func = _timedelta_to_milliseconds)
 
839
    checker_command = notifychangeproperty(dbus.String, "Checker")
 
840
    
 
841
    del notifychangeproperty
810
842
    
811
843
    def __del__(self, *args, **kwargs):
812
844
        try:
821
853
                         *args, **kwargs):
822
854
        self.checker_callback_tag = None
823
855
        self.checker = None
824
 
        # Emit D-Bus signal
825
 
        self.PropertyChanged(dbus.String("CheckerRunning"),
826
 
                             dbus.Boolean(False, variant_level=1))
827
856
        if os.WIFEXITED(condition):
828
857
            exitstatus = os.WEXITSTATUS(condition)
829
858
            # Emit D-Bus signal
838
867
        
839
868
        return Client.checker_callback(self, pid, condition, command,
840
869
                                       *args, **kwargs)
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
 
    
 
870
 
859
871
    def start_checker(self, *args, **kwargs):
860
872
        old_checker = self.checker
861
873
        if self.checker is not None:
868
880
            and old_checker_pid != self.checker.pid):
869
881
            # Emit D-Bus signal
870
882
            self.CheckerStarted(self.current_checker_command)
871
 
            self.PropertyChanged(
872
 
                dbus.String("CheckerRunning"),
873
 
                dbus.Boolean(True, variant_level=1))
874
883
        return r
875
884
    
876
 
    def stop_checker(self, *args, **kwargs):
877
 
        old_checker = getattr(self, "checker", None)
878
 
        r = Client.stop_checker(self, *args, **kwargs)
879
 
        if (old_checker is not None
880
 
            and getattr(self, "checker", None) is None):
881
 
            self.PropertyChanged(dbus.String("CheckerRunning"),
882
 
                                 dbus.Boolean(False, variant_level=1))
883
 
        return r
884
 
 
885
885
    def _reset_approved(self):
886
886
        self._approved = None
887
887
        return False
889
889
    def approve(self, value=True):
890
890
        self.send_changedstate()
891
891
        self._approved = value
892
 
        gobject.timeout_add(self._timedelta_to_milliseconds
 
892
        gobject.timeout_add(_timedelta_to_milliseconds
893
893
                            (self.approval_duration),
894
894
                            self._reset_approved)
895
895
    
987
987
        if value is None:       # get
988
988
            return dbus.Boolean(self.approved_by_default)
989
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))
993
990
    
994
991
    # ApprovalDelay - property
995
992
    @dbus_service_property(_interface, signature="t",
998
995
        if value is None:       # get
999
996
            return dbus.UInt64(self.approval_delay_milliseconds())
1000
997
        self.approval_delay = datetime.timedelta(0, 0, 0, value)
1001
 
        # Emit D-Bus signal
1002
 
        self.PropertyChanged(dbus.String("ApprovalDelay"),
1003
 
                             dbus.UInt64(value, variant_level=1))
1004
998
    
1005
999
    # ApprovalDuration - property
1006
1000
    @dbus_service_property(_interface, signature="t",
1007
1001
                           access="readwrite")
1008
1002
    def ApprovalDuration_dbus_property(self, value=None):
1009
1003
        if value is None:       # get
1010
 
            return dbus.UInt64(self._timedelta_to_milliseconds(
 
1004
            return dbus.UInt64(_timedelta_to_milliseconds(
1011
1005
                    self.approval_duration))
1012
1006
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
1013
 
        # Emit D-Bus signal
1014
 
        self.PropertyChanged(dbus.String("ApprovalDuration"),
1015
 
                             dbus.UInt64(value, variant_level=1))
1016
1007
    
1017
1008
    # Name - property
1018
1009
    @dbus_service_property(_interface, signature="s", access="read")
1031
1022
        if value is None:       # get
1032
1023
            return dbus.String(self.host)
1033
1024
        self.host = value
1034
 
        # Emit D-Bus signal
1035
 
        self.PropertyChanged(dbus.String("Host"),
1036
 
                             dbus.String(value, variant_level=1))
1037
1025
    
1038
1026
    # Created - property
1039
1027
    @dbus_service_property(_interface, signature="s", access="read")
1040
1028
    def Created_dbus_property(self):
1041
 
        return dbus.String(self._datetime_to_dbus(self.created))
 
1029
        return dbus.String(datetime_to_dbus(self.created))
1042
1030
    
1043
1031
    # LastEnabled - property
1044
1032
    @dbus_service_property(_interface, signature="s", access="read")
1045
1033
    def LastEnabled_dbus_property(self):
1046
 
        if self.last_enabled is None:
1047
 
            return dbus.String("")
1048
 
        return dbus.String(self._datetime_to_dbus(self.last_enabled))
 
1034
        return datetime_to_dbus(self.last_enabled)
1049
1035
    
1050
1036
    # Enabled - property
1051
1037
    @dbus_service_property(_interface, signature="b",
1065
1051
        if value is not None:
1066
1052
            self.checked_ok()
1067
1053
            return
1068
 
        if self.last_checked_ok is None:
1069
 
            return dbus.String("")
1070
 
        return dbus.String(self._datetime_to_dbus(self
1071
 
                                                  .last_checked_ok))
 
1054
        return datetime_to_dbus(self.last_checked_ok)
 
1055
    
 
1056
    # Expires - property
 
1057
    @dbus_service_property(_interface, signature="s", access="read")
 
1058
    def Expires_dbus_property(self):
 
1059
        return datetime_to_dbus(self.expires)
1072
1060
    
1073
1061
    # LastApprovalRequest - property
1074
1062
    @dbus_service_property(_interface, signature="s", access="read")
1075
1063
    def LastApprovalRequest_dbus_property(self):
1076
 
        if self.last_approval_request is None:
1077
 
            return dbus.String("")
1078
 
        return dbus.String(self.
1079
 
                           _datetime_to_dbus(self
1080
 
                                             .last_approval_request))
 
1064
        return datetime_to_dbus(self.last_approval_request)
1081
1065
    
1082
1066
    # Timeout - property
1083
1067
    @dbus_service_property(_interface, signature="t",
1086
1070
        if value is None:       # get
1087
1071
            return dbus.UInt64(self.timeout_milliseconds())
1088
1072
        self.timeout = datetime.timedelta(0, 0, 0, value)
1089
 
        # Emit D-Bus signal
1090
 
        self.PropertyChanged(dbus.String("Timeout"),
1091
 
                             dbus.UInt64(value, variant_level=1))
1092
1073
        if getattr(self, "disable_initiator_tag", None) is None:
1093
1074
            return
1094
1075
        # Reschedule timeout
1095
1076
        gobject.source_remove(self.disable_initiator_tag)
1096
1077
        self.disable_initiator_tag = None
 
1078
        self.expires = None
1097
1079
        time_to_die = (self.
1098
1080
                       _timedelta_to_milliseconds((self
1099
1081
                                                   .last_checked_ok
1104
1086
            # The timeout has passed
1105
1087
            self.disable()
1106
1088
        else:
 
1089
            self.expires = (datetime.datetime.utcnow()
 
1090
                            + datetime.timedelta(milliseconds = time_to_die))
1107
1091
            self.disable_initiator_tag = (gobject.timeout_add
1108
1092
                                          (time_to_die, self.disable))
1109
 
    
 
1093
 
 
1094
    # ExtendedTimeout - property
 
1095
    @dbus_service_property(_interface, signature="t",
 
1096
                           access="readwrite")
 
1097
    def ExtendedTimeout_dbus_property(self, value=None):
 
1098
        if value is None:       # get
 
1099
            return dbus.UInt64(self.extended_timeout_milliseconds())
 
1100
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
 
1101
 
1110
1102
    # Interval - property
1111
1103
    @dbus_service_property(_interface, signature="t",
1112
1104
                           access="readwrite")
1114
1106
        if value is None:       # get
1115
1107
            return dbus.UInt64(self.interval_milliseconds())
1116
1108
        self.interval = datetime.timedelta(0, 0, 0, value)
1117
 
        # Emit D-Bus signal
1118
 
        self.PropertyChanged(dbus.String("Interval"),
1119
 
                             dbus.UInt64(value, variant_level=1))
1120
1109
        if getattr(self, "checker_initiator_tag", None) is None:
1121
1110
            return
1122
1111
        # Reschedule checker run
1132
1121
        if value is None:       # get
1133
1122
            return dbus.String(self.checker_command)
1134
1123
        self.checker_command = value
1135
 
        # Emit D-Bus signal
1136
 
        self.PropertyChanged(dbus.String("Checker"),
1137
 
                             dbus.String(self.checker_command,
1138
 
                                         variant_level=1))
1139
1124
    
1140
1125
    # CheckerRunning - property
1141
1126
    @dbus_service_property(_interface, signature="b",
1329
1314
 
1330
1315
                logger.info("Sending secret to %s", client.name)
1331
1316
                # bump the timeout as if seen
1332
 
                client.checked_ok()
 
1317
                client.checked_ok(client.extended_timeout)
1333
1318
                if self.server.use_dbus:
1334
1319
                    # Emit D-Bus signal
1335
1320
                    client.GotSecret()
1796
1781
                                % server_settings["servicename"]))
1797
1782
    
1798
1783
    # Parse config file with clients
1799
 
    client_defaults = { "timeout": "1h",
1800
 
                        "interval": "5m",
 
1784
    client_defaults = { "timeout": "5m",
 
1785
                        "extended_timeout": "15m",
 
1786
                        "interval": "2m",
1801
1787
                        "checker": "fping -q -- %%(host)s",
1802
1788
                        "host": "",
1803
1789
                        "approval_delay": "0s",