/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk
24.1.116 by Björn Påhlsson
added a mandos list client program
1
#!/usr/bin/python
985 by Teddy Hogeborn
Make Emacs run tests when mandos-ctl file is saved
2
# -*- mode: python; coding: utf-8; after-save-hook: (lambda () (let ((command (if (and (boundp 'tramp-file-name-structure) (string-match (car tramp-file-name-structure) (buffer-file-name))) (tramp-file-name-localname (tramp-dissect-file-name (buffer-file-name))) (buffer-file-name)))) (if (= (shell-command (format "%s --check" (shell-quote-argument command)) "*Test*") 0) (let ((w (get-buffer-window "*Test*"))) (if w (delete-window w)) (kill-buffer "*Test*")) (display-buffer "*Test*")))); -*-
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
3
#
444 by Teddy Hogeborn
Update copyright year to "2010" wherever appropriate.
4
# Mandos Monitor - Control and monitor the Mandos server
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
5
#
969 by Teddy Hogeborn
Update copyright year to 2019
6
# Copyright © 2008-2019 Teddy Hogeborn
7
# Copyright © 2008-2019 Björn Påhlsson
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
8
#
907 by Teddy Hogeborn
Alter copyright notices slightly. Actual license is unchanged!
9
# This file is part of Mandos.
10
#
11
# Mandos is free software: you can redistribute it and/or modify it
12
# under the terms of the GNU General Public License as published by
444 by Teddy Hogeborn
Update copyright year to "2010" wherever appropriate.
13
# the Free Software Foundation, either version 3 of the License, or
14
# (at your option) any later version.
15
#
907 by Teddy Hogeborn
Alter copyright notices slightly. Actual license is unchanged!
16
#     Mandos is distributed in the hope that it will be useful, but
17
#     WITHOUT ANY WARRANTY; without even the implied warranty of
444 by Teddy Hogeborn
Update copyright year to "2010" wherever appropriate.
18
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
#     GNU General Public License for more details.
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
20
#
444 by Teddy Hogeborn
Update copyright year to "2010" wherever appropriate.
21
# You should have received a copy of the GNU General Public License
907 by Teddy Hogeborn
Alter copyright notices slightly. Actual license is unchanged!
22
# along with Mandos.  If not, see <http://www.gnu.org/licenses/>.
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
23
#
505.1.2 by Teddy Hogeborn
Change "fukt.bsnet.se" to "recompile.se" throughout.
24
# Contact the authors at <mandos@recompile.se>.
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
25
#
24.1.116 by Björn Påhlsson
added a mandos list client program
26
463.1.9 by teddy at bsnet
* mandos-ctl: Use print function.
27
from __future__ import (division, absolute_import, print_function,
28
                        unicode_literals)
463.1.8 by teddy at bsnet
* mandos-ctl: Use unicode string literals.
29
718 by Teddy Hogeborn
mandos-ctl: Make it work in Python 3.
30
try:
31
    from future_builtins import *
32
except ImportError:
33
    pass
579 by Teddy Hogeborn
* mandos: Use all new builtins.
34
24.1.119 by Björn Påhlsson
Added more method support for mandos clients through mandos-ctl
35
import sys
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
36
import argparse
240 by Teddy Hogeborn
Merge "mandos-list" from belorn.
37
import locale
24.1.121 by Björn Påhlsson
mandos-ctl: Added support for all client calls
38
import datetime
39
import re
24.1.163 by Björn Påhlsson
mandos-client: Added never ending loop for --connect
40
import os
608 by Teddy Hogeborn
* Makefile (check): Also check mandos-ctl.
41
import collections
863 by Teddy Hogeborn
mandos-ctl: Implement --dump-json option
42
import json
984 by Teddy Hogeborn
Make mandos-ctl use unittest instead of doctest module
43
import unittest
987 by Teddy Hogeborn
mandos-ctl: Use logging module instead of print() for errors
44
import logging
608 by Teddy Hogeborn
* Makefile (check): Also check mandos-ctl.
45
46
import dbus
240 by Teddy Hogeborn
Merge "mandos-list" from belorn.
47
987 by Teddy Hogeborn
mandos-ctl: Use logging module instead of print() for errors
48
log = logging.getLogger(sys.argv[0])
49
logging.basicConfig(level="INFO", # Show info level messages
50
                    format="%(message)s") # Show basic log messages
51
723.1.7 by Teddy Hogeborn
Use the .major attribute on sys.version_info instead of using "[0]".
52
if sys.version_info.major == 2:
718 by Teddy Hogeborn
mandos-ctl: Make it work in Python 3.
53
    str = unicode
