/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

* debian/mandos-client.postinst: Converted to Bourne shell.  Also
                                 minor message change.
* debian/mandos-client.postrm: Minor message change.
* debian/mandos.postinst: Converted to Bourne shell.  Also minor
                          message change.
* debian/mandos.prerm: Minor message change.

Show diffs side-by-side

added added

removed removed

Lines of Context:
11
11
# and some lines in "main".
12
12
13
13
# Everything else is
14
 
# Copyright © 2008 Teddy Hogeborn
15
 
# Copyright © 2008 Björn Påhlsson
 
14
# Copyright © 2008,2009 Teddy Hogeborn
 
15
# Copyright © 2008,2009 Björn Påhlsson
16
16
17
17
# This program is free software: you can redistribute it and/or modify
18
18
# it under the terms of the GNU General Public License as published by
35
35
 
36
36
import SocketServer
37
37
import socket
38
 
from optparse import OptionParser
 
38
import optparse
39
39
import datetime
40
40
import errno
41
41
import gnutls.crypto
66
66
import ctypes
67
67
import ctypes.util
68
68
 
69
 
version = "1.0.2"
 
69
version = "1.0.5"
70
70
 
71
71
logger = logging.Logger('mandos')
72
72
syslogger = (logging.handlers.SysLogHandler
82
82
logger.addHandler(console)
83
83
 
84
84
class AvahiError(Exception):
85
 
    def __init__(self, value):
 
85
    def __init__(self, value, *args, **kwargs):
86
86
        self.value = value
87
 
        super(AvahiError, self).__init__()
88
 
    def __str__(self):
89
 
        return repr(self.value)
 
87
        super(AvahiError, self).__init__(value, *args, **kwargs)
 
88
    def __unicode__(self):
 
89
        return unicode(repr(self.value))
90
90
 
91
91
class AvahiServiceError(AvahiError):
92
92
    pass
129
129
            logger.critical(u"No suitable Zeroconf service name found"
130
130
                            u" after %i retries, exiting.",
131
131
                            self.rename_count)
132
 
            raise AvahiServiceError("Too many renames")
 
132
            raise AvahiServiceError(u"Too many renames")
133
133
        self.name = server.GetAlternativeServiceName(self.name)
134
134
        logger.info(u"Changing Zeroconf service name to %r ...",
135
135
                    str(self.name))
170
170
# End of Avahi example code
171
171
 
172
172
 
 
173
def _datetime_to_dbus(dt, variant_level=0):
 
174
    """Convert a UTC datetime.datetime() to a D-Bus type."""
 
175
    return dbus.String(dt.isoformat(), variant_level=variant_level)
 
176
 
 
177
 
173
178
class Client(dbus.service.Object):
174
179
    """A representation of a client host served by this server.
175
180
    Attributes:
176
 
    name:      string; from the config file, used in log messages
 
181
    name:       string; from the config file, used in log messages
177
182
    fingerprint: string (40 or 32 hexadecimal digits); used to
178
183
                 uniquely identify the client
179
 
    secret:    bytestring; sent verbatim (over TLS) to client
180
 
    host:      string; available for use by the checker command
181
 
    created:   datetime.datetime(); (UTC) object creation
182
 
    started:   datetime.datetime(); (UTC) last started
 
184
    secret:     bytestring; sent verbatim (over TLS) to client
 
185
    host:       string; available for use by the checker command
 
186
    created:    datetime.datetime(); (UTC) object creation
 
187
    last_enabled: datetime.datetime(); (UTC)
 
188
    enabled:    bool()
183
189
    last_checked_ok: datetime.datetime(); (UTC) or None
184
 
    timeout:   datetime.timedelta(); How long from last_checked_ok
185
 
                                     until this client is invalid
186
 
    interval:  datetime.timedelta(); How often to start a new checker
187
 
    stop_hook: If set, called by stop() as stop_hook(self)
188
 
    checker:   subprocess.Popen(); a running checker process used
189
 
                                   to see if the client lives.
190
 
                                   'None' if no process is running.
 
190
    timeout:    datetime.timedelta(); How long from last_checked_ok
 
191
                                      until this client is invalid
 
192
    interval:   datetime.timedelta(); How often to start a new checker
 
193
    disable_hook:  If set, called by disable() as disable_hook(self)
 
194
    checker:    subprocess.Popen(); a running checker process used
 
195
                                    to see if the client lives.
 
196
                                    'None' if no process is running.
191
197
    checker_initiator_tag: a gobject event source tag, or None
192
 
    stop_initiator_tag:    - '' -
 
198
    disable_initiator_tag:    - '' -
193
199
    checker_callback_tag:  - '' -
194
200
    checker_command: string; External command which is run to check if
195
201
                     client lives.  %() expansions are done at
196
202
                     runtime with vars(self) as dict, so that for
197
203
                     instance %(name)s can be used in the command.
198
 
    Private attibutes:
199
 
    _timeout: Real variable for 'timeout'
200
 
    _interval: Real variable for 'interval'
201
 
    _timeout_milliseconds: Used when calling gobject.timeout_add()
202
 
    _interval_milliseconds: - '' -
 
204
    use_dbus: bool(); Whether to provide D-Bus interface and signals
 
205
    dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
203
206
    """
204
 
    def _set_timeout(self, timeout):
205
 
        "Setter function for the 'timeout' attribute"
206
 
        self._timeout = timeout
207
 
        self._timeout_milliseconds = ((self.timeout.days
208
 
                                       * 24 * 60 * 60 * 1000)
209
 
                                      + (self.timeout.seconds * 1000)
210
 
                                      + (self.timeout.microseconds
211
 
                                         // 1000))
212
 
        # Emit D-Bus signal
213
 
        self.TimeoutChanged(self._timeout_milliseconds)
214
 
    timeout = property(lambda self: self._timeout, _set_timeout)
215
 
    del _set_timeout
216
 
    
217
 
    def _set_interval(self, interval):
218
 
        "Setter function for the 'interval' attribute"
219
 
        self._interval = interval
220
 
        self._interval_milliseconds = ((self.interval.days
221
 
                                        * 24 * 60 * 60 * 1000)
222
 
                                       + (self.interval.seconds
223
 
                                          * 1000)
224
 
                                       + (self.interval.microseconds
225
 
                                          // 1000))
226
 
        # Emit D-Bus signal
227
 
        self.IntervalChanged(self._interval_milliseconds)
228
 
    interval = property(lambda self: self._interval, _set_interval)
229
 
    del _set_interval
230
 
    
231
 
    def __init__(self, name = None, stop_hook=None, config=None):
 
207
    def timeout_milliseconds(self):
 
208
        "Return the 'timeout' attribute in milliseconds"
 
209
        return ((self.timeout.days * 24 * 60 * 60 * 1000)
 
210
                + (self.timeout.seconds * 1000)
 
211
                + (self.timeout.microseconds // 1000))
 
212
    
 
213
    def interval_milliseconds(self):
 
214
        "Return the 'interval' attribute in milliseconds"
 
215
        return ((self.interval.days * 24 * 60 * 60 * 1000)
 
216
                + (self.interval.seconds * 1000)
 
217
                + (self.interval.microseconds // 1000))
 
218
    
 
219
    def __init__(self, name = None, disable_hook=None, config=None,
 
220
                 use_dbus=True):
232
221
        """Note: the 'checker' key in 'config' sets the
233
222
        'checker_command' attribute and *not* the 'checker'
234
223
        attribute."""
235
 
        dbus.service.Object.__init__(self, bus,
236
 
                                     "/Mandos/clients/%s"
237
 
                                     % name.replace(".", "_"))
 
224
        self.name = name
238
225
        if config is None:
239
226
            config = {}
240
 
        self.name = name
241
227
        logger.debug(u"Creating client %r", self.name)
 
228
        self.use_dbus = use_dbus
 
229
        if self.use_dbus:
 
230
            self.dbus_object_path = (dbus.ObjectPath
 
231
                                     ("/Mandos/clients/"
 
232
                                      + self.name.replace(".", "_")))
 
233
            dbus.service.Object.__init__(self, bus,
 
234
                                         self.dbus_object_path)
242
235
        # Uppercase and remove spaces from fingerprint for later
243
236
        # comparison purposes with return value from the fingerprint()
244
237
        # function
257
250
                            % self.name)
258
251
        self.host = config.get("host", "")
259
252
        self.created = datetime.datetime.utcnow()
260
 
        self.started = None
 
253
        self.enabled = False
 
254
        self.last_enabled = None
261
255
        self.last_checked_ok = None
262
256
        self.timeout = string_to_delta(config["timeout"])
263
257
        self.interval = string_to_delta(config["interval"])
264
 
        self.stop_hook = stop_hook
 
258
        self.disable_hook = disable_hook
265
259
        self.checker = None
266
260
        self.checker_initiator_tag = None
267
 
        self.stop_initiator_tag = None
 
261
        self.disable_initiator_tag = None
268
262
        self.checker_callback_tag = None
269
 
        self.check_command = config["checker"]
 
263
        self.checker_command = config["checker"]
270
264
    
271
 
    def start(self):
 
265
    def enable(self):
272
266
        """Start this client's checker and timeout hooks"""
273
 
        self.started = datetime.datetime.utcnow()
 
267
        self.last_enabled = datetime.datetime.utcnow()
274
268
        # Schedule a new checker to be started an 'interval' from now,
275
269
        # and every interval from then on.
276
270
        self.checker_initiator_tag = (gobject.timeout_add
277
 
                                      (self._interval_milliseconds,
 
271
                                      (self.interval_milliseconds(),
278
272
                                       self.start_checker))
279
273
        # Also start a new checker *right now*.
280
274
        self.start_checker()
281
 
        # Schedule a stop() when 'timeout' has passed
282
 
        self.stop_initiator_tag = (gobject.timeout_add
283
 
                                   (self._timeout_milliseconds,
284
 
                                    self.stop))
285
 
        # Emit D-Bus signal
286
 
        self.StateChanged(True)
 
275
        # Schedule a disable() when 'timeout' has passed
 
276
        self.disable_initiator_tag = (gobject.timeout_add
 
277
                                   (self.timeout_milliseconds(),
 
278
                                    self.disable))
 
279
        self.enabled = True
 
280
        if self.use_dbus:
 
281
            # Emit D-Bus signals
 
282
            self.PropertyChanged(dbus.String(u"enabled"),
 
283
                                 dbus.Boolean(True, variant_level=1))
 
284
            self.PropertyChanged(dbus.String(u"last_enabled"),
 
285
                                 (_datetime_to_dbus(self.last_enabled,
 
286
                                                    variant_level=1)))
287
287
    
288
 
    def stop(self):
289
 
        """Stop this client."""
290
 
        if getattr(self, "started", None) is not None:
291
 
            logger.info(u"Stopping client %s", self.name)
292
 
        else:
 
288
    def disable(self):
 
289
        """Disable this client."""
 
290
        if not getattr(self, "enabled", False):
293
291
            return False
294
 
        if getattr(self, "stop_initiator_tag", False):
295
 
            gobject.source_remove(self.stop_initiator_tag)
296
 
            self.stop_initiator_tag = None
 
292
        logger.info(u"Disabling client %s", self.name)
 
293
        if getattr(self, "disable_initiator_tag", False):
 
294
            gobject.source_remove(self.disable_initiator_tag)
 
295
            self.disable_initiator_tag = None
297
296
        if getattr(self, "checker_initiator_tag", False):
298
297
            gobject.source_remove(self.checker_initiator_tag)
299
298
            self.checker_initiator_tag = None
300
299
        self.stop_checker()
301
 
        if self.stop_hook:
302
 
            self.stop_hook(self)
303
 
        self.started = None
304
 
        # Emit D-Bus signal
305
 
        self.StateChanged(False)
 
300
        if self.disable_hook:
 
301
            self.disable_hook(self)
 
302
        self.enabled = False
 
303
        if self.use_dbus:
 
304
            # Emit D-Bus signal
 
305
            self.PropertyChanged(dbus.String(u"enabled"),
 
306
                                 dbus.Boolean(False, variant_level=1))
306
307
        # Do not run this again if called by a gobject.timeout_add
307
308
        return False
308
309
    
309
310
    def __del__(self):
310
 
        self.stop_hook = None
311
 
        self.stop()
 
311
        self.disable_hook = None
 
312
        self.disable()
312
313
    
313
 
    def checker_callback(self, pid, condition):
 
314
    def checker_callback(self, pid, condition, command):
314
315
        """The checker has completed, so take appropriate actions."""
315
316
        self.checker_callback_tag = None
316
317
        self.checker = None
317
 
        if (os.WIFEXITED(condition) 
 
318
        if self.use_dbus:
 
319
            # Emit D-Bus signal
 
320
            self.PropertyChanged(dbus.String(u"checker_running"),
 
321
                                 dbus.Boolean(False, variant_level=1))
 
322
        if (os.WIFEXITED(condition)
318
323
            and (os.WEXITSTATUS(condition) == 0)):
319
324
            logger.info(u"Checker for %(name)s succeeded",
320
325
                        vars(self))
321
 
            # Emit D-Bus signal
322
 
            self.CheckerCompleted(True)
 
326
            if self.use_dbus:
 
327
                # Emit D-Bus signal
 
328
                self.CheckerCompleted(dbus.Boolean(True),
 
329
                                      dbus.UInt16(condition),
 
330
                                      dbus.String(command))
323
331
            self.bump_timeout()
324
332
        elif not os.WIFEXITED(condition):
325
333
            logger.warning(u"Checker for %(name)s crashed?",
326
334
                           vars(self))
327
 
            # Emit D-Bus signal
328
 
            self.CheckerCompleted(False)
 
335
            if self.use_dbus:
 
336
                # Emit D-Bus signal
 
337
                self.CheckerCompleted(dbus.Boolean(False),
 
338
                                      dbus.UInt16(condition),
 
339
                                      dbus.String(command))
329
340
        else:
330
341
            logger.info(u"Checker for %(name)s failed",
331
342
                        vars(self))
332
 
            # Emit D-Bus signal
333
 
            self.CheckerCompleted(False)
 
343
            if self.use_dbus:
 
344
                # Emit D-Bus signal
 
345
                self.CheckerCompleted(dbus.Boolean(False),
 
346
                                      dbus.UInt16(condition),
 
347
                                      dbus.String(command))
334
348
    
335
349
    def bump_timeout(self):
336
350
        """Bump up the timeout for this client.
338
352
        alive and well.
339
353
        """
340
354
        self.last_checked_ok = datetime.datetime.utcnow()
341
 
        gobject.source_remove(self.stop_initiator_tag)
342
 
        self.stop_initiator_tag = (gobject.timeout_add
343
 
                                   (self._timeout_milliseconds,
344
 
                                    self.stop))
 
355
        gobject.source_remove(self.disable_initiator_tag)
 
356
        self.disable_initiator_tag = (gobject.timeout_add
 
357
                                      (self.timeout_milliseconds(),
 
358
                                       self.disable))
 
359
        if self.use_dbus:
 
360
            # Emit D-Bus signal
 
361
            self.PropertyChanged(
 
362
                dbus.String(u"last_checked_ok"),
 
363
                (_datetime_to_dbus(self.last_checked_ok,
 
364
                                   variant_level=1)))
345
365
    
346
366
    def start_checker(self):
347
367
        """Start a new checker subprocess if one is not running.
357
377
        # is as it should be.
358
378
        if self.checker is None:
359
379
            try:
360
 
                # In case check_command has exactly one % operator
361
 
                command = self.check_command % self.host
 
380
                # In case checker_command has exactly one % operator
 
381
                command = self.checker_command % self.host
362
382
            except TypeError:
363
383
                # Escape attributes for the shell
364
384
                escaped_attrs = dict((key, re.escape(str(val)))
365
385
                                     for key, val in
366
386
                                     vars(self).iteritems())
367
387
                try:
368
 
                    command = self.check_command % escaped_attrs
 
388
                    command = self.checker_command % escaped_attrs
369
389
                except TypeError, error:
370
390
                    logger.error(u'Could not format string "%s":'
371
 
                                 u' %s', self.check_command, error)
 
391
                                 u' %s', self.checker_command, error)
372
392
                    return True # Try again later
373
393
            try:
374
394
                logger.info(u"Starting checker %r for %s",
380
400
                self.checker = subprocess.Popen(command,
381
401
                                                close_fds=True,
382
402
                                                shell=True, cwd="/")
 
403
                if self.use_dbus:
 
404
                    # Emit D-Bus signal
 
405
                    self.CheckerStarted(command)
 
406
                    self.PropertyChanged(
 
407
                        dbus.String("checker_running"),
 
408
                        dbus.Boolean(True, variant_level=1))
383
409
                self.checker_callback_tag = (gobject.child_watch_add
384
410
                                             (self.checker.pid,
385
 
                                              self.checker_callback))
386
 
                # Emit D-Bus signal
387
 
                self.CheckerStarted(command)
 
411
                                              self.checker_callback,
 
412
                                              data=command))
388
413
            except OSError, error:
389
414
                logger.error(u"Failed to start subprocess: %s",
390
415
                             error)
408
433
            if error.errno != errno.ESRCH: # No such process
409
434
                raise
410
435
        self.checker = None
 
436
        if self.use_dbus:
 
437
            self.PropertyChanged(dbus.String(u"checker_running"),
 
438
                                 dbus.Boolean(False, variant_level=1))
411
439
    
412
440
    def still_valid(self):
413
441
        """Has the timeout not yet passed for this client?"""
414
 
        if not self.started:
 
442
        if not getattr(self, "enabled", False):
415
443
            return False
416
444
        now = datetime.datetime.utcnow()
417
445
        if self.last_checked_ok is None:
422
450
    ## D-Bus methods & signals
423
451
    _interface = u"org.mandos_system.Mandos.Client"
424
452
    
425
 
    def _datetime_to_dbus_struct(dt):
426
 
        return dbus.Struct(dt.year, dt.month, dt.day, dt.hour,
427
 
                           dt.minute, dt.second, dt.microsecond,
428
 
                           signature="nyyyyyu")
429
 
    
430
453
    # BumpTimeout - method
431
454
    BumpTimeout = dbus.service.method(_interface)(bump_timeout)
432
455
    BumpTimeout.__name__ = "BumpTimeout"
433
456
    
434
 
    # IntervalChanged - signal
435
 
    @dbus.service.signal(_interface, signature="t")
436
 
    def IntervalChanged(self, t):
437
 
        "D-Bus signal"
438
 
        pass
439
 
    
440
457
    # CheckerCompleted - signal
441
 
    @dbus.service.signal(_interface, signature="b")
442
 
    def CheckerCompleted(self, success):
 
458
    @dbus.service.signal(_interface, signature="bqs")
 
459
    def CheckerCompleted(self, success, condition, command):
443
460
        "D-Bus signal"
444
461
        pass
445
462
    
446
 
    # CheckerIsRunning - method
447
 
    @dbus.service.method(_interface, out_signature="b")
448
 
    def CheckerIsRunning(self):
449
 
        "D-Bus getter method"
450
 
        return self.checker is not None
451
 
    
452
463
    # CheckerStarted - signal
453
464
    @dbus.service.signal(_interface, signature="s")
454
465
    def CheckerStarted(self, command):
455
466
        "D-Bus signal"
456
467
        pass
457
468
    
458
 
    # GetChecker - method
459
 
    @dbus.service.method(_interface, out_signature="s")
460
 
    def GetChecker(self):
461
 
        "D-Bus getter method"
462
 
        return self.checker_command
463
 
    
464
 
    # GetCreated - method
465
 
    @dbus.service.method(_interface, out_signature="(nyyyyyu)")
466
 
    def GetCreated(self):
467
 
        "D-Bus getter method"
468
 
        return datetime_to_dbus_struct(self.created)
469
 
    
470
 
    # GetFingerprint - method
471
 
    @dbus.service.method(_interface, out_signature="s")
472
 
    def GetFingerprint(self):
473
 
        "D-Bus getter method"
474
 
        return self.fingerprint
475
 
    
476
 
    # GetHost - method
477
 
    @dbus.service.method(_interface, out_signature="s")
478
 
    def GetHost(self):
479
 
        "D-Bus getter method"
480
 
        return self.host
481
 
    
482
 
    # GetInterval - method
483
 
    @dbus.service.method(_interface, out_signature="t")
484
 
    def GetInterval(self):
485
 
        "D-Bus getter method"
486
 
        return self._interval_milliseconds
487
 
    
488
 
    # GetName - method
489
 
    @dbus.service.method(_interface, out_signature="s")
490
 
    def GetName(self):
491
 
        "D-Bus getter method"
492
 
        return self.name
493
 
    
494
 
    # GetStarted - method
495
 
    @dbus.service.method(_interface, out_signature="(nyyyyyu)")
496
 
    def GetStarted(self):
497
 
        "D-Bus getter method"
498
 
        if self.started is not None:
499
 
            return datetime_to_dbus_struct(self.started)
500
 
        else:
501
 
            return dbus.Struct(0, 0, 0, 0, 0, 0, 0,
502
 
                               signature="nyyyyyu")
503
 
    
504
 
    # GetTimeout - method
505
 
    @dbus.service.method(_interface, out_signature="t")
506
 
    def GetTimeout(self):
507
 
        "D-Bus getter method"
508
 
        return self._timeout_milliseconds
 
469
    # GetAllProperties - method
 
470
    @dbus.service.method(_interface, out_signature="a{sv}")
 
471
    def GetAllProperties(self):
 
472
        "D-Bus method"
 
473
        return dbus.Dictionary({
 
474
                dbus.String("name"):
 
475
                    dbus.String(self.name, variant_level=1),
 
476
                dbus.String("fingerprint"):
 
477
                    dbus.String(self.fingerprint, variant_level=1),
 
478
                dbus.String("host"):
 
479
                    dbus.String(self.host, variant_level=1),
 
480
                dbus.String("created"):
 
481
                    _datetime_to_dbus(self.created, variant_level=1),
 
482
                dbus.String("last_enabled"):
 
483
                    (_datetime_to_dbus(self.last_enabled,
 
484
                                       variant_level=1)
 
485
                     if self.last_enabled is not None
 
486
                     else dbus.Boolean(False, variant_level=1)),
 
487
                dbus.String("enabled"):
 
488
                    dbus.Boolean(self.enabled, variant_level=1),
 
489
                dbus.String("last_checked_ok"):
 
490
                    (_datetime_to_dbus(self.last_checked_ok,
 
491
                                       variant_level=1)
 
492
                     if self.last_checked_ok is not None
 
493
                     else dbus.Boolean (False, variant_level=1)),
 
494
                dbus.String("timeout"):
 
495
                    dbus.UInt64(self.timeout_milliseconds(),
 
496
                                variant_level=1),
 
497
                dbus.String("interval"):
 
498
                    dbus.UInt64(self.interval_milliseconds(),
 
499
                                variant_level=1),
 
500
                dbus.String("checker"):
 
501
                    dbus.String(self.checker_command,
 
502
                                variant_level=1),
 
503
                dbus.String("checker_running"):
 
504
                    dbus.Boolean(self.checker is not None,
 
505
                                 variant_level=1),
 
506
                }, signature="sv")
 
507
    
 
508
    # IsStillValid - method
 
509
    IsStillValid = (dbus.service.method(_interface, out_signature="b")
 
510
                    (still_valid))
 
511
    IsStillValid.__name__ = "IsStillValid"
 
512
    
 
513
    # PropertyChanged - signal
 
514
    @dbus.service.signal(_interface, signature="sv")
 
515
    def PropertyChanged(self, property, value):
 
516
        "D-Bus signal"
 
517
        pass
509
518
    
510
519
    # SetChecker - method
511
520
    @dbus.service.method(_interface, in_signature="s")
512
521
    def SetChecker(self, checker):
513
522
        "D-Bus setter method"
514
523
        self.checker_command = checker
 
524
        # Emit D-Bus signal
 
525
        self.PropertyChanged(dbus.String(u"checker"),
 
526
                             dbus.String(self.checker_command,
 
527
                                         variant_level=1))
515
528
    
516
529
    # SetHost - method
517
530
    @dbus.service.method(_interface, in_signature="s")
518
531
    def SetHost(self, host):
519
532
        "D-Bus setter method"
520
533
        self.host = host
 
534
        # Emit D-Bus signal
 
535
        self.PropertyChanged(dbus.String(u"host"),
 
536
                             dbus.String(self.host, variant_level=1))
521
537
    
522
538
    # SetInterval - method
523
539
    @dbus.service.method(_interface, in_signature="t")
524
540
    def SetInterval(self, milliseconds):
525
 
        self.interval = datetime.timdeelta(0, 0, 0, milliseconds)
526
 
    
527
 
    # SetTimeout - method
528
 
    @dbus.service.method(_interface, in_signature="t")
529
 
    def SetTimeout(self, milliseconds):
530
 
        self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
 
541
        self.interval = datetime.timedelta(0, 0, 0, milliseconds)
 
542
        # Emit D-Bus signal
 
543
        self.PropertyChanged(dbus.String(u"interval"),
 
544
                             (dbus.UInt64(self.interval_milliseconds(),
 
545
                                          variant_level=1)))
531
546
    
532
547
    # SetSecret - method
533
548
    @dbus.service.method(_interface, in_signature="ay",
536
551
        "D-Bus setter method"
537
552
        self.secret = str(secret)
538
553
    
539
 
    # Start - method
540
 
    Start = dbus.service.method(_interface)(start)
541
 
    Start.__name__ = "Start"
 
554
    # SetTimeout - method
 
555
    @dbus.service.method(_interface, in_signature="t")
 
556
    def SetTimeout(self, milliseconds):
 
557
        self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
 
558
        # Emit D-Bus signal
 
559
        self.PropertyChanged(dbus.String(u"timeout"),
 
560
                             (dbus.UInt64(self.timeout_milliseconds(),
 
561
                                          variant_level=1)))
 
562
    
 
563
    # Enable - method
 
564
    Enable = dbus.service.method(_interface)(enable)
 
565
    Enable.__name__ = "Enable"
542
566
    
543
567
    # StartChecker - method
544
 
    StartChecker = dbus.service.method(_interface)(start_checker)
545
 
    StartChecker.__name__ = "StartChecker"
546
 
    
547
 
    # StateChanged - signal
548
 
    @dbus.service.signal(_interface, signature="b")
549
 
    def StateChanged(self, started):
550
 
        "D-Bus signal"
551
 
        pass
552
 
    
553
 
    # StillValid - method
554
 
    StillValid = (dbus.service.method(_interface, out_signature="b")
555
 
                  (still_valid))
556
 
    StillValid.__name__ = "StillValid"
557
 
    
558
 
    # Stop - method
559
 
    Stop = dbus.service.method(_interface)(stop)
560
 
    Stop.__name__ = "Stop"
 
568
    @dbus.service.method(_interface)
 
569
    def StartChecker(self):
 
570
        "D-Bus method"
 
571
        self.start_checker()
 
572
    
 
573
    # Disable - method
 
574
    @dbus.service.method(_interface)
 
575
    def Disable(self):
 
576
        "D-Bus method"
 
577
        self.disable()
561
578
    
562
579
    # StopChecker - method
563
580
    StopChecker = dbus.service.method(_interface)(stop_checker)
564
581
    StopChecker.__name__ = "StopChecker"
565
582
    
566
 
    # TimeoutChanged - signal
567
 
    @dbus.service.signal(_interface, signature="t")
568
 
    def TimeoutChanged(self, t):
569
 
        "D-Bus signal"
570
 
        pass
571
 
    
572
 
    del _datetime_to_dbus_struct
573
583
    del _interface
574
584
 
575
585
 
830
840
    elif state == avahi.ENTRY_GROUP_FAILURE:
831
841
        logger.critical(u"Avahi: Error in group state changed %s",
832
842
                        unicode(error))
833
 
        raise AvahiGroupError("State changed: %s", str(error))
 
843
        raise AvahiGroupError(u"State changed: %s" % unicode(error))
834
844
 
835
845
def if_nametoindex(interface):
836
846
    """Call the C function if_nametoindex(), or equivalent"""
879
889
 
880
890
 
881
891
def main():
882
 
    parser = OptionParser(version = "%%prog %s" % version)
 
892
    parser = optparse.OptionParser(version = "%%prog %s" % version)
883
893
    parser.add_option("-i", "--interface", type="string",
884
894
                      metavar="IF", help="Bind to interface IF")
885
895
    parser.add_option("-a", "--address", type="string",
886
896
                      help="Address to listen for requests on")
887
897
    parser.add_option("-p", "--port", type="int",
888
898
                      help="Port number to receive requests on")
889
 
    parser.add_option("--check", action="store_true", default=False,
 
899
    parser.add_option("--check", action="store_true",
890
900
                      help="Run self-test")
891
901
    parser.add_option("--debug", action="store_true",
892
902
                      help="Debug mode; run in foreground and log to"
899
909
                      default="/etc/mandos", metavar="DIR",
900
910
                      help="Directory to search for configuration"
901
911
                      " files")
 
912
    parser.add_option("--no-dbus", action="store_false",
 
913
                      dest="use_dbus",
 
914
                      help="Do not provide D-Bus system bus"
 
915
                      " interface")
902
916
    options = parser.parse_args()[0]
903
917
    
904
918
    if options.check:
914
928
                        "priority":
915
929
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
916
930
                        "servicename": "Mandos",
 
931
                        "use_dbus": "True",
917
932
                        }
918
933
    
919
934
    # Parse config file for server-global settings
922
937
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
923
938
    # Convert the SafeConfigParser object to a dict
924
939
    server_settings = server_config.defaults()
925
 
    # Use getboolean on the boolean config option
 
940
    # Use getboolean on the boolean config options
926
941
    server_settings["debug"] = (server_config.getboolean
927
942
                                ("DEFAULT", "debug"))
 
943
    server_settings["use_dbus"] = (server_config.getboolean
 
944
                                   ("DEFAULT", "use_dbus"))
928
945
    del server_config
929
946
    
930
947
    # Override the settings from the config file with command line
931
948
    # options, if set.
932
949
    for option in ("interface", "address", "port", "debug",
933
 
                   "priority", "servicename", "configdir"):
 
950
                   "priority", "servicename", "configdir",
 
951
                   "use_dbus"):
934
952
        value = getattr(options, option)
935
953
        if value is not None:
936
954
            server_settings[option] = value
937
955
    del options
938
956
    # Now we have our good server settings in "server_settings"
939
957
    
 
958
    # For convenience
940
959
    debug = server_settings["debug"]
 
960
    use_dbus = server_settings["use_dbus"]
941
961
    
942
962
    if not debug:
943
963
        syslogger.setLevel(logging.WARNING)
952
972
    # Parse config file with clients
953
973
    client_defaults = { "timeout": "1h",
954
974
                        "interval": "5m",
955
 
                        "checker": "fping -q -- %(host)s",
 
975
                        "checker": "fping -q -- %%(host)s",
956
976
                        "host": "",
957
977
                        }
958
978
    client_config = ConfigParser.SafeConfigParser(client_defaults)
971
991
    except IOError, error:
972
992
        logger.error("Could not open file %r", pidfilename)
973
993
    
974
 
    uid = 65534
975
 
    gid = 65534
976
 
    try:
977
 
        uid = pwd.getpwnam("mandos").pw_uid
978
 
    except KeyError:
979
 
        try:
980
 
            uid = pwd.getpwnam("nobody").pw_uid
981
 
        except KeyError:
982
 
            pass
983
 
    try:
984
 
        gid = pwd.getpwnam("mandos").pw_gid
985
 
    except KeyError:
986
 
        try:
987
 
            gid = pwd.getpwnam("nogroup").pw_gid
988
 
        except KeyError:
989
 
            pass
 
994
    try:
 
995
        uid = pwd.getpwnam("_mandos").pw_uid
 
996
        gid = pwd.getpwnam("_mandos").pw_gid
 
997
    except KeyError:
 
998
        try:
 
999
            uid = pwd.getpwnam("mandos").pw_uid
 
1000
            gid = pwd.getpwnam("mandos").pw_gid
 
1001
        except KeyError:
 
1002
            try:
 
1003
                uid = pwd.getpwnam("nobody").pw_uid
 
1004
                gid = pwd.getpwnam("nogroup").pw_gid
 
1005
            except KeyError:
 
1006
                uid = 65534
 
1007
                gid = 65534
990
1008
    try:
991
1009
        os.setuid(uid)
992
1010
        os.setgid(gid)
1012
1030
                                           avahi.DBUS_PATH_SERVER),
1013
1031
                            avahi.DBUS_INTERFACE_SERVER)
1014
1032
    # End of Avahi example code
1015
 
    bus_name = dbus.service.BusName(u"org.mandos-system.Mandos", bus)
1016
 
    
1017
 
    def remove_from_clients(client):
1018
 
        clients.remove(client)
1019
 
        if not clients:
1020
 
            logger.critical(u"No clients left, exiting")
1021
 
            sys.exit()
 
1033
    if use_dbus:
 
1034
        bus_name = dbus.service.BusName(u"org.mandos-system.Mandos",
 
1035
                                        bus)
1022
1036
    
1023
1037
    clients.update(Set(Client(name = section,
1024
 
                              stop_hook = remove_from_clients,
1025
1038
                              config
1026
 
                              = dict(client_config.items(section)))
 
1039
                              = dict(client_config.items(section)),
 
1040
                              use_dbus = use_dbus)
1027
1041
                       for section in client_config.sections()))
1028
1042
    if not clients:
1029
 
        logger.critical(u"No clients defined")
1030
 
        sys.exit(1)
 
1043
        logger.warning(u"No clients defined")
1031
1044
    
1032
1045
    if debug:
1033
1046
        # Redirect stdin so all checkers get /dev/null
1065
1078
        
1066
1079
        while clients:
1067
1080
            client = clients.pop()
1068
 
            client.stop_hook = None
1069
 
            client.stop()
 
1081
            client.disable_hook = None
 
1082
            client.disable()
1070
1083
    
1071
1084
    atexit.register(cleanup)
1072
1085
    
1075
1088
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1076
1089
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1077
1090
    
 
1091
    if use_dbus:
 
1092
        class MandosServer(dbus.service.Object):
 
1093
            """A D-Bus proxy object"""
 
1094
            def __init__(self):
 
1095
                dbus.service.Object.__init__(self, bus,
 
1096
                                             "/Mandos")
 
1097
            _interface = u"org.mandos_system.Mandos"
 
1098
 
 
1099
            @dbus.service.signal(_interface, signature="oa{sv}")
 
1100
            def ClientAdded(self, objpath, properties):
 
1101
                "D-Bus signal"
 
1102
                pass
 
1103
 
 
1104
            @dbus.service.signal(_interface, signature="o")
 
1105
            def ClientRemoved(self, objpath):
 
1106
                "D-Bus signal"
 
1107
                pass
 
1108
 
 
1109
            @dbus.service.method(_interface, out_signature="ao")
 
1110
            def GetAllClients(self):
 
1111
                return dbus.Array(c.dbus_object_path for c in clients)
 
1112
 
 
1113
            @dbus.service.method(_interface, out_signature="a{oa{sv}}")
 
1114
            def GetAllClientsWithProperties(self):
 
1115
                return dbus.Dictionary(
 
1116
                    ((c.dbus_object_path, c.GetAllProperties())
 
1117
                     for c in clients),
 
1118
                    signature="oa{sv}")
 
1119
 
 
1120
            @dbus.service.method(_interface, in_signature="o")
 
1121
            def RemoveClient(self, object_path):
 
1122
                for c in clients:
 
1123
                    if c.dbus_object_path == object_path:
 
1124
                        clients.remove(c)
 
1125
                        # Don't signal anything except ClientRemoved
 
1126
                        c.use_dbus = False
 
1127
                        c.disable()
 
1128
                        # Emit D-Bus signal
 
1129
                        self.ClientRemoved(object_path)
 
1130
                        return
 
1131
                raise KeyError
 
1132
            @dbus.service.method(_interface)
 
1133
            def Quit(self):
 
1134
                main_loop.quit()
 
1135
 
 
1136
            del _interface
 
1137
    
 
1138
        mandos_server = MandosServer()
 
1139
    
1078
1140
    for client in clients:
1079
 
        client.start()
 
1141
        if use_dbus:
 
1142
            # Emit D-Bus signal
 
1143
            mandos_server.ClientAdded(client.dbus_object_path,
 
1144
                                      client.GetAllProperties())
 
1145
        client.enable()
1080
1146
    
1081
1147
    tcp_server.enable()
1082
1148
    tcp_server.server_activate()
1106
1172
        logger.debug(u"Starting main loop")
1107
1173
        main_loop.run()
1108
1174
    except AvahiError, error:
1109
 
        logger.critical(u"AvahiError: %s" + unicode(error))
 
1175
        logger.critical(u"AvahiError: %s", error)
1110
1176
        sys.exit(1)
1111
1177
    except KeyboardInterrupt:
1112
1178
        if debug: