/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: 2015-07-20 03:03:33 UTC
  • Revision ID: teddy@recompile.se-20150720030333-203m2aeblypcsfte
Bug fix for GnuTLS 3: be compatible with old 2048-bit DSA keys.

The mandos-keygen program in Mandos version 1.6.0 and older generated
2048-bit DSA keys, and when GnuTLS uses these it has trouble
connecting using the Mandos default priority string.  This was
previously fixed in Mandos 1.6.2, but the bug reappeared when using
GnuTLS 3, so the default priority string has to change again; this
time also the Mandos client has to change its default, so now the
server and the client should use the same default priority string:

SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA:+SIGN-DSA-SHA256

* mandos (main/server_defaults): Changed default priority string.
* mandos-options.xml (/section/para[id="priority_compat"]): Removed.
  (/section/para[id="priority"]): Changed default priority string.
* mandos.conf ([DEFAULT]/priority): - '' -
* mandos.conf.xml (OPTIONS/priority): Refer to the id "priority"
                                      instead of "priority_compat".
* mandos.xml (OPTIONS/--priority): - '' -
* plugins.d/mandos-client.c (main): Changed default priority string.

Show diffs side-by-side

added added

removed removed

Lines of Context:
3
3
4
4
# Mandos Monitor - Control and monitor the Mandos server
5
5
6
 
# Copyright © 2008-2012 Teddy Hogeborn
7
 
# Copyright © 2008-2012 Björn Påhlsson
 
6
# Copyright © 2008-2015 Teddy Hogeborn
 
7
# Copyright © 2008-2015 Björn Påhlsson
8
8
9
9
# This program is free software: you can redistribute it and/or modify
10
10
# it under the terms of the GNU General Public License as published by
26
26
from __future__ import (division, absolute_import, print_function,
27
27
                        unicode_literals)
28
28
 
29
 
from future_builtins import *
 
29
try:
 
30
    from future_builtins import *
 
31
except ImportError:
 
32
    pass
30
33
 
31
34
import sys
32
35
import argparse
39
42
 
40
43
import dbus
41
44
 
 
45
if sys.version_info.major == 2:
 
46
    str = unicode
 
47
 
42
48
locale.setlocale(locale.LC_ALL, "")
43
49
 
44
50
tablewords = {
58
64
    "ApprovalDelay": "Approval Delay",
59
65
    "ApprovalDuration": "Approval Duration",
60
66
    "Checker": "Checker",
61
 
    "ExtendedTimeout" : "Extended Timeout"
62
 
    }
 
67
    "ExtendedTimeout": "Extended Timeout"
 
68
}
63
69
defaultkeywords = ("Name", "Enabled", "Timeout", "LastCheckedOK")
64
70
domain = "se.recompile"
65
71
busname = domain + ".Mandos"
66
72
server_path = "/"
67
73
server_interface = domain + ".Mandos"
68
74
client_interface = domain + ".Mandos.Client"
69
 
version = "1.6.2"
 
75
version = "1.6.9"
70
76
 
71
 
def timedelta_to_milliseconds(td):
72
 
    """Convert a datetime.timedelta object to milliseconds"""
73
 
    return ((td.days * 24 * 60 * 60 * 1000)
74
 
            + (td.seconds * 1000)
75
 
            + (td.microseconds // 1000))
76
77
 
77
78
def milliseconds_to_string(ms):
78
79
    td = datetime.timedelta(0, 0, 0, ms)
79
 
    return ("{days}{hours:02}:{minutes:02}:{seconds:02}"
80
 
            .format(days = "{0}T".format(td.days) if td.days else "",
81
 
                    hours = td.seconds // 3600,
82
 
                    minutes = (td.seconds % 3600) // 60,
83
 
                    seconds = td.seconds % 60,
84
 
                    ))
 
80
    return ("{days}{hours:02}:{minutes:02}:{seconds:02}".format(
 
81
        days = "{}T".format(td.days) if td.days else "",
 
82
        hours = td.seconds // 3600,
 
83
        minutes = (td.seconds % 3600) // 60,
 
84
        seconds = td.seconds % 60))
85
85
 
86
86
 
87
87
def rfc3339_duration_to_delta(duration):
111
111
    # avoid excessive use of external libraries.
112
112
    
113
113
    # New type for defining tokens, syntax, and semantics all-in-one
114
 
    Token = collections.namedtuple("Token",
115
 
                                   ("regexp", # To match token; if
116
 
                                              # "value" is not None,
117
 
                                              # must have a "group"
118
 
                                              # containing digits
119
 
                                    "value",  # datetime.timedelta or
120
 
                                              # None
121
 
                                    "followers")) # Tokens valid after
122
 
                                                  # this token
 
114
    Token = collections.namedtuple("Token", (
 
115
        "regexp",  # To match token; if "value" is not None, must have
 
116
                   # a "group" containing digits
 
117
        "value",   # datetime.timedelta or None
 
118
        "followers"))           # Tokens valid after this token
123
119
    # RFC 3339 "duration" tokens, syntax, and semantics; taken from
124
120
    # the "duration" ABNF definition in RFC 3339, Appendix A.
125
121
    token_end = Token(re.compile(r"$"), None, frozenset())
126
122
    token_second = Token(re.compile(r"(\d+)S"),
127
123
                         datetime.timedelta(seconds=1),
128
 
                         frozenset((token_end,)))
 
124
                         frozenset((token_end, )))
129
125
    token_minute = Token(re.compile(r"(\d+)M"),
130
126
                         datetime.timedelta(minutes=1),
131
127
                         frozenset((token_second, token_end)))
147
143
                       frozenset((token_month, token_end)))