54
463.1.8 by teddy at bsnet
* mandos-ctl: Use unicode string literals.
55
locale.setlocale(locale.LC_ALL, "")
24.1.116 by Björn Påhlsson
added a mandos list client program
56
57
tablewords = {
463.1.8 by teddy at bsnet
* mandos-ctl: Use unicode string literals.
58
    "Name": "Name",
59
    "Enabled": "Enabled",
60
    "Timeout": "Timeout",
61
    "LastCheckedOK": "Last Successful Check",
62
    "LastApprovalRequest": "Last Approval Request",
63
    "Created": "Created",
64
    "Interval": "Interval",
65
    "Host": "Host",
66
    "Fingerprint": "Fingerprint",
962 by Teddy Hogeborn
Add support for using raw public keys in TLS (RFC 7250)
67
    "KeyID": "Key ID",
463.1.8 by teddy at bsnet
* mandos-ctl: Use unicode string literals.
68
    "CheckerRunning": "Check Is Running",
69
    "LastEnabled": "Last Enabled",
70
    "ApprovalPending": "Approval Is Pending",
71
    "ApprovedByDefault": "Approved By Default",
72
    "ApprovalDelay": "Approval Delay",
73
    "ApprovalDuration": "Approval Duration",
74
    "Checker": "Checker",
865 by Teddy Hogeborn
mandos-ctl: Include "Expires" and "LastCheckerStatus" properties
75
    "ExtendedTimeout": "Extended Timeout",
76
    "Expires": "Expires",
77
    "LastCheckerStatus": "Last Checker Status",
745 by Teddy Hogeborn
mandos-ctl: Do minor formatting and whitespace adjustments.
78
}
463.1.8 by teddy at bsnet
* mandos-ctl: Use unicode string literals.
79
defaultkeywords = ("Name", "Enabled", "Timeout", "LastCheckedOK")
24.1.186 by Björn Påhlsson
transitional stuff actually working
80
domain = "se.recompile"
463.1.8 by teddy at bsnet
* mandos-ctl: Use unicode string literals.
81
busname = domain + ".Mandos"
82
server_path = "/"
83
server_interface = domain + ".Mandos"
84
client_interface = domain + ".Mandos.Client"
237.4.108 by Teddy Hogeborn
* Makefile (version): Change to 1.8.3.
85
version = "1.8.3"
24.1.118 by Björn Påhlsson
Added enable/disable
86
745 by Teddy Hogeborn
mandos-ctl: Do minor formatting and whitespace adjustments.
87
785 by Teddy Hogeborn
Support the standard org.freedesktop.DBus.ObjectManager interface.
88
try:
89
    dbus.OBJECT_MANAGER_IFACE
90
except AttributeError:
91
    dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
92
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
93
24.1.121 by Björn Påhlsson
mandos-ctl: Added support for all client calls
94
def milliseconds_to_string(ms):
95
    td = datetime.timedelta(0, 0, 0, ms)
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
96
    return ("{days}{hours:02}:{minutes:02}:{seconds:02}"
97
            .format(days="{}T".format(td.days) if td.days else "",
98
                    hours=td.seconds // 3600,
99
                    minutes=(td.seconds % 3600) // 60,
100
                    seconds=td.seconds % 60))
24.1.121 by Björn Påhlsson
mandos-ctl: Added support for all client calls
101
608 by Teddy Hogeborn
* Makefile (check): Also check mandos-ctl.
102
103
def rfc3339_duration_to_delta(duration):
609 by Teddy Hogeborn
* clients.conf: Convert all time intervals to new RFC 3339 syntax.
104
    """Parse an RFC 3339 "duration" and return a datetime.timedelta
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
105
608 by Teddy Hogeborn
* Makefile (check): Also check mandos-ctl.
106
    >>> rfc3339_duration_to_delta("P7D")
107
    datetime.timedelta(7)
108
    >>> rfc3339_duration_to_delta("PT60S")
109
    datetime.timedelta(0, 60)
110
    >>> rfc3339_duration_to_delta("PT60M")
111
    datetime.timedelta(0, 3600)
112
    >>> rfc3339_duration_to_delta("PT24H")
113
    datetime.timedelta(1)
114
    >>> rfc3339_duration_to_delta("P1W")
115
    datetime.timedelta(7)
116
    >>> rfc3339_duration_to_delta("PT5M30S")
117
    datetime.timedelta(0, 330)
118
    >>> rfc3339_duration_to_delta("P1DT3M20S")
119
    datetime.timedelta(1, 200)
120
    """
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
121
609 by Teddy Hogeborn
* clients.conf: Convert all time intervals to new RFC 3339 syntax.
122
    # Parsing an RFC 3339 duration with regular expressions is not
608 by Teddy Hogeborn
* Makefile (check): Also check mandos-ctl.
123
    # possible - there would have to be multiple places for the same
609 by Teddy Hogeborn
* clients.conf: Convert all time intervals to new RFC 3339 syntax.
124
    # values, like seconds.  The current code, while more esoteric, is
125
    # cleaner without depending on a parsing library.  If Python had a
608 by Teddy Hogeborn
* Makefile (check): Also check mandos-ctl.
126
    # built-in library for parsing we would use it, but we'd like to
127
    # avoid excessive use of external libraries.
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
128
608 by Teddy Hogeborn
* Makefile (check): Also check mandos-ctl.
129
    # New type for defining tokens, syntax, and semantics all-in-one
753 by Teddy Hogeborn
mandos-ctl: Generate better messages in exceptions.
130
    Token = collections.namedtuple("Token", (
131
        "regexp",  # To match token; if "value" is not None, must have
132
                   # a "group" containing digits
133
        "value",   # datetime.timedelta or None
134
        "followers"))           # Tokens valid after this token
608 by Teddy Hogeborn
* Makefile (check): Also check mandos-ctl.
135
    # RFC 3339 "duration" tokens, syntax, and semantics; taken from
136
    # the "duration" ABNF definition in RFC 3339, Appendix A.
137
    token_end = Token(re.compile(r"$"), None, frozenset())
138
    token_second = Token(re.compile(r"(\d+)S"),
139
                         datetime.timedelta(seconds=1),
745 by Teddy Hogeborn
mandos-ctl: Do minor formatting and whitespace adjustments.
140
                         frozenset((token_end, )))
608 by Teddy Hogeborn
* Makefile (check): Also check mandos-ctl.
141
    token_minute = Token(re.compile(r"(\d+)M"),
142
                         datetime.timedelta(minutes=1),
143
                         frozenset((token_second, token_end)))
144
    token_hour = Token(re.compile(r"(\d+)H"),
145
                       datetime.timedelta(hours=1),
146
                       frozenset((token_minute, token_end)))
147
    token_time = Token(re.compile(r"T"),
148
                       None,
149
                       frozenset((token_hour, token_minute,
150
                                  token_second)))
151
    token_day = Token(re.compile(r"(\d+)D"),
152
                      datetime.timedelta(days=1),
153
                      frozenset((token_time, token_end)))
154
    token_month = Token(re.compile(r"(\d+)M"),
155
                        datetime.timedelta(weeks=4),
156
                        frozenset((token_day, token_end)))
157
    token_year = Token(re.compile(r"(\d+)Y"),
158
                       datetime.timedelta(weeks=52),
159
                       frozenset((token_month, token_end)))
160
    token_week = Token(re.compile(r"(\d+)W"),
161
                       datetime.timedelta(weeks=1),
745 by Teddy Hogeborn
mandos-ctl: Do minor formatting and whitespace adjustments.
162
                       frozenset((token_end, )))
608 by Teddy Hogeborn
* Makefile (check): Also check mandos-ctl.
163
    token_duration = Token(re.compile(r"P"), None,
164
                           frozenset((token_year, token_month,
165
                                      token_day, token_time,
721 by Teddy Hogeborn
Fix two mutually cancelling bugs.
166
                                      token_week)))
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
167
    # Define starting values:
168
    # Value so far
169
    value = datetime.timedelta()
608 by Teddy Hogeborn
* Makefile (check): Also check mandos-ctl.
170
    found_token = None
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
171
    # Following valid tokens
172
    followers = frozenset((token_duration, ))
173
    # String left to parse
174
    s = duration
608 by Teddy Hogeborn
* Makefile (check): Also check mandos-ctl.
175
    # Loop until end token is found
176
    while found_token is not token_end:
177
        # Search for any currently valid tokens
178
        for token in followers:
179
            match = token.regexp.match(s)
180
            if match is not None:
181
                # Token found
182
                if token.value is not None:
183
                    # Value found, parse digits
184
                    factor = int(match.group(1), 10)
185
                    # Add to value so far
186
                    value += factor * token.value
187
                # Strip token from string
188
                s = token.regexp.sub("", s, 1)
189
                # Go to found token
190
                found_token = token
191
                # Set valid next tokens
192
                followers = found_token.followers
193
                break
194
        else:
195
            # No currently valid tokens were found
753 by Teddy Hogeborn
mandos-ctl: Generate better messages in exceptions.
196
            raise ValueError("Invalid RFC 3339 duration: {!r}"
197
                             .format(duration))
608 by Teddy Hogeborn
* Makefile (check): Also check mandos-ctl.
198
    # End token found
199
    return value
200
201
24.1.121 by Björn Påhlsson
mandos-ctl: Added support for all client calls
202
def string_to_delta(interval):
203
    """Parse a string and return a datetime.timedelta
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
204
753 by Teddy Hogeborn
mandos-ctl: Generate better messages in exceptions.
205
    >>> string_to_delta('7d')
24.1.121 by Björn Påhlsson
mandos-ctl: Added support for all client calls
206
    datetime.timedelta(7)
753 by Teddy Hogeborn
mandos-ctl: Generate better messages in exceptions.
207
    >>> string_to_delta('60s')
24.1.121 by Björn Påhlsson
mandos-ctl: Added support for all client calls
208
    datetime.timedelta(0, 60)
753 by Teddy Hogeborn
mandos-ctl: Generate better messages in exceptions.
209
    >>> string_to_delta('60m')
24.1.121 by Björn Påhlsson
mandos-ctl: Added support for all client calls
210
    datetime.timedelta(0, 3600)
753 by Teddy Hogeborn
mandos-ctl: Generate better messages in exceptions.
211
    >>> string_to_delta('24h')
24.1.121 by Björn Påhlsson
mandos-ctl: Added support for all client calls
212
    datetime.timedelta(1)
753 by Teddy Hogeborn
mandos-ctl: Generate better messages in exceptions.
213
    >>> string_to_delta('1w')
24.1.121 by Björn Påhlsson
mandos-ctl: Added support for all client calls
214
    datetime.timedelta(7)
753 by Teddy Hogeborn
mandos-ctl: Generate better messages in exceptions.
215
    >>> string_to_delta('5m 30s')
24.1.121 by Björn Påhlsson
mandos-ctl: Added support for all client calls
216
    datetime.timedelta(0, 330)
217
    """
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
218
608 by Teddy Hogeborn
* Makefile (check): Also check mandos-ctl.
219
    try:
220
        return rfc3339_duration_to_delta(interval)
221
    except ValueError:
222
        pass
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
223
616 by Teddy Hogeborn
* mandos-ctl (string_to_delta): Try to parse RFC 3339 duration before
224
    value = datetime.timedelta(0)
