/mandos/release

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/release

« back to all changes in this revision

Viewing changes to mandos-ctl

First version of a somewhat complete D-Bus server interface.  Also
change user/group name to "_mandos".

* debian/mandos.postinst: Rename old "mandos" user and group to
                          "_mandos"; create "_mandos" user and group
                          if none exist.
* debian/mandos-client.postinst: - '' -

* initramfs-tools-hook: Try "_mandos" before "mandos" as user and
                        group name.

* mandos (_datetime_to_dbus_struct): New; was previously local.
  (Client.started): Renamed to "last_started".  All users changed.
  (Client.started): New; boolean.
  (Client.dbus_object_path): New.
  (Client.check_command): Renamed to "checker_command".  All users
                          changed.
  (Client.__init__): Set and use "self.dbus_object_path".  Set
                     "self.started".
  (Client.start): Update "self.started".  Emit "self.PropertyChanged"
                  signals for both "started" and "last_started".
  (Client.stop): Update "self.started".  Emit "self.PropertyChanged"
                 signal for "started".
  (Client.checker_callback): Take additional "command" argument.  All
                             callers changed. Emit
                             "self.PropertyChanged" signal.
  (Client.bump_timeout): Emit "self.PropertyChanged" signal for
                         "last_checked_ok".
  (Client.start_checker): Emit "self.PropertyChanged" signal for
                          "checker_running".
  (Client.stop_checker): Emit "self.PropertyChanged" signal for
                         "checker_running".
  (Client.still_valid): Bug fix: use "getattr(self, started, False)"
                        instead of "self.started" in case this client
                        object is so new that the "started" attribute
                        has not been created yet.
  (Client.IntervalChanged, Client.CheckerIsRunning, Client.GetChecker,
  Client.GetCreated, Client.GetFingerprint, Client.GetHost,
  Client.GetInterval, Client.GetName, Client.GetStarted,
  Client.GetTimeout, Client.StateChanged, Client.TimeoutChanged):
  Removed; all callers changed.
  (Client.CheckerCompleted): Add "condition" and "command" arguments.
                             All callers changed.
  (Client.GetAllProperties, Client.PropertyChanged): New.
  (Client.StillValid): Renamed to "IsStillValid".
  (Client.StartChecker): Changed to its own function to avoid the
                         return value from "Client.start_checker()".
  (Client.Stop): Changed to its own function to avoid the return value
                 from "Client.stop()".
  (main): Try "_mandos" before "mandos" as user and group name.
          Removed inner function "remove_from_clients".  New inner
          class "MandosServer".

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-2011 Teddy Hogeborn
7
 
# Copyright © 2008-2011 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.4.1"
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)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,
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"):
120
 
            return milliseconds_to_string(value)
121
 
        return unicode(value)
122
 
    
123
 
    # Create format string to print table rows
124
 
    format_string = " ".join("%%-%ds" %
125
 
                             max(len(tablewords[key]),
126
 
                                 max(len(valuetostring(client[key],
127
 
                                                       key))
128
 
                                     for client in
129
 
                                     clients))
130
 
                             for key in keywords)
131
 
    # Print header line
132
 
    print(format_string % tuple(tablewords[key] for key in keywords))
133
 
    for client in clients:
134
 
        print(format_string % tuple(valuetostring(client[key], key)
135
 
                                    for key in keywords))
136
 
 
137
 
def has_actions(options):
138
 
    return any((options.enable,
139
 
                options.disable,
140
 
                options.bump_timeout,
141
 
                options.start_checker,
142
 
                options.stop_checker,
143
 
                options.is_enabled,
144
 
                options.remove,
145
 
                options.checker is not None,
146
 
                options.timeout is not None,
147
 
                options.extended_timeout is not None,
148
 
                options.interval is not None,
149
 
                options.approved_by_default is not None,
150
 
                options.approval_delay is not None,
151
 
                options.approval_duration is not None,
152
 
                options.host is not None,
153
 
                options.secret is not None,
154
 
                options.approve,
155
 
                options.deny))
156
 
 
157
 
def main():
158
 
    parser = argparse.ArgumentParser()
159
 
    parser.add_argument("--version", action="version",
160
 
                        version = "%%prog %s" % version,
161
 
                        help="show version number and exit")
