/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.0"
 
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):
88
 
    """Parse a RFC 3339 "duration" and return a datetime.timedelta
 
88
    """Parse an RFC 3339 "duration" and return a datetime.timedelta
89
89
    
90
90
    >>> rfc3339_duration_to_delta("P7D")
91
91
    datetime.timedelta(7)
103
103
    datetime.timedelta(1, 200)
104
104
    """
105
105
    
106
 
    # Parsing a RFC 3339 duration with regular expressions is not
 
106
    # Parsing an RFC 3339 duration with regular expressions is not
107
107
    # possible - there would have to be multiple places for the same
108
 
    # values, like seconds.  This, while more esoteric, is cleaner
109
 
    # without depending on a parsing library.  If Python had a
 
108
    # values, like seconds.  The current code, while more esoteric, is
 
109
    # cleaner without depending on a parsing library.  If Python had a
110
110
    # built-in library for parsing we would use it, but we'd like to
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
 
    value = datetime.timedelta(0)
203
 
    regexp = re.compile(r"(\d+)([dsmhw]?)")
204
199
    
205
200
    try:
206
201
        return rfc3339_duration_to_delta(interval)
207
202
    except ValueError:
208
203
        pass
209
204
    
 
205
    value = datetime.timedelta(0)
 
206
    regexp = re.compile(r"(\d+)([dsmhw]?)")
 
207
    
210
208
    for num, suffix in regexp.findall(interval):
211
209
        if suffix == "d":
212
210
            value += datetime.timedelta(int(num))
222
220
            value += datetime.timedelta(0, 0, 0, int(num))
223
221
    return value
224
222
 
 
223
 
225
224
def print_clients(clients, keywords):
226
225
    def valuetostring(value, keyword):
227
226
        if type(value) is dbus.Boolean:
229
228
        if keyword in ("Timeout", "Interval", "ApprovalDelay",
230
229
                       "ApprovalDuration", "ExtendedTimeout"):
231
230
            return milliseconds_to_string(value)
232
 
        return unicode(value)
 
231
        return str(value)
233
232
    
234
233
    # Create format string to print table rows
235
234
    format_string = " ".join("{{{key}:{width}}}".format(
236
 
            width = max(len(tablewords[key]),
237
 
                        max(len(valuetostring(client[key],
238
 
                                              key))
239
 
                            for client in
240
 
                            clients)),
241
 
            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)
242
240
    # Print header line
243
241
    print(format_string.format(**tablewords))
244
242
    for client in clients:
245
 
        print(format_string.format(**dict((key,
246
 
                                           valuetostring(client[key],
247
 
                                                         key))
248
 
                                          for key in keywords)))
 
243
        print(format_string.format(**{
 
244
            key: valuetostring(client[key], key)
 
245
            for key in keywords }))
 
246
 
249
247
 
250
248
def has_actions(options):
251
249
    return any((options.enable,
267
265
                options.approve,
268
266
                options.deny))
269
267
 
 
268
 
270
269
def main():
271
270
    parser = argparse.ArgumentParser()
272
271
    parser.add_argument("--version", action="version",
273
 
                        version = "%(prog)s {0}".format(version),
 
272
                        version = "%(prog)s {}".format(version),
274
273
                        help="show version number and exit")
275
274
    parser.add_argument("-a", "--all", action="store_true",
276
275
                        help="Select all clients")
309
308
    parser.add_argument("--approval-duration",
310
309
                        help="Set duration of one client approval")
311
310
    parser.add_argument("-H", "--host", help="Set host for client")
312
 
    parser.add_argument("-s", "--secret", type=file,
 
311
    parser.add_argument("-s", "--secret",
 
312
                        type=argparse.FileType(mode="rb"),
313
313
                        help="Set password blob (file) for client")
314
314
    parser.add_argument("-A", "--approve", action="store_true",
315
315
                        help="Approve any current client request")
330
330
 
331
331
    if options.check:
332
332
        fail_count, test_count = doctest.testmod()
333
 
        sys.exit(0 if fail_count == 0 else 1)
 
333
        sys.exit(os.EX_OK if fail_count == 0 else 1)
334
334
    
335
335
    try:
336
336
        bus = dbus.SystemBus()
337
337
        mandos_dbus_objc = bus.get_object(busname, server_path)
338
338
    except dbus.exceptions.DBusException:
339
 
        print("Could not connect to Mandos server",
340
 
              file=sys.stderr)
 
339
        print("Could not connect to Mandos server", file=sys.stderr)
341
340
        sys.exit(1)
342
341
    
343
342
    mandos_serv = dbus.Interface(mandos_dbus_objc,
364
363
    clients={}
365
364
    
366
365
    if options.all or not options.client:
367
 
        clients = dict((bus.get_object(busname, path), properties)
368
 
                       for path, properties in
369
 
                       mandos_clients.iteritems())
 
366
        clients = { bus.get_object(busname, path): properties
 
367
                    for path, properties in mandos_clients.items() }
370
368
    else:
371
369
        for name in options.client:
372
 
            for path, client in mandos_clients.iteritems():
 
370
            for path, client in mandos_clients.items():
373
371
                if client["Name"] == name:
374
372
                    client_objc = bus.get_object(busname, path)
375
373
                    clients[client_objc] = client
376
374
                    break
377
375
            else:
378
 
                print("Client not found on server: {0!r}"
 
376
                print("Client not found on server: {!r}"
379
377
                      .format(name), file=sys.stderr)
380
378
                sys.exit(1)
381
379
    
382
380
    if not has_actions(options) and clients:
383
381
        if options.verbose:
384
 
            keywords = ("Name", "Enabled", "Timeout",
385
 
                        "LastCheckedOK", "Created", "Interval",
386
 
                        "Host", "Fingerprint", "CheckerRunning",
387
 
                        "LastEnabled", "ApprovalPending",
388
 
                        "ApprovedByDefault",
 
382
            keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK",
 
383
                        "Created", "Interval", "Host", "Fingerprint",
 
384
                        "CheckerRunning", "LastEnabled",
 
385
                        "ApprovalPending", "ApprovedByDefault",
389
386
                        "LastApprovalRequest", "ApprovalDelay",
390
387
                        "ApprovalDuration", "Checker",
391
388
                        "ExtendedTimeout")
396
393
    else:
397
394
        # Process each client in the list by all selected options
398
395
        for client in clients:
 
396
            
399
397
            def set_client_prop(prop, value):
400
398
                """Set a Client D-Bus property"""
401
399
                client.Set(client_interface, prop, value,
402
400
                           dbus_interface=dbus.PROPERTIES_IFACE)
 
401
            
403
402
            def set_client_prop_ms(prop, value):
404
403
                """Set a Client D-Bus property, converted
405
404
                from a string to milliseconds."""
406
405
                set_client_prop(prop,
407
 
                                timedelta_to_milliseconds
408
 
                                (string_to_delta(value)))
 
406
                                string_to_delta(value).total_seconds()
 
407
                                * 1000)
 
408
            
409
409
            if options.remove:
410
410
                mandos_serv.RemoveClient(client.__dbus_object_path__)
411
411
            if options.enable:
455
455
                client.Approve(dbus.Boolean(False),
456
456
                               dbus_interface=client_interface)
457
457
 
 
458
 
458
459
if __name__ == "__main__":
459
460
    main()