/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

* mandos-keygen: Loop until passwords match when run interactively.

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
 
        
273
267
class Client(object):
274
268
    """A representation of a client host served by this server.
275
269
    
303
297
    secret:     bytestring; sent verbatim (over TLS) to client
304
298
    timeout:    datetime.timedelta(); How long from last_checked_ok
305
299
                                      until this client is disabled
306
 
    extended_timeout:   extra long timeout when password has been sent
307
300
    runtime_expansions: Allowed attributes for runtime expansion.
308
 
    expires:    datetime.datetime(); time (UTC) when a client will be
309
 
                disabled, or None
310
301
    """
311
302
    
312
303
    runtime_expansions = ("approval_delay", "approval_duration",
313
304
                          "created", "enabled", "fingerprint",
314
305
                          "host", "interval", "last_checked_ok",
315
306
                          "last_enabled", "name", "timeout")
316
 
        
 
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
    
317
315
    def timeout_milliseconds(self):
318
316
        "Return the 'timeout' attribute in milliseconds"
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)    
 
317
        return self._timedelta_to_milliseconds(self.timeout)
324
318
    
325
319
    def interval_milliseconds(self):
326
320
        "Return the 'interval' attribute in milliseconds"
327
 
        return _timedelta_to_milliseconds(self.interval)
 
321
        return self._timedelta_to_milliseconds(self.interval)
328
322
 
329
323
    def approval_delay_milliseconds(self):
330
 
        return _timedelta_to_milliseconds(self.approval_delay)
 
324
        return self._timedelta_to_milliseconds(self.approval_delay)
331
325
    
332
326
    def __init__(self, name = None, disable_hook=None, config=None):
333
327
        """Note: the 'checker' key in 'config' sets the
360
354
        self.last_enabled = None
361
355
        self.last_checked_ok = None
362
356
        self.timeout = string_to_delta(config["timeout"])
363
 
        self.extended_timeout = string_to_delta(config["extended_timeout"])
364
357
        self.interval = string_to_delta(config["interval"])
365
358
        self.disable_hook = disable_hook
366
359
        self.checker = None
367
360
        self.checker_initiator_tag = None
368
361
        self.disable_initiator_tag = None
369
 
        self.expires = None
370
362
        self.checker_callback_tag = None
371
363
        self.checker_command = config["checker"]
372
364
        self.current_checker_command = None
392
384
            # Already enabled
393
385
            return
394
386
        self.send_changedstate()
 
387
        self.last_enabled = datetime.datetime.utcnow()
395
388
        # Schedule a new checker to be started an 'interval' from now,
396
389
        # and every interval from then on.
397
390
        self.checker_initiator_tag = (gobject.timeout_add
398
391
                                      (self.interval_milliseconds(),
399
392
                                       self.start_checker))
400
393
        # Schedule a disable() when 'timeout' has passed
401
 
        self.expires = datetime.datetime.utcnow() + self.timeout
402
394
        self.disable_initiator_tag = (gobject.timeout_add
403
395
                                   (self.timeout_milliseconds(),
404
396
                                    self.disable))
405
397
        self.enabled = True
406
 
        self.last_enabled = datetime.datetime.utcnow()
407
398
        # Also start a new checker *right now*.
408
399
        self.start_checker()
409
400
    
418
409
        if getattr(self, "disable_initiator_tag", False):
419
410
            gobject.source_remove(self.disable_initiator_tag)
420
411
            self.disable_initiator_tag = None
421
 
        self.expires = None
422
412
        if getattr(self, "checker_initiator_tag", False):
423
413
            gobject.source_remove(self.checker_initiator_tag)
424
414
            self.checker_initiator_tag = None
450
440
            logger.warning("Checker for %(name)s crashed?",
451
441
                           vars(self))
452
442
    
453
 
    def checked_ok(self, timeout=None):
 
443
    def checked_ok(self):
454
444
        """Bump up the timeout for this client.
455
445
        
456
446
        This should only be called when the client has been seen,
457
447
        alive and well.
458
448
        """
459
 
        if timeout is None:
460
 
            timeout = self.timeout
461
449
        self.last_checked_ok = datetime.datetime.utcnow()
462
450
        gobject.source_remove(self.disable_initiator_tag)
463
 
        self.expires = datetime.datetime.utcnow() + timeout
464
451
        self.disable_initiator_tag = (gobject.timeout_add
465
 
                                      (_timedelta_to_milliseconds(timeout),
 
452
                                      (self.timeout_milliseconds(),
466
453
                                       self.disable))
467
454
    
468
455
    def need_approval(self):
750
737
        return xmlstring
751
738
 
752
739
 
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
 
 
760
740
class ClientDBus(Client, DBusObjectWithProperties):
761
741
    """A Client class using D-Bus
762
742
    
784
764
        DBusObjectWithProperties.__init__(self, self.bus,
785
765
                                          self.dbus_object_path)
786
766
        
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
 
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
842
810
    
843
811
    def __del__(self, *args, **kwargs):
844
812
        try:
853
821
                         *args, **kwargs):
854
822
        self.checker_callback_tag = None
855
823
        self.checker = None
 
824
        # Emit D-Bus signal
 
825
        self.PropertyChanged(dbus.String("CheckerRunning"),
 
826
                             dbus.Boolean(False, variant_level=1))
856
827
        if os.WIFEXITED(condition):
857
828
            exitstatus = os.WEXITSTATUS(condition)
858
829
            # Emit D-Bus signal
867
838
        