162
 
    parser.add_argument("-a", "--all", action="store_true",
163
 
                        help="Select all clients")
164
 
    parser.add_argument("-v", "--verbose", action="store_true",
165
 
                        help="Print all fields")
166
 
    parser.add_argument("-e", "--enable", action="store_true",
167
 
                        help="Enable client")
168
 
    parser.add_argument("-d", "--disable", action="store_true",
169
 
                        help="disable client")
170
 
    parser.add_argument("-b", "--bump-timeout", action="store_true",
171
 
                        help="Bump timeout for client")
172
 
    parser.add_argument("--start-checker", action="store_true",
173
 
                        help="Start checker for client")
174
 
    parser.add_argument("--stop-checker", action="store_true",
175
 
                        help="Stop checker for client")
176
 
    parser.add_argument("-V", "--is-enabled", action="store_true",
177
 
                        help="Check if client is enabled")
178
 
    parser.add_argument("-r", "--remove", action="store_true",
179
 
                        help="Remove client")
180
 
    parser.add_argument("-c", "--checker",
181
 
                        help="Set checker command for client")
182
 
    parser.add_argument("-t", "--timeout",
183
 
                        help="Set timeout for client")
184
 
    parser.add_argument("--extended-timeout",
185
 
                        help="Set extended timeout for client")
186
 
    parser.add_argument("-i", "--interval",
187
 
                        help="Set checker interval for client")
188
 
    parser.add_argument("--approve-by-default", action="store_true",
189
 
                        default=None, dest="approved_by_default",
190
 
                        help="Set client to be approved by default")
191
 
    parser.add_argument("--deny-by-default", action="store_false",
192
 
                        dest="approved_by_default",
193
 
                        help="Set client to be denied by default")
194
 
    parser.add_argument("--approval-delay",
195
 
                        help="Set delay before client approve/deny")
196
 
    parser.add_argument("--approval-duration",
197
 
                        help="Set duration of one client approval")
198
 
    parser.add_argument("-H", "--host", help="Set host for client")
199
 
    parser.add_argument("-s", "--secret", type=file,
200
 
                        help="Set password blob (file) for client")
201
 
    parser.add_argument("-A", "--approve", action="store_true",
202
 
                        help="Approve any current client request")
203
 
    parser.add_argument("-D", "--deny", action="store_true",
204
 
                        help="Deny any current client request")
205
 
    parser.add_argument("client", nargs="*", help="Client name")
206
 
    options = parser.parse_args()
207
 
    
208
 
    if has_actions(options) and not options.client and not options.all:
209
 
        parser.error("Options require clients names or --all.")
210
 
    if options.verbose and has_actions(options):
211
 
        parser.error("--verbose can only be used alone or with"
212
 
                     " --all.")
213
 
    if options.all and not has_actions(options):
214
 
        parser.error("--all requires an action.")
215
 
    
216
 
    try:
217
 
        bus = dbus.SystemBus()
218
 
        mandos_dbus_objc = bus.get_object(busname, server_path)
219
 
    except dbus.exceptions.DBusException:
220
 
        print("Could not connect to Mandos server",
221
 
              file=sys.stderr)
222
 
        sys.exit(1)
223
 
    
224
 
    mandos_serv = dbus.Interface(mandos_dbus_objc,
225
 
                                 dbus_interface = server_interface)
226
 
    
227
 
    #block stderr since dbus library prints to stderr
228
 
    null = os.open(os.path.devnull, os.O_RDWR)
229
 
    stderrcopy = os.dup(sys.stderr.fileno())
230
 
    os.dup2(null, sys.stderr.fileno())
231
 
    os.close(null)
232
 
    try:
233
 
        try:
234
 
            mandos_clients = mandos_serv.GetAllClientsWithProperties()
235
 
        finally:
236
 
            #restore stderr
237
 
            os.dup2(stderrcopy, sys.stderr.fileno())
238
 
            os.close(stderrcopy)
239
 
    except dbus.exceptions.DBusException:
240
 
        print("Access denied: Accessing mandos server through dbus.",
241
 
              file=sys.stderr)
242
 
        sys.exit(1)
