/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-ctl

  • Committer: Teddy Hogeborn
  • Date: 2019-03-09 23:55:43 UTC
  • Revision ID: teddy@recompile.se-20190309235543-g36rsz4ephch356t
mandos-ctl: Don't call D-Bus to get property for --is-enabled

* mandos-ctl (IsEnabledCmd.is_enabled): Check properties dict, don't
                                        use D-Bus.

Show diffs side-by-side

added added

removed removed

Lines of Context:
64
64
 
65
65
locale.setlocale(locale.LC_ALL, "")
66
66
 
67
 
dbus_busname_domain = "se.recompile"
68
 
dbus_busname = dbus_busname_domain + ".Mandos"
69
 
server_dbus_path = "/"
70
 
server_dbus_interface = dbus_busname_domain + ".Mandos"
71
 
client_dbus_interface = dbus_busname_domain + ".Mandos.Client"
72
 
del dbus_busname_domain
 
67
domain = "se.recompile"
 
68
busname = domain + ".Mandos"
 
69
server_path = "/"
 
70
server_interface = domain + ".Mandos"
 
71
client_interface = domain + ".Mandos.Client"
73
72
version = "1.8.3"
74
73
 
75
74
 
277
276
# Abstract classes first
278
277
class Command(object):
279
278
    """Abstract class for commands"""
280
 
    def run(self, clients, bus=None, mandos=None):
 
279
    def run(self, mandos, clients):
281
280
        """Normal commands should implement run_on_one_client(), but
282
281
        commands which want to operate on all clients at the same time
283
282
        can override this run() method instead."""
284
283
        self.mandos = mandos
285
 
        for clientpath, properties in clients.items():
286
 
            log.debug("D-Bus: Connect to: (busname=%r, path=%r)",
287
 
                      dbus_busname, str(clientpath))
288
 
            client = bus.get_object(dbus_busname, clientpath)
 
284
        for client, properties in clients.items():
289
285
            self.run_on_one_client(client, properties)
290
286
 
291
287
class PrintCmd(Command):
297
293
                    "LastApprovalRequest", "ApprovalDelay",
298
294
                    "ApprovalDuration", "Checker", "ExtendedTimeout",
299
295
                    "Expires", "LastCheckerStatus")
300
 
    def run(self, clients, bus=None, mandos=None):
 
296
    def run(self, mandos, clients):
301
297
        print(self.output(clients.values()))
302
298
    def output(self, clients):
303
299
        raise NotImplementedError()
306
302
    """Abstract class for Actions for setting one client property"""
307
303
    def run_on_one_client(self, client, properties):
308
304
        """Set the Client's D-Bus property"""
