/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: Björn Påhlsson
  • Date: 2008-07-20 02:52:20 UTC
  • Revision ID: belorn@braxen-20080720025220-r5u0388uy9iu23h6
Added following support:
Pluginbased client handler
rewritten Mandos client
       Avahi instead of udp server discovery
       openpgp encrypted key support
Passprompt stand alone application for direct console input
Added logging for Mandos server

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python
2
 
# -*- mode: python; coding: utf-8 -*-
3
 
4
 
# Mandos Monitor - Control and monitor the Mandos server
5
 
6
 
# Copyright © 2008-2012 Teddy Hogeborn
7
 
# Copyright © 2008-2012 Björn Påhlsson
8
 
9
 
# This program is free software: you can redistribute it and/or modify
10
 
# it under the terms of the GNU General Public License as published by
11
 
# the Free Software Foundation, either version 3 of the License, or
12
 
# (at your option) any later version.
13
 
#
14
 
#     This program is distributed in the hope that it will be useful,
15
 
#     but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 
#     GNU General Public License for more details.
18
 
19
 
# You should have received a copy of the GNU General Public License
20
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 
22
 
# Contact the authors at <mandos@recompile.se>.
23
 
24
 
 
25
 
from __future__ import (division, absolute_import, print_function,
26
 
                        unicode_literals)
27
 
 
28
 
import sys
29
 
import dbus
30
 
import argparse
31
 
import locale
32
 
import datetime
33
 
import re
34
 
import os
35
 
 
36
 
locale.setlocale(locale.LC_ALL, "")
37
 
 
38
 
tablewords = {
39
 
    "Name": "Name",
40
 
    "Enabled": "Enabled",
41
 
    "Timeout": "Timeout",
42
 
    "LastCheckedOK": "Last Successful Check",
43
 
    "LastApprovalRequest": "Last Approval Request",
44
 
    "Created": "Created",
45
 
    "Interval": "Interval",
46
 
    "Host": "Host",
47
 
    "Fingerprint": "Fingerprint",
48
 
    "CheckerRunning": "Check Is Running",
49
 
    "LastEnabled": "Last Enabled",
50
 
    "ApprovalPending": "Approval Is Pending",
51
 
    "ApprovedByDefault": "Approved By Default",
52
 
    "ApprovalDelay": "Approval Delay",
53
 
    "ApprovalDuration": "Approval Duration",
54
 
    "Checker": "Checker",
55
 
    "ExtendedTimeout" : "Extended Timeout"
56
 
    }
57
 
defaultkeywords = ("Name", "Enabled", "Timeout", "LastCheckedOK")
58
 
domain = "se.recompile"
59
 
busname = domain + ".Mandos"
60
 
server_path = "/"
61
 
server_interface = domain + ".Mandos"
62
 
client_interface = domain + ".Mandos.Client"
63
 
version = "1.5.3"
64
 
 
65
 
def timedelta_to_milliseconds(td):
66
 
    """Convert a datetime.timedelta object to milliseconds"""