225
    regexp = re.compile(r"(\d+)([dsmhw]?)")
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
226
518.2.9 by Teddy Hogeborn
* mandos (ClientDBus.approval_delay, ClientDBus.approval_duration,
227
    for num, suffix in regexp.findall(interval):
228
        if suffix == "d":
229
            value += datetime.timedelta(int(num))
230
        elif suffix == "s":
231
            value += datetime.timedelta(0, int(num))
232
        elif suffix == "m":
233
            value += datetime.timedelta(0, 0, 0, 0, int(num))
234
        elif suffix == "h":
235
            value += datetime.timedelta(0, 0, 0, 0, 0, int(num))
236
        elif suffix == "w":
237
            value += datetime.timedelta(0, 0, 0, 0, 0, 0, int(num))
238
        elif suffix == "":
239
            value += datetime.timedelta(0, 0, 0, int(num))
240
    return value
24.1.121 by Björn Påhlsson
mandos-ctl: Added support for all client calls
241
745 by Teddy Hogeborn
mandos-ctl: Do minor formatting and whitespace adjustments.
242
24.1.163 by Björn Påhlsson
mandos-client: Added never ending loop for --connect
243
def print_clients(clients, keywords):
24.1.121 by Björn Påhlsson
mandos-ctl: Added support for all client calls
244
    def valuetostring(value, keyword):
245
        if type(value) is dbus.Boolean:
463.1.8 by teddy at bsnet
* mandos-ctl: Use unicode string literals.
246
            return "Yes" if value else "No"
247
        if keyword in ("Timeout", "Interval", "ApprovalDelay",
571 by Teddy Hogeborn
* mandos-ctl (print_clients): Bug fix: Don't show "Extended Timeout"
248
                       "ApprovalDuration", "ExtendedTimeout"):
24.1.121 by Björn Påhlsson
mandos-ctl: Added support for all client calls
249
            return milliseconds_to_string(value)
718 by Teddy Hogeborn
mandos-ctl: Make it work in Python 3.
250
        return str(value)
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
251
382 by Teddy Hogeborn
* mandos-ctl: Made work again after D-Bus API changes.
252
    # Create format string to print table rows
569 by Teddy Hogeborn
* mandos-ctl: Use new string format method. Bug fix: --version now
253
    format_string = " ".join("{{{key}:{width}}}".format(
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
254
        width=max(len(tablewords[key]),
255
                  max(len(valuetostring(client[key], key))
256
                      for client in clients)),
257
        key=key)
745 by Teddy Hogeborn
mandos-ctl: Do minor formatting and whitespace adjustments.
258
                             for key in keywords)
382 by Teddy Hogeborn
* mandos-ctl: Made work again after D-Bus API changes.
259
    # Print header line
569 by Teddy Hogeborn
* mandos-ctl: Use new string format method. Bug fix: --version now
260
    print(format_string.format(**tablewords))
24.1.121 by Björn Påhlsson
mandos-ctl: Added support for all client calls
261
    for client in clients:
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
262
        print(format_string
263
              .format(**{key: valuetostring(client[key], key)
264
                         for key in keywords}))
745 by Teddy Hogeborn
mandos-ctl: Do minor formatting and whitespace adjustments.
265
438 by Teddy Hogeborn
* mandos (Client.runtime_expansions): New attribute containing the
266
24.1.163 by Björn Påhlsson
mandos-client: Added never ending loop for --connect
267
def has_actions(options):
268
    return any((options.enable,
269
                options.disable,
270
                options.bump_timeout,
271
                options.start_checker,
272
                options.stop_checker,
273
                options.is_enabled,
274
                options.remove,
275
                options.checker is not None,
276
                options.timeout is not None,
24.1.179 by Björn Påhlsson
New feature:
277
                options.extended_timeout is not None,
24.1.163 by Björn Påhlsson
mandos-client: Added never ending loop for --connect
278
                options.interval is not None,
441 by Teddy Hogeborn
* mandos (ClientDBus.__init__): Bug fix: Translate "-" in client names
279
                options.approved_by_default is not None,
280
                options.approval_delay is not None,
281
                options.approval_duration is not None,
24.1.163 by Björn Påhlsson
mandos-client: Added never ending loop for --connect
282
                options.host is not None,
283
                options.secret is not None,
284
                options.approve,
285
                options.deny))
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
286
745 by Teddy Hogeborn
mandos-ctl: Do minor formatting and whitespace adjustments.
287
24.1.163 by Björn Påhlsson
mandos-client: Added never ending loop for --connect
288
def main():
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
289
    parser = argparse.ArgumentParser()
290
    parser.add_argument("--version", action="version",
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
291
                        version="%(prog)s {}".format(version),
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
292
                        help="show version number and exit")
293
    parser.add_argument("-a", "--all", action="store_true",
294
                        help="Select all clients")
295
    parser.add_argument("-v", "--verbose", action="store_true",
296
                        help="Print all fields")
863 by Teddy Hogeborn
mandos-ctl: Implement --dump-json option
297
    parser.add_argument("-j", "--dump-json", action="store_true",
298
                        help="Dump client data in JSON format")
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
299
    parser.add_argument("-e", "--enable", action="store_true",
300
                        help="Enable client")
301
    parser.add_argument("-d", "--disable", action="store_true",
302
                        help="disable client")
303
    parser.add_argument("-b", "--bump-timeout", action="store_true",
304
                        help="Bump timeout for client")
305
    parser.add_argument("--start-checker", action="store_true",
306
                        help="Start checker for client")
307
    parser.add_argument("--stop-checker", action="store_true",
308
                        help="Stop checker for client")
309
    parser.add_argument("-V", "--is-enabled", action="store_true",
310
                        help="Check if client is enabled")
311
    parser.add_argument("-r", "--remove", action="store_true",
312
                        help="Remove client")
313
    parser.add_argument("-c", "--checker",
314
                        help="Set checker command for client")
315
    parser.add_argument("-t", "--timeout",
316
                        help="Set timeout for client")
24.1.179 by Björn Påhlsson
New feature:
317
    parser.add_argument("--extended-timeout",
318
                        help="Set extended timeout for client")
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
319
    parser.add_argument("-i", "--interval",
320
                        help="Set checker interval for client")
321
    parser.add_argument("--approve-by-default", action="store_true",
322
                        default=None, dest="approved_by_default",
323
                        help="Set client to be approved by default")
324
    parser.add_argument("--deny-by-default", action="store_false",
325
                        dest="approved_by_default",
326
                        help="Set client to be denied by default")
327
    parser.add_argument("--approval-delay",
328
                        help="Set delay before client approve/deny")
329
    parser.add_argument("--approval-duration",
330
                        help="Set duration of one client approval")
331
    parser.add_argument("-H", "--host", help="Set host for client")
718 by Teddy Hogeborn
mandos-ctl: Make it work in Python 3.
332
    parser.add_argument("-s", "--secret",
333
                        type=argparse.FileType(mode="rb"),
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
334
                        help="Set password blob (file) for client")
335
    parser.add_argument("-A", "--approve", action="store_true",
336
                        help="Approve any current client request")
337
    parser.add_argument("-D", "--deny", action="store_true",
338
                        help="Deny any current client request")
608 by Teddy Hogeborn
* Makefile (check): Also check mandos-ctl.
339
    parser.add_argument("--check", action="store_true",
340
                        help="Run self-test")
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
341
    parser.add_argument("client", nargs="*", help="Client name")
342
    options = parser.parse_args()
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
343
572 by Teddy Hogeborn
* mandos-ctl: Break long lines.
344
    if has_actions(options) and not (options.client or options.all):
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
345
        parser.error("Options require clients names or --all.")
346
    if options.verbose and has_actions(options):
863 by Teddy Hogeborn
mandos-ctl: Implement --dump-json option
347
        parser.error("--verbose can only be used alone.")
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
348
    if options.dump_json and (options.verbose
349
                              or has_actions(options)):
863 by Teddy Hogeborn
mandos-ctl: Implement --dump-json option
350
        parser.error("--dump-json can only be used alone.")
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
351
    if options.all and not has_actions(options):
352
        parser.error("--all requires an action.")
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
353
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
354
    try:
355
        bus = dbus.SystemBus()
356
        mandos_dbus_objc = bus.get_object(busname, server_path)
357
    except dbus.exceptions.DBusException:
987 by Teddy Hogeborn
mandos-ctl: Use logging module instead of print() for errors
358
        log.critical("Could not connect to Mandos server")
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
359
        sys.exit(1)
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
360
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
361
    mandos_serv = dbus.Interface(mandos_dbus_objc,
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
362
                                 dbus_interface=server_interface)
785 by Teddy Hogeborn
Support the standard org.freedesktop.DBus.ObjectManager interface.
363
    mandos_serv_object_manager = dbus.Interface(
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
364
        mandos_dbus_objc, dbus_interface=dbus.OBJECT_MANAGER_IFACE)
365
366
    # block stderr since dbus library prints to stderr
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
367
    null = os.open(os.path.devnull, os.O_RDWR)
368
    stderrcopy = os.dup(sys.stderr.fileno())
369
    os.dup2(null, sys.stderr.fileno())
370
    os.close(null)
371
    try:
372
        try:
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
373
            mandos_clients = {path: ifs_and_props[client_interface]
374
                              for path, ifs_and_props in
375
                              mandos_serv_object_manager
376
                              .GetManagedObjects().items()
377
                              if client_interface in ifs_and_props}
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
378
        finally:
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
379
            # restore stderr
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
380
            os.dup2(stderrcopy, sys.stderr.fileno())
381
            os.close(stderrcopy)
785 by Teddy Hogeborn
Support the standard org.freedesktop.DBus.ObjectManager interface.
382
    except dbus.exceptions.DBusException as e:
987 by Teddy Hogeborn
mandos-ctl: Use logging module instead of print() for errors
383
        log.critical("Failed to access Mandos server through D-Bus:"
384
                     "\n%s", e)
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
385
        sys.exit(1)
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
386
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
387
    # Compile dict of (clients: properties) to process
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
388
    clients = {}
389
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
390
    if options.all or not options.client:
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
391
        clients = {bus.get_object(busname, path): properties
392
                   for path, properties in mandos_clients.items()}
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
393
    else:
394
        for name in options.client:
723.1.4 by Teddy Hogeborn
Use the .items() method instead of .iteritems().
395
            for path, client in mandos_clients.items():
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
396
                if client["Name"] == name:
397
                    client_objc = bus.get_object(busname, path)
398
                    clients[client_objc] = client
399
                    break
24.1.163 by Björn Påhlsson
mandos-client: Added never ending loop for --connect
400
            else:
987 by Teddy Hogeborn
mandos-ctl: Use logging module instead of print() for errors
401
                log.critical("Client not found on server: %r", name)
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
402
                sys.exit(1)
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
403
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
404
    if not has_actions(options) and clients:
863 by Teddy Hogeborn
mandos-ctl: Implement --dump-json option
405
        if options.verbose or options.dump_json:
745 by Teddy Hogeborn
mandos-ctl: Do minor formatting and whitespace adjustments.
406
            keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK",
962 by Teddy Hogeborn
Add support for using raw public keys in TLS (RFC 7250)
407
                        "Created", "Interval", "Host", "KeyID",
408
                        "Fingerprint", "CheckerRunning",
409
                        "LastEnabled", "ApprovalPending",
410
                        "ApprovedByDefault", "LastApprovalRequest",
411
                        "ApprovalDelay", "ApprovalDuration",
412
                        "Checker", "ExtendedTimeout", "Expires",
865 by Teddy Hogeborn
mandos-ctl: Include "Expires" and "LastCheckerStatus" properties
413
                        "LastCheckerStatus")