148
144
    token_week = Token(re.compile(r"(\d+)W"),
149
145
                       datetime.timedelta(weeks=1),
150
 
                       frozenset((token_end,)))
 
146
                       frozenset((token_end, )))
151
147
    token_duration = Token(re.compile(r"P"), None,
152
148
                           frozenset((token_year, token_month,
153
149
                                      token_day, token_time,
154
 
                                      token_week))),
 
150
                                      token_week)))
155
151
    # Define starting values
156
152
    value = datetime.timedelta() # Value so far
157
153
    found_token = None
158
 
    followers = frozenset(token_duration,) # Following valid tokens
 
154
    followers = frozenset((token_duration, )) # Following valid tokens
159
155
    s = duration                # String left to parse
160
156
    # Loop until end token is found
161
157
    while found_token is not token_end:
178
174
                break
179
175
        else:
180
176
            # No currently valid tokens were found
181
 
            raise ValueError("Invalid RFC 3339 duration")
 
177
            raise ValueError("Invalid RFC 3339 duration: {!r}"
 
178
                             .format(duration))
182
179
    # End token found
183
180
    return value
184
181
 
186
183
def string_to_delta(interval):
187
184
    """Parse a string and return a datetime.timedelta
188
185
    
189
 
    >>> string_to_delta("7d")
 
186
    >>> string_to_delta('7d')
190
187
    datetime.timedelta(7)
191
 
    >>> string_to_delta("60s")
 
188
    >>> string_to_delta('60s')
192
189
    datetime.timedelta(0, 60)
193
 
    >>> string_to_delta("60m")
 
190
    >>> string_to_delta('60m')
194
191
    datetime.timedelta(0, 3600)
195
 
    >>> string_to_delta("24h")
 
192
    >>> string_to_delta('24h')
196
193
    datetime.timedelta(1)
197
 
    >>> string_to_delta("1w")
 
194
    >>> string_to_delta('1w')
198
195
    datetime.timedelta(7)
199
 
    >>> string_to_delta("5m 30s")
 
196
    >>> string_to_delta('5m 30s')
200
197
    datetime.timedelta(0, 330)
201
198
    """
202
199
    
223
220
            value += datetime.timedelta(0, 0, 0, int(num))
224
221
    return value
225
222
 
 
223
 
226
224
def print_clients(clients, keywords):
227
225
    def valuetostring(value, keyword):
228
226
        if type(value) is dbus.Boolean:
230
228
        if keyword in ("Timeout", "Interval", "ApprovalDelay",
231
229
                       "ApprovalDuration", "ExtendedTimeout"):
232
230
            return milliseconds_to_string(value)
233
 
        return unicode(value)
 
231
        return str(value)
234
232
    
235
233
    # Create format string to print table rows
236
234
    format_string = " ".join("{{{key}:{width}}}".format(
237
 
            width = max(len(tablewords[key]),
238
 
                        max(len(valuetostring(client[key],
239
 
                                              key))
240
 
                            for client in
241
 
                            clients)),
242
 
            key = key) for key in keywords)
 
235
        width = max(len(tablewords[key]),
 
236
                    max(len(valuetostring(client[key], key))
 
237
                        for client in clients)),
 
238
        key = key)
 
239
                             for key in keywords)
243
240
    # Print header line
244
241
    print(format_string.format(**tablewords))
245
242
    for client in clients:
246
 
        print(format_string.format(**dict((key,
247
 
                                           valuetostring(client[key],
248
 
                                                         key))
249
 
                                          for key in keywords)))
 
243
        print(format_string.format(**{
 
244
            key: valuetostring(client[key], key)
 
245
            for key in keywords }))
 