309
 
        log.debug("D-Bus: %s:%s:%s.Set(%r, %r, %r)", dbus_busname,
 
305
        log.debug("D-Bus: %s:%s:%s.Set(%r, %r, %r)", busname,
310
306
                  client.__dbus_object_path__,
311
 
                  dbus.PROPERTIES_IFACE, client_dbus_interface,
 
307
                  dbus.PROPERTIES_IFACE, client_interface,
312
308
                  self.propname, self.value_to_set
313
309
                  if not isinstance(self.value_to_set, dbus.Boolean)
314
310
                  else bool(self.value_to_set))
315
 
        client.Set(client_dbus_interface, self.propname,
316
 
                   self.value_to_set,
 
311
        client.Set(client_interface, self.propname, self.value_to_set,
317
312
                   dbus_interface=dbus.PROPERTIES_IFACE)
318
313
    @property
319
314
    def propname(self):
320
315
        raise NotImplementedError()
321
316
 
322
 
class PropertyValueCmd(PropertyCmd):
323
 
    """Abstract class for PropertyCmd recieving a value as argument"""
 
317
class ValueArgumentMixIn(object):
 
318
    """Mixin class for commands taking a value as argument"""
324
319
    def __init__(self, value):
325
320
        self.value_to_set = value
326
321
 
327
 
class MillisecondsPropertyValueArgumentCmd(PropertyValueCmd):
328
 
    """Abstract class for PropertyValueCmd taking a value argument as
329
 
a datetime.timedelta() but should store it as milliseconds."""
 
322
class MillisecondsValueArgumentMixIn(ValueArgumentMixIn):
 
323
    """Mixin class for commands taking a value argument as
 
324
    milliseconds."""
330
325
    @property
331
326
    def value_to_set(self):
332
327
        return self._vts
333
328
    @value_to_set.setter
334
329
    def value_to_set(self, value):
335
 
        """When setting, convert value from a datetime.timedelta"""
 
330
        """When setting, convert value to a datetime.timedelta"""
336
331
        self._vts = int(round(value.total_seconds() * 1000))
337
332
 
338
333
# Actual (non-abstract) command classes
438
433
        return value
439
434
 
440
435
class IsEnabledCmd(Command):
441
 
    def run(self, clients, bus=None, mandos=None):
442
 
        client, properties = next(iter(clients.items()))
 
436
    def run_on_one_client(self, client, properties):
443
437
        if self.is_enabled(client, properties):
444
438
            sys.exit(0)
445
439
        sys.exit(1)
448
442
 
449
443
class RemoveCmd(Command):
450
444
    def run_on_one_client(self, client, properties):
451
 
        log.debug("D-Bus: %s:%s:%s.RemoveClient(%r)", dbus_busname,
452
 
                  server_dbus_path, server_dbus_interface,
 
445
        log.debug("D-Bus: %s:%s:%s.RemoveClient(%r)", busname,
 
446
                  server_path, server_interface,
453
447
                  str(client.__dbus_object_path__))
454
448
        self.mandos.RemoveClient(client.__dbus_object_path__)
455
449
 
456
450
class ApproveCmd(Command):
457
451
    def run_on_one_client(self, client, properties):
458
 
        log.debug("D-Bus: %s:%s:%s.Approve(True)", dbus_busname,
459
 
                  client.__dbus_object_path__, client_dbus_interface)
 
452
        log.debug("D-Bus: %s:%s.Approve(True)",
 
453
                  client.__dbus_object_path__, client_interface)
460
454
        client.Approve(dbus.Boolean(True),
461
 
                       dbus_interface=client_dbus_interface)
 
455
                       dbus_interface=client_interface)
462
456
 
463
457
class DenyCmd(Command):
464
458
    def run_on_one_client(self, client, properties):
465
 
        log.debug("D-Bus: %s:%s:%s.Approve(False)", dbus_busname,
466
 
                  client.__dbus_object_path__, client_dbus_interface)
 
459
        log.debug("D-Bus: %s:%s.Approve(False)",
 
460
                  client.__dbus_object_path__, client_interface)
467
461
        client.Approve(dbus.Boolean(False),
468
 
                       dbus_interface=client_dbus_interface)
 
462
                       dbus_interface=client_interface)
469
463
 
470
464
class EnableCmd(PropertyCmd):
471
465
    propname = "Enabled"
495
489
    propname = "ApprovedByDefault"
496
490
    value_to_set = dbus.Boolean(False)
497
491
 
498
 
class SetCheckerCmd(PropertyValueCmd):
 
492
class SetCheckerCmd(PropertyCmd, ValueArgumentMixIn):
499
493
    propname = "Checker"
500
494
 
501
 
class SetHostCmd(PropertyValueCmd):
 
495
class SetHostCmd(PropertyCmd, ValueArgumentMixIn):
502
496
    propname = "Host"
503
497
 
504
 
class SetSecretCmd(PropertyValueCmd):
 
498
class SetSecretCmd(PropertyCmd, ValueArgumentMixIn):
505
499
    propname = "Secret"
506
500
    @property
507
501
    def value_to_set(self):
512
506
        self._vts = value.read()
513
507
        value.close()
514
508
 
515
 
class SetTimeoutCmd(MillisecondsPropertyValueArgumentCmd):
 
509
class SetTimeoutCmd(PropertyCmd, MillisecondsValueArgumentMixIn):
516
510
    propname = "Timeout"
517
511
 
518
 
class SetExtendedTimeoutCmd(MillisecondsPropertyValueArgumentCmd):
 
512
class SetExtendedTimeoutCmd(PropertyCmd,
 
513
                            MillisecondsValueArgumentMixIn):
519
514
    propname = "ExtendedTimeout"
520
515
 
521
 
class SetIntervalCmd(MillisecondsPropertyValueArgumentCmd):
 
516
class SetIntervalCmd(PropertyCmd, MillisecondsValueArgumentMixIn):
522
517
    propname = "Interval"
523
518
 
524
 
class SetApprovalDelayCmd(MillisecondsPropertyValueArgumentCmd):
 
519
class SetApprovalDelayCmd(PropertyCmd,
 
520
                          MillisecondsValueArgumentMixIn):
525
521
    propname = "ApprovalDelay"
526
522
 
527
 
class SetApprovalDurationCmd(MillisecondsPropertyValueArgumentCmd):
 
523
class SetApprovalDurationCmd(PropertyCmd,
 
524
                             MillisecondsValueArgumentMixIn):
528
525
    propname = "ApprovalDuration"
529
526
 
530
527
def add_command_line_options(parser):
726
723
 
727
724
    try:
728
725
        bus = dbus.SystemBus()
729
 
        log.debug("D-Bus: Connect to: (busname=%r, path=%r)",
730
 
                  dbus_busname, server_dbus_path)
731
 
        mandos_dbus_objc = bus.get_object(dbus_busname,
732
 
                                          server_dbus_path)
 
726
        log.debug("D-Bus: Connect to: (name=%r, path=%r)", busname,
 
727
                  server_path)
 
728
        mandos_dbus_objc = bus.get_object(busname, server_path)
733
729
    except dbus.exceptions.DBusException:
734
730
        log.critical("Could not connect to Mandos server")
735
731
        sys.exit(1)
736
732
 
737
733
    mandos_serv = dbus.Interface(mandos_dbus_objc,
738
 
                                 dbus_interface=server_dbus_interface)
 
734
                                 dbus_interface=server_interface)