868
839
        return Client.checker_callback(self, pid, condition, command,
869
840
                                       *args, **kwargs)
870
 
 
 
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
    
871
859
    def start_checker(self, *args, **kwargs):
872
860
        old_checker = self.checker
873
861
        if self.checker is not None:
880
868
            and old_checker_pid != self.checker.pid):
881
869
            # Emit D-Bus signal
882
870
            self.CheckerStarted(self.current_checker_command)
 
871
            self.PropertyChanged(
 
872
                dbus.String("CheckerRunning"),
 
873
                dbus.Boolean(True, variant_level=1))
883
874
        return r
884
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
 
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(_timedelta_to_milliseconds
 
892
        gobject.timeout_add(self._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))
990
993
    
991
994
    # ApprovalDelay - property
992
995
    @dbus_service_property(_interface, signature="t",
995
998
        if value is None:       # get
996
999
            return dbus.UInt64(self.approval_delay_milliseconds())
997
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))
998
1004
    
999
1005
    # ApprovalDuration - property
1000
1006
    @dbus_service_property(_interface, signature="t",
1001
1007
                           access="readwrite")
1002
1008
    def ApprovalDuration_dbus_property(self, value=None):
1003
1009
        if value is None:       # get
1004
 
            return dbus.UInt64(_timedelta_to_milliseconds(
 
1010
            return dbus.UInt64(self._timedelta_to_milliseconds(
1005
1011
                    self.approval_duration))
1006
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))
1007
1016
    
1008
1017
    # Name - property
1009
1018
    @dbus_service_property(_interface, signature="s", access="read")
1022
1031
        if value is None:       # get
1023
1032
            return dbus.String(self.host)
1024
1033
        self.host = value
 
1034
        # Emit D-Bus signal
 
1035
        self.PropertyChanged(dbus.String("Host"),
 
1036
                             dbus.String(value, variant_level=1))
1025
1037
    
1026
1038
    # Created - property
1027
1039
    @dbus_service_property(_interface, signature="s", access="read")
1028
1040
    def Created_dbus_property(self):
1029
 
        return dbus.String(datetime_to_dbus(self.created))
 
1041
        return dbus.String(self._datetime_to_dbus(self.created))
1030
1042
    
1031
1043
    # LastEnabled - property
1032
1044
    @dbus_service_property(_interface, signature="s", access="read")
1033
1045
    def LastEnabled_dbus_property(self):
1034
 
        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))
1035
1049
    
1036
1050
    # Enabled - property
1037
1051
    @dbus_service_property(_interface, signature="b",
1051
1065
        if value is not None:
1052
1066
            self.checked_ok()
1053
1067
            return
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)
 
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))
1060
1072
    
1061
1073
    # LastApprovalRequest - property
1062
1074
    @dbus_service_property(_interface, signature="s", access="read")
1063
1075
    def LastApprovalRequest_dbus_property(self):
1064
 
        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))
1065
1081
    
1066
1082
    # Timeout - property
1067
1083
    @dbus_service_property(_interface, signature="t",
1070
1086
        if value is None:       # get
1071
1087
            return dbus.UInt64(self.timeout_milliseconds())
1072
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))
1073
1092
        if getattr(self, "disable_initiator_tag", None) is None:
1074
1093
            return
1075
1094
        # Reschedule timeout
1076
1095
        gobject.source_remove(self.disable_initiator_tag)
1077
1096
        self.disable_initiator_tag = None
1078
 
        self.expires = None
1079
1097
        time_to_die = (self.
1080
1098
                       _timedelta_to_milliseconds((self
1081
1099
                                                   .last_checked_ok
1086
1104
            # The timeout has passed
1087
1105
            self.disable()
1088
1106
        else:
1089
 
            self.expires = (datetime.datetime.utcnow()
1090
 
                            + datetime.timedelta(milliseconds = time_to_die))
1091
1107
            self.disable_initiator_tag = (gobject.timeout_add
1092
1108
                                          (time_to_die, self.disable))
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
 
 
 
1109
    
1102
1110
    # Interval - property
1103
1111
    @dbus_service_property(_interface, signature="t",
1104
1112
                           access="readwrite")
1106
1114
        if value is None:       # get
1107
1115
            return dbus.UInt64(self.interval_milliseconds())
1108
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))
1109
1120
        if getattr(self, "checker_initiator_tag", None) is None:
1110
1121
            return
1111
1122
        # Reschedule checker run
1121
1132
        if value is None:       # get
1122
1133
            return dbus.String(self.checker_command)
1123
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))
1124
1139
    
1125
1140
    # CheckerRunning - property
1126
1141
    @dbus_service_property(_interface, signature="b",
1314
1329
 
1315
1330
                logger.info("Sending secret to %s", client.name)
1316
1331
                # bump the timeout as if seen
1317
 
                client.checked_ok(client.extended_timeout)
 
1332
                client.checked_ok()
1318
1333
                if self.server.use_dbus:
1319
1334
                    # Emit D-Bus signal
1320
1335
                    client.GotSecret()
1781
1796
                                % server_settings["servicename"]))
1782
1797
    
1783
1798
    # Parse config file with clients
1784
 
    client_defaults = { "timeout": "5m",
1785
 
                        "extended_timeout": "15m",
1786
 
                        "interval": "2m",
 
1799
    client_defaults = { "timeout": "1h",
 
1800
                        "interval": "5m",
1787
1801
                        "checker": "fping -q -- %%(host)s",
1788
1802
                        "host": "",
1789
1803
                        "approval_delay": "0s",