67
 
    return ((td.days * 24 * 60 * 60 * 1000)
68
 
            + (td.seconds * 1000)
69
 
            + (td.microseconds // 1000))
70
 
 
71
 
def milliseconds_to_string(ms):
72
 
    td = datetime.timedelta(0, 0, 0, ms)
73
 
    return ("{days}{hours:02}:{minutes:02}:{seconds:02}"
74
 
            .format(days = "{0}T".format(td.days) if td.days else "",
75
 
                    hours = td.seconds // 3600,
76
 
                    minutes = (td.seconds % 3600) // 60,
77
 
                    seconds = td.seconds % 60,
78
 
                    ))
79
 
 
80
 
def string_to_delta(interval):
81
 
    """Parse a string and return a datetime.timedelta
82
 
    
83
 
    >>> string_to_delta("7d")
84
 
    datetime.timedelta(7)
85
 
    >>> string_to_delta("60s")
86
 
    datetime.timedelta(0, 60)
87
 
    >>> string_to_delta("60m")
88
 
    datetime.timedelta(0, 3600)
89
 
    >>> string_to_delta("24h")
90
 
    datetime.timedelta(1)
91
 
    >>> string_to_delta("1w")
92
 
    datetime.timedelta(7)
93
 
    >>> string_to_delta("5m 30s")
94
 
    datetime.timedelta(0, 330)
95
 
    """
96
 
    value = datetime.timedelta(0)
97
 
    regexp = re.compile("(\d+)([dsmhw]?)")
98
 
    
99
 
    for num, suffix in regexp.findall(interval):
100
 
        if suffix == "d":
101
 
            value += datetime.timedelta(int(num))
102
 
        elif suffix == "s":
103
 
            value += datetime.timedelta(0, int(num))
104
 
        elif suffix == "m":
105
 
            value += datetime.timedelta(0, 0, 0, 0, int(num))
106
 
        elif suffix == "h":
107
 
            value += datetime.timedelta(0, 0, 0, 0, 0, int(num))
108
 
        elif suffix == "w":
109
 
            value += datetime.timedelta(0, 0, 0, 0, 0, 0, int(num))
110
 
        elif suffix == "":
111
 
            value += datetime.timedelta(0, 0, 0, int(num))
112
 
    return value
113
 
 
114
 
def print_clients(clients, keywords):
115
 
    def valuetostring(value, keyword):
116
 
        if type(value) is dbus.Boolean:
117
 
            return "Yes" if value else "No"
118
 
        if keyword in ("Timeout", "Interval", "ApprovalDelay",
119
 
                       "ApprovalDuration", "ExtendedTimeout"):
120
 
            return milliseconds_to_string(value)
121
 
        return unicode(value)
122
 
    
123
 
    # Create format string to print table rows
124
 
    format_string = " ".join("{{{key}:{width}}}".format(
125
 
            width = max(len(tablewords[key]),
126
 
                        max(len(valuetostring(client[key],
127
 
                                              key))
128
 
                            for client in
129
 
                            clients)),
130
 
            key = key) for key in keywords)
131
 
    # Print header line
132
 
    print(format_string.format(**tablewords))
133
 
    for client in clients:
134
 
        print(format_string.format(**dict((key,
135
 
                                           valuetostring(client[key],
136
 
                                                         key))
137
 
                                          for key in keywords)))
138
 
 
139
 
def has_actions(options):
140
 
    return any((options.enable,
141
 
                options.disable,
142
 
                options.bump_timeout,
143
 
                options.start_checker,
144
 
                options.stop_checker,
145
 
                options.is_enabled,
146
 
                options.remove,
147
 
                options.checker is not None,
148
 
                options.timeout is not None,
149
 
                options.extended_timeout is not None,
150
 
                options.interval is not None,
151
 
                options.approved_by_default is not None,
152
 
                options.approval_delay is not None,
153
 
                options.approval_duration is not None,
154
 
                options.host is not None,
155
 
                options.secret is not None,
156
 
                options.approve,
157
 
                options.deny))
158
 
 
159
 
def main():
160
 
    parser = argparse.ArgumentParser()
161
 
    parser.add_argument("--version", action="version",
162
 
                        version = "%(prog)s {0}".format(version),
163
 
                        help="show version number and exit")
164
 
    parser.add_argument("-a", "--all", action="store_true",
165
 
                        help="Select all clients")
166
 
    parser.add_argument("-v", "--verbose", action="store_true",
167
 
                        help="Print all fields")
168
 
    parser.add_argument("-e", "--enable", action="store_true",
169
 
                        help="Enable client")
170
 
    parser.add_argument("-d", "--disable", action="store_true",
171
 
                        help="disable client")
172
 
    parser.add_argument("-b", "--bump-timeout", action="store_true",
173
 
                        help="Bump timeout for client")
174
 
    parser.add_argument("--start-checker", action="store_true",
175
 
                        help="Start checker for client")
176
 
    parser.add_argument("--stop-checker", action="store_true",
177
 
                        help="Stop checker for client")
178
 
    parser.add_argument("-V", "--is-enabled", action="store_true",
179
 
                        help="Check if client is enabled")
180
 
    parser.add_argument("-r", "--remove", action="store_true",
181
 
                        help="Remove client")
182
 
    parser.add_argument("-c", "--checker",
183
 
                        help="Set checker command for client")
184
 
    parser.add_argument("-t", "--timeout",
185
 
                        help="Set timeout for client")
186
 
    parser.add_argument("--extended-timeout",
187
 
                        help="Set extended timeout for client")
188
 
    parser.add_argument("-i", "--interval",
189
 
                        help="Set checker interval for client")
190
 
    parser.add_argument("--approve-by-default", action="store_true",
191
 
                        default=None, dest="approved_by_default",
192
 
                        help="Set client to be approved by default")
193
 
    parser.add_argument("--deny-by-default", action="store_false",
194
 
                        dest="approved_by_default",
195
 
                        help="Set client to be denied by default")
196
 
    parser.add_argument("--approval-delay",
197
 
                        help="Set delay before client approve/deny")
198
 
    parser.add_argument("--approval-duration",
199
 
                        help="Set duration of one client approval")
200
 
    parser.add_argument("-H", "--host", help="Set host for client")
201
 
    parser.add_argument("-s", "--secret", type=file,
202
 
                        help="Set password blob (file) for client")
203
 
    parser.add_argument("-A", "--approve", action="store_true",
204
 
                        help="Approve any current client request")
205
 
    parser.add_argument("-D", "--deny", action="store_true",
206
 
                        help="Deny any current client request")
207
 
    parser.add_argument("client", nargs="*", help="Client name")
208
 
    options = parser.parse_args()
209
 
    
210
 
    if has_actions(options) and not options.client and not options.all:
211
 
        parser.error("Options require clients names or --all.")
212
 
    if options.verbose and has_actions(options):
213
 
        parser.error("--verbose can only be used alone or with"
214
 
                     " --all.")
215
 
    if options.all and not has_actions(options):
216
 
        parser.error("--all requires an action.")
217
 
    
218
 
    try:
219
 
        bus = dbus.SystemBus()
220
 
        mandos_dbus_objc = bus.get_object(busname, server_path)
221
 
    except dbus.exceptions.DBusException:
222
 
        print("Could not connect to Mandos server",
223
 
              file=sys.stderr)
224
 
        sys.exit(1)
225
 
    
226
 
    mandos_serv = dbus.Interface(mandos_dbus_objc,
227
 
                                 dbus_interface = server_interface)
228
 
    
229
 
    #block stderr since dbus library prints to stderr
230
 
    null = os.open(os.path.devnull, os.O_RDWR)
231
 
    stderrcopy = os.dup(sys.stderr.fileno())
232
 
    os.dup2(null, sys.stderr.fileno())
233
 
    os.close(null)
234
 
    try:
235
 
        try:
236
 
            mandos_clients = mandos_serv.GetAllClientsWithProperties()
237
 
        finally:
238
 
            #restore stderr
239
 
            os.dup2(stderrcopy, sys.stderr.fileno())
240
 
            os.close(stderrcopy)
241
 
    except dbus.exceptions.DBusException:
242
 
        print("Access denied: Accessing mandos server through dbus.",
243
 
              file=sys.stderr)
244
 
        sys.exit(1)
245
 
    
246
 
    # Compile dict of (clients: properties) to process
247
 
    clients={}
248
 
    
249
 
    if options.all or not options.client:
250
 
        clients = dict((bus.get_object(busname, path), properties)
251
 
                       for path, properties in
252
 
                       mandos_clients.iteritems())
253
 
    else:
254
 
        for name in options.client:
255
 
            for path, client in mandos_clients.iteritems():
256
 
                if client["Name"] == name:
257
 
                    client_objc = bus.get_object(busname, path)
258
 
                    clients[client_objc] = client
259
 
                    break
260
 
            else:
261
 
                print("Client not found on server: {0!r}"
262
 
                      .format(name), file=sys.stderr)
263
 
                sys.exit(1)
264
 
    
265
 
    if not has_actions(options) and clients:
266
 
        if options.verbose:
267
 
            keywords = ("Name", "Enabled", "Timeout",
268
 
                        "LastCheckedOK", "Created", "Interval",
269
 
                        "Host", "Fingerprint", "CheckerRunning",
270
 
                        "LastEnabled", "ApprovalPending",
271
 
                        "ApprovedByDefault",
272
 
                        "LastApprovalRequest", "ApprovalDelay",
273
 
                        "ApprovalDuration", "Checker",
274
 
                        "ExtendedTimeout")
275
 
        else:
276
 
            keywords = defaultkeywords
277
 
        
278
 
        print_clients(clients.values(), keywords)
279
 
    else:
280
 
        # Process each client in the list by all selected options
281
 
        for client in clients:
282
 
            def set_client_prop(prop, value):
283
 
                """Set a Client D-Bus property"""
284
 
                client.Set(client_interface, prop, value,
285
 
                           dbus_interface=dbus.PROPERTIES_IFACE)
286
 
            def set_client_prop_ms(prop, value):
287
 
                """Set a Client D-Bus property, converted
288
 
                from a string to milliseconds."""
289
 
                set_client_prop(prop,
290
 
                                timedelta_to_milliseconds
291
 
                                (string_to_delta(value)))
292
 
            if options.remove:
293
 
                mandos_serv.RemoveClient(client.__dbus_object_path__)
294
 
            if options.enable:
295
 
                set_client_prop("Enabled", dbus.Boolean(True))
296
 
            if options.disable:
297
 
                set_client_prop("Enabled", dbus.Boolean(False))
298
 
            if options.bump_timeout:
299
 
                set_client_prop("LastCheckedOK", "")
300
 
            if options.start_checker:
301
 
                set_client_prop("CheckerRunning", dbus.Boolean(True))
302
 
            if options.stop_checker:
303
 
                set_client_prop("CheckerRunning", dbus.Boolean(False))
304
 
            if options.is_enabled:
305
 
                sys.exit(0 if client.Get(client_interface,
306
 
                                         "Enabled",
307
 
                                         dbus_interface=
308
 
                                         dbus.PROPERTIES_IFACE)
309
 
                         else 1)
310
 
            if options.checker is not None:
311
 
                set_client_prop("Checker", options.checker)
312
 
            if options.host is not None:
313
 
                set_client_prop("Host", options.host)
314
 
            if options.interval is not None:
315
 
                set_client_prop_ms("Interval", options.interval)
316
 
            if options.approval_delay is not None:
317
 
                set_client_prop_ms("ApprovalDelay",
318
 
                                   options.approval_delay)
319
 
            if options.approval_duration is not None:
320
 
                set_client_prop_ms("ApprovalDuration",
321
 
                                   options.approval_duration)
322
 
            if options.timeout is not None:
323
 
                set_client_prop_ms("Timeout", options.timeout)
324
 
            if options.extended_timeout is not None:
325
 
                set_client_prop_ms("ExtendedTimeout",
326
 
                                   options.extended_timeout)
327
 
            if options.secret is not None:
328
 
                set_client_prop("Secret",
329
 
                                dbus.ByteArray(options.secret.read()))
330
 
            if options.approved_by_default is not None:
331
 
                set_client_prop("ApprovedByDefault",
332
 
                                dbus.Boolean(options
333
 
                                             .approved_by_default))
334
 
            if options.approve:
335
 
                client.Approve(dbus.Boolean(True),
336
 
                               dbus_interface=client_interface)
337
 
            elif options.deny:
338
 
                client.Approve(dbus.Boolean(False),
339
 
                               dbus_interface=client_interface)
340
 
 
341
 
if __name__ == "__main__":
342
 
    main()