/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-13 05:38:21 UTC
  • Revision ID: teddy@fukt.bsnet.se-20090213053821-03e696gckk4nbjps
Support not using IPv6 in server:

* mandos (AvahiService.__init__): Take new "protocol" parameter.  All
                                  callers changed.
  (IPv6_TCPServer.__init__): Take new "use_ipv6" parameter.  All
                             callers changed.
  (IPv6_TCPServer.server_bind): Create IPv4 socket if not using IPv6.
  (main): New "--no-ipv6" command line option.  New "use_ipv6" config
          option.
* mandos-options.xml ([@id="address"]): Document conditional IPv4
                                        address support.
  ([@id="ipv6"]): New paragraph.
* mandos.conf (use_ipv6): New config option.
* mandos.conf.xml (OPTIONS): Document new "use_dbus" option.
  (EXAMPLE): Changed to use IPv6 link-local address.  Added "use_ipv6"
             option.
* mandos.xml (SYNOPSIS): New "--no-ipv6" option.
  (OPTIONS): Document new "--no-ipv6" option.

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 & Björn Påhlsson
 
14
# Copyright © 2008,2009 Teddy Hogeborn
 
15
# Copyright © 2008,2009 Björn Påhlsson
15
16
16
17
# This program is free software: you can redistribute it and/or modify
17
18
# it under the terms of the GNU General Public License as published by
30
31
# Contact the authors at <mandos@fukt.bsnet.se>.
31
32
32
33
 
33
 
from __future__ import division
 
34
from __future__ import division, with_statement, absolute_import
34
35
 
35
36
import SocketServer
36
37
import socket
37
 
from optparse import OptionParser
 
38
import optparse
38
39
import datetime
39
40
import errno
40
41
import gnutls.crypto
55
56
import logging
56
57
import logging.handlers
57
58
import pwd
 
59
from contextlib import closing
58
60
 
59
61
import dbus
 
62
import dbus.service
60
63
import gobject
61
64
import avahi
62
65
from dbus.mainloop.glib import DBusGMainLoop
63
66
import ctypes
64
67
import ctypes.util
65
68
 
66
 
version = "1.0"
 
69
version = "1.0.5"
67
70
 
68
71
logger = logging.Logger('mandos')
69
 
syslogger = logging.handlers.SysLogHandler\
70
 
            (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
71
 
             address = "/dev/log")
72
 
syslogger.setFormatter(logging.Formatter\
73
 
                        ('Mandos: %(levelname)s: %(message)s'))
 
72
syslogger = (logging.handlers.SysLogHandler
 
73
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
 
74
              address = "/dev/log"))
 
75
syslogger.setFormatter(logging.Formatter
 
76
                       ('Mandos [%(process)d]: %(levelname)s:'
 
77
                        ' %(message)s'))
74
78
logger.addHandler(syslogger)
75
79
 
76
80
console = logging.StreamHandler()
77
 
console.setFormatter(logging.Formatter('%(name)s: %(levelname)s:'
78
 
                                       ' %(message)s'))
 
81
console.setFormatter(logging.Formatter('%(name)s [%(process)d]:'
 
82
                                       ' %(levelname)s: %(message)s'))
79
83
logger.addHandler(console)
80
84
 
81
85
class AvahiError(Exception):
82
 
    def __init__(self, value):
 
86
    def __init__(self, value, *args, **kwargs):
83
87
        self.value = value
84
 
        super(AvahiError, self).__init__()
85
 
    def __str__(self):
86
 
        return repr(self.value)
 
88
        super(AvahiError, self).__init__(value, *args, **kwargs)
 
89
    def __unicode__(self):
 
90
        return unicode(repr(self.value))
87
91
 
88
92
class AvahiServiceError(AvahiError):
89
93
    pass
109
113
                  a sensible number of times
110
114
    """
111
115
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
112
 
                 servicetype = None, port = None, TXT = None, domain = "",
113
 
                 host = "", max_renames = 32768):
 
116
                 servicetype = None, port = None, TXT = None,
 
117
                 domain = "", host = "", max_renames = 32768,
 
118
                 protocol = avahi.PROTO_UNSPEC):
114
119
        self.interface = interface
115
120
        self.name = name
116
121
        self.type = servicetype
117
122
        self.port = port
118
 
        if TXT is None:
119
 
            self.TXT = []
120
 
        else:
121
 
            self.TXT = TXT
 
123
        self.TXT = TXT if TXT is not None else []
122
124
        self.domain = domain
123
125
        self.host = host
124
126
        self.rename_count = 0
125
127
        self.max_renames = max_renames
 
128
        self.protocol = protocol
126
129
    def rename(self):
127
130
        """Derived from the Avahi example code"""
128
131
        if self.rename_count >= self.max_renames:
129
132
            logger.critical(u"No suitable Zeroconf service name found"
130
133
                            u" after %i retries, exiting.",
131
134
                            self.rename_count)
132
 
            raise AvahiServiceError("Too many renames")
 
135
            raise AvahiServiceError(u"Too many renames")
133
136
        self.name = server.GetAlternativeServiceName(self.name)
134
137
        logger.info(u"Changing Zeroconf service name to %r ...",
135
138
                    str(self.name))
136
 
        syslogger.setFormatter(logging.Formatter\
 
139
        syslogger.setFormatter(logging.Formatter
137
140
                               ('Mandos (%s): %%(levelname)s:'
138
 
                               ' %%(message)s' % self.name))
 
141
                                ' %%(message)s' % self.name))
139
142
        self.remove()
140
143
        self.add()
141
144
        self.rename_count += 1
147
150
        """Derived from the Avahi example code"""
148
151
        global group
149
152
        if group is None:
150
 
            group = dbus.Interface\
151
 
                    (bus.get_object(avahi.DBUS_NAME,
 
153
            group = dbus.Interface(bus.get_object
 
154
                                   (avahi.DBUS_NAME,
152
155
                                    server.EntryGroupNew()),
153
 
                     avahi.DBUS_INTERFACE_ENTRY_GROUP)
 
156
                                   avahi.DBUS_INTERFACE_ENTRY_GROUP)
154
157
            group.connect_to_signal('StateChanged',
155
158
                                    entry_group_state_changed)
156
159
        logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
157
160
                     service.name, service.type)
158
161
        group.AddService(
159
162
                self.interface,         # interface
160
 
                avahi.PROTO_INET6,      # protocol
 
163
                self.protocol,          # protocol
161
164
                dbus.UInt32(0),         # flags
162
165
                self.name, self.type,
163
166
                self.domain, self.host,
170
173
# End of Avahi example code
171
174
 
172
175
 
173
 
class Client(object):
 
176
def _datetime_to_dbus(dt, variant_level=0):
 
177
    """Convert a UTC datetime.datetime() to a D-Bus type."""
 
178
    return dbus.String(dt.isoformat(), variant_level=variant_level)
 
179
 
 
180
 
 
181
class Client(dbus.service.Object):
174
182
    """A representation of a client host served by this server.