24.1.163 by Björn Påhlsson
mandos-client: Added never ending loop for --connect
414
        else:
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
415
            keywords = defaultkeywords
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
416
863 by Teddy Hogeborn
mandos-ctl: Implement --dump-json option
417
        if options.dump_json:
864 by Teddy Hogeborn
mandos-ctl: Dump booleans as booleans in --dump-json output.
418
            json.dump({client["Name"]: {key:
419
                                        bool(client[key])
420
                                        if isinstance(client[key],
421
                                                      dbus.Boolean)
422
                                        else client[key]
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
423
                                        for key in keywords}
424
                       for client in clients.values()},
425
                      fp=sys.stdout, indent=4,
426
                      separators=(',', ': '))
863 by Teddy Hogeborn
mandos-ctl: Implement --dump-json option
427
            print()
428
        else:
429
            print_clients(clients.values(), keywords)
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
430
    else:
431
        # Process each client in the list by all selected options
432
        for client in clients:
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
433
569.1.2 by Teddy Hogeborn
* mandos-ctl (main): Use helper functions to shorten code.
434
            def set_client_prop(prop, value):
435
                """Set a Client D-Bus property"""
436
                client.Set(client_interface, prop, value,
437
                           dbus_interface=dbus.PROPERTIES_IFACE)
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
438
569.1.2 by Teddy Hogeborn
* mandos-ctl (main): Use helper functions to shorten code.
439
            def set_client_prop_ms(prop, value):
