52
66
    "ApprovalDelay": "Approval Delay",
 
53
67
    "ApprovalDuration": "Approval Duration",
 
54
68
    "Checker": "Checker",
 
55
 
    "ExtendedTimeout" : "Extended Timeout"
 
 
69
    "ExtendedTimeout": "Extended Timeout",
 
 
71
    "LastCheckerStatus": "Last Checker Status",
 
57
73
defaultkeywords = ("Name", "Enabled", "Timeout", "LastCheckedOK")
 
58
74
domain = "se.recompile"
 
59
75
busname = domain + ".Mandos"
 
61
77
server_interface = domain + ".Mandos"
 
62
78
client_interface = domain + ".Mandos.Client"
 
65
 
def timedelta_to_milliseconds(td):
 
66
 
    """Convert a datetime.timedelta object to milliseconds"""
 
67
 
    return ((td.days * 24 * 60 * 60 * 1000)
 
69
 
            + (td.microseconds // 1000))
 
 
83
    dbus.OBJECT_MANAGER_IFACE
 
 
84
except AttributeError:
 
 
85
    dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
 
71
88
def milliseconds_to_string(ms):
 
72
89
    td = datetime.timedelta(0, 0, 0, ms)
 
73
 
    return ("%(days)s%(hours)02d:%(minutes)02d:%(seconds)02d"
 
74
 
            % { "days": "%dT" % td.days if td.days else "",
 
75
 
                "hours": td.seconds // 3600,
 
76
 
                "minutes": (td.seconds % 3600) // 60,
 
77
 
                "seconds": td.seconds % 60,
 
 
90
    return ("{days}{hours:02}:{minutes:02}:{seconds:02}"
 
 
91
            .format(days="{}T".format(td.days) if td.days else "",
 
 
92
                    hours=td.seconds // 3600,
 
 
93
                    minutes=(td.seconds % 3600) // 60,
 
 
94
                    seconds=td.seconds % 60))
 
 
97
def rfc3339_duration_to_delta(duration):
 
 
98
    """Parse an RFC 3339 "duration" and return a datetime.timedelta
 
 
100
    >>> rfc3339_duration_to_delta("P7D")
 
 
101
    datetime.timedelta(7)
 
 
102
    >>> rfc3339_duration_to_delta("PT60S")
 
 
103
    datetime.timedelta(0, 60)
 
 
104
    >>> rfc3339_duration_to_delta("PT60M")
 
 
105
    datetime.timedelta(0, 3600)
 
 
106
    >>> rfc3339_duration_to_delta("PT24H")
 
 
107
    datetime.timedelta(1)
 
 
108
    >>> rfc3339_duration_to_delta("P1W")
 
 
109
    datetime.timedelta(7)
 
 
110
    >>> rfc3339_duration_to_delta("PT5M30S")
 
 
111
    datetime.timedelta(0, 330)
 
 
112
    >>> rfc3339_duration_to_delta("P1DT3M20S")
 
 
113
    datetime.timedelta(1, 200)
 
 
116
    # Parsing an RFC 3339 duration with regular expressions is not
 
 
117
    # possible - there would have to be multiple places for the same
 
 
118
    # values, like seconds.  The current code, while more esoteric, is
 
 
119
    # cleaner without depending on a parsing library.  If Python had a
 
 
120
    # built-in library for parsing we would use it, but we'd like to
 
 
121
    # avoid excessive use of external libraries.
 
 
123
    # New type for defining tokens, syntax, and semantics all-in-one
 
 
124
    Token = collections.namedtuple("Token", (
 
 
125
        "regexp",  # To match token; if "value" is not None, must have
 
 
126
                   # a "group" containing digits
 
 
127
        "value",   # datetime.timedelta or None
 
 
128
        "followers"))           # Tokens valid after this token
 
 
129
    # RFC 3339 "duration" tokens, syntax, and semantics; taken from
 
 
130
    # the "duration" ABNF definition in RFC 3339, Appendix A.
 
 
131
    token_end = Token(re.compile(r"$"), None, frozenset())
 
 
132
    token_second = Token(re.compile(r"(\d+)S"),
 
 
133
                         datetime.timedelta(seconds=1),
 
 
134
                         frozenset((token_end, )))
 
 
135
    token_minute = Token(re.compile(r"(\d+)M"),
 
 
136
                         datetime.timedelta(minutes=1),
 
 
137
                         frozenset((token_second, token_end)))
 
 
138
    token_hour = Token(re.compile(r"(\d+)H"),
 
 
139
                       datetime.timedelta(hours=1),
 
 
140
                       frozenset((token_minute, token_end)))
 
 
141
    token_time = Token(re.compile(r"T"),
 
 
143
                       frozenset((token_hour, token_minute,
 
 
145
    token_day = Token(re.compile(r"(\d+)D"),
 
 
146
                      datetime.timedelta(days=1),
 
 
147
                      frozenset((token_time, token_end)))
 
 
148
    token_month = Token(re.compile(r"(\d+)M"),
 
 
149
                        datetime.timedelta(weeks=4),
 
 
150
                        frozenset((token_day, token_end)))
 
 
151
    token_year = Token(re.compile(r"(\d+)Y"),
 
 
152
                       datetime.timedelta(weeks=52),
 
 
153
                       frozenset((token_month, token_end)))
 
 
154
    token_week = Token(re.compile(r"(\d+)W"),
 
 
155
                       datetime.timedelta(weeks=1),
 
 
156
                       frozenset((token_end, )))
 
 
157
    token_duration = Token(re.compile(r"P"), None,
 
 
158
                           frozenset((token_year, token_month,
 
 
159
                                      token_day, token_time,
 
 
161
    # Define starting values:
 
 
163
    value = datetime.timedelta()
 
 
165
    # Following valid tokens
 
 
166
    followers = frozenset((token_duration, ))
 
 
167
    # String left to parse
 
 
169
    # Loop until end token is found
 
 
170
    while found_token is not token_end:
 
 
171
        # Search for any currently valid tokens
 
 
172
        for token in followers:
 
 
173
            match = token.regexp.match(s)
 
 
174
            if match is not None:
 
 
176
                if token.value is not None:
 
 
177
                    # Value found, parse digits
 
 
178
                    factor = int(match.group(1), 10)
 
 
179
                    # Add to value so far
 
 
180
                    value += factor * token.value
 
 
181
                # Strip token from string
 
 
182
                s = token.regexp.sub("", s, 1)
 
 
185
                # Set valid next tokens
 
 
186
                followers = found_token.followers
 
 
189
            # No currently valid tokens were found
 
 
190
            raise ValueError("Invalid RFC 3339 duration: {!r}"
 
80
196
def string_to_delta(interval):
 
81
197
    """Parse a string and return a datetime.timedelta
 
83
 
    >>> string_to_delta("7d")
 
 
199
    >>> string_to_delta('7d')
 
84
200
    datetime.timedelta(7)
 
85
 
    >>> string_to_delta("60s")
 
 
201
    >>> string_to_delta('60s')
 
86
202
    datetime.timedelta(0, 60)
 
87
 
    >>> string_to_delta("60m")
 
 
203
    >>> string_to_delta('60m')
 
88
204
    datetime.timedelta(0, 3600)
 
89
 
    >>> string_to_delta("24h")
 
 
205
    >>> string_to_delta('24h')
 
90
206
    datetime.timedelta(1)
 
91
 
    >>> string_to_delta("1w")
 
 
207
    >>> string_to_delta('1w')
 
92
208
    datetime.timedelta(7)
 
93
 
    >>> string_to_delta("5m 30s")
 
 
209
    >>> string_to_delta('5m 30s')
 
94
210
    datetime.timedelta(0, 330)
 
 
214
        return rfc3339_duration_to_delta(interval)
 
96
218
    value = datetime.timedelta(0)
 
97
 
    regexp = re.compile("(\d+)([dsmhw]?)")
 
 
219
    regexp = re.compile(r"(\d+)([dsmhw]?)")
 
99
221
    for num, suffix in regexp.findall(interval):
 
100
222
        if suffix == "d":
 
101
223
            value += datetime.timedelta(int(num))
 
 
196
323
    parser.add_argument("--approval-duration",
 
197
324
                        help="Set duration of one client approval")
 
198
325
    parser.add_argument("-H", "--host", help="Set host for client")
 
199
 
    parser.add_argument("-s", "--secret", type=file,
 
 
326
    parser.add_argument("-s", "--secret",
 
 
327
                        type=argparse.FileType(mode="rb"),
 
200
328
                        help="Set password blob (file) for client")
 
201
329
    parser.add_argument("-A", "--approve", action="store_true",
 
202
330
                        help="Approve any current client request")
 
203
331
    parser.add_argument("-D", "--deny", action="store_true",
 
204
332
                        help="Deny any current client request")
 
 
333
    parser.add_argument("--check", action="store_true",
 
 
334
                        help="Run self-test")
 
205
335
    parser.add_argument("client", nargs="*", help="Client name")
 
206
336
    options = parser.parse_args()
 
208
 
    if has_actions(options) and not options.client and not options.all:
 
 
338
    if has_actions(options) and not (options.client or options.all):
 
209
339
        parser.error("Options require clients names or --all.")
 
210
340
    if options.verbose and has_actions(options):
 
211
 
        parser.error("--verbose can only be used alone or with"
 
 
341
        parser.error("--verbose can only be used alone.")
 
 
342
    if options.dump_json and (options.verbose
 
 
343
                              or has_actions(options)):
 
 
344
        parser.error("--dump-json can only be used alone.")
 
213
345
    if options.all and not has_actions(options):
 
214
346
        parser.error("--all requires an action.")
 
 
350
        fail_count, test_count = doctest.testmod()
 
 
351
        sys.exit(os.EX_OK if fail_count == 0 else 1)
 
217
354
        bus = dbus.SystemBus()
 
218
355
        mandos_dbus_objc = bus.get_object(busname, server_path)
 
219
356
    except dbus.exceptions.DBusException:
 
220
 
        print("Could not connect to Mandos server",
 
 
357
        print("Could not connect to Mandos server", file=sys.stderr)
 
224
360
    mandos_serv = dbus.Interface(mandos_dbus_objc,
 
225
 
                                 dbus_interface = server_interface)
 
227
 
    #block stderr since dbus library prints to stderr
 
 
361
                                 dbus_interface=server_interface)
 
 
362
    mandos_serv_object_manager = dbus.Interface(
 
 
363
        mandos_dbus_objc, dbus_interface=dbus.OBJECT_MANAGER_IFACE)
 
 
365
    # block stderr since dbus library prints to stderr
 
228
366
    null = os.open(os.path.devnull, os.O_RDWR)
 
229
367
    stderrcopy = os.dup(sys.stderr.fileno())
 
230
368
    os.dup2(null, sys.stderr.fileno())
 
234
 
            mandos_clients = mandos_serv.GetAllClientsWithProperties()
 
 
372
            mandos_clients = {path: ifs_and_props[client_interface]
 
 
373
                              for path, ifs_and_props in
 
 
374
                              mandos_serv_object_manager
 
 
375
                              .GetManagedObjects().items()
 
 
376
                              if client_interface in ifs_and_props}
 
237
379
            os.dup2(stderrcopy, sys.stderr.fileno())
 
238
380
            os.close(stderrcopy)
 
239
 
    except dbus.exceptions.DBusException:
 
240
 
        print("Access denied: Accessing mandos server through dbus.",
 
 
381
    except dbus.exceptions.DBusException as e:
 
 
382
        print("Access denied: "
 
 
383
              "Accessing mandos server through D-Bus: {}".format(e),
 
244
387
    # Compile dict of (clients: properties) to process
 
247
390
    if options.all or not options.client:
 
248
 
        clients = dict((bus.get_object(busname, path), properties)
 
249
 
                       for path, properties in
 
250
 
                       mandos_clients.iteritems())
 
 
391
        clients = {bus.get_object(busname, path): properties
 
 
392
                   for path, properties in mandos_clients.items()}
 
252
394
        for name in options.client:
 
253
 
            for path, client in mandos_clients.iteritems():
 
 
395
            for path, client in mandos_clients.items():
 
254
396
                if client["Name"] == name:
 
255
397
                    client_objc = bus.get_object(busname, path)
 
256
398
                    clients[client_objc] = client
 
259
 
                print("Client not found on server: %r" % name,
 
 
401
                print("Client not found on server: {!r}"
 
 
402
                      .format(name), file=sys.stderr)
 
263
405
    if not has_actions(options) and clients:
 
265
 
            keywords = ("Name", "Enabled", "Timeout",
 
266
 
                        "LastCheckedOK", "Created", "Interval",
 
267
 
                        "Host", "Fingerprint", "CheckerRunning",
 
 
406
        if options.verbose or options.dump_json:
 
 
407
            keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK",
 
 
408
                        "Created", "Interval", "Host", "KeyID",
 
 
409
                        "Fingerprint", "CheckerRunning",
 
268
410
                        "LastEnabled", "ApprovalPending",
 
270
 
                        "LastApprovalRequest", "ApprovalDelay",
 
271
 
                        "ApprovalDuration", "Checker",
 
 
411
                        "ApprovedByDefault", "LastApprovalRequest",
 
 
412
                        "ApprovalDelay", "ApprovalDuration",
 
 
413
                        "Checker", "ExtendedTimeout", "Expires",
 
274
416
            keywords = defaultkeywords
 
276
 
        print_clients(clients.values(), keywords)
 
 
418
        if options.dump_json:
 
 
419
            json.dump({client["Name"]: {key:
 
 
421
                                        if isinstance(client[key],
 
 
425
                       for client in clients.values()},
 
 
426
                      fp=sys.stdout, indent=4,
 
 
427
                      separators=(',', ': '))
 
 
430
            print_clients(clients.values(), keywords)
 
278
432
        # Process each client in the list by all selected options
 
279
433
        for client in clients:
 
 
435
            def set_client_prop(prop, value):
 
 
436
                """Set a Client D-Bus property"""
 
 
437
                client.Set(client_interface, prop, value,
 
 
438
                           dbus_interface=dbus.PROPERTIES_IFACE)
 
 
440
            def set_client_prop_ms(prop, value):
 
 
441
                """Set a Client D-Bus property, converted
 
 
442
                from a string to milliseconds."""
 
 
443
                set_client_prop(prop,
 
 
444
                                string_to_delta(value).total_seconds()
 
280
447
            if options.remove:
 
281
448
                mandos_serv.RemoveClient(client.__dbus_object_path__)
 
282
449
            if options.enable:
 
283
 
                client.Enable(dbus_interface=client_interface)
 
 
450
                set_client_prop("Enabled", dbus.Boolean(True))
 
284
451
            if options.disable:
 
285
 
                client.Disable(dbus_interface=client_interface)
 
 
452
                set_client_prop("Enabled", dbus.Boolean(False))
 
286
453
            if options.bump_timeout:
 
287
 
                client.CheckedOK(dbus_interface=client_interface)
 
 
454
                set_client_prop("LastCheckedOK", "")
 
288
455
            if options.start_checker:
 
289
 
                client.StartChecker(dbus_interface=client_interface)
 
 
456
                set_client_prop("CheckerRunning", dbus.Boolean(True))
 
290
457
            if options.stop_checker:
 
291
 
                client.StopChecker(dbus_interface=client_interface)
 
 
458
                set_client_prop("CheckerRunning", dbus.Boolean(False))
 
292
459
            if options.is_enabled:
 
293
 
                sys.exit(0 if client.Get(client_interface,
 
296
 
                                         dbus.PROPERTIES_IFACE)
 
 
460
                if client.Get(client_interface, "Enabled",
 
 
461
                              dbus_interface=dbus.PROPERTIES_IFACE):
 
298
465
            if options.checker is not None:
 
299
 
                client.Set(client_interface, "Checker",
 
301
 
                           dbus_interface=dbus.PROPERTIES_IFACE)
 
 
466
                set_client_prop("Checker", options.checker)
 
302
467
            if options.host is not None:
 
303
 
                client.Set(client_interface, "Host", options.host,
 
304
 
                           dbus_interface=dbus.PROPERTIES_IFACE)
 
 
468
                set_client_prop("Host", options.host)
 
305
469
            if options.interval is not None:
 
306
 
                client.Set(client_interface, "Interval",
 
307
 
                           timedelta_to_milliseconds
 
308
 
                           (string_to_delta(options.interval)),
 
309
 
                           dbus_interface=dbus.PROPERTIES_IFACE)
 
 
470
                set_client_prop_ms("Interval", options.interval)
 
310
471
            if options.approval_delay is not None:
 
311
 
                client.Set(client_interface, "ApprovalDelay",
 
312
 
                           timedelta_to_milliseconds
 
313
 
                           (string_to_delta(options.
 
315
 
                           dbus_interface=dbus.PROPERTIES_IFACE)
 
 
472
                set_client_prop_ms("ApprovalDelay",
 
 
473
                                   options.approval_delay)
 
316
474
            if options.approval_duration is not None:
 
317
 
                client.Set(client_interface, "ApprovalDuration",
 
318
 
                           timedelta_to_milliseconds
 
319
 
                           (string_to_delta(options.
 
321
 
                           dbus_interface=dbus.PROPERTIES_IFACE)
 
 
475
                set_client_prop_ms("ApprovalDuration",
 
 
476
                                   options.approval_duration)
 
322
477
            if options.timeout is not None:
 
323
 
                client.Set(client_interface, "Timeout",
 
324
 
                           timedelta_to_milliseconds
 
325
 
                           (string_to_delta(options.timeout)),
 
326
 
                           dbus_interface=dbus.PROPERTIES_IFACE)
 
 
478
                set_client_prop_ms("Timeout", options.timeout)
 
327
479
            if options.extended_timeout is not None:
 
328
 
                client.Set(client_interface, "ExtendedTimeout",
 
329
 
                           timedelta_to_milliseconds
 
330
 
                           (string_to_delta(options.extended_timeout)),
 
331
 
                           dbus_interface=dbus.PROPERTIES_IFACE)
 
 
480
                set_client_prop_ms("ExtendedTimeout",
 
 
481
                                   options.extended_timeout)
 
332
482
            if options.secret is not None:
 
333
 
                client.Set(client_interface, "Secret",
 
334
 
                           dbus.ByteArray(open(options.secret,
 
336
 
                           dbus_interface=dbus.PROPERTIES_IFACE)
 
 
483
                set_client_prop("Secret",
 
 
484
                                dbus.ByteArray(options.secret.read()))
 
337
485
            if options.approved_by_default is not None:
 
338
 
                client.Set(client_interface, "ApprovedByDefault",
 
340
 
                                        .approved_by_default),
 
341
 
                           dbus_interface=dbus.PROPERTIES_IFACE)
 
 
486
                set_client_prop("ApprovedByDefault",
 
 
488
                                             .approved_by_default))
 
342
489
            if options.approve:
 
343
490
                client.Approve(dbus.Boolean(True),
 
344
491
                               dbus_interface=client_interface)