175
183
    Attributes:
176
 
    name:      string; from the config file, used in log messages
 
184
    name:       string; from the config file, used in log messages and
 
185
                        D-Bus identifiers
177
186
    fingerprint: string (40 or 32 hexadecimal digits); used to
178
187
                 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(); object creation, not client host
182
 
    last_checked_ok: datetime.datetime() or None if not yet checked OK
183
 
    timeout:   datetime.timedelta(); How long from last_checked_ok
184
 
                                     until this client is invalid
185
 
    interval:  datetime.timedelta(); How often to start a new checker
186
 
    stop_hook: If set, called by stop() as stop_hook(self)
187
 
    checker:   subprocess.Popen(); a running checker process used
188
 
                                   to see if the client lives.
189
 
                                   'None' if no process is running.
 
188
    secret:     bytestring; sent verbatim (over TLS) to client
 
189
    host:       string; available for use by the checker command
 
190
    created:    datetime.datetime(); (UTC) object creation
 
191
    last_enabled: datetime.datetime(); (UTC)
 
192
    enabled:    bool()
 
193
    last_checked_ok: datetime.datetime(); (UTC) or None
 
194
    timeout:    datetime.timedelta(); How long from last_checked_ok
 
195
                                      until this client is invalid
 
196
    interval:   datetime.timedelta(); How often to start a new checker
 
197
    disable_hook:  If set, called by disable() as disable_hook(self)
 
198
    checker:    subprocess.Popen(); a running checker process used
 
199
                                    to see if the client lives.
 
200
                                    'None' if no process is running.
190
201
    checker_initiator_tag: a gobject event source tag, or None
191
 
    stop_initiator_tag:    - '' -
 
202
    disable_initiator_tag:    - '' -
192
203
    checker_callback_tag:  - '' -
193
204
    checker_command: string; External command which is run to check if
194
205
                     client lives.  %() expansions are done at
195
206
                     runtime with vars(self) as dict, so that for
196
207
                     instance %(name)s can be used in the command.
197
 
    Private attibutes:
198
 
    _timeout: Real variable for 'timeout'
199
 
    _interval: Real variable for 'interval'
200
 
    _timeout_milliseconds: Used when calling gobject.timeout_add()
201
 
    _interval_milliseconds: - '' -
 
208
    use_dbus: bool(); Whether to provide D-Bus interface and signals
 
209
    dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
202
210
    """
203
 
    def _set_timeout(self, timeout):
204
 
        "Setter function for 'timeout' attribute"
205
 
        self._timeout = timeout
206
 
        self._timeout_milliseconds = ((self.timeout.days
207
 
                                       * 24 * 60 * 60 * 1000)
208
 
                                      + (self.timeout.seconds * 1000)
209
 
                                      + (self.timeout.microseconds
210
 
                                         // 1000))
211
 
    timeout = property(lambda self: self._timeout,
212
 
                       _set_timeout)
213
 
    del _set_timeout
214
 
    def _set_interval(self, interval):
215
 
        "Setter function for 'interval' attribute"
216
 
        self._interval = interval
217
 
        self._interval_milliseconds = ((self.interval.days
218
 
                                        * 24 * 60 * 60 * 1000)
219
 
                                       + (self.interval.seconds
220
 
                                          * 1000)
221
 
                                       + (self.interval.microseconds
222
 
                                          // 1000))
223
 
    interval = property(lambda self: self._interval,
224
 
                        _set_interval)
225
 
    del _set_interval
226
 
    def __init__(self, name = None, stop_hook=None, config=None):
 
211
    def timeout_milliseconds(self):
 
212
        "Return the 'timeout' attribute in milliseconds"
 
213
        return ((self.timeout.days * 24 * 60 * 60 * 1000)
 
214
                + (self.timeout.seconds * 1000)
 
215
                + (self.timeout.microseconds // 1000))
 
216
    
 
217
    def interval_milliseconds(self):
 
218
        "Return the 'interval' attribute in milliseconds"
 
219
        return ((self.interval.days * 24 * 60 * 60 * 1000)
 
220
                + (self.interval.seconds * 1000)
 
221
                + (self.interval.microseconds // 1000))
 
222
    
 
223
    def __init__(self, name = None, disable_hook=None, config=None,
 
224
                 use_dbus=True):
227
225
        """Note: the 'checker' key in 'config' sets the
228
226
        'checker_command' attribute and *not* the 'checker'
229
227
        attribute."""
 
228
        self.name = name
230
229
        if config is None:
231
230
            config = {}
232
 
        self.name = name
233
231
        logger.debug(u"Creating client %r", self.name)
 
232
        self.use_dbus = False   # During __init__
234
233
        # Uppercase and remove spaces from fingerprint for later
235
234
        # comparison purposes with return value from the fingerprint()
236
235
        # function
237
 
        self.fingerprint = config["fingerprint"].upper()\
238
 
                           .replace(u" ", u"")
 
236
        self.fingerprint = (config["fingerprint"].upper()
 
237
                            .replace(u" ", u""))
239
238
        logger.debug(u"  Fingerprint: %s", self.fingerprint)
240
239
        if "secret" in config:
241
240
            self.secret = config["secret"].decode(u"base64")
242
241
        elif "secfile" in config:
243
 
            secfile = open(config["secfile"])
244
 
            self.secret = secfile.read()
245
 
            secfile.close()
 
242
            with closing(open(os.path.expanduser
 
243
                              (os.path.expandvars
 
244
                               (config["secfile"])))) as secfile:
 
245
                self.secret = secfile.read()
246
246
        else:
247
247
            raise TypeError(u"No secret or secfile for client %s"
248
248
                            % self.name)
249
249
        self.host = config.get("host", "")
250
 
        self.created = datetime.datetime.now()
 
250
        self.created = datetime.datetime.utcnow()
 
251
        self.enabled = False
 
252
        self.last_enabled = None
251
253
        self.last_checked_ok = None
252
254
        self.timeout = string_to_delta(config["timeout"])
253
255
        self.interval = string_to_delta(config["interval"])
254
 
        self.stop_hook = stop_hook
 
256
        self.disable_hook = disable_hook
255
257
        self.checker = None
256
258
        self.checker_initiator_tag = None
257
 
        self.stop_initiator_tag = None
 
259
        self.disable_initiator_tag = None
258
260
        self.checker_callback_tag = None
259
 
        self.check_command = config["checker"]
260
 
    def start(self):
 
261
        self.checker_command = config["checker"]
 
262
        self.last_connect = None
 
263
        # Only now, when this client is initialized, can it show up on
 
264
        # the D-Bus
 
265
        self.use_dbus = use_dbus
 
266
        if self.use_dbus:
 
267
            self.dbus_object_path = (dbus.ObjectPath
 
268
                                     ("/clients/"
 
269
                                      + self.name.replace(".", "_")))
 
270
            dbus.service.Object.__init__(self, bus,
 
271
                                         self.dbus_object_path)
 
272
    
 
273
    def enable(self):
261
274
        """Start this client's checker and timeout hooks"""
 
275
        self.last_enabled = datetime.datetime.utcnow()
262
276
        # Schedule a new checker to be started an 'interval' from now,
263
277
        # and every interval from then on.
264
 
        self.checker_initiator_tag = gobject.timeout_add\
265
 
                                     (self._interval_milliseconds,
266
 
                                      self.start_checker)
 
278
        self.checker_initiator_tag = (gobject.timeout_add
 
279
                                      (self.interval_milliseconds(),
 
280
                                       self.start_checker))
267
281
        # Also start a new checker *right now*.
268
282
        self.start_checker()
269
 
        # Schedule a stop() when 'timeout' has passed
270
 
        self.stop_initiator_tag = gobject.timeout_add\
271
 
                                  (self._timeout_milliseconds,
272
 
                                   self.stop)
273
 
    def stop(self):
274
 
        """Stop this client.
275
 
        The possibility that a client might be restarted is left open,
276
 
        but not currently used."""
277
 
        # If this client doesn't have a secret, it is already stopped.
278
 
        if hasattr(self, "secret") and self.secret:
279
 
            logger.info(u"Stopping client %s", self.name)
280
 
            self.secret = None
281
 
        else:
 
283
        # Schedule a disable() when 'timeout' has passed
 
284
        self.disable_initiator_tag = (gobject.timeout_add
 
285
                                   (self.timeout_milliseconds(),
 
286
                                    self.disable))
 
287
        self.enabled = True
 
288
        if self.use_dbus:
 
289
            # Emit D-Bus signals
 
290
            self.PropertyChanged(dbus.String(u"enabled"),
 
291
                                 dbus.Boolean(True, variant_level=1))
 
292
            self.PropertyChanged(dbus.String(u"last_enabled"),
 
293
                                 (_datetime_to_dbus(self.last_enabled,
 
294
                                                    variant_level=1)))
 
295
    
 
296
    def disable(self):
 
297
        """Disable this client."""
 
298
        if not getattr(self, "enabled", False):
282
299
            return False
283
 
        if getattr(self, "stop_initiator_tag", False):
284
 
            gobject.source_remove(self.stop_initiator_tag)
285
 
            self.stop_initiator_tag = None
 
300
        logger.info(u"Disabling client %s", self.name)
 
301
        if getattr(self, "disable_initiator_tag", False):
 
302
            gobject.source_remove(self.disable_initiator_tag)
 
303
            self.disable_initiator_tag = None
286
304
        if getattr(self, "checker_initiator_tag", False):
287
305
            gobject.source_remove(self.checker_initiator_tag)
288
306
            self.checker_initiator_tag = None
289
307
        self.stop_checker()
290
 
        if self.stop_hook:
291
 
            self.stop_hook(self)
 
308
        if self.disable_hook:
 
309
            self.disable_hook(self)
 
310
        self.enabled = False
 
311
        if self.use_dbus:
 
312
            # Emit D-Bus signal
 
313
            self.PropertyChanged(dbus.String(u"enabled"),
 
314
                                 dbus.Boolean(False, variant_level=1))
292
315
        # Do not run this again if called by a gobject.timeout_add
293
316
        return False
 
317
    
294
318
    def __del__(self):
295
 
        self.stop_hook = None
296
 
        self.stop()
297
 
    def checker_callback(self, pid, condition):
 
319
        self.disable_hook = None
 
320
        self.disable()
 
321
    
 
322
    def checker_callback(self, pid, condition, command):
298
323
        """The checker has completed, so take appropriate actions."""
299
 
        now = datetime.datetime.now()
300
324
        self.checker_callback_tag = None
301
325
        self.checker = None
302
 
        if os.WIFEXITED(condition) \
303
 
               and (os.WEXITSTATUS(condition) == 0):
304
 
            logger.info(u"Checker for %(name)s succeeded",
305
 
                        vars(self))
306
 
            self.last_checked_ok = now
307
 
            gobject.source_remove(self.stop_initiator_tag)
308
 
            self.stop_initiator_tag = gobject.timeout_add\
309
 
                                      (self._timeout_milliseconds,
310
 
                                       self.stop)
311
 
        elif not os.WIFEXITED(condition):
 
326
        if self.use_dbus:
 
327
            # Emit D-Bus signal
 
328
            self.PropertyChanged(dbus.String(u"checker_running"),
 
329
                                 dbus.Boolean(False, variant_level=1))
 
330
        if os.WIFEXITED(condition):
 
331
            exitstatus = os.WEXITSTATUS(condition)
 
332
            if exitstatus == 0:
 
333
                logger.info(u"Checker for %(name)s succeeded",
 
334
                            vars(self))
 
335
                self.checked_ok()
 
336
            else:
 
337
                logger.info(u"Checker for %(name)s failed",
 
338
                            vars(self))
 
339
            if self.use_dbus:
 
340
                # Emit D-Bus signal
 
341
                self.CheckerCompleted(dbus.Int16(exitstatus),
 
342
                                      dbus.Int64(condition),
 
343
                                      dbus.String(command))
 
344
        else:
312
345
            logger.warning(u"Checker for %(name)s crashed?",
313
346
                           vars(self))
314
 
        else:
315
 
            logger.info(u"Checker for %(name)s failed",
316
 
                        vars(self))
 
347
            if self.use_dbus:
 
348
                # Emit D-Bus signal
 
349
                self.CheckerCompleted(dbus.Int16(-1),
 
350
                                      dbus.Int64(condition),
 
351
                                      dbus.String(command))
 
352
    
 
353
    def checked_ok(self):
 
354
        """Bump up the timeout for this client.
 
355
        This should only be called when the client has been seen,
 
356
        alive and well.
 
357
        """
 