246
 
250
247
 
251
248
def has_actions(options):
252
249
    return any((options.enable,
268
265
                options.approve,
269
266
                options.deny))
270
267
 
 
268
 
271
269
def main():
272
270
    parser = argparse.ArgumentParser()
273
271
    parser.add_argument("--version", action="version",
274
 
                        version = "%(prog)s {0}".format(version),
 
272
                        version = "%(prog)s {}".format(version),
275
273
                        help="show version number and exit")
276
274
    parser.add_argument("-a", "--all", action="store_true",
277
275
                        help="Select all clients")
310
308
    parser.add_argument("--approval-duration",
311
309
                        help="Set duration of one client approval")
312
310
    parser.add_argument("-H", "--host", help="Set host for client")
313
 
    parser.add_argument("-s", "--secret", type=file,
 
311
    parser.add_argument("-s", "--secret",
 
312
                        type=argparse.FileType(mode="rb"),
314
313
                        help="Set password blob (file) for client")
315
314
    parser.add_argument("-A", "--approve", action="store_true",
316
315
                        help="Approve any current client request")
331
330
 
332
331
    if options.check:
333
332
        fail_count, test_count = doctest.testmod()
334
 
        sys.exit(0 if fail_count == 0 else 1)
 
333
        sys.exit(os.EX_OK if fail_count == 0 else 1)
335
334
    
336
335
    try:
337
336
        bus = dbus.SystemBus()
338
337
        mandos_dbus_objc = bus.get_object(busname, server_path)
339
338
    except dbus.exceptions.DBusException:
340
 
        print("Could not connect to Mandos server",
341
 
              file=sys.stderr)
 
339
        print("Could not connect to Mandos server", file=sys.stderr)
342
340
        sys.exit(1)
343
341
    
344
342
    mandos_serv = dbus.Interface(mandos_dbus_objc,
365
363
    clients={}
366
364
    
367
365
    if options.all or not options.client:
368
 
        clients = dict((bus.get_object(busname, path), properties)
369
 
                       for path, properties in
370
 
                       mandos_clients.iteritems())
 
366
        clients = { bus.get_object(busname, path): properties
 
367
                    for path, properties in mandos_clients.items() }
371
368
    else:
372
369
        for name in options.client:
373
 
            for path, client in mandos_clients.iteritems():
 
370
            for path, client in mandos_clients.items():
374
371
                if client["Name"] == name:
375
372
                    client_objc = bus.get_object(busname, path)
376
373
                    clients[client_objc] = client
377
374
                    break
378
375
            else:
379
 
                print("Client not found on server: {0!r}"
 
376
                print("Client not found on server: {!r}"
380
377
                      .format(name), file=sys.stderr)
381
378
                sys.exit(1)
382
379
    
383
380
    if not has_actions(options) and clients:
384
381
        if options.verbose:
385
 
            keywords = ("Name", "Enabled", "Timeout",
386
 
                        "LastCheckedOK", "Created", "Interval",
387
 
                        "Host", "Fingerprint", "CheckerRunning",
388
 
                        "LastEnabled", "ApprovalPending",
389
 
                        "ApprovedByDefault",
 
382
            keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK",
 
383
                        "Created", "Interval", "Host", "Fingerprint",
 
384
                        "CheckerRunning", "LastEnabled",
 
385
                        "ApprovalPending", "ApprovedByDefault",
390
386
                        "LastApprovalRequest", "ApprovalDelay",
391
387
                        "ApprovalDuration", "Checker",
392
388
                        "ExtendedTimeout")
397
393
    else:
398
394
        # Process each client in the list by all selected options
399
395
        for client in clients:
 
396
            
400
397
            def set_client_prop(prop, value):
401
398
                """Set a Client D-Bus property"""
402
399
                client.Set(client_interface, prop, value,
403
400
                           dbus_interface=dbus.PROPERTIES_IFACE)
 
401
            
404
402
            def set_client_prop_ms(prop, value):
405
403
                """Set a Client D-Bus property, converted
406
404
                from a string to milliseconds."""
407
405
                set_client_prop(prop,
408
 
                                timedelta_to_milliseconds
409
 
                                (string_to_delta(value)))
 
406
                                string_to_delta(value).total_seconds()
 
407
                                * 1000)
 
408
            
410
409
            if options.remove:
411
410
                mandos_serv.RemoveClient(client.__dbus_object_path__)
412
411
            if options.enable:
456
455
                client.Approve(dbus.Boolean(False),
457
456
                               dbus_interface=client_interface)
458
457
 
 
458
 
459
459
if __name__ == "__main__":
460
460
    main()