243
 
    
244
 
    # Compile dict of (clients: properties) to process
245
 
    clients={}
246
 
    
247
 
    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())
251
 
    else:
252
 
        for name in options.client:
253
 
            for path, client in mandos_clients.iteritems():
254
 
                if client["Name"] == name:
255
 
                    client_objc = bus.get_object(busname, path)
256
 
                    clients[client_objc] = client
257
 
                    break
258
 
            else:
259
 
                print("Client not found on server: %r" % name,
260
 
                      file=sys.stderr)
261
 
                sys.exit(1)
262
 
    
263
 
    if not has_actions(options) and clients:
264
 
        if options.verbose:
265
 
            keywords = ("Name", "Enabled", "Timeout",
266
 
                        "LastCheckedOK", "Created", "Interval",
267
 
                        "Host", "Fingerprint", "CheckerRunning",
268
 
                        "LastEnabled", "ApprovalPending",
269
 
                        "ApprovedByDefault",
270
 
                        "LastApprovalRequest", "ApprovalDelay",
271
 
                        "ApprovalDuration", "Checker",
272
 
                        "ExtendedTimeout")
273
 
        else:
274
 
            keywords = defaultkeywords
275
 
        
276
 
        print_clients(clients.values(), keywords)
277
 
    else:
278
 
        # Process each client in the list by all selected options
279
 
        for client in clients:
280
 
            if options.remove:
281
 
                mandos_serv.RemoveClient(client.__dbus_object_path__)
282
 
            if options.enable:
283
 
                client.Enable(dbus_interface=client_interface)
284
 
            if options.disable:
285
 
                client.Disable(dbus_interface=client_interface)
286
 
            if options.bump_timeout:
287
 
                client.CheckedOK(dbus_interface=client_interface)
288
 
            if options.start_checker:
289
 
                client.StartChecker(dbus_interface=client_interface)
290
 
            if options.stop_checker:
291
 
                client.StopChecker(dbus_interface=client_interface)
292
 
            if options.is_enabled:
293
 
                sys.exit(0 if client.Get(client_interface,
294
 
                                         "Enabled",
295
 
                                         dbus_interface=
296
 
                                         dbus.PROPERTIES_IFACE)
297
 
                         else 1)
298
 
            if options.checker is not None:
299
 
                client.Set(client_interface, "Checker",
300
 
                           options.checker,
301
 
                           dbus_interface=dbus.PROPERTIES_IFACE)
302
 
            if options.host is not None:
303
 
                client.Set(client_interface, "Host", options.host,
304
 
                           dbus_interface=dbus.PROPERTIES_IFACE)
305
 
            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)
310
 
            if options.approval_delay is not None:
311
 
                client.Set(client_interface, "ApprovalDelay",
312
 
                           timedelta_to_milliseconds
313
 
                           (string_to_delta(options.
314
 
                                            approval_delay)),
315
 
                           dbus_interface=dbus.PROPERTIES_IFACE)
316
 
            if options.approval_duration is not None:
317
 
                client.Set(client_interface, "ApprovalDuration",
318
 
                           timedelta_to_milliseconds
319
 
                           (string_to_delta(options.
320
 
                                            approval_duration)),
321
 
                           dbus_interface=dbus.PROPERTIES_IFACE)
322
 
            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)
327
 
            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)
332
 
            if options.secret is not None:
333
 
                client.Set(client_interface, "Secret",
334
 
                           dbus.ByteArray(open(options.secret,
335
 
                                               "rb").read()),
336
 
                           dbus_interface=dbus.PROPERTIES_IFACE)
337
 
            if options.approved_by_default is not None:
338
 
                client.Set(client_interface, "ApprovedByDefault",
339
 
                           dbus.Boolean(options
340
 
                                        .approved_by_default),
341
 
                           dbus_interface=dbus.PROPERTIES_IFACE)
342
 
            if options.approve:
343
 
                client.Approve(dbus.Boolean(True),
344
 
                               dbus_interface=client_interface)
345
 
            elif options.deny:
346
 
                client.Approve(dbus.Boolean(False),
347
 
                               dbus_interface=client_interface)
348
 
 
349
 
if __name__ == "__main__":
350
 
    main()