358
        self.last_checked_ok = datetime.datetime.utcnow()
 
359
        gobject.source_remove(self.disable_initiator_tag)
 
360
        self.disable_initiator_tag = (gobject.timeout_add
 
361
                                      (self.timeout_milliseconds(),
 
362
                                       self.disable))
 
363
        if self.use_dbus:
 
364
            # Emit D-Bus signal
 
365
            self.PropertyChanged(
 
366
                dbus.String(u"last_checked_ok"),
 
367
                (_datetime_to_dbus(self.last_checked_ok,
 
368
                                   variant_level=1)))
 
369
    
317
370
    def start_checker(self):
318
371
        """Start a new checker subprocess if one is not running.
319
372
        If a checker already exists, leave it running and do
328
381
        # is as it should be.
329
382
        if self.checker is None:
330
383
            try:
331
 
                # In case check_command has exactly one % operator
332
 
                command = self.check_command % self.host
 
384
                # In case checker_command has exactly one % operator
 
385
                command = self.checker_command % self.host
333
386
            except TypeError:
334
387
                # Escape attributes for the shell
335
388
                escaped_attrs = dict((key, re.escape(str(val)))
336
389
                                     for key, val in
337
390
                                     vars(self).iteritems())
338
391
                try:
339
 
                    command = self.check_command % escaped_attrs
 
392
                    command = self.checker_command % escaped_attrs
340
393
                except TypeError, error:
341
394
                    logger.error(u'Could not format string "%s":'
342
 
                                 u' %s', self.check_command, error)
 
395
                                 u' %s', self.checker_command, error)
343
396
                    return True # Try again later
344
397
            try:
345
398
                logger.info(u"Starting checker %r for %s",
351
404
                self.checker = subprocess.Popen(command,
352
405
                                                close_fds=True,
353
406
                                                shell=True, cwd="/")
354
 
                self.checker_callback_tag = gobject.child_watch_add\
355
 
                                            (self.checker.pid,
356
 
                                             self.checker_callback)
 
407
                if self.use_dbus:
 
408
                    # Emit D-Bus signal
 
409
                    self.CheckerStarted(command)
 
410
                    self.PropertyChanged(
 
411
                        dbus.String("checker_running"),
 
412
                        dbus.Boolean(True, variant_level=1))
 
413
                self.checker_callback_tag = (gobject.child_watch_add
 
414
                                             (self.checker.pid,
 
415
                                              self.checker_callback,
 
416
                                              data=command))
 
417
                # The checker may have completed before the gobject
 
418
                # watch was added.  Check for this.
 
419
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
 
420
                if pid:
 
421
                    gobject.source_remove(self.checker_callback_tag)
 
422
                    self.checker_callback(pid, status, command)
357
423
            except OSError, error:
358
424
                logger.error(u"Failed to start subprocess: %s",
359
425
                             error)
360
426
        # Re-run this periodically if run by gobject.timeout_add
361
427
        return True
 
428
    
362
429
    def stop_checker(self):
363
430
        """Force the checker process, if any, to stop."""
364
431
        if self.checker_callback_tag:
376
443
            if error.errno != errno.ESRCH: # No such process
377
444
                raise
378
445
        self.checker = None
 
446
        if self.use_dbus:
 
447
            self.PropertyChanged(dbus.String(u"checker_running"),
 
448
                                 dbus.Boolean(False, variant_level=1))
 
449
    
379
450
    def still_valid(self):
380
451
        """Has the timeout not yet passed for this client?"""
381
 
        now = datetime.datetime.now()
 
452
        if not getattr(self, "enabled", False):
 
453
            return False
 
454
        now = datetime.datetime.utcnow()
382
455
        if self.last_checked_ok is None:
383
456
            return now < (self.created + self.timeout)
384
457
        else:
385
458
            return now < (self.last_checked_ok + self.timeout)
 
459
    
 
460
    ## D-Bus methods & signals
 
461
    _interface = u"se.bsnet.fukt.Mandos.Client"
 
462
    
 
463
    # CheckedOK - method
 
464
    CheckedOK = dbus.service.method(_interface)(checked_ok)
 
465
    CheckedOK.__name__ = "CheckedOK"
 
466
    
 
467
    # CheckerCompleted - signal
 
468
    @dbus.service.signal(_interface, signature="nxs")
 
469
    def CheckerCompleted(self, exitcode, waitstatus, command):
 
470
        "D-Bus signal"
 
471
        pass
 
472
    
 
473
    # CheckerStarted - signal
 
474
    @dbus.service.signal(_interface, signature="s")
 
475
    def CheckerStarted(self, command):
 
476
        "D-Bus signal"
 
477
        pass
 
478
    
 
479
    # GetAllProperties - method
 
480
    @dbus.service.method(_interface, out_signature="a{sv}")
 
481
    def GetAllProperties(self):
 
482
        "D-Bus method"
 
483
        return dbus.Dictionary({
 
484
                dbus.String("name"):
 
485
                    dbus.String(self.name, variant_level=1),
 
486
                dbus.String("fingerprint"):
 
487
                    dbus.String(self.fingerprint, variant_level=1),
 
488
                dbus.String("host"):
 
489
                    dbus.String(self.host, variant_level=1),
 
490
                dbus.String("created"):
 
491
                    _datetime_to_dbus(self.created, variant_level=1),
 
492
                dbus.String("last_enabled"):
 
493
                    (_datetime_to_dbus(self.last_enabled,
 
494
                                       variant_level=1)
 
495
                     if self.last_enabled is not None
 
496
                     else dbus.Boolean(False, variant_level=1)),
 
497
                dbus.String("enabled"):
 
498
                    dbus.Boolean(self.enabled, variant_level=1),
 
499
                dbus.String("last_checked_ok"):
 
500
                    (_datetime_to_dbus(self.last_checked_ok,
 
501
                                       variant_level=1)
 
502
                     if self.last_checked_ok is not None
 
503
                     else dbus.Boolean (False, variant_level=1)),
 
504
                dbus.String("timeout"):
 
505
                    dbus.UInt64(self.timeout_milliseconds(),
 
506
                                variant_level=1),
 
507
                dbus.String("interval"):
 
508
                    dbus.UInt64(self.interval_milliseconds(),
 
509
                                variant_level=1),
 
510
                dbus.String("checker"):
 
511
                    dbus.String(self.checker_command,
 
512
                                variant_level=1),
 
513
                dbus.String("checker_running"):
 
514
                    dbus.Boolean(self.checker is not None,
 
515
                                 variant_level=1),
 
516
                dbus.String("object_path"):
 
517
                    dbus.ObjectPath(self.dbus_object_path,
 
518
                                    variant_level=1)
 
519
                }, signature="sv")
 
520
    
 
521
    # IsStillValid - method
 
522
    IsStillValid = (dbus.service.method(_interface, out_signature="b")
 
523
                    (still_valid))
 
524
    IsStillValid.__name__ = "IsStillValid"
 
525
    
 
526
    # PropertyChanged - signal
 
527
    @dbus.service.signal(_interface, signature="sv")
 
528
    def PropertyChanged(self, property, value):
 
529
        "D-Bus signal"
 
530
        pass
 
531
    
 
532
    # SetChecker - method
 
533
    @dbus.service.method(_interface, in_signature="s")
 
534
    def SetChecker(self, checker):
 
535
        "D-Bus setter method"
 
536
        self.checker_command = checker
 
537
        # Emit D-Bus signal
 
538
        self.PropertyChanged(dbus.String(u"checker"),
 
539
                             dbus.String(self.checker_command,
 
540
                                         variant_level=1))
 
541
    
 
542
    # SetHost - method
 
543
    @dbus.service.method(_interface, in_signature="s")
 
544
    def SetHost(self, host):
 
545
        "D-Bus setter method"
 
546
        self.host = host
 
547
        # Emit D-Bus signal
 
548
        self.PropertyChanged(dbus.String(u"host"),
 
549
                             dbus.String(self.host, variant_level=1))
 
550
    
 
551
    # SetInterval - method
 
552
    @dbus.service.method(_interface, in_signature="t")
 
553
    def SetInterval(self, milliseconds):
 
554
        self.interval = datetime.timedelta(0, 0, 0, milliseconds)
 
555
        # Emit D-Bus signal
 
556
        self.PropertyChanged(dbus.String(u"interval"),
 
557
                             (dbus.UInt64(self.interval_milliseconds(),
 
558
                                          variant_level=1)))
 
559
    
 
560
    # SetSecret - method
 
561
    @dbus.service.method(_interface, in_signature="ay",
 
562
                         byte_arrays=True)
 
563
    def SetSecret(self, secret):
 
564
        "D-Bus setter method"
 
565
        self.secret = str(secret)
 
566
    
 
567
    # SetTimeout - method
 
568
    @dbus.service.method(_interface, in_signature="t")
 
569
    def SetTimeout(self, milliseconds):
 
570
        self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
 
571
        # Emit D-Bus signal
 
572
        self.PropertyChanged(dbus.String(u"timeout"),
 
573
                             (dbus.UInt64(self.timeout_milliseconds(),
 
574
                                          variant_level=1)))
 
575
    
 
576
    # Enable - method
 
577
    Enable = dbus.service.method(_interface)(enable)
 
578
    Enable.__name__ = "Enable"
 
579
    
 
580
    # StartChecker - method
 
581
    @dbus.service.method(_interface)
 
582
    def StartChecker(self):
 
583
        "D-Bus method"
 
584
        self.start_checker()
 
585
    
 
586
    # Disable - method
 
587
    @dbus.service.method(_interface)
 
588
    def Disable(self):
 
589
        "D-Bus method"
 
590
        self.disable()
 
591
    
 
592
    # StopChecker - method
 
593
    StopChecker = dbus.service.method(_interface)(stop_checker)
 
594
    StopChecker.__name__ = "StopChecker"
 
595
    
 
596
    del _interface
386
597
 
387
598
 
388
599
def peer_certificate(session):
389
600
    "Return the peer's OpenPGP certificate as a bytestring"
390
601
    # If not an OpenPGP certificate...
391
 
    if gnutls.library.functions.gnutls_certificate_type_get\
392
 
            (session._c_object) \
393
 
           != gnutls.library.constants.GNUTLS_CRT_OPENPGP:
 
602
    if (gnutls.library.functions
 
603
        .gnutls_certificate_type_get(session._c_object)
 
604
        != gnutls.library.constants.GNUTLS_CRT_OPENPGP):
394
605
        # ...do the normal thing
395
606
        return session.peer_certificate
396
 
    list_size = ctypes.c_uint()
397
 
    cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
398
 
        (session._c_object, ctypes.byref(list_size))
 
607
    list_size = ctypes.c_uint(1)
 
608
    cert_list = (gnutls.library.functions
 
609
                 .gnutls_certificate_get_peers
 
610
                 (session._c_object, ctypes.byref(list_size)))
 
611
    if not bool(cert_list) and list_size.value != 0:
 
612
        raise gnutls.errors.GNUTLSError("error getting peer"
 
613
                                        " certificate")
399
614
    if list_size.value == 0:
400
615
        return None
401
616
    cert = cert_list[0]
405
620
def fingerprint(openpgp):
406
621
    "Convert an OpenPGP bytestring to a hexdigit fingerprint string"
407
622
    # New GnuTLS "datum" with the OpenPGP public key
408
 
    datum = gnutls.library.types.gnutls_datum_t\
409
 
        (ctypes.cast(ctypes.c_char_p(openpgp),
410
 
                     ctypes.POINTER(ctypes.c_ubyte)),
411
 
         ctypes.c_uint(len(openpgp)))
 
623
    datum = (gnutls.library.types
 
624
             .gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
 
625
                                         ctypes.POINTER
 
626
                                         (ctypes.c_ubyte)),
 
627
                             ctypes.c_uint(len(openpgp))))
412
628
    # New empty GnuTLS certificate
413
629
    crt = gnutls.library.types.gnutls_openpgp_crt_t()
414
 
    gnutls.library.functions.gnutls_openpgp_crt_init\
415
 
        (ctypes.byref(crt))
 
630
    (gnutls.library.functions
 
631
     .gnutls_openpgp_crt_init(ctypes.byref(crt)))
416
632
    # Import the OpenPGP public key into the certificate
417
 
    gnutls.library.functions.gnutls_openpgp_crt_import\
418
 
                    (crt, ctypes.byref(datum),
419
 
                     gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
 
633
    (gnutls.library.functions
 
634
     .gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
 
635
                                gnutls.library.constants
 
636
                                .GNUTLS_OPENPGP_FMT_RAW))
420
637
    # Verify the self signature in the key
421
638
    crtverify = ctypes.c_uint()
422
 
    gnutls.library.functions.gnutls_openpgp_crt_verify_self\
423
 
        (crt, 0, ctypes.byref(crtverify))
 
639
    (gnutls.library.functions
 
640
     .gnutls_openpgp_crt_verify_self(crt, 0, ctypes.byref(crtverify)))
424
641
    if crtverify.value != 0:
425
642
        gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
426
643
        raise gnutls.errors.CertificateSecurityError("Verify failed")
428
645
    buf = ctypes.create_string_buffer(20)
429
646
    buf_len = ctypes.c_size_t()
430
647
    # Get the fingerprint from the certificate into the buffer
431
 
    gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
432
 
        (crt, ctypes.byref(buf), ctypes.byref(buf_len))
 
648
    (gnutls.library.functions
 
649
     .gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
 
650
                                         ctypes.byref(buf_len)))
433
651
    # Deinit the certificate
434
652
    gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
435
653
    # Convert the buffer to a Python bytestring
446
664
    
447
665
    def handle(self):
448
666
        logger.info(u"TCP connection from: %s",
449
 
                     unicode(self.client_address))
450
 
        session = gnutls.connection.ClientSession\
451
 
                  (self.request, gnutls.connection.X509Credentials())
 
667
                    unicode(self.client_address))
 
668
        session = (gnutls.connection
 
669
                   .ClientSession(self.request,
 
670
                                  gnutls.connection
 
671
                                  .X509Credentials()))
452
672
        
453
673
        line = self.request.makefile().readline()
454
674
        logger.debug(u"Protocol version: %r", line)
465
685
        # using OpenPGP certificates.
466
686
        
467
687
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
468
 
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
469
 
        #                "+DHE-DSS"))
470
 
        priority = "NORMAL"             # Fallback default, since this
471
 
                                        # MUST be set.
472
 
        if self.server.settings["priority"]:
473
 
            priority = self.server.settings["priority"]
474
 
        gnutls.library.functions.gnutls_priority_set_direct\
475
 
            (session._c_object, priority, None)
 
688
        #                     "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
 
689
        #                     "+DHE-DSS"))
 
690
        # Use a fallback default, since this MUST be set.
 
691
        priority = self.server.settings.get("priority", "NORMAL")
 
692
        (gnutls.library.functions
 
693
         .gnutls_priority_set_direct(session._c_object,
 
694
                                     priority, None))
476
695
        
477
696
        try:
478
697
            session.handshake()
481
700
            # Do not run session.bye() here: the session is not
482
701
            # established.  Just abandon the request.
483
702
            return
 
703
        logger.debug(u"Handshake succeeded")
484
704
        try:
485
705
            fpr = fingerprint(peer_certificate(session))
486
706
        except (TypeError, gnutls.errors.GNUTLSError), error:
488
708
            session.bye()
489
709
            return
490
710
        logger.debug(u"Fingerprint: %s", fpr)
491
 
        client = None
 
711
        
492
712
        for c in self.server.clients:
493
713
            if c.fingerprint == fpr:
494
714
                client = c
495
715
                break
496
 
        if not client:
 
716
        else:
497
717
            logger.warning(u"Client not found for fingerprint: %s",
498
718
                           fpr)
499
719
            session.bye()
506
726
                           vars(client))
507
727
            session.bye()
508
728
            return
 
729
        ## This won't work here, since we're in a fork.
 
730
        # client.checked_ok()
509
731
        sent_size = 0
510
732
        while sent_size < len(client.secret):
511
733
            sent = session.send(client.secret[sent_size:])
516
738
        session.bye()
517
739
 
518
740
 
519
 
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
520
 
    """IPv6 TCP server.  Accepts 'None' as address and/or port.
 
