/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

  • Committer: Teddy Hogeborn
  • Date: 2009-02-07 04:50:39 UTC
  • Revision ID: teddy@fukt.bsnet.se-20090207045039-xkr6b80vtqwqrq8l
* Makefile (install-client-nokey): Move "initramfs-tools-script" from
                                   "/scripts/local-top/mandos" to
                                   "/scripts/init-premount/mandos".
  (uninstall-client): - '' -
* debian/mandos-client.dirs: - '' -
* initramfs-tools-script (PREREQ): Added "udev".

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
73
73
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
74
74
              address = "/dev/log"))
75
75
syslogger.setFormatter(logging.Formatter
76
 
                       ('Mandos: %(levelname)s: %(message)s'))
 
76
                       ('Mandos [%(process)d]: %(levelname)s:'
 
77
                        ' %(message)s'))
77
78
logger.addHandler(syslogger)
78
79
 
79
80
console = logging.StreamHandler()
80
 
console.setFormatter(logging.Formatter('%(name)s: %(levelname)s:'
81
 
                                       ' %(message)s'))
 
81
console.setFormatter(logging.Formatter('%(name)s [%(process)d]:'
 
82
                                       ' %(levelname)s: %(message)s'))
82
83
logger.addHandler(console)
83
84
 
84
85
class AvahiError(Exception):
85
 
    def __init__(self, value):
 
86
    def __init__(self, value, *args, **kwargs):
86
87
        self.value = value
87
 
        super(AvahiError, self).__init__()
88
 
    def __str__(self):
89
 
        return repr(self.value)
 
88
        super(AvahiError, self).__init__(value, *args, **kwargs)
 
89
    def __unicode__(self):
 
90
        return unicode(repr(self.value))
90
91
 
91
92
class AvahiServiceError(AvahiError):
92
93
    pass
129
130
            logger.critical(u"No suitable Zeroconf service name found"
130
131
                            u" after %i retries, exiting.",
131
132
                            self.rename_count)
132
 
            raise AvahiServiceError("Too many renames")
 
133
            raise AvahiServiceError(u"Too many renames")
133
134
        self.name = server.GetAlternativeServiceName(self.name)
134
135
        logger.info(u"Changing Zeroconf service name to %r ...",
135
136
                    str(self.name))
170
171
# End of Avahi example code
171
172
 
172
173
 
 
174
def _datetime_to_dbus(dt, variant_level=0):
 
175
    """Convert a UTC datetime.datetime() to a D-Bus type."""
 
176
    return dbus.String(dt.isoformat(), variant_level=variant_level)
 
177
 
 
178
 
173
179
class Client(dbus.service.Object):
174
180
    """A representation of a client host served by this server.
175
181
    Attributes:
176
 
    name:      string; from the config file, used in log messages
 
182
    name:       string; from the config file, used in log messages and
 
183
                        D-Bus identifiers
177
184
    fingerprint: string (40 or 32 hexadecimal digits); used to
178
185
                 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
 
186
    secret:     bytestring; sent verbatim (over TLS) to client
 
187
    host:       string; available for use by the checker command
 
188
    created:    datetime.datetime(); (UTC) object creation
 
189
    last_enabled: datetime.datetime(); (UTC)
 
190
    enabled:    bool()
183
191
    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.
 
192
    timeout:    datetime.timedelta(); How long from last_checked_ok
 
193
                                      until this client is invalid
 
194
    interval:   datetime.timedelta(); How often to start a new checker
 
195
    disable_hook:  If set, called by disable() as disable_hook(self)
 
196
    checker:    subprocess.Popen(); a running checker process used
 
197
                                    to see if the client lives.
 
198
                                    'None' if no process is running.
191
199
    checker_initiator_tag: a gobject event source tag, or None
192
 
    stop_initiator_tag:    - '' -
 
200
    disable_initiator_tag:    - '' -
193
201
    checker_callback_tag:  - '' -
194
202
    checker_command: string; External command which is run to check if
195
203
                     client lives.  %() expansions are done at
196
204
                     runtime with vars(self) as dict, so that for
197
205
                     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: - '' -
 
206
    use_dbus: bool(); Whether to provide D-Bus interface and signals
 
207
    dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
203
208
    """
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):
 
209
    def timeout_milliseconds(self):
 
210
        "Return the 'timeout' attribute in milliseconds"
 
211
        return ((self.timeout.days * 24 * 60 * 60 * 1000)
 
212
                + (self.timeout.seconds * 1000)
 
213
                + (self.timeout.microseconds // 1000))
 
214
    
 
215
    def interval_milliseconds(self):
 
216
        "Return the 'interval' attribute in milliseconds"
 
217
        return ((self.interval.days * 24 * 60 * 60 * 1000)
 
218
                + (self.interval.seconds * 1000)
 
219
                + (self.interval.microseconds // 1000))
 
220
    
 
221
    def __init__(self, name = None, disable_hook=None, config=None,
 
222
                 use_dbus=True):
232
223
        """Note: the 'checker' key in 'config' sets the
233
224
        'checker_command' attribute and *not* the 'checker'
234
225
        attribute."""
235
 
        dbus.service.Object.__init__(self, bus,
236
 
                                     "/Mandos/clients/%s"
237
 
                                     % name.replace(".", "_"))
 
226
        self.name = name
238
227
        if config is None:
239
228
            config = {}
240
 
        self.name = name
241
229
        logger.debug(u"Creating client %r", self.name)
 
230
        self.use_dbus = False   # During __init__
242
231
        # Uppercase and remove spaces from fingerprint for later
243
232
        # comparison purposes with return value from the fingerprint()
244
233
        # function
257
246
                            % self.name)
258
247
        self.host = config.get("host", "")
259
248
        self.created = datetime.datetime.utcnow()
260
 
        self.started = None
 
249
        self.enabled = False
 
250
        self.last_enabled = None
261
251
        self.last_checked_ok = None
262
252
        self.timeout = string_to_delta(config["timeout"])
263
253
        self.interval = string_to_delta(config["interval"])
264
 
        self.stop_hook = stop_hook
 
254
        self.disable_hook = disable_hook
265
255
        self.checker = None
266
256
        self.checker_initiator_tag = None
267
 
        self.stop_initiator_tag = None
 
257
        self.disable_initiator_tag = None
268
258
        self.checker_callback_tag = None
269
 
        self.check_command = config["checker"]
 
259
        self.checker_command = config["checker"]
 
260
        self.last_connect = None
 
261
        # Only now, when this client is initialized, can it show up on
 
262
        # the D-Bus
 
263
        self.use_dbus = use_dbus
 
264
        if self.use_dbus:
 
265
            self.dbus_object_path = (dbus.ObjectPath
 
266
                                     ("/clients/"
 
267
                                      + self.name.replace(".", "_")))
 
268
            dbus.service.Object.__init__(self, bus,
 
269
                                         self.dbus_object_path)
270
270
    
271
 
    def start(self):
 
271
    def enable(self):
272
272
        """Start this client's checker and timeout hooks"""
273
 
        self.started = datetime.datetime.utcnow()
 
273
        self.last_enabled = datetime.datetime.utcnow()
274
274
        # Schedule a new checker to be started an 'interval' from now,
275
275
        # and every interval from then on.
276
276
        self.checker_initiator_tag = (gobject.timeout_add
277
 
                                      (self._interval_milliseconds,
 
277
                                      (self.interval_milliseconds(),
278
278
                                       self.start_checker))
279
279
        # Also start a new checker *right now*.
280
280
        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)
 
281
        # Schedule a disable() when 'timeout' has passed
 
282
        self.disable_initiator_tag = (gobject.timeout_add
 
283
                                   (self.timeout_milliseconds(),
 
284
                                    self.disable))
 
285
        self.enabled = True
 
286
        if self.use_dbus:
 
287
            # Emit D-Bus signals
 
288
            self.PropertyChanged(dbus.String(u"enabled"),
 
289
                                 dbus.Boolean(True, variant_level=1))
 
290
            self.PropertyChanged(dbus.String(u"last_enabled"),
 
291
                                 (_datetime_to_dbus(self.last_enabled,
 
292
                                                    variant_level=1)))
287
293
    
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:
 
294
    def disable(self):
 
295
        """Disable this client."""
 
296
        if not getattr(self, "enabled", False):
293
297
            return False
294
 
        if getattr(self, "stop_initiator_tag", False):
295
 
            gobject.source_remove(self.stop_initiator_tag)
296
 
            self.stop_initiator_tag = None
 
298
        logger.info(u"Disabling client %s", self.name)
 
299
        if getattr(self, "disable_initiator_tag", False):
 
300
            gobject.source_remove(self.disable_initiator_tag)
 
301
            self.disable_initiator_tag = None
297
302
        if getattr(self, "checker_initiator_tag", False):
298
303
            gobject.source_remove(self.checker_initiator_tag)
299
304
            self.checker_initiator_tag = None
300
305
        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)
 
306
        if self.disable_hook:
 
307
            self.disable_hook(self)
 
308
        self.enabled = False
 
309
        if self.use_dbus:
 
310
            # Emit D-Bus signal
 
311
            self.PropertyChanged(dbus.String(u"enabled"),
 
312
                                 dbus.Boolean(False, variant_level=1))
306
313
        # Do not run this again if called by a gobject.timeout_add
307
314
        return False
308
315
    
309
316
    def __del__(self):
310
 
        self.stop_hook = None
311
 
        self.stop()
 
317
        self.disable_hook = None
 
318
        self.disable()
312
319
    
313
 
    def checker_callback(self, pid, condition):
 
320
    def checker_callback(self, pid, condition, command):
314
321
        """The checker has completed, so take appropriate actions."""
315
322
        self.checker_callback_tag = None
316
323
        self.checker = None
317
 
        if (os.WIFEXITED(condition) 
318
 
            and (os.WEXITSTATUS(condition) == 0)):
319
 
            logger.info(u"Checker for %(name)s succeeded",
320
 
                        vars(self))
 
324
        if self.use_dbus:
321
325
            # Emit D-Bus signal
322
 
            self.CheckerCompleted(True)
323
 
            self.bump_timeout()
324
 
        elif not os.WIFEXITED(condition):
 
326
            self.PropertyChanged(dbus.String(u"checker_running"),
 
327
                                 dbus.Boolean(False, variant_level=1))
 
328
        if os.WIFEXITED(condition):
 
329
            exitstatus = os.WEXITSTATUS(condition)
 
330
            if exitstatus == 0:
 
331
                logger.info(u"Checker for %(name)s succeeded",
 
332
                            vars(self))
 
333
                self.checked_ok()
 
334
            else:
 
335
                logger.info(u"Checker for %(name)s failed",
 
336
                            vars(self))
 
337
            if self.use_dbus:
 
338
                # Emit D-Bus signal
 
339
                self.CheckerCompleted(dbus.Int16(exitstatus),
 
340
                                      dbus.Int64(condition),
 
341
                                      dbus.String(command))
 
342
        else:
325
343
            logger.warning(u"Checker for %(name)s crashed?",
326
344
                           vars(self))
327
 
            # Emit D-Bus signal
328
 
            self.CheckerCompleted(False)
329
 
        else:
330
 
            logger.info(u"Checker for %(name)s failed",
331
 
                        vars(self))
332
 
            # Emit D-Bus signal
333
 
            self.CheckerCompleted(False)
 
345
            if self.use_dbus:
 
346
                # Emit D-Bus signal
 
347
                self.CheckerCompleted(dbus.Int16(-1),
 
348
                                      dbus.Int64(condition),
 
349
                                      dbus.String(command))
334
350
    
335
 
    def bump_timeout(self):
 
351
    def checked_ok(self):
336
352
        """Bump up the timeout for this client.
337
353
        This should only be called when the client has been seen,
338
354
        alive and well.
339
355
        """
340
356
        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))
 
357
        gobject.source_remove(self.disable_initiator_tag)
 
358
        self.disable_initiator_tag = (gobject.timeout_add
 
359
                                      (self.timeout_milliseconds(),
 
360
                                       self.disable))
 
361
        if self.use_dbus:
 
362
            # Emit D-Bus signal
 
363
            self.PropertyChanged(
 
364
                dbus.String(u"last_checked_ok"),
 
365
                (_datetime_to_dbus(self.last_checked_ok,
 
366
                                   variant_level=1)))
345
367
    
346
368
    def start_checker(self):
347
369
        """Start a new checker subprocess if one is not running.
357
379
        # is as it should be.
358
380
        if self.checker is None:
359
381
            try:
360
 
                # In case check_command has exactly one % operator
361
 
                command = self.check_command % self.host
 
382
                # In case checker_command has exactly one % operator
 
383
                command = self.checker_command % self.host
362
384
            except TypeError:
363
385
                # Escape attributes for the shell
364
386
                escaped_attrs = dict((key, re.escape(str(val)))
365
387
                                     for key, val in
366
388
                                     vars(self).iteritems())
367
389
                try:
368
 
                    command = self.check_command % escaped_attrs
 
390
                    command = self.checker_command % escaped_attrs
369
391
                except TypeError, error:
370
392
                    logger.error(u'Could not format string "%s":'
371
 
                                 u' %s', self.check_command, error)
 
393
                                 u' %s', self.checker_command, error)
372
394
                    return True # Try again later
373
395
            try:
374
396
                logger.info(u"Starting checker %r for %s",
380
402
                self.checker = subprocess.Popen(command,
381
403
                                                close_fds=True,
382
404
                                                shell=True, cwd="/")
 
405
                if self.use_dbus:
 
406
                    # Emit D-Bus signal
 
407
                    self.CheckerStarted(command)
 
408
                    self.PropertyChanged(
 
409
                        dbus.String("checker_running"),
 
410
                        dbus.Boolean(True, variant_level=1))
383
411
                self.checker_callback_tag = (gobject.child_watch_add
384
412
                                             (self.checker.pid,
385
 
                                              self.checker_callback))
386
 
                # Emit D-Bus signal
387
 
                self.CheckerStarted(command)
 
413
                                              self.checker_callback,
 
414
                                              data=command))
388
415
            except OSError, error:
389
416
                logger.error(u"Failed to start subprocess: %s",
390
417
                             error)
408
435
            if error.errno != errno.ESRCH: # No such process
409
436
                raise
410
437
        self.checker = None
 
438
        if self.use_dbus:
 
439
            self.PropertyChanged(dbus.String(u"checker_running"),
 
440
                                 dbus.Boolean(False, variant_level=1))
411
441
    
412
442
    def still_valid(self):
413
443
        """Has the timeout not yet passed for this client?"""
414
 
        if not self.started:
 
444
        if not getattr(self, "enabled", False):
415
445
            return False
416
446
        now = datetime.datetime.utcnow()
417
447
        if self.last_checked_ok is None:
420
450
            return now < (self.last_checked_ok + self.timeout)
421
451
    
422
452
    ## D-Bus methods & signals
423
 
    _interface = u"org.mandos_system.Mandos.Client"
424
 
    
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
 
    # BumpTimeout - method
431
 
    BumpTimeout = dbus.service.method(_interface)(bump_timeout)
432
 
    BumpTimeout.__name__ = "BumpTimeout"
433
 
    
434
 
    # IntervalChanged - signal
435
 
    @dbus.service.signal(_interface, signature="t")
436
 
    def IntervalChanged(self, t):
437
 
        "D-Bus signal"
438
 
        pass
 
453
    _interface = u"se.bsnet.fukt.Mandos.Client"
 
454
    
 
455
    # CheckedOK - method
 
456
    CheckedOK = dbus.service.method(_interface)(checked_ok)
 
457
    CheckedOK.__name__ = "CheckedOK"
439
458
    
440
459
    # CheckerCompleted - signal
441
 
    @dbus.service.signal(_interface, signature="b")
442
 
    def CheckerCompleted(self, success):
 
460
    @dbus.service.signal(_interface, signature="nxs")
 
461
    def CheckerCompleted(self, exitcode, waitstatus, command):
443
462
        "D-Bus signal"
444
463
        pass
445
464
    
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
465
    # CheckerStarted - signal
453
466
    @dbus.service.signal(_interface, signature="s")
454
467
    def CheckerStarted(self, command):
455
468
        "D-Bus signal"
456
469
        pass
457
470
    
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
 
471
    # GetAllProperties - method
 
472
    @dbus.service.method(_interface, out_signature="a{sv}")
 
473
    def GetAllProperties(self):
 
474
        "D-Bus method"
 
475
        return dbus.Dictionary({
 
476
                dbus.String("name"):
 
477
                    dbus.String(self.name, variant_level=1),
 
478
                dbus.String("fingerprint"):
 
479
                    dbus.String(self.fingerprint, variant_level=1),
 
480
                dbus.String("host"):
 
481
                    dbus.String(self.host, variant_level=1),
 
482
                dbus.String("created"):
 
483
                    _datetime_to_dbus(self.created, variant_level=1),
 
484
                dbus.String("last_enabled"):
 
485
                    (_datetime_to_dbus(self.last_enabled,
 
486
                                       variant_level=1)
 
487
                     if self.last_enabled is not None
 
488
                     else dbus.Boolean(False, variant_level=1)),
 
489
                dbus.String("enabled"):
 
490
                    dbus.Boolean(self.enabled, variant_level=1),
 
491
                dbus.String("last_checked_ok"):
 
492
                    (_datetime_to_dbus(self.last_checked_ok,
 
493
                                       variant_level=1)
 
494
                     if self.last_checked_ok is not None
 
495
                     else dbus.Boolean (False, variant_level=1)),
 
496
                dbus.String("timeout"):
 
497
                    dbus.UInt64(self.timeout_milliseconds(),
 
498
                                variant_level=1),
 
499
                dbus.String("interval"):
 
500
                    dbus.UInt64(self.interval_milliseconds(),
 
501
                                variant_level=1),
 
502
                dbus.String("checker"):
 
503
                    dbus.String(self.checker_command,
 
504
                                variant_level=1),
 
505
                dbus.String("checker_running"):
 
506
                    dbus.Boolean(self.checker is not None,
 
507
                                 variant_level=1),
 
508
                dbus.String("object_path"):
 
509
                    dbus.ObjectPath(self.dbus_object_path,
 
510
                                    variant_level=1)
 
511
                }, signature="sv")
 
512
    
 
513
    # IsStillValid - method
 
514
    IsStillValid = (dbus.service.method(_interface, out_signature="b")
 
515
                    (still_valid))
 
516
    IsStillValid.__name__ = "IsStillValid"
 
517
    
 
518
    # PropertyChanged - signal
 
519
    @dbus.service.signal(_interface, signature="sv")
 
520
    def PropertyChanged(self, property, value):
 
521
        "D-Bus signal"
 
522
        pass
509
523
    
510
524
    # SetChecker - method
511
525
    @dbus.service.method(_interface, in_signature="s")
512
526
    def SetChecker(self, checker):
513
527
        "D-Bus setter method"
514
528
        self.checker_command = checker
 
529
        # Emit D-Bus signal
 
530
        self.PropertyChanged(dbus.String(u"checker"),
 
531
                             dbus.String(self.checker_command,
 
532
                                         variant_level=1))
515
533
    
516
534
    # SetHost - method
517
535
    @dbus.service.method(_interface, in_signature="s")
518
536
    def SetHost(self, host):
519
537
        "D-Bus setter method"
520
538
        self.host = host
 
539
        # Emit D-Bus signal
 
540
        self.PropertyChanged(dbus.String(u"host"),
 
541
                             dbus.String(self.host, variant_level=1))
521
542
    
522
543
    # SetInterval - method
523
544
    @dbus.service.method(_interface, in_signature="t")
524
545
    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)
 
546
        self.interval = datetime.timedelta(0, 0, 0, milliseconds)
 
547
        # Emit D-Bus signal
 
548
        self.PropertyChanged(dbus.String(u"interval"),
 
549
                             (dbus.UInt64(self.interval_milliseconds(),
 
550
                                          variant_level=1)))
531
551
    
532
552
    # SetSecret - method
533
553
    @dbus.service.method(_interface, in_signature="ay",
536
556
        "D-Bus setter method"
537
557
        self.secret = str(secret)
538
558
    
539
 
    # Start - method
540
 
    Start = dbus.service.method(_interface)(start)
541
 
    Start.__name__ = "Start"
 
559
    # SetTimeout - method
 
560
    @dbus.service.method(_interface, in_signature="t")
 
561
    def SetTimeout(self, milliseconds):
 
562
        self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
 
563
        # Emit D-Bus signal
 
564
        self.PropertyChanged(dbus.String(u"timeout"),
 
565
                             (dbus.UInt64(self.timeout_milliseconds(),
 
566
                                          variant_level=1)))
 
567
    
 
568
    # Enable - method
 
569
    Enable = dbus.service.method(_interface)(enable)
 
570
    Enable.__name__ = "Enable"
542
571
    
543
572
    # 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"
 
573
    @dbus.service.method(_interface)
 
574
    def StartChecker(self):
 
575
        "D-Bus method"
 
576
        self.start_checker()
 
577
    
 
578
    # Disable - method
 
579
    @dbus.service.method(_interface)
 
580
    def Disable(self):
 
581
        "D-Bus method"
 
582
        self.disable()
561
583
    
562
584
    # StopChecker - method
563
585
    StopChecker = dbus.service.method(_interface)(stop_checker)
564
586
    StopChecker.__name__ = "StopChecker"
565
587
    
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
588
    del _interface
574
589
 
575
590
 
581
596
        != gnutls.library.constants.GNUTLS_CRT_OPENPGP):
582
597
        # ...do the normal thing
583
598
        return session.peer_certificate
584
 
    list_size = ctypes.c_uint()
 
599
    list_size = ctypes.c_uint(1)
585
600
    cert_list = (gnutls.library.functions
586
601
                 .gnutls_certificate_get_peers
587
602
                 (session._c_object, ctypes.byref(list_size)))
 
603
    if not bool(cert_list) and list_size.value != 0:
 
604
        raise gnutls.errors.GNUTLSError("error getting peer"
 
605
                                        " certificate")
588
606
    if list_size.value == 0:
589
607
        return None
590
608
    cert = cert_list[0]
659
677
        # using OpenPGP certificates.
660
678
        
661
679
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
662
 
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
663
 
        #                "+DHE-DSS"))
 
680
        #                     "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
 
681
        #                     "+DHE-DSS"))
664
682
        # Use a fallback default, since this MUST be set.
665
683
        priority = self.server.settings.get("priority", "NORMAL")
666
684
        (gnutls.library.functions
674
692
            # Do not run session.bye() here: the session is not
675
693
            # established.  Just abandon the request.
676
694
            return
 
695
        logger.debug(u"Handshake succeeded")
677
696
        try:
678
697
            fpr = fingerprint(peer_certificate(session))
679
698
        except (TypeError, gnutls.errors.GNUTLSError), error:
681
700
            session.bye()
682
701
            return
683
702
        logger.debug(u"Fingerprint: %s", fpr)
 
703
        
684
704
        for c in self.server.clients:
685
705
            if c.fingerprint == fpr:
686
706
                client = c
699
719
            session.bye()
700
720
            return
701
721
        ## This won't work here, since we're in a fork.
702
 
        # client.bump_timeout()
 
722
        # client.checked_ok()
703
723
        sent_size = 0
704
724
        while sent_size < len(client.secret):
705
725
            sent = session.send(client.secret[sent_size:])
745
765
                                 u" bind to interface %s",
746
766
                                 self.settings["interface"])
747
767
                else:
748
 
                    raise error
 
768
                    raise
749
769
        # Only bind(2) the socket if we really need to.
750
770
        if self.server_address[0] or self.server_address[1]:
751
771
            if not self.server_address[0]:
772
792
 
773
793
def string_to_delta(interval):
774
794
    """Parse a string and return a datetime.timedelta
775
 
 
 
795
    
776
796
    >>> string_to_delta('7d')
777
797
    datetime.timedelta(7)
778
798
    >>> string_to_delta('60s')
830
850
    elif state == avahi.ENTRY_GROUP_FAILURE:
831
851
        logger.critical(u"Avahi: Error in group state changed %s",
832
852
                        unicode(error))
833
 
        raise AvahiGroupError("State changed: %s", str(error))
 
853
        raise AvahiGroupError(u"State changed: %s" % unicode(error))
834
854
 
835
855
def if_nametoindex(interface):
836
856
    """Call the C function if_nametoindex(), or equivalent"""
879
899
 
880
900
 
881
901
def main():
882
 
    parser = OptionParser(version = "%%prog %s" % version)
 
902
    parser = optparse.OptionParser(version = "%%prog %s" % version)
883
903
    parser.add_option("-i", "--interface", type="string",
884
904
                      metavar="IF", help="Bind to interface IF")
885
905
    parser.add_option("-a", "--address", type="string",
886
906
                      help="Address to listen for requests on")
887
907
    parser.add_option("-p", "--port", type="int",
888
908
                      help="Port number to receive requests on")
889
 
    parser.add_option("--check", action="store_true", default=False,
 
909
    parser.add_option("--check", action="store_true",
890
910
                      help="Run self-test")
891
911
    parser.add_option("--debug", action="store_true",
892
912
                      help="Debug mode; run in foreground and log to"
899
919
                      default="/etc/mandos", metavar="DIR",
900
920
                      help="Directory to search for configuration"
901
921
                      " files")
 
922
    parser.add_option("--no-dbus", action="store_false",
 
923
                      dest="use_dbus",
 
924
                      help="Do not provide D-Bus system bus"
 
925
                      " interface")
902
926
    options = parser.parse_args()[0]
903
927
    
904
928
    if options.check:
914
938
                        "priority":
915
939
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
916
940
                        "servicename": "Mandos",
 
941
                        "use_dbus": "True",
917
942
                        }
918
943
    
919
944
    # Parse config file for server-global settings
922
947
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
923
948
    # Convert the SafeConfigParser object to a dict
924
949
    server_settings = server_config.defaults()
925
 
    # Use getboolean on the boolean config option
926
 
    server_settings["debug"] = (server_config.getboolean
927
 
                                ("DEFAULT", "debug"))
 
950
    # Use the appropriate methods on the non-string config options
 
951
    server_settings["debug"] = server_config.getboolean("DEFAULT",
 
952
                                                        "debug")
 
953
    server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
 
954
                                                           "use_dbus")
 
955
    if server_settings["port"]:
 
956
        server_settings["port"] = server_config.getint("DEFAULT",
 
957
                                                       "port")
928
958
    del server_config
929
959
    
930
960
    # Override the settings from the config file with command line
931
961
    # options, if set.
932
962
    for option in ("interface", "address", "port", "debug",
933
 
                   "priority", "servicename", "configdir"):
 
963
                   "priority", "servicename", "configdir",
 
964
                   "use_dbus"):
934
965
        value = getattr(options, option)
935
966
        if value is not None:
936
967
            server_settings[option] = value
937
968
    del options
938
969
    # Now we have our good server settings in "server_settings"
939
970
    
 
971
    # For convenience
940
972
    debug = server_settings["debug"]
 
973
    use_dbus = server_settings["use_dbus"]
941
974
    
942
975
    if not debug:
943
976
        syslogger.setLevel(logging.WARNING)
952
985
    # Parse config file with clients
953
986
    client_defaults = { "timeout": "1h",
954
987
                        "interval": "5m",
955
 
                        "checker": "fping -q -- %(host)s",
 
988
                        "checker": "fping -q -- %%(host)s",
956
989
                        "host": "",
957
990
                        }
958
991
    client_config = ConfigParser.SafeConfigParser(client_defaults)
968
1001
    pidfilename = "/var/run/mandos.pid"
969
1002
    try:
970
1003
        pidfile = open(pidfilename, "w")
971
 
    except IOError, error:
 
1004
    except IOError:
972
1005
        logger.error("Could not open file %r", pidfilename)
973
1006
    
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
990
 
    try:
 
1007
    try:
 
1008
        uid = pwd.getpwnam("_mandos").pw_uid
 
1009
        gid = pwd.getpwnam("_mandos").pw_gid
 
1010
    except KeyError:
 
1011
        try:
 
1012
            uid = pwd.getpwnam("mandos").pw_uid
 
1013
            gid = pwd.getpwnam("mandos").pw_gid
 
1014
        except KeyError:
 
1015
            try:
 
1016
                uid = pwd.getpwnam("nobody").pw_uid
 
1017
                gid = pwd.getpwnam("nogroup").pw_gid
 
1018
            except KeyError:
 
1019
                uid = 65534
 
1020
                gid = 65534
 
1021
    try:
 
1022
        os.setgid(gid)
991
1023
        os.setuid(uid)
992
 
        os.setgid(gid)
993
1024
    except OSError, error:
994
1025
        if error[0] != errno.EPERM:
995
1026
            raise error
996
1027
    
 
1028
    # Enable all possible GnuTLS debugging
 
1029
    if debug:
 
1030
        # "Use a log level over 10 to enable all debugging options."
 
1031
        # - GnuTLS manual
 
1032
        gnutls.library.functions.gnutls_global_set_log_level(11)
 
1033
        
 
1034
        @gnutls.library.types.gnutls_log_func
 
1035
        def debug_gnutls(level, string):
 
1036
            logger.debug("GnuTLS: %s", string[:-1])
 
1037
        
 
1038
        (gnutls.library.functions
 
1039
         .gnutls_global_set_log_function(debug_gnutls))
 
1040
    
997
1041
    global service
998
1042
    service = AvahiService(name = server_settings["servicename"],
999
1043
                           servicetype = "_mandos._tcp", )
1012
1056
                                           avahi.DBUS_PATH_SERVER),
1013
1057
                            avahi.DBUS_INTERFACE_SERVER)
1014
1058
    # 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()
 
1059
    if use_dbus:
 
1060
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1022
1061
    
1023
1062
    clients.update(Set(Client(name = section,
1024
 
                              stop_hook = remove_from_clients,
1025
1063
                              config
1026
 
                              = dict(client_config.items(section)))
 
1064
                              = dict(client_config.items(section)),
 
1065
                              use_dbus = use_dbus)
1027
1066
                       for section in client_config.sections()))
1028
1067
    if not clients:
1029
 
        logger.critical(u"No clients defined")
1030
 
        sys.exit(1)
 
1068
        logger.warning(u"No clients defined")
1031
1069
    
1032
1070
    if debug:
1033
1071
        # Redirect stdin so all checkers get /dev/null
1065
1103
        
1066
1104
        while clients:
1067
1105
            client = clients.pop()
1068
 
            client.stop_hook = None
1069
 
            client.stop()
 
1106
            client.disable_hook = None
 
1107
            client.disable()
1070
1108
    
1071
1109
    atexit.register(cleanup)
1072
1110
    
1075
1113
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1076
1114
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1077
1115
    
 
1116
    if use_dbus:
 
1117
        class MandosServer(dbus.service.Object):
 
1118
            """A D-Bus proxy object"""
 
1119
            def __init__(self):
 
1120
                dbus.service.Object.__init__(self, bus, "/")
 
1121
            _interface = u"se.bsnet.fukt.Mandos"
 
1122
            
 
1123
            @dbus.service.signal(_interface, signature="oa{sv}")
 
1124
            def ClientAdded(self, objpath, properties):
 
1125
                "D-Bus signal"
 
1126
                pass
 
1127
            
 
1128
            @dbus.service.signal(_interface, signature="os")
 
1129
            def ClientRemoved(self, objpath, name):
 
1130
                "D-Bus signal"
 
1131
                pass
 
1132
            
 
1133
            @dbus.service.method(_interface, out_signature="ao")
 
1134
            def GetAllClients(self):
 
1135
                "D-Bus method"
 
1136
                return dbus.Array(c.dbus_object_path for c in clients)
 
1137
            
 
1138
            @dbus.service.method(_interface, out_signature="a{oa{sv}}")
 
1139
            def GetAllClientsWithProperties(self):
 
1140
                "D-Bus method"
 
1141
                return dbus.Dictionary(
 
1142
                    ((c.dbus_object_path, c.GetAllProperties())
 
1143
                     for c in clients),
 
1144
                    signature="oa{sv}")
 
1145
            
 
1146
            @dbus.service.method(_interface, in_signature="o")
 
1147
            def RemoveClient(self, object_path):
 
1148
                "D-Bus method"
 
1149
                for c in clients:
 
1150
                    if c.dbus_object_path == object_path:
 
1151
                        clients.remove(c)
 
1152
                        # Don't signal anything except ClientRemoved
 
1153
                        c.use_dbus = False
 
1154
                        c.disable()
 
1155
                        # Emit D-Bus signal
 
1156
                        self.ClientRemoved(object_path, c.name)
 
1157
                        return
 
1158
                raise KeyError
 
1159
            
 
1160
            del _interface
 
1161
        
 
1162
        mandos_server = MandosServer()
 
1163
    
1078
1164
    for client in clients:
1079
 
        client.start()
 
1165
        if use_dbus:
 
1166
            # Emit D-Bus signal
 
1167
            mandos_server.ClientAdded(client.dbus_object_path,
 
1168
                                      client.GetAllProperties())
 
1169
        client.enable()
1080
1170
    
1081
1171
    tcp_server.enable()
1082
1172
    tcp_server.server_activate()
1106
1196
        logger.debug(u"Starting main loop")
1107
1197
        main_loop.run()
1108
1198
    except AvahiError, error:
1109
 
        logger.critical(u"AvahiError: %s" + unicode(error))
 
1199
        logger.critical(u"AvahiError: %s", error)
1110
1200
        sys.exit(1)
1111
1201
    except KeyboardInterrupt:
1112
1202
        if debug:
1113
 
            print
 
1203
            print >> sys.stderr
 
1204
        logger.debug("Server received KeyboardInterrupt")
 
1205
    logger.debug("Server exiting")
1114
1206
 
1115
1207
if __name__ == '__main__':
1116
1208
    main()