739
735
    mandos_serv_object_manager = dbus.Interface(
740
736
        mandos_dbus_objc, dbus_interface=dbus.OBJECT_MANAGER_IFACE)
741
737
 
747
743
    dbus_filter = NullFilter()
748
744
    try:
749
745
        dbus_logger.addFilter(dbus_filter)
750
 
        log.debug("D-Bus: %s:%s:%s.GetManagedObjects()", dbus_busname,
751
 
                  server_dbus_path, dbus.OBJECT_MANAGER_IFACE)
752
 
        mandos_clients = {path: ifs_and_props[client_dbus_interface]
 
746
        log.debug("D-Bus: %s:%s:%s.GetManagedObjects()", busname,
 
747
                  server_path, dbus.OBJECT_MANAGER_IFACE)
 
748
        mandos_clients = {path: ifs_and_props[client_interface]
753
749
                          for path, ifs_and_props in
754
750
                          mandos_serv_object_manager
755
751
                          .GetManagedObjects().items()
756
 
                          if client_dbus_interface in ifs_and_props}
 
752
                          if client_interface in ifs_and_props}
757
753
    except dbus.exceptions.DBusException as e:
758
754
        log.critical("Failed to access Mandos server through D-Bus:"
759
755
                     "\n%s", e)
766
762
    clients = {}
767
763
 
768
764
    if not clientnames:
769
 
        clients = {objpath: properties
770
 
                   for objpath, properties in mandos_clients.items()}
 
765
        clients = {(log.debug("D-Bus: Connect to: (name=%r, path=%r)",
 
766
                              busname, str(path)) and False) or
 
767
                   bus.get_object(busname, path): properties
 
768
                   for path, properties in mandos_clients.items()}
771
769
    else:
772
770
        for name in clientnames:
773
 
            for objpath, properties in mandos_clients.items():
774
 
                if properties["Name"] == name:
775
 
                    clients[objpath] = properties
 
771
            for path, client in mandos_clients.items():
 
772
                if client["Name"] == name:
 
773
                    log.debug("D-Bus: Connect to: (name=%r, path=%r)",
 
774
                              busname, str(path))
 
775
                    client_objc = bus.get_object(busname, path)
 
776
                    clients[client_objc] = client
776
777
                    break
777
778
            else:
778
779
                log.critical("Client not found on server: %r", name)
781
782
    # Run all commands on clients
782
783
    commands = commands_from_options(options)
783
784
    for command in commands:
784
 
        command.run(clients, bus, mandos_serv)
 
785
        command.run(mandos_serv, clients)
785
786
 
786
787
 
787
788
class Test_milliseconds_to_string(unittest.TestCase):
836
837
        testcase = self
837
838
        class MockClient(object):
838
839
            def __init__(self, name, **attributes):
839
 
                self.__dbus_object_path__ = "/clients/{}".format(name)
 
840
                self.__dbus_object_path__ = "objpath_{}".format(name)
840
841
                self.attributes = attributes
841
842
                self.attributes["Name"] = name
842
843
                self.calls = []
843
844
            def Set(self, interface, propname, value, dbus_interface):
844
 
                testcase.assertEqual(interface, client_dbus_interface)
 
845
                testcase.assertEqual(interface, client_interface)
845
846
                testcase.assertEqual(dbus_interface,
846
847
                                     dbus.PROPERTIES_IFACE)
847
848
                self.attributes[propname] = value
848
849
            def Get(self, interface, propname, dbus_interface):
849
 
                testcase.assertEqual(interface, client_dbus_interface)
 
850
                testcase.assertEqual(interface, client_interface)
850
851
                testcase.assertEqual(dbus_interface,
851
852
                                     dbus.PROPERTIES_IFACE)
852
853
                return self.attributes[propname]
853
854
            def Approve(self, approve, dbus_interface):
854
 
                testcase.assertEqual(dbus_interface,
855
 
                                     client_dbus_interface)
 
855
                testcase.assertEqual(dbus_interface, client_interface)
856
856
                self.calls.append(("Approve", (approve,
857
857
                                               dbus_interface)))
858
858
        self.client = MockClient(
905
905
            LastCheckerStatus=-2)
906
906
        self.clients =  collections.OrderedDict(
907
907
            [
908
 
                ("/clients/foo", self.client.attributes),
909
 
                ("/clients/barbar", self.other_client.attributes),
 
908
                (self.client, self.client.attributes),
 
909
                (self.other_client, self.other_client.attributes),
910
910
            ])
911
 
        self.one_client = {"/clients/foo": self.client.attributes}
912
 
    @property
913
 
    def bus(self):
914
 
        class Bus(object):
915
 
            @staticmethod
916
 
            def get_object(client_bus_name, path):
917
 
                self.assertEqual(client_bus_name, dbus_busname)
918
 
                return {
919
 
                    "/clients/foo": self.client,
920
 
                    "/clients/barbar": self.other_client,
921
 
                }[path]
922
 
        return Bus()
 
911
        self.one_client = {self.client: self.client.attributes}
923
912
 
924
913
class TestPrintTableCmd(TestCmd):
925
914
    def test_normal(self):
1015
1004
                            for client, properties in self.clients.items()))
1016
1005
    def test_is_enabled_run_exits_successfully(self):
1017
1006
        with self.assertRaises(SystemExit) as e:
1018
 
            IsEnabledCmd().run(self.one_client)
 
1007
            IsEnabledCmd().run(None, self.one_client)
1019
1008
        if e.exception.code is not None:
1020
1009
            self.assertEqual(e.exception.code, 0)
1021
1010
        else:
1023
1012
    def test_is_enabled_run_exits_with_failure(self):
1024
1013
        self.client.attributes["Enabled"] = dbus.Boolean(False)
1025
1014
        with self.assertRaises(SystemExit) as e:
1026
 
            IsEnabledCmd().run(self.one_client)
 
1015
            IsEnabledCmd().run(None, self.one_client)
1027
1016
        if isinstance(e.exception.code, int):
1028
1017
            self.assertNotEqual(e.exception.code, 0)
1029
1018
        else:
1038
1027
                self.calls.append(("RemoveClient", (dbus_path,)))
1039
1028
        mandos = MockMandos()
1040
1029
        super(TestRemoveCmd, self).setUp()
1041
 
        RemoveCmd().run(self.clients, self.bus, mandos)
 
1030
        RemoveCmd().run(mandos, self.clients)
1042
1031
        self.assertEqual(len(mandos.calls), 2)
1043
 
        for clientpath in self.clients:
1044
 
            self.assertIn(("RemoveClient", (clientpath,)),
 
1032
        for client in self.clients:
 
1033
            self.assertIn(("RemoveClient",
 
1034
                           (client.__dbus_object_path__,)),
1045
1035
                          mandos.calls)
1046
1036
 
1047
1037
class TestApproveCmd(TestCmd):
1048
1038
    def test_approve(self):
1049
 
        ApproveCmd().run(self.clients, self.bus)
1050
 
        for clientpath in self.clients:
1051
 
            client = self.bus.get_object(dbus_busname, clientpath)
1052
 
            self.assertIn(("Approve", (True, client_dbus_interface)),
 
1039
        ApproveCmd().run(None, self.clients)
 
1040
        for client in self.clients:
 
1041
            self.assertIn(("Approve", (True, client_interface)),
1053
1042
                          client.calls)
1054
1043
 
1055
1044
class TestDenyCmd(TestCmd):
1056
1045
    def test_deny(self):
1057
 
        DenyCmd().run(self.clients, self.bus)
1058
 
        for clientpath in self.clients:
1059
 
            client = self.bus.get_object(dbus_busname, clientpath)
1060
 
            self.assertIn(("Approve", (False, client_dbus_interface)),
 
1046
        DenyCmd().run(None, self.clients)
 
1047
        for client in self.clients:
 
1048
            self.assertIn(("Approve", (False, client_interface)),
1061
1049
                          client.calls)
1062
1050
 
1063
1051
class TestEnableCmd(TestCmd):
1064
1052
    def test_enable(self):
1065
 
        for clientpath in self.clients:
1066
 
            client = self.bus.get_object(dbus_busname, clientpath)
 
1053
        for client in self.clients:
1067
1054
            client.attributes["Enabled"] = False
1068
1055
 
1069
 
        EnableCmd().run(self.clients, self.bus)
 
1056
        EnableCmd().run(None, self.clients)
1070
1057
 
1071
 
        for clientpath in self.clients:
1072
 
            client = self.bus.get_object(dbus_busname, clientpath)
 
1058
        for client in self.clients:
1073
1059
            self.assertTrue(client.attributes["Enabled"])
1074
1060
 
1075
1061
class TestDisableCmd(TestCmd):
1076
1062
    def test_disable(self):
1077
 
        DisableCmd().run(self.clients, self.bus)
1078
 
        for clientpath in self.clients:
1079
 
            client = self.bus.get_object(dbus_busname, clientpath)
 
1063
        DisableCmd().run(None, self.clients)
 
1064
 
 
1065
        for client in self.clients:
1080
1066
            self.assertFalse(client.attributes["Enabled"])
1081
1067
 
1082
1068
class Unique(object):
1092
1078
                                self.values_to_set)
1093
1079
        for value_to_set, value_to_get in zip(self.values_to_set,
1094
1080
                                              values_to_get):
1095
 
            for clientpath in self.clients:
1096
 
                client = self.bus.get_object(dbus_busname, clientpath)
 
1081
            for client in self.clients:
1097
1082
                old_value = client.attributes[self.propname]
1098
1083
                self.assertNotIsInstance(old_value, Unique)
1099
1084
                client.attributes[self.propname] = Unique()
1100
1085
            self.run_command(value_to_set, self.clients)
1101
 
            for clientpath in self.clients:
1102
 
                client = self.bus.get_object(dbus_busname, clientpath)
 
1086
            for client in self.clients:
1103
1087
                value = client.attributes[self.propname]
1104
1088
                self.assertNotIsInstance(value, Unique)
1105
1089
                self.assertEqual(value, value_to_get)
1106
1090
    def run_command(self, value, clients):
1107
 
        self.command().run(clients, self.bus)
 
1091
        self.command().run(None, clients)
1108
1092
 
1109
1093
class TestBumpTimeoutCmd(TestPropertyCmd):
1110
1094
    command = BumpTimeoutCmd
1131
1115
    propname = "ApprovedByDefault"
1132
1116
    values_to_set = [dbus.Boolean(False)]
1133
1117
 
1134
 
class TestPropertyValueCmd(TestPropertyCmd):
1135
 
    """Abstract class for tests of PropertyValueCmd classes"""
 
1118
class TestValueArgumentPropertyCmd(TestPropertyCmd):
 
1119
    """Abstract class for tests of PropertyCmd classes using the
 
1120
ValueArgumentMixIn"""
1136
1121
    def runTest(self):
1137
 
        if type(self) is TestPropertyValueCmd:
 
1122
        if type(self) is TestValueArgumentPropertyCmd:
1138
1123
            return
1139
 
        return super(TestPropertyValueCmd, self).runTest()
 
1124
        return super(TestValueArgumentPropertyCmd, self).runTest()
1140
1125
    def run_command(self, value, clients):
1141
 
        self.command(value).run(clients, self.bus)
 
1126
        self.command(value).run(None, clients)
1142
1127
 
1143
 
class TestSetCheckerCmd(TestPropertyValueCmd):
 
1128
class TestSetCheckerCmd(TestValueArgumentPropertyCmd):
1144
1129
    command = SetCheckerCmd
1145
1130
    propname = "Checker"
1146
1131
    values_to_set = ["", ":", "fping -q -- %s"]
1147
1132
 
1148
 
class TestSetHostCmd(TestPropertyValueCmd):
 
1133
class TestSetHostCmd(TestValueArgumentPropertyCmd):
1149
1134
    command = SetHostCmd
1150
1135
    propname = "Host"
1151
1136
    values_to_set = ["192.0.2.3", "foo.example.org"]
1152
1137
 
1153
 
class TestSetSecretCmd(TestPropertyValueCmd):
 
1138
class TestSetSecretCmd(TestValueArgumentPropertyCmd):
1154
1139
    command = SetSecretCmd
1155
1140
    propname = "Secret"
1156
1141
    values_to_set = [io.BytesIO(b""),
1157
1142
                     io.BytesIO(b"secret\0xyzzy\nbar")]
1158
1143
    values_to_get = [b"", b"secret\0xyzzy\nbar"]
1159
1144
 
1160
 
class TestSetTimeoutCmd(TestPropertyValueCmd):
 
1145
class TestSetTimeoutCmd(TestValueArgumentPropertyCmd):
1161
1146
    command = SetTimeoutCmd
1162
1147
    propname = "Timeout"
1163
1148
    values_to_set = [datetime.timedelta(),
1167
1152
                     datetime.timedelta(weeks=52)]
1168
1153
    values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1169
1154
 
1170
 
class TestSetExtendedTimeoutCmd(TestPropertyValueCmd):
 
1155
class TestSetExtendedTimeoutCmd(TestValueArgumentPropertyCmd):
1171
1156
    command = SetExtendedTimeoutCmd
1172
1157
    propname = "ExtendedTimeout"
1173
1158
    values_to_set = [datetime.timedelta(),
1177
1162
                     datetime.timedelta(weeks=52)]
1178
1163
    values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1179
1164
 
1180
 
class TestSetIntervalCmd(TestPropertyValueCmd):
 
1165
class TestSetIntervalCmd(TestValueArgumentPropertyCmd):
1181
1166
    command = SetIntervalCmd
1182
1167
    propname = "Interval"
1183
1168
    values_to_set = [datetime.timedelta(),
1187
1172
                     datetime.timedelta(weeks=52)]
1188
1173
    values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1189
1174
 
1190
 
class TestSetApprovalDelayCmd(TestPropertyValueCmd):
 
1175
class TestSetApprovalDelayCmd(TestValueArgumentPropertyCmd):
1191
1176
    command = SetApprovalDelayCmd
1192
1177
    propname = "ApprovalDelay"
1193
1178
    values_to_set = [datetime.timedelta(),
1197
1182
                     datetime.timedelta(weeks=52)]
1198
1183
    values_to_get = [0, 300000, 1000, 604800000, 31449600000]
1199
1184
 
1200
 
class TestSetApprovalDurationCmd(TestPropertyValueCmd):
 
1185
class TestSetApprovalDurationCmd(TestValueArgumentPropertyCmd):
1201
1186
    command = SetApprovalDurationCmd
1202
1187
    propname = "ApprovalDuration"
1203
1188
    values_to_set = [datetime.timedelta(),