741
class IPv6_TCPServer(SocketServer.ForkingMixIn,
 
742
                     SocketServer.TCPServer, object):
 
743
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port.
521
744
    Attributes:
522
745
        settings:       Server settings
523
746
        clients:        Set() of Client objects
531
754
        if "clients" in kwargs:
532
755
            self.clients = kwargs["clients"]
533
756
            del kwargs["clients"]
 
757
        if "use_ipv6" in kwargs:
 
758
            if not kwargs["use_ipv6"]:
 
759
                self.address_family = socket.AF_INET
 
760
            del kwargs["use_ipv6"]
534
761
        self.enabled = False
535
762
        super(IPv6_TCPServer, self).__init__(*args, **kwargs)
536
763
    def server_bind(self):
550
777
                                 u" bind to interface %s",
551
778
                                 self.settings["interface"])
552
779
                else:
553
 
                    raise error
 
780
                    raise
554
781
        # Only bind(2) the socket if we really need to.
555
782
        if self.server_address[0] or self.server_address[1]:
556
783
            if not self.server_address[0]:
557
 
                in6addr_any = "::"
558
 
                self.server_address = (in6addr_any,
 
784
                if self.address_family == socket.AF_INET6:
 
785
                    any_address = "::" # in6addr_any
 
786
                else:
 
787
                    any_address = socket.INADDR_ANY
 
788
                self.server_address = (any_address,
559
789
                                       self.server_address[1])
560
790
            elif not self.server_address[1]:
561
791
                self.server_address = (self.server_address[0],
577
807
 
578
808
def string_to_delta(interval):
579
809
    """Parse a string and return a datetime.timedelta
580
 
 
 
810
    
581
811
    >>> string_to_delta('7d')
582
812
    datetime.timedelta(7)
583
813
    >>> string_to_delta('60s')
635
865
    elif state == avahi.ENTRY_GROUP_FAILURE:
636
866
        logger.critical(u"Avahi: Error in group state changed %s",
637
867
                        unicode(error))
638
 
        raise AvahiGroupError("State changed: %s", str(error))
 
868
        raise AvahiGroupError(u"State changed: %s" % unicode(error))
639
869
 
640
870
def if_nametoindex(interface):
641
871
    """Call the C function if_nametoindex(), or equivalent"""
642
872
    global if_nametoindex
643
873
    try:
644
 
        if_nametoindex = ctypes.cdll.LoadLibrary\
645
 
            (ctypes.util.find_library("c")).if_nametoindex
 
874
        if_nametoindex = (ctypes.cdll.LoadLibrary
 
875
                          (ctypes.util.find_library("c"))
 
876
                          .if_nametoindex)
646
877
    except (OSError, AttributeError):
647
878
        if "struct" not in sys.modules:
648
879
            import struct
651
882
        def if_nametoindex(interface):
652
883
            "Get an interface index the hard way, i.e. using fcntl()"
653
884
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
654
 
            s = socket.socket()
655
 
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
656
 
                                struct.pack("16s16x", interface))
657
 
            s.close()
 
885
            with closing(socket.socket()) as s:
 
886
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
 
887
                                    struct.pack("16s16x", interface))
658
888
            interface_index = struct.unpack("I", ifreq[16:20])[0]
659
889
            return interface_index
660
890
    return if_nametoindex(interface)
684
914
 
685
915
 
686
916
def main():
687
 
    parser = OptionParser(version = "%%prog %s" % version)
 
917
    parser = optparse.OptionParser(version = "%%prog %s" % version)
688
918
    parser.add_option("-i", "--interface", type="string",
689
919
                      metavar="IF", help="Bind to interface IF")
690
920
    parser.add_option("-a", "--address", type="string",
691
921
                      help="Address to listen for requests on")
692
922
    parser.add_option("-p", "--port", type="int",
693
923
                      help="Port number to receive requests on")
694
 
    parser.add_option("--check", action="store_true", default=False,
 
924
    parser.add_option("--check", action="store_true",
695
925
                      help="Run self-test")
696
926
    parser.add_option("--debug", action="store_true",
697
927
                      help="Debug mode; run in foreground and log to"
704
934
                      default="/etc/mandos", metavar="DIR",
705
935
                      help="Directory to search for configuration"
706
936
                      " files")
 
937
    parser.add_option("--no-dbus", action="store_false",
 
938
                      dest="use_dbus",
 
939
                      help="Do not provide D-Bus system bus"
 
940
                      " interface")
 
941
    parser.add_option("--no-ipv6", action="store_false",
 
942
                      dest="use_ipv6", help="Do not use IPv6")
707
943
    options = parser.parse_args()[0]
708
944
    
709
945
    if options.check:
719
955
                        "priority":
720
956
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
721
957
                        "servicename": "Mandos",
 
958
                        "use_dbus": "True",
 
959
                        "use_ipv6": "True",
722
960
                        }
723
961
    
724
962
    # Parse config file for server-global settings
727
965
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
728
966
    # Convert the SafeConfigParser object to a dict
729
967
    server_settings = server_config.defaults()
730
 
    # Use getboolean on the boolean config option
731
 
    server_settings["debug"] = server_config.getboolean\
732
 
                               ("DEFAULT", "debug")
 
968
    # Use the appropriate methods on the non-string config options
 
969
    server_settings["debug"] = server_config.getboolean("DEFAULT",
 
970
                                                        "debug")
 
971
    server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
 
972
                                                           "use_dbus")
 
973
    server_settings["use_ipv6"] = server_config.getboolean("DEFAULT",
 
974
                                                           "use_ipv6")
 
975
    if server_settings["port"]:
 
976
        server_settings["port"] = server_config.getint("DEFAULT",
 
977
                                                       "port")
733
978
    del server_config
734
979
    
735
980
    # Override the settings from the config file with command line
736
981
    # options, if set.
737
982
    for option in ("interface", "address", "port", "debug",
738
 
                   "priority", "servicename", "configdir"):
 
983
                   "priority", "servicename", "configdir",
 
984
                   "use_dbus", "use_ipv6"):
739
985
        value = getattr(options, option)
740
986
        if value is not None:
741
987
            server_settings[option] = value
742
988
    del options
743
989
    # Now we have our good server settings in "server_settings"
744
990
    
 
991
    # For convenience
745
992
    debug = server_settings["debug"]
 
993
    use_dbus = server_settings["use_dbus"]
 
994
    use_ipv6 = server_settings["use_ipv6"]
746
995
    
747
996
    if not debug:
748
997
        syslogger.setLevel(logging.WARNING)
749
998
        console.setLevel(logging.WARNING)
750
999
    
751
1000
    if server_settings["servicename"] != "Mandos":
752
 
        syslogger.setFormatter(logging.Formatter\
 
1001
        syslogger.setFormatter(logging.Formatter
753
1002
                               ('Mandos (%s): %%(levelname)s:'
754
1003
                                ' %%(message)s'
755
1004
                                % server_settings["servicename"]))
757
1006
    # Parse config file with clients
758
1007
    client_defaults = { "timeout": "1h",
759
1008
                        "interval": "5m",
760
 
                        "checker": "fping -q -- %(host)s",
 
1009
                        "checker": "fping -q -- %%(host)s",
761
1010
                        "host": "",
762
1011
                        }
763
1012
    client_config = ConfigParser.SafeConfigParser(client_defaults)
769
1018
                                 server_settings["port"]),
770
1019
                                TCP_handler,
771
1020
                                settings=server_settings,
772
 
                                clients=clients)
 
1021
                                clients=clients, use_ipv6=use_ipv6)
773
1022
    pidfilename = "/var/run/mandos.pid"
774
1023
    try:
775
1024
        pidfile = open(pidfilename, "w")
776
 
    except IOError, error:
 
1025
    except IOError:
777
1026
        logger.error("Could not open file %r", pidfilename)
778
1027
    
779
 
    uid = 65534
780
 
    gid = 65534
781
 
    try:
782
 
        uid = pwd.getpwnam("mandos").pw_uid
783
 
    except KeyError:
784
 
        try:
785
 
            uid = pwd.getpwnam("nobody").pw_uid
786
 
        except KeyError:
787
 
            pass
788
 
    try:
789
 
        gid = pwd.getpwnam("mandos").pw_gid
790
 
    except KeyError:
791
 
        try:
792
 
            gid = pwd.getpwnam("nogroup").pw_gid
793
 
        except KeyError:
794
 
            pass
795
 
    try:
 
1028
    try:
 
1029
        uid = pwd.getpwnam("_mandos").pw_uid
 
1030
        gid = pwd.getpwnam("_mandos").pw_gid
 
1031
    except KeyError:
 
1032
        try:
 
1033
            uid = pwd.getpwnam("mandos").pw_uid
 
1034
            gid = pwd.getpwnam("mandos").pw_gid
 
1035
        except KeyError:
 
1036
            try:
 
1037
                uid = pwd.getpwnam("nobody").pw_uid
 
1038
                gid = pwd.getpwnam("nogroup").pw_gid
 
1039
            except KeyError:
 
1040
                uid = 65534
 
1041
                gid = 65534
 
1042
    try:
 
1043
        os.setgid(gid)
796
1044
        os.setuid(uid)
797
 
        os.setgid(gid)
798
1045
    except OSError, error:
799
1046
        if error[0] != errno.EPERM:
800
1047
            raise error
801
1048
    
 
1049
    # Enable all possible GnuTLS debugging
 
1050
    if debug:
 
1051
        # "Use a log level over 10 to enable all debugging options."
 
1052
        # - GnuTLS manual
 
1053
        gnutls.library.functions.gnutls_global_set_log_level(11)
 
1054
        
 
1055
        @gnutls.library.types.gnutls_log_func
 
1056
        def debug_gnutls(level, string):
 
1057
            logger.debug("GnuTLS: %s", string[:-1])
 
1058
        
 
1059
        (gnutls.library.functions
 
1060
         .gnutls_global_set_log_function(debug_gnutls))
 
1061
    
802
1062
    global service
 
1063
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
803
1064
    service = AvahiService(name = server_settings["servicename"],
804
 
                           servicetype = "_mandos._tcp", )
 
1065
                           servicetype = "_mandos._tcp",
 
1066
                           protocol = protocol)
805
1067
    if server_settings["interface"]:
806
 
        service.interface = if_nametoindex\
807
 
                            (server_settings["interface"])
 
1068
        service.interface = (if_nametoindex
 
1069
                             (server_settings["interface"]))
808
1070
    
809
1071
    global main_loop
810
1072
    global bus
817
1079
                                           avahi.DBUS_PATH_SERVER),
818
1080
                            avahi.DBUS_INTERFACE_SERVER)
819
1081
    # End of Avahi example code
820
 
    
821
 
    def remove_from_clients(client):
822
 
        clients.remove(client)
823
 
        if not clients:
824
 
            logger.critical(u"No clients left, exiting")
825
 
            sys.exit()
 
1082
    if use_dbus:
 
1083
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
826
1084
    
827
1085
    clients.update(Set(Client(name = section,
828
 
                              stop_hook = remove_from_clients,
829
1086
                              config
830
 
                              = dict(client_config.items(section)))
 
1087
                              = dict(client_config.items(section)),
 
1088
                              use_dbus = use_dbus)
831
1089
                       for section in client_config.sections()))
832
1090
    if not clients:
833
 
        logger.critical(u"No clients defined")
834
 
        sys.exit(1)
 
1091
        logger.warning(u"No clients defined")
835
1092
    
836
1093
    if debug:
837
1094
        # Redirect stdin so all checkers get /dev/null
869
1126
        
870
1127
        while clients:
871
1128
            client = clients.pop()
872
 
            client.stop_hook = None
873
 
            client.stop()
 
1129
            client.disable_hook = None
 
1130
            client.disable()
874
1131
    
875
1132
    atexit.register(cleanup)
876
1133
    
879
1136
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
880
1137
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
881
1138
    
 
1139
    if use_dbus:
 
1140
        class MandosServer(dbus.service.Object):
 
1141
            """A D-Bus proxy object"""
 
1142
            def __init__(self):
 
1143
                dbus.service.Object.__init__(self, bus, "/")
 
1144
            _interface = u"se.bsnet.fukt.Mandos"
 
1145
            
 
1146
            @dbus.service.signal(_interface, signature="oa{sv}")
 
1147
            def ClientAdded(self, objpath, properties):
 
1148
                "D-Bus signal"
 
1149
                pass
 
1150
            
 
1151
            @dbus.service.signal(_interface, signature="os")
 
1152
            def ClientRemoved(self, objpath, name):
 
1153
                "D-Bus signal"
 
1154
                pass
 
1155
            
 
1156
            @dbus.service.method(_interface, out_signature="ao")
 
1157
            def GetAllClients(self):
 
1158
                "D-Bus method"
 
1159
                return dbus.Array(c.dbus_object_path for c in clients)
 
1160
            
 
1161
            @dbus.service.method(_interface, out_signature="a{oa{sv}}")
 
1162
            def GetAllClientsWithProperties(self):
 
1163
                "D-Bus method"
 
1164
                return dbus.Dictionary(
 
1165
                    ((c.dbus_object_path, c.GetAllProperties())
 
1166
                     for c in clients),
 
1167
                    signature="oa{sv}")
 
1168
            
 
1169
            @dbus.service.method(_interface, in_signature="o")
 
1170
            def RemoveClient(self, object_path):
 
1171
                "D-Bus method"
 
1172
                for c in clients:
 
1173
                    if c.dbus_object_path == object_path:
 
1174
                        clients.remove(c)
 
1175
                        # Don't signal anything except ClientRemoved
 
1176
                        c.use_dbus = False
 
1177
                        c.disable()
 
1178
                        # Emit D-Bus signal
 
1179
                        self.ClientRemoved(object_path, c.name)
 
1180
                        return
 
1181
                raise KeyError
 
1182
            
 
1183
            del _interface
 
1184
        
 
1185
        mandos_server = MandosServer()
 
1186
    
882
1187
    for client in clients:
883
 
        client.start()
 
1188
        if use_dbus:
 
1189
            # Emit D-Bus signal
 
1190
            mandos_server.ClientAdded(client.dbus_object_path,
 
1191
                                      client.GetAllProperties())
 
1192
        client.enable()
884
1193
    
885
1194
    tcp_server.enable()
886
1195
    tcp_server.server_activate()
887
1196
    
888
1197
    # Find out what port we got
889
1198
    service.port = tcp_server.socket.getsockname()[1]
890
 
    logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
891
 
                u" scope_id %d" % tcp_server.socket.getsockname())
 
1199
    if use_ipv6:
 
1200
        logger.info(u"Now listening on address %r, port %d,"
 
1201
                    " flowinfo %d, scope_id %d"
 
1202
                    % tcp_server.socket.getsockname())
 
1203
    else:                       # IPv4
 
1204
        logger.info(u"Now listening on address %r, port %d"
 
1205
                    % tcp_server.socket.getsockname())
892
1206
    
893
1207
    #service.interface = tcp_server.socket.getsockname()[3]
894
1208
    
904
1218
        
905
1219
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
906
1220
                             lambda *args, **kwargs:
907
 
                             tcp_server.handle_request\
908
 
                             (*args[2:], **kwargs) or True)
 
1221
                             (tcp_server.handle_request
 
1222
                              (*args[2:], **kwargs) or True))
909
1223
        
910
1224
        logger.debug(u"Starting main loop")
911
1225
        main_loop.run()
912
1226
    except AvahiError, error:
913
 
        logger.critical(u"AvahiError: %s" + unicode(error))
 
1227
        logger.critical(u"AvahiError: %s", error)
914
1228
        sys.exit(1)
915
1229
    except KeyboardInterrupt:
916
1230
        if debug:
917
 
            print
 
1231
            print >> sys.stderr
 
1232
        logger.debug("Server received KeyboardInterrupt")
 
1233
    logger.debug("Server exiting")
918
1234
 
919
1235
if __name__ == '__main__':
920
1236
    main()