440
                """Set a Client D-Bus property, converted
441
                from a string to milliseconds."""
442
                set_client_prop(prop,
723.1.6 by Teddy Hogeborn
Use the new .total_seconds() method on datetime.timedelta objects.
443
                                string_to_delta(value).total_seconds()
444
                                * 1000)
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
445
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
446
            if options.remove:
447
                mandos_serv.RemoveClient(client.__dbus_object_path__)
448
            if options.enable:
569.1.2 by Teddy Hogeborn
* mandos-ctl (main): Use helper functions to shorten code.
449
                set_client_prop("Enabled", dbus.Boolean(True))
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
450
            if options.disable:
569.1.2 by Teddy Hogeborn
* mandos-ctl (main): Use helper functions to shorten code.
451
                set_client_prop("Enabled", dbus.Boolean(False))
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
452
            if options.bump_timeout:
569.1.2 by Teddy Hogeborn
* mandos-ctl (main): Use helper functions to shorten code.
453
                set_client_prop("LastCheckedOK", "")
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
454
            if options.start_checker:
569.1.2 by Teddy Hogeborn
* mandos-ctl (main): Use helper functions to shorten code.
455
                set_client_prop("CheckerRunning", dbus.Boolean(True))
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
456
            if options.stop_checker:
569.1.2 by Teddy Hogeborn
* mandos-ctl (main): Use helper functions to shorten code.
457
                set_client_prop("CheckerRunning", dbus.Boolean(False))
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
458
            if options.is_enabled:
872 by Teddy Hogeborn
PEP8 compliance: mandos-ctl
459
                if client.Get(client_interface, "Enabled",
460
                              dbus_interface=dbus.PROPERTIES_IFACE):
461
                    sys.exit(0)
462
                else:
463
                    sys.exit(1)
505.1.16 by Teddy Hogeborn
* mandos-ctl (main): Bug fix: Handle empty strings correctly.
464
            if options.checker is not None:
569.1.2 by Teddy Hogeborn
* mandos-ctl (main): Use helper functions to shorten code.
465
                set_client_prop("Checker", options.checker)
505.1.16 by Teddy Hogeborn
* mandos-ctl (main): Bug fix: Handle empty strings correctly.
466
            if options.host is not None:
569.1.2 by Teddy Hogeborn
* mandos-ctl (main): Use helper functions to shorten code.
467
                set_client_prop("Host", options.host)
505.1.16 by Teddy Hogeborn
* mandos-ctl (main): Bug fix: Handle empty strings correctly.
468
            if options.interval is not None:
569.1.2 by Teddy Hogeborn
* mandos-ctl (main): Use helper functions to shorten code.
469
                set_client_prop_ms("Interval", options.interval)
505.1.16 by Teddy Hogeborn
* mandos-ctl (main): Bug fix: Handle empty strings correctly.
470
            if options.approval_delay is not None:
569.1.2 by Teddy Hogeborn
* mandos-ctl (main): Use helper functions to shorten code.
471
                set_client_prop_ms("ApprovalDelay",
472
                                   options.approval_delay)
505.1.16 by Teddy Hogeborn
* mandos-ctl (main): Bug fix: Handle empty strings correctly.
473
            if options.approval_duration is not None:
569.1.2 by Teddy Hogeborn
* mandos-ctl (main): Use helper functions to shorten code.
474
                set_client_prop_ms("ApprovalDuration",
475
                                   options.approval_duration)
505.1.16 by Teddy Hogeborn
* mandos-ctl (main): Bug fix: Handle empty strings correctly.
476
            if options.timeout is not None:
569.1.2 by Teddy Hogeborn
* mandos-ctl (main): Use helper functions to shorten code.
477
                set_client_prop_ms("Timeout", options.timeout)
505.1.16 by Teddy Hogeborn
* mandos-ctl (main): Bug fix: Handle empty strings correctly.
478
            if options.extended_timeout is not None:
569.1.2 by Teddy Hogeborn
* mandos-ctl (main): Use helper functions to shorten code.
479
                set_client_prop_ms("ExtendedTimeout",
480
                                   options.extended_timeout)
505.1.16 by Teddy Hogeborn
* mandos-ctl (main): Bug fix: Handle empty strings correctly.
481
            if options.secret is not None:
569.1.2 by Teddy Hogeborn
* mandos-ctl (main): Use helper functions to shorten code.
482
                set_client_prop("Secret",
483
                                dbus.ByteArray(options.secret.read()))
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
484
            if options.approved_by_default is not None:
569.1.2 by Teddy Hogeborn
* mandos-ctl (main): Use helper functions to shorten code.
485
                set_client_prop("ApprovedByDefault",
486
                                dbus.Boolean(options
487
                                             .approved_by_default))
475 by teddy at bsnet
* mandos-ctl: Use the new argparse library instead of optparse.
488
            if options.approve:
489
                client.Approve(dbus.Boolean(True),
490
                               dbus_interface=client_interface)
491
            elif options.deny:
492
                client.Approve(dbus.Boolean(False),
493
                               dbus_interface=client_interface)
24.1.163 by Björn Påhlsson
mandos-client: Added never ending loop for --connect
494
984 by Teddy Hogeborn
Make mandos-ctl use unittest instead of doctest module
495

986 by Teddy Hogeborn
Add tests to mandos-ctl's milliseconds_to_string function
496
class Test_milliseconds_to_string(unittest.TestCase):
497
    def test_all(self):
498
        self.assertEqual(milliseconds_to_string(93785000),
499
                         "1T02:03:05")
500
    def test_no_days(self):
501
        self.assertEqual(milliseconds_to_string(7385000), "02:03:05")
502
    def test_all_zero(self):
503
        self.assertEqual(milliseconds_to_string(0), "00:00:00")
504
    def test_no_fractional_seconds(self):
505
        self.assertEqual(milliseconds_to_string(400), "00:00:00")
506
        self.assertEqual(milliseconds_to_string(900), "00:00:00")
507
        self.assertEqual(milliseconds_to_string(1900), "00:00:01")
508
509

984 by Teddy Hogeborn
Make mandos-ctl use unittest instead of doctest module
510
def should_only_run_tests():
511
    parser = argparse.ArgumentParser(add_help=False)
512
    parser.add_argument("--check", action='store_true')
513
    args, unknown_args = parser.parse_known_args()
514
    run_tests = args.check
515
    if run_tests:
516
        # Remove --check argument from sys.argv
517
        sys.argv[1:] = unknown_args
518
    return run_tests
519
520
# Add all tests from doctest strings
521
def load_tests(loader, tests, none):
522
    import doctest
523
    tests.addTests(doctest.DocTestSuite())
524
    return tests
745 by Teddy Hogeborn
mandos-ctl: Do minor formatting and whitespace adjustments.
525
463.1.8 by teddy at bsnet
* mandos-ctl: Use unicode string literals.
526
if __name__ == "__main__":
984 by Teddy Hogeborn
Make mandos-ctl use unittest instead of doctest module
527
    if should_only_run_tests():
528
        # Call using ./tdd-python-script --check [--verbose]
529
        unittest.main()
530
    else:
531
        main()