/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-04-16 01:00:35 UTC
  • Revision ID: teddy@fukt.bsnet.se-20090416010035-y7ta6ra2da4gf6mp
Minor code cleanup; one minor bug fix.

* initramfs-tools-hook: Bug fix: Use the primary group of the first
                        suitable user found, do not look for a
                        group separately.
* mandos: Unconditionally import "struct" and "fcntl".  Use unicode
          strings everywhere possible.
  (Client._datetime_to_milliseconds): New static method.
  (Client.timeout_milliseconds, Client.interval_milliseconds): Use
                                                               above
                                                               method.
  (ClientDBus.CheckedOK,
  ClientDBus.Enable, ClientDBus.StopChecker): Define normally.
  (if_nametoindex): Document non-acceptance of unicode strings.  All
                    callers adjusted.  Do not import "struct" or
                    "fcntl".  Log warning message if if_nametoindex
                    cannot be found using ctypes modules.
  (main): Bug fix: Do not look for user named "nogroup".

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 © 2007-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
 
import select
38
 
from optparse import OptionParser
 
38
import optparse
39
39
import datetime
40
40
import errno
41
41
import gnutls.crypto
56
56
import logging
57
57
import logging.handlers
58
58
import pwd
 
59
from contextlib import closing
 
60
import struct
 
61
import fcntl
59
62
 
60
63
import dbus
 
64
import dbus.service
61
65
import gobject
62
66
import avahi
63
67
from dbus.mainloop.glib import DBusGMainLoop
64
68
import ctypes
65
 
 
66
 
version = "1.0"
67
 
 
68
 
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'))
 
69
import ctypes.util
 
70
 
 
71
try:
 
72
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
 
73
except AttributeError:
 
74
    try:
 
75
        from IN import SO_BINDTODEVICE
 
76
    except ImportError:
 
77
        # From /usr/include/asm/socket.h
 
78
        SO_BINDTODEVICE = 25
 
79
 
 
80
 
 
81
version = "1.0.8"
 
82
 
 
83
logger = logging.Logger(u'mandos')
 
84
syslogger = (logging.handlers.SysLogHandler
 
85
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
 
86
              address = "/dev/log"))
 
87
syslogger.setFormatter(logging.Formatter
 
88
                       (u'Mandos [%(process)d]: %(levelname)s:'
 
89
                        u' %(message)s'))
74
90
logger.addHandler(syslogger)
75
91
 
76
92
console = logging.StreamHandler()
77
 
console.setFormatter(logging.Formatter('%(name)s: %(levelname)s:'
78
 
                                       ' %(message)s'))
 
93
console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:'
 
94
                                       u' %(levelname)s:'
 
95
                                       u' %(message)s'))
79
96
logger.addHandler(console)
80
97
 
81
98
class AvahiError(Exception):
82
 
    def __init__(self, value):
 
99
    def __init__(self, value, *args, **kwargs):
83
100
        self.value = value
84
 
    def __str__(self):
85
 
        return repr(self.value)
 
101
        super(AvahiError, self).__init__(value, *args, **kwargs)
 
102
    def __unicode__(self):
 
103
        return unicode(repr(self.value))
86
104
 
87
105
class AvahiServiceError(AvahiError):
88
106
    pass
93
111
 
94
112
class AvahiService(object):
95
113
    """An Avahi (Zeroconf) service.
 
114
    
96
115
    Attributes:
97
116
    interface: integer; avahi.IF_UNSPEC or an interface index.
98
117
               Used to optionally bind to the specified interface.
99
 
    name: string; Example: 'Mandos'
100
 
    type: string; Example: '_mandos._tcp'.
 
118
    name: string; Example: u'Mandos'
 
119
    type: string; Example: u'_mandos._tcp'.
101
120
                  See <http://www.dns-sd.org/ServiceTypes.html>
102
121
    port: integer; what port to announce
103
122
    TXT: list of strings; TXT record for the service
108
127
                  a sensible number of times
109
128
    """
110
129
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
111
 
                 type = None, port = None, TXT = None, domain = "",
112
 
                 host = "", max_renames = 32768):
 
130
                 servicetype = None, port = None, TXT = None,
 
131
                 domain = u"", host = u"", max_renames = 32768,
 
132
                 protocol = avahi.PROTO_UNSPEC):
113
133
        self.interface = interface
114
134
        self.name = name
115
 
        self.type = type
 
135
        self.type = servicetype
116
136
        self.port = port
117
 
        if TXT is None:
118
 
            self.TXT = []
119
 
        else:
120
 
            self.TXT = TXT
 
137
        self.TXT = TXT if TXT is not None else []
121
138
        self.domain = domain
122
139
        self.host = host
123
140
        self.rename_count = 0
124
141
        self.max_renames = max_renames
 
142
        self.protocol = protocol
125
143
    def rename(self):
126
144
        """Derived from the Avahi example code"""
127
145
        if self.rename_count >= self.max_renames:
128
146
            logger.critical(u"No suitable Zeroconf service name found"
129
147
                            u" after %i retries, exiting.",
130
 
                            rename_count)
131
 
            raise AvahiServiceError("Too many renames")
 
148
                            self.rename_count)
 
149
            raise AvahiServiceError(u"Too many renames")
132
150
        self.name = server.GetAlternativeServiceName(self.name)
133
151
        logger.info(u"Changing Zeroconf service name to %r ...",
134
 
                    str(self.name))
135
 
        syslogger.setFormatter(logging.Formatter\
136
 
                               ('Mandos (%s): %%(levelname)s:'
137
 
                               ' %%(message)s' % self.name))
 
152
                    self.name)
 
153
        syslogger.setFormatter(logging.Formatter
 
154
                               (u'Mandos (%s) [%%(process)d]:'
 
155
                                u' %%(levelname)s: %%(message)s'
 
156
                                % self.name))
138
157
        self.remove()
139
158
        self.add()
140
159
        self.rename_count += 1
146
165
        """Derived from the Avahi example code"""
147
166
        global group
148
167
        if group is None:
149
 
            group = dbus.Interface\
150
 
                    (bus.get_object(avahi.DBUS_NAME,
 
168
            group = dbus.Interface(bus.get_object
 
169
                                   (avahi.DBUS_NAME,
151
170
                                    server.EntryGroupNew()),
152
 
                     avahi.DBUS_INTERFACE_ENTRY_GROUP)
 
171
                                   avahi.DBUS_INTERFACE_ENTRY_GROUP)
153
172
            group.connect_to_signal('StateChanged',
154
173
                                    entry_group_state_changed)
155
174
        logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
156
175
                     service.name, service.type)
157
176
        group.AddService(
158
177
                self.interface,         # interface
159
 
                avahi.PROTO_INET6,      # protocol
 
178
                self.protocol,          # protocol
160
179
                dbus.UInt32(0),         # flags
161
180
                self.name, self.type,
162
181
                self.domain, self.host,
169
188
# End of Avahi example code
170
189
 
171
190
 
 
191
def _datetime_to_dbus(dt, variant_level=0):
 
192
    """Convert a UTC datetime.datetime() to a D-Bus type."""
 
193
    return dbus.String(dt.isoformat(), variant_level=variant_level)
 
194
 
 
195
 
172
196
class Client(object):
173
197
    """A representation of a client host served by this server.
 
198
    
174
199
    Attributes:
175
 
    name:      string; from the config file, used in log messages
 
200
    name:       string; from the config file, used in log messages and
 
201
                        D-Bus identifiers
176
202
    fingerprint: string (40 or 32 hexadecimal digits); used to
177
203
                 uniquely identify the client
178
 
    secret:    bytestring; sent verbatim (over TLS) to client
179
 
    host:      string; available for use by the checker command
180
 
    created:   datetime.datetime(); object creation, not client host
181
 
    last_checked_ok: datetime.datetime() or None if not yet checked OK
182
 
    timeout:   datetime.timedelta(); How long from last_checked_ok
183
 
                                     until this client is invalid
184
 
    interval:  datetime.timedelta(); How often to start a new checker
185
 
    stop_hook: If set, called by stop() as stop_hook(self)
186
 
    checker:   subprocess.Popen(); a running checker process used
187
 
                                   to see if the client lives.
188
 
                                   'None' if no process is running.
 
204
    secret:     bytestring; sent verbatim (over TLS) to client
 
205
    host:       string; available for use by the checker command
 
206
    created:    datetime.datetime(); (UTC) object creation
 
207
    last_enabled: datetime.datetime(); (UTC)
 
208
    enabled:    bool()
 
209
    last_checked_ok: datetime.datetime(); (UTC) or None
 
210
    timeout:    datetime.timedelta(); How long from last_checked_ok
 
211
                                      until this client is invalid
 
212
    interval:   datetime.timedelta(); How often to start a new checker
 
213
    disable_hook:  If set, called by disable() as disable_hook(self)
 
214
    checker:    subprocess.Popen(); a running checker process used
 
215
                                    to see if the client lives.
 
216
                                    'None' if no process is running.
189
217
    checker_initiator_tag: a gobject event source tag, or None
190
 
    stop_initiator_tag:    - '' -
 
218
    disable_initiator_tag:    - '' -
191
219
    checker_callback_tag:  - '' -
192
220
    checker_command: string; External command which is run to check if
193
221
                     client lives.  %() expansions are done at
194
222
                     runtime with vars(self) as dict, so that for
195
223
                     instance %(name)s can be used in the command.
196
 
    Private attibutes:
197
 
    _timeout: Real variable for 'timeout'
198
 
    _interval: Real variable for 'interval'
199
 
    _timeout_milliseconds: Used when calling gobject.timeout_add()
200
 
    _interval_milliseconds: - '' -
 
224
    current_checker_command: string; current running checker_command
201
225
    """
202
 
    def _set_timeout(self, timeout):
203
 
        "Setter function for 'timeout' attribute"
204
 
        self._timeout = timeout
205
 
        self._timeout_milliseconds = ((self.timeout.days
206
 
                                       * 24 * 60 * 60 * 1000)
207
 
                                      + (self.timeout.seconds * 1000)
208
 
                                      + (self.timeout.microseconds
209
 
                                         // 1000))
210
 
    timeout = property(lambda self: self._timeout,
211
 
                       _set_timeout)
212
 
    del _set_timeout
213
 
    def _set_interval(self, interval):
214
 
        "Setter function for 'interval' attribute"
215
 
        self._interval = interval
216
 
        self._interval_milliseconds = ((self.interval.days
217
 
                                        * 24 * 60 * 60 * 1000)
218
 
                                       + (self.interval.seconds
219
 
                                          * 1000)
220
 
                                       + (self.interval.microseconds
221
 
                                          // 1000))
222
 
    interval = property(lambda self: self._interval,
223
 
                        _set_interval)
224
 
    del _set_interval
225
 
    def __init__(self, name = None, stop_hook=None, config={}):
 
226
    
 
227
    @staticmethod
 
228
    def _datetime_to_milliseconds(dt):
 
229
        "Convert a datetime.datetime() to milliseconds"
 
230
        return ((dt.days * 24 * 60 * 60 * 1000)
 
231
                + (dt.seconds * 1000)
 
232
                + (dt.microseconds // 1000))
 
233
    
 
234
    def timeout_milliseconds(self):
 
235
        "Return the 'timeout' attribute in milliseconds"
 
236
        return self._datetime_to_milliseconds(self.timeout)
 
237
    
 
238
    def interval_milliseconds(self):
 
239
        "Return the 'interval' attribute in milliseconds"
 
240
        return self._datetime_to_milliseconds(self.interval)
 
241
    
 
242
    def __init__(self, name = None, disable_hook=None, config=None):
226
243
        """Note: the 'checker' key in 'config' sets the
227
244
        'checker_command' attribute and *not* the 'checker'
228
245
        attribute."""
229
246
        self.name = name
 
247
        if config is None:
 
248
            config = {}
230
249
        logger.debug(u"Creating client %r", self.name)
231
250
        # Uppercase and remove spaces from fingerprint for later
232
251
        # comparison purposes with return value from the fingerprint()
233
252
        # function
234
 
        self.fingerprint = config["fingerprint"].upper()\
235
 
                           .replace(u" ", u"")
 
253
        self.fingerprint = (config[u"fingerprint"].upper()
 
254
                            .replace(u" ", u""))
236
255
        logger.debug(u"  Fingerprint: %s", self.fingerprint)
237
 
        if "secret" in config:
238
 
            self.secret = config["secret"].decode(u"base64")
239
 
        elif "secfile" in config:
240
 
            sf = open(config["secfile"])
241
 
            self.secret = sf.read()
242
 
            sf.close()
 
256
        if u"secret" in config:
 
257
            self.secret = config[u"secret"].decode(u"base64")
 
258
        elif u"secfile" in config:
 
259
            with closing(open(os.path.expanduser
 
260
                              (os.path.expandvars
 
261
                               (config[u"secfile"])))) as secfile:
 
262
                self.secret = secfile.read()
243
263
        else:
244
264
            raise TypeError(u"No secret or secfile for client %s"
245
265
                            % self.name)
246
 
        self.host = config.get("host", "")
247
 
        self.created = datetime.datetime.now()
 
266
        self.host = config.get(u"host", u"")
 
267
        self.created = datetime.datetime.utcnow()
 
268
        self.enabled = False
 
269
        self.last_enabled = None
248
270
        self.last_checked_ok = None
249
 
        self.timeout = string_to_delta(config["timeout"])
250
 
        self.interval = string_to_delta(config["interval"])
251
 
        self.stop_hook = stop_hook
 
271
        self.timeout = string_to_delta(config[u"timeout"])
 
272
        self.interval = string_to_delta(config[u"interval"])
 
273
        self.disable_hook = disable_hook
252
274
        self.checker = None
253
275
        self.checker_initiator_tag = None
254
 
        self.stop_initiator_tag = None
 
276
        self.disable_initiator_tag = None
255
277
        self.checker_callback_tag = None
256
 
        self.check_command = config["checker"]
257
 
    def start(self):
 
278
        self.checker_command = config[u"checker"]
 
279
        self.current_checker_command = None
 
280
        self.last_connect = None
 
281
    
 
282
    def enable(self):
258
283
        """Start this client's checker and timeout hooks"""
 
284
        self.last_enabled = datetime.datetime.utcnow()
259
285
        # Schedule a new checker to be started an 'interval' from now,
260
286
        # and every interval from then on.
261
 
        self.checker_initiator_tag = gobject.timeout_add\
262
 
                                     (self._interval_milliseconds,
263
 
                                      self.start_checker)
 
287
        self.checker_initiator_tag = (gobject.timeout_add
 
288
                                      (self.interval_milliseconds(),
 
289
                                       self.start_checker))
264
290
        # Also start a new checker *right now*.
265
291
        self.start_checker()
266
 
        # Schedule a stop() when 'timeout' has passed
267
 
        self.stop_initiator_tag = gobject.timeout_add\
268
 
                                  (self._timeout_milliseconds,
269
 
                                   self.stop)
270
 
    def stop(self):
271
 
        """Stop this client.
272
 
        The possibility that a client might be restarted is left open,
273
 
        but not currently used."""
274
 
        # If this client doesn't have a secret, it is already stopped.
275
 
        if hasattr(self, "secret") and self.secret:
276
 
            logger.info(u"Stopping client %s", self.name)
277
 
            self.secret = None
278
 
        else:
 
292
        # Schedule a disable() when 'timeout' has passed
 
293
        self.disable_initiator_tag = (gobject.timeout_add
 
294
                                   (self.timeout_milliseconds(),
 
295
                                    self.disable))
 
296
        self.enabled = True
 
297
    
 
298
    def disable(self):
 
299
        """Disable this client."""
 
300
        if not getattr(self, "enabled", False):
279
301
            return False
280
 
        if getattr(self, "stop_initiator_tag", False):
281
 
            gobject.source_remove(self.stop_initiator_tag)
282
 
            self.stop_initiator_tag = None
283
 
        if getattr(self, "checker_initiator_tag", False):
 
302
        logger.info(u"Disabling client %s", self.name)
 
303
        if getattr(self, u"disable_initiator_tag", False):
 
304
            gobject.source_remove(self.disable_initiator_tag)
 
305
            self.disable_initiator_tag = None
 
306
        if getattr(self, u"checker_initiator_tag", False):
284
307
            gobject.source_remove(self.checker_initiator_tag)
285
308
            self.checker_initiator_tag = None
286
309
        self.stop_checker()
287
 
        if self.stop_hook:
288
 
            self.stop_hook(self)
 
310
        if self.disable_hook:
 
311
            self.disable_hook(self)
 
312
        self.enabled = False
289
313
        # Do not run this again if called by a gobject.timeout_add
290
314
        return False
 
315
    
291
316
    def __del__(self):
292
 
        self.stop_hook = None
293
 
        self.stop()
294
 
    def checker_callback(self, pid, condition):
 
317
        self.disable_hook = None
 
318
        self.disable()
 
319
    
 
320
    def checker_callback(self, pid, condition, command):
295
321
        """The checker has completed, so take appropriate actions."""
296
 
        now = datetime.datetime.now()
297
322
        self.checker_callback_tag = None
298
323
        self.checker = None
299
 
        if os.WIFEXITED(condition) \
300
 
               and (os.WEXITSTATUS(condition) == 0):
301
 
            logger.info(u"Checker for %(name)s succeeded",
302
 
                        vars(self))
303
 
            self.last_checked_ok = now
304
 
            gobject.source_remove(self.stop_initiator_tag)
305
 
            self.stop_initiator_tag = gobject.timeout_add\
306
 
                                      (self._timeout_milliseconds,
307
 
                                       self.stop)
308
 
        elif not os.WIFEXITED(condition):
 
324
        if os.WIFEXITED(condition):
 
325
            exitstatus = os.WEXITSTATUS(condition)
 
326
            if exitstatus == 0:
 
327
                logger.info(u"Checker for %(name)s succeeded",
 
328
                            vars(self))
 
329
                self.checked_ok()
 
330
            else:
 
331
                logger.info(u"Checker for %(name)s failed",
 
332
                            vars(self))
 
333
        else:
309
334
            logger.warning(u"Checker for %(name)s crashed?",
310
335
                           vars(self))
311
 
        else:
312
 
            logger.info(u"Checker for %(name)s failed",
313
 
                        vars(self))
 
336
    
 
337
    def checked_ok(self):
 
338
        """Bump up the timeout for this client.
 
339
        
 
340
        This should only be called when the client has been seen,
 
341
        alive and well.
 
342
        """
 
343
        self.last_checked_ok = datetime.datetime.utcnow()
 
344
        gobject.source_remove(self.disable_initiator_tag)
 
345
        self.disable_initiator_tag = (gobject.timeout_add
 
346
                                      (self.timeout_milliseconds(),
 
347
                                       self.disable))
 
348
    
314
349
    def start_checker(self):
315
350
        """Start a new checker subprocess if one is not running.
 
351
        
316
352
        If a checker already exists, leave it running and do
317
353
        nothing."""
318
354
        # The reason for not killing a running checker is that if we
323
359
        # checkers alone, the checker would have to take more time
324
360
        # than 'timeout' for the client to be declared invalid, which
325
361
        # is as it should be.
 
362
        
 
363
        # If a checker exists, make sure it is not a zombie
 
364
        if self.checker is not None:
 
365
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
 
366
            if pid:
 
367
                logger.warning(u"Checker was a zombie")
 
368
                gobject.source_remove(self.checker_callback_tag)
 
369
                self.checker_callback(pid, status,
 
370
                                      self.current_checker_command)
 
371
        # Start a new checker if needed
326
372
        if self.checker is None:
327
373
            try:
328
 
                # In case check_command has exactly one % operator
329
 
                command = self.check_command % self.host
 
374
                # In case checker_command has exactly one % operator
 
375
                command = self.checker_command % self.host
330
376
            except TypeError:
331
377
                # Escape attributes for the shell
332
 
                escaped_attrs = dict((key, re.escape(str(val)))
 
378
                escaped_attrs = dict((key,
 
379
                                      re.escape(unicode(str(val),
 
380
                                                        errors=
 
381
                                                        u'replace')))
333
382
                                     for key, val in
334
383
                                     vars(self).iteritems())
335
384
                try:
336
 
                    command = self.check_command % escaped_attrs
 
385
                    command = self.checker_command % escaped_attrs
337
386
                except TypeError, error:
338
387
                    logger.error(u'Could not format string "%s":'
339
 
                                 u' %s', self.check_command, error)
 
388
                                 u' %s', self.checker_command, error)
340
389
                    return True # Try again later
 
390
            self.current_checker_command = command
341
391
            try:
342
392
                logger.info(u"Starting checker %r for %s",
343
393
                            command, self.name)
347
397
                # always replaced by /dev/null.)
348
398
                self.checker = subprocess.Popen(command,
349
399
                                                close_fds=True,
350
 
                                                shell=True, cwd="/")
351
 
                self.checker_callback_tag = gobject.child_watch_add\
352
 
                                            (self.checker.pid,
353
 
                                             self.checker_callback)
 
400
                                                shell=True, cwd=u"/")
 
401
                self.checker_callback_tag = (gobject.child_watch_add
 
402
                                             (self.checker.pid,
 
403
                                              self.checker_callback,
 
404
                                              data=command))
 
405
                # The checker may have completed before the gobject
 
406
                # watch was added.  Check for this.
 
407
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
 
408
                if pid:
 
409
                    gobject.source_remove(self.checker_callback_tag)
 
410
                    self.checker_callback(pid, status, command)
354
411
            except OSError, error:
355
412
                logger.error(u"Failed to start subprocess: %s",
356
413
                             error)
357
414
        # Re-run this periodically if run by gobject.timeout_add
358
415
        return True
 
416
    
359
417
    def stop_checker(self):
360
418
        """Force the checker process, if any, to stop."""
361
419
        if self.checker_callback_tag:
362
420
            gobject.source_remove(self.checker_callback_tag)
363
421
            self.checker_callback_tag = None
364
 
        if getattr(self, "checker", None) is None:
 
422
        if getattr(self, u"checker", None) is None:
365
423
            return
366
424
        logger.debug(u"Stopping checker for %(name)s", vars(self))
367
425
        try:
373
431
            if error.errno != errno.ESRCH: # No such process
374
432
                raise
375
433
        self.checker = None
 
434
    
376
435
    def still_valid(self):
377
436
        """Has the timeout not yet passed for this client?"""
378
 
        now = datetime.datetime.now()
 
437
        if not getattr(self, u"enabled", False):
 
438
            return False
 
439
        now = datetime.datetime.utcnow()
379
440
        if self.last_checked_ok is None:
380
441
            return now < (self.created + self.timeout)
381
442
        else:
382
443
            return now < (self.last_checked_ok + self.timeout)
383
444
 
384
445
 
385
 
def peer_certificate(session):
386
 
    "Return the peer's OpenPGP certificate as a bytestring"
387
 
    # If not an OpenPGP certificate...
388
 
    if gnutls.library.functions.gnutls_certificate_type_get\
389
 
            (session._c_object) \
390
 
           != gnutls.library.constants.GNUTLS_CRT_OPENPGP:
391
 
        # ...do the normal thing
392
 
        return session.peer_certificate
393
 
    list_size = ctypes.c_uint()
394
 
    cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
395
 
        (session._c_object, ctypes.byref(list_size))
396
 
    if list_size.value == 0:
397
 
        return None
398
 
    cert = cert_list[0]
399
 
    return ctypes.string_at(cert.data, cert.size)
400
 
 
401
 
 
402
 
def fingerprint(openpgp):
403
 
    "Convert an OpenPGP bytestring to a hexdigit fingerprint string"
404
 
    # New GnuTLS "datum" with the OpenPGP public key
405
 
    datum = gnutls.library.types.gnutls_datum_t\
406
 
        (ctypes.cast(ctypes.c_char_p(openpgp),
407
 
                     ctypes.POINTER(ctypes.c_ubyte)),
408
 
         ctypes.c_uint(len(openpgp)))
409
 
    # New empty GnuTLS certificate
410
 
    crt = gnutls.library.types.gnutls_openpgp_crt_t()
411
 
    gnutls.library.functions.gnutls_openpgp_crt_init\
412
 
        (ctypes.byref(crt))
413
 
    # Import the OpenPGP public key into the certificate
414
 
    gnutls.library.functions.gnutls_openpgp_crt_import\
415
 
                    (crt, ctypes.byref(datum),
416
 
                     gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
417
 
    # Verify the self signature in the key
418
 
    crtverify = ctypes.c_uint();
419
 
    gnutls.library.functions.gnutls_openpgp_crt_verify_self\
420
 
        (crt, 0, ctypes.byref(crtverify))
421
 
    if crtverify.value != 0:
422
 
        gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
423
 
        raise gnutls.errors.CertificateSecurityError("Verify failed")
424
 
    # New buffer for the fingerprint
425
 
    buffer = ctypes.create_string_buffer(20)
426
 
    buffer_length = ctypes.c_size_t()
427
 
    # Get the fingerprint from the certificate into the buffer
428
 
    gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
429
 
        (crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
430
 
    # Deinit the certificate
431
 
    gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
432
 
    # Convert the buffer to a Python bytestring
433
 
    fpr = ctypes.string_at(buffer, buffer_length.value)
434
 
    # Convert the bytestring to hexadecimal notation
435
 
    hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
436
 
    return hex_fpr
437
 
 
438
 
 
439
 
class tcp_handler(SocketServer.BaseRequestHandler, object):
440
 
    """A TCP request handler class.
441
 
    Instantiated by IPv6_TCPServer for each request to handle it.
 
446
class ClientDBus(Client, dbus.service.Object):
 
447
    """A Client class using D-Bus
 
448
    
 
449
    Attributes:
 
450
    dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
 
451
    """
 
452
    # dbus.service.Object doesn't use super(), so we can't either.
 
453
    
 
454
    def __init__(self, *args, **kwargs):
 
455
        Client.__init__(self, *args, **kwargs)
 
456
        # Only now, when this client is initialized, can it show up on
 
457
        # the D-Bus
 
458
        self.dbus_object_path = (dbus.ObjectPath
 
459
                                 (u"/clients/"
 
460
                                  + self.name.replace(u".", u"_")))
 
461
        dbus.service.Object.__init__(self, bus,
 
462
                                     self.dbus_object_path)
 
463
    def enable(self):
 
464
        oldstate = getattr(self, u"enabled", False)
 
465
        r = Client.enable(self)
 
466
        if oldstate != self.enabled:
 
467
            # Emit D-Bus signals
 
468
            self.PropertyChanged(dbus.String(u"enabled"),
 
469
                                 dbus.Boolean(True, variant_level=1))
 
470
            self.PropertyChanged(dbus.String(u"last_enabled"),
 
471
                                 (_datetime_to_dbus(self.last_enabled,
 
472
                                                    variant_level=1)))
 
473
        return r
 
474
    
 
475
    def disable(self, signal = True):
 
476
        oldstate = getattr(self, u"enabled", False)
 
477
        r = Client.disable(self)
 
478
        if signal and oldstate != self.enabled:
 
479
            # Emit D-Bus signal
 
480
            self.PropertyChanged(dbus.String(u"enabled"),
 
481
                                 dbus.Boolean(False, variant_level=1))
 
482
        return r
 
483
    
 
484
    def __del__(self, *args, **kwargs):
 
485
        try:
 
486
            self.remove_from_connection()
 
487
        except LookupError:
 
488
            pass
 
489
        if hasattr(dbus.service.Object, u"__del__"):
 
490
            dbus.service.Object.__del__(self, *args, **kwargs)
 
491
        Client.__del__(self, *args, **kwargs)
 
492
    
 
493
    def checker_callback(self, pid, condition, command,
 
494
                         *args, **kwargs):
 
495
        self.checker_callback_tag = None
 
496
        self.checker = None
 
497
        # Emit D-Bus signal
 
498
        self.PropertyChanged(dbus.String(u"checker_running"),
 
499
                             dbus.Boolean(False, variant_level=1))
 
500
        if os.WIFEXITED(condition):
 
501
            exitstatus = os.WEXITSTATUS(condition)
 
502
            # Emit D-Bus signal
 
503
            self.CheckerCompleted(dbus.Int16(exitstatus),
 
504
                                  dbus.Int64(condition),
 
505
                                  dbus.String(command))
 
506
        else:
 
507
            # Emit D-Bus signal
 
508
            self.CheckerCompleted(dbus.Int16(-1),
 
509
                                  dbus.Int64(condition),
 
510
                                  dbus.String(command))
 
511
        
 
512
        return Client.checker_callback(self, pid, condition, command,
 
513
                                       *args, **kwargs)
 
514
    
 
515
    def checked_ok(self, *args, **kwargs):
 
516
        r = Client.checked_ok(self, *args, **kwargs)
 
517
        # Emit D-Bus signal
 
518
        self.PropertyChanged(
 
519
            dbus.String(u"last_checked_ok"),
 
520
            (_datetime_to_dbus(self.last_checked_ok,
 
521
                               variant_level=1)))
 
522
        return r
 
523
    
 
524
    def start_checker(self, *args, **kwargs):
 
525
        old_checker = self.checker
 
526
        if self.checker is not None:
 
527
            old_checker_pid = self.checker.pid
 
528
        else:
 
529
            old_checker_pid = None
 
530
        r = Client.start_checker(self, *args, **kwargs)
 
531
        # Only if new checker process was started
 
532
        if (self.checker is not None
 
533
            and old_checker_pid != self.checker.pid):
 
534
            # Emit D-Bus signal
 
535
            self.CheckerStarted(self.current_checker_command)
 
536
            self.PropertyChanged(
 
537
                dbus.String(u"checker_running"),
 
538
                dbus.Boolean(True, variant_level=1))
 
539
        return r
 
540
    
 
541
    def stop_checker(self, *args, **kwargs):
 
542
        old_checker = getattr(self, u"checker", None)
 
543
        r = Client.stop_checker(self, *args, **kwargs)
 
544
        if (old_checker is not None
 
545
            and getattr(self, u"checker", None) is None):
 
546
            self.PropertyChanged(dbus.String(u"checker_running"),
 
547
                                 dbus.Boolean(False, variant_level=1))
 
548
        return r
 
549
    
 
550
    ## D-Bus methods & signals
 
551
    _interface = u"se.bsnet.fukt.Mandos.Client"
 
552
    
 
553
    # CheckedOK - method
 
554
    @dbus.service.method(_interface)
 
555
    def CheckedOK(self):
 
556
        return self.checked_ok()
 
557
    
 
558
    # CheckerCompleted - signal
 
559
    @dbus.service.signal(_interface, signature=u"nxs")
 
560
    def CheckerCompleted(self, exitcode, waitstatus, command):
 
561
        "D-Bus signal"
 
562
        pass
 
563
    
 
564
    # CheckerStarted - signal
 
565
    @dbus.service.signal(_interface, signature=u"s")
 
566
    def CheckerStarted(self, command):
 
567
        "D-Bus signal"
 
568
        pass
 
569
    
 
570
    # GetAllProperties - method
 
571
    @dbus.service.method(_interface, out_signature=u"a{sv}")
 
572
    def GetAllProperties(self):
 
573
        "D-Bus method"
 
574
        return dbus.Dictionary({
 
575
                dbus.String(u"name"):
 
576
                    dbus.String(self.name, variant_level=1),
 
577
                dbus.String(u"fingerprint"):
 
578
                    dbus.String(self.fingerprint, variant_level=1),
 
579
                dbus.String(u"host"):
 
580
                    dbus.String(self.host, variant_level=1),
 
581
                dbus.String(u"created"):
 
582
                    _datetime_to_dbus(self.created, variant_level=1),
 
583
                dbus.String(u"last_enabled"):
 
584
                    (_datetime_to_dbus(self.last_enabled,
 
585
                                       variant_level=1)
 
586
                     if self.last_enabled is not None
 
587
                     else dbus.Boolean(False, variant_level=1)),
 
588
                dbus.String(u"enabled"):
 
589
                    dbus.Boolean(self.enabled, variant_level=1),
 
590
                dbus.String(u"last_checked_ok"):
 
591
                    (_datetime_to_dbus(self.last_checked_ok,
 
592
                                       variant_level=1)
 
593
                     if self.last_checked_ok is not None
 
594
                     else dbus.Boolean (False, variant_level=1)),
 
595
                dbus.String(u"timeout"):
 
596
                    dbus.UInt64(self.timeout_milliseconds(),
 
597
                                variant_level=1),
 
598
                dbus.String(u"interval"):
 
599
                    dbus.UInt64(self.interval_milliseconds(),
 
600
                                variant_level=1),
 
601
                dbus.String(u"checker"):
 
602
                    dbus.String(self.checker_command,
 
603
                                variant_level=1),
 
604
                dbus.String(u"checker_running"):
 
605
                    dbus.Boolean(self.checker is not None,
 
606
                                 variant_level=1),
 
607
                dbus.String(u"object_path"):
 
608
                    dbus.ObjectPath(self.dbus_object_path,
 
609
                                    variant_level=1)
 
610
                }, signature=u"sv")
 
611
    
 
612
    # IsStillValid - method
 
613
    @dbus.service.method(_interface, out_signature=u"b")
 
614
    def IsStillValid(self):
 
615
        return self.still_valid()
 
616
    
 
617
    # PropertyChanged - signal
 
618
    @dbus.service.signal(_interface, signature=u"sv")
 
619
    def PropertyChanged(self, property, value):
 
620
        "D-Bus signal"
 
621
        pass
 
622
    
 
623
    # ReceivedSecret - signal
 
624
    @dbus.service.signal(_interface)
 
625
    def ReceivedSecret(self):
 
626
        "D-Bus signal"
 
627
        pass
 
628
    
 
629
    # Rejected - signal
 
630
    @dbus.service.signal(_interface)
 
631
    def Rejected(self):
 
632
        "D-Bus signal"
 
633
        pass
 
634
    
 
635
    # SetChecker - method
 
636
    @dbus.service.method(_interface, in_signature=u"s")
 
637
    def SetChecker(self, checker):
 
638
        "D-Bus setter method"
 
639
        self.checker_command = checker
 
640
        # Emit D-Bus signal
 
641
        self.PropertyChanged(dbus.String(u"checker"),
 
642
                             dbus.String(self.checker_command,
 
643
                                         variant_level=1))
 
644
    
 
645
    # SetHost - method
 
646
    @dbus.service.method(_interface, in_signature=u"s")
 
647
    def SetHost(self, host):
 
648
        "D-Bus setter method"
 
649
        self.host = host
 
650
        # Emit D-Bus signal
 
651
        self.PropertyChanged(dbus.String(u"host"),
 
652
                             dbus.String(self.host, variant_level=1))
 
653
    
 
654
    # SetInterval - method
 
655
    @dbus.service.method(_interface, in_signature=u"t")
 
656
    def SetInterval(self, milliseconds):
 
657
        self.interval = datetime.timedelta(0, 0, 0, milliseconds)
 
658
        # Emit D-Bus signal
 
659
        self.PropertyChanged(dbus.String(u"interval"),
 
660
                             (dbus.UInt64(self.interval_milliseconds(),
 
661
                                          variant_level=1)))
 
662
    
 
663
    # SetSecret - method
 
664
    @dbus.service.method(_interface, in_signature=u"ay",
 
665
                         byte_arrays=True)
 
666
    def SetSecret(self, secret):
 
667
        "D-Bus setter method"
 
668
        self.secret = str(secret)
 
669
    
 
670
    # SetTimeout - method
 
671
    @dbus.service.method(_interface, in_signature=u"t")
 
672
    def SetTimeout(self, milliseconds):
 
673
        self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
 
674
        # Emit D-Bus signal
 
675
        self.PropertyChanged(dbus.String(u"timeout"),
 
676
                             (dbus.UInt64(self.timeout_milliseconds(),
 
677
                                          variant_level=1)))
 
678
    
 
679
    # Enable - method
 
680
    @dbus.service.method(_interface)
 
681
    def Enable(self):
 
682
        "D-Bus method"
 
683
        self.enable()
 
684
    
 
685
    # StartChecker - method
 
686
    @dbus.service.method(_interface)
 
687
    def StartChecker(self):
 
688
        "D-Bus method"
 
689
        self.start_checker()
 
690
    
 
691
    # Disable - method
 
692
    @dbus.service.method(_interface)
 
693
    def Disable(self):
 
694
        "D-Bus method"
 
695
        self.disable()
 
696
    
 
697
    # StopChecker - method
 
698
    @dbus.service.method(_interface)
 
699
    def StopChecker(self):
 
700
        self.stop_checker()
 
701
    
 
702
    del _interface
 
703
 
 
704
 
 
705
class ClientHandler(SocketServer.BaseRequestHandler, object):
 
706
    """A class to handle client connections.
 
707
    
 
708
    Instantiated once for each connection to handle it.
442
709
    Note: This will run in its own forked process."""
443
710
    
444
711
    def handle(self):
445
712
        logger.info(u"TCP connection from: %s",
446
 
                     unicode(self.client_address))
447
 
        session = gnutls.connection.ClientSession\
448
 
                  (self.request, gnutls.connection.X509Credentials())
449
 
        
450
 
        line = self.request.makefile().readline()
451
 
        logger.debug(u"Protocol version: %r", line)
452
 
        try:
453
 
            if int(line.strip().split()[0]) > 1:
454
 
                raise RuntimeError
455
 
        except (ValueError, IndexError, RuntimeError), error:
456
 
            logger.error(u"Unknown protocol version: %s", error)
457
 
            return
458
 
        
459
 
        # Note: gnutls.connection.X509Credentials is really a generic
460
 
        # GnuTLS certificate credentials object so long as no X.509
461
 
        # keys are added to it.  Therefore, we can use it here despite
462
 
        # using OpenPGP certificates.
463
 
        
464
 
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
465
 
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
466
 
        #                "+DHE-DSS"))
467
 
        priority = "NORMAL"             # Fallback default, since this
468
 
                                        # MUST be set.
469
 
        if self.server.settings["priority"]:
470
 
            priority = self.server.settings["priority"]
471
 
        gnutls.library.functions.gnutls_priority_set_direct\
472
 
            (session._c_object, priority, None);
473
 
        
474
 
        try:
475
 
            session.handshake()
476
 
        except gnutls.errors.GNUTLSError, error:
477
 
            logger.warning(u"Handshake failed: %s", error)
478
 
            # Do not run session.bye() here: the session is not
479
 
            # established.  Just abandon the request.
480
 
            return
481
 
        try:
482
 
            fpr = fingerprint(peer_certificate(session))
483
 
        except (TypeError, gnutls.errors.GNUTLSError), error:
484
 
            logger.warning(u"Bad certificate: %s", error)
485
 
            session.bye()
486
 
            return
487
 
        logger.debug(u"Fingerprint: %s", fpr)
488
 
        client = None
489
 
        for c in self.server.clients:
490
 
            if c.fingerprint == fpr:
491
 
                client = c
492
 
                break
493
 
        if not client:
494
 
            logger.warning(u"Client not found for fingerprint: %s",
495
 
                           fpr)
496
 
            session.bye()
497
 
            return
498
 
        # Have to check if client.still_valid(), since it is possible
499
 
        # that the client timed out while establishing the GnuTLS
500
 
        # session.
501
 
        if not client.still_valid():
502
 
            logger.warning(u"Client %(name)s is invalid",
503
 
                           vars(client))
504
 
            session.bye()
505
 
            return
506
 
        sent_size = 0
507
 
        while sent_size < len(client.secret):
508
 
            sent = session.send(client.secret[sent_size:])
509
 
            logger.debug(u"Sent: %d, remaining: %d",
510
 
                         sent, len(client.secret)
511
 
                         - (sent_size + sent))
512
 
            sent_size += sent
513
 
        session.bye()
514
 
 
515
 
 
516
 
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
517
 
    """IPv6 TCP server.  Accepts 'None' as address and/or port.
 
713
                    unicode(self.client_address))
 
714
        logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
 
715
        # Open IPC pipe to parent process
 
716
        with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
 
717
            session = (gnutls.connection
 
718
                       .ClientSession(self.request,
 
719
                                      gnutls.connection
 
720
                                      .X509Credentials()))
 
721
            
 
722
            line = self.request.makefile().readline()
 
723
            logger.debug(u"Protocol version: %r", line)
 
724
            try:
 
725
                if int(line.strip().split()[0]) > 1:
 
726
                    raise RuntimeError
 
727
            except (ValueError, IndexError, RuntimeError), error:
 
728
                logger.error(u"Unknown protocol version: %s", error)
 
729
                return
 
730
            
 
731
            # Note: gnutls.connection.X509Credentials is really a
 
732
            # generic GnuTLS certificate credentials object so long as
 
733
            # no X.509 keys are added to it.  Therefore, we can use it
 
734
            # here despite using OpenPGP certificates.
 
735
            
 
736
            #priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
 
737
            #                      u"+AES-256-CBC", u"+SHA1",
 
738
            #                      u"+COMP-NULL", u"+CTYPE-OPENPGP",
 
739
            #                      u"+DHE-DSS"))
 
740
            # Use a fallback default, since this MUST be set.
 
741
            priority = self.server.gnutls_priority
 
742
            if priority is None:
 
743
                priority = u"NORMAL"
 
744
            (gnutls.library.functions
 
745
             .gnutls_priority_set_direct(session._c_object,
 
746
                                         priority, None))
 
747
            
 
748
            try:
 
749
                session.handshake()
 
750
            except gnutls.errors.GNUTLSError, error:
 
751
                logger.warning(u"Handshake failed: %s", error)
 
752
                # Do not run session.bye() here: the session is not
 
753
                # established.  Just abandon the request.
 
754
                return
 
755
            logger.debug(u"Handshake succeeded")
 
756
            try:
 
757
                fpr = self.fingerprint(self.peer_certificate(session))
 
758
            except (TypeError, gnutls.errors.GNUTLSError), error:
 
759
                logger.warning(u"Bad certificate: %s", error)
 
760
                session.bye()
 
761
                return
 
762
            logger.debug(u"Fingerprint: %s", fpr)
 
763
            
 
764
            for c in self.server.clients:
 
765
                if c.fingerprint == fpr:
 
766
                    client = c
 
767
                    break
 
768
            else:
 
769
                ipc.write(u"NOTFOUND %s\n" % fpr)
 
770
                session.bye()
 
771
                return
 
772
            # Have to check if client.still_valid(), since it is
 
773
            # possible that the client timed out while establishing
 
774
            # the GnuTLS session.
 
775
            if not client.still_valid():
 
776
                ipc.write(u"INVALID %s\n" % client.name)
 
777
                session.bye()
 
778
                return
 
779
            ipc.write(u"SENDING %s\n" % client.name)
 
780
            sent_size = 0
 
781
            while sent_size < len(client.secret):
 
782
                sent = session.send(client.secret[sent_size:])
 
783
                logger.debug(u"Sent: %d, remaining: %d",
 
784
                             sent, len(client.secret)
 
785
                             - (sent_size + sent))
 
786
                sent_size += sent
 
787
            session.bye()
 
788
    
 
789
    @staticmethod
 
790
    def peer_certificate(session):
 
791
        "Return the peer's OpenPGP certificate as a bytestring"
 
792
        # If not an OpenPGP certificate...
 
793
        if (gnutls.library.functions
 
794
            .gnutls_certificate_type_get(session._c_object)
 
795
            != gnutls.library.constants.GNUTLS_CRT_OPENPGP):
 
796
            # ...do the normal thing
 
797
            return session.peer_certificate
 
798
        list_size = ctypes.c_uint(1)
 
799
        cert_list = (gnutls.library.functions
 
800
                     .gnutls_certificate_get_peers
 
801
                     (session._c_object, ctypes.byref(list_size)))
 
802
        if not bool(cert_list) and list_size.value != 0:
 
803
            raise gnutls.errors.GNUTLSError(u"error getting peer"
 
804
                                            u" certificate")
 
805
        if list_size.value == 0:
 
806
            return None
 
807
        cert = cert_list[0]
 
808
        return ctypes.string_at(cert.data, cert.size)
 
809
    
 
810
    @staticmethod
 
811
    def fingerprint(openpgp):
 
812
        "Convert an OpenPGP bytestring to a hexdigit fingerprint"
 
813
        # New GnuTLS "datum" with the OpenPGP public key
 
814
        datum = (gnutls.library.types
 
815
                 .gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
 
816
                                             ctypes.POINTER
 
817
                                             (ctypes.c_ubyte)),
 
818
                                 ctypes.c_uint(len(openpgp))))
 
819
        # New empty GnuTLS certificate
 
820
        crt = gnutls.library.types.gnutls_openpgp_crt_t()
 
821
        (gnutls.library.functions
 
822
         .gnutls_openpgp_crt_init(ctypes.byref(crt)))
 
823
        # Import the OpenPGP public key into the certificate
 
824
        (gnutls.library.functions
 
825
         .gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
 
826
                                    gnutls.library.constants
 
827
                                    .GNUTLS_OPENPGP_FMT_RAW))
 
828
        # Verify the self signature in the key
 
829
        crtverify = ctypes.c_uint()
 
830
        (gnutls.library.functions
 
831
         .gnutls_openpgp_crt_verify_self(crt, 0,
 
832
                                         ctypes.byref(crtverify)))
 
833
        if crtverify.value != 0:
 
834
            gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
 
835
            raise (gnutls.errors.CertificateSecurityError
 
836
                   (u"Verify failed"))
 
837
        # New buffer for the fingerprint
 
838
        buf = ctypes.create_string_buffer(20)
 
839
        buf_len = ctypes.c_size_t()
 
840
        # Get the fingerprint from the certificate into the buffer
 
841
        (gnutls.library.functions
 
842
         .gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
 
843
                                             ctypes.byref(buf_len)))
 
844
        # Deinit the certificate
 
845
        gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
 
846
        # Convert the buffer to a Python bytestring
 
847
        fpr = ctypes.string_at(buf, buf_len.value)
 
848
        # Convert the bytestring to hexadecimal notation
 
849
        hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
 
850
        return hex_fpr
 
851
 
 
852
 
 
853
class ForkingMixInWithPipe(SocketServer.ForkingMixIn, object):
 
854
    """Like SocketServer.ForkingMixIn, but also pass a pipe.
 
855
    
 
856
    Assumes a gobject.MainLoop event loop.
 
857
    """
 
858
    def process_request(self, request, client_address):
 
859
        """Overrides and wraps the original process_request().
 
860
        
 
861
        This function creates a new pipe in self.pipe 
 
862
        """
 
863
        self.pipe = os.pipe()
 
864
        super(ForkingMixInWithPipe,
 
865
              self).process_request(request, client_address)
 
866
        os.close(self.pipe[1])  # close write end
 
867
        # Call "handle_ipc" for both data and EOF events
 
868
        gobject.io_add_watch(self.pipe[0],
 
869
                             gobject.IO_IN | gobject.IO_HUP,
 
870
                             self.handle_ipc)
 
871
    def handle_ipc(source, condition):
 
872
        """Dummy function; override as necessary"""
 
873
        os.close(source)
 
874
        return False
 
875
 
 
876
 
 
877
class IPv6_TCPServer(ForkingMixInWithPipe,
 
878
                     SocketServer.TCPServer, object):
 
879
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
 
880
    
518
881
    Attributes:
519
 
        settings:       Server settings
 
882
        enabled:        Boolean; whether this server is activated yet
 
883
        interface:      None or a network interface name (string)
 
884
        use_ipv6:       Boolean; to use IPv6 or not
 
885
        ----
520
886
        clients:        Set() of Client objects
521
 
        enabled:        Boolean; whether this server is activated yet
 
887
        gnutls_priority GnuTLS priority string
 
888
        use_dbus:       Boolean; to emit D-Bus signals or not
522
889
    """
523
 
    address_family = socket.AF_INET6
524
 
    def __init__(self, *args, **kwargs):
525
 
        if "settings" in kwargs:
526
 
            self.settings = kwargs["settings"]
527
 
            del kwargs["settings"]
528
 
        if "clients" in kwargs:
529
 
            self.clients = kwargs["clients"]
530
 
            del kwargs["clients"]
 
890
    def __init__(self, server_address, RequestHandlerClass,
 
891
                 interface=None, use_ipv6=True, clients=None,
 
892
                 gnutls_priority=None, use_dbus=True):
531
893
        self.enabled = False
532
 
        return super(type(self), self).__init__(*args, **kwargs)
 
894
        self.interface = interface
 
895
        if use_ipv6:
 
896
            self.address_family = socket.AF_INET6
 
897
        self.clients = clients
 
898
        self.use_dbus = use_dbus
 
899
        self.gnutls_priority = gnutls_priority
 
900
        SocketServer.TCPServer.__init__(self, server_address,
 
901
                                        RequestHandlerClass)
533
902
    def server_bind(self):
534
903
        """This overrides the normal server_bind() function
535
904
        to bind to an interface if one was specified, and also NOT to
536
905
        bind to an address or port if they were not specified."""
537
 
        if self.settings["interface"]:
538
 
            # 25 is from /usr/include/asm-i486/socket.h
539
 
            SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
 
906
        if self.interface is not None:
540
907
            try:
541
908
                self.socket.setsockopt(socket.SOL_SOCKET,
542
909
                                       SO_BINDTODEVICE,
543
 
                                       self.settings["interface"])
 
910
                                       str(self.interface + u'\0'))
544
911
            except socket.error, error:
545
912
                if error[0] == errno.EPERM:
546
913
                    logger.error(u"No permission to"
547
914
                                 u" bind to interface %s",
548
 
                                 self.settings["interface"])
 
915
                                 self.interface)
549
916
                else:
550
 
                    raise error
 
917
                    raise
551
918
        # Only bind(2) the socket if we really need to.
552
919
        if self.server_address[0] or self.server_address[1]:
553
920
            if not self.server_address[0]:
554
 
                in6addr_any = "::"
555
 
                self.server_address = (in6addr_any,
 
921
                if self.address_family == socket.AF_INET6:
 
922
                    any_address = u"::" # in6addr_any
 
923
                else:
 
924
                    any_address = socket.INADDR_ANY
 
925
                self.server_address = (any_address,
556
926
                                       self.server_address[1])
557
927
            elif not self.server_address[1]:
558
928
                self.server_address = (self.server_address[0],
559
929
                                       0)
560
 
#                 if self.settings["interface"]:
 
930
#                 if self.interface:
561
931
#                     self.server_address = (self.server_address[0],
562
932
#                                            0, # port
563
933
#                                            0, # flowinfo
564
934
#                                            if_nametoindex
565
 
#                                            (self.settings
566
 
#                                             ["interface"]))
567
 
            return super(type(self), self).server_bind()
 
935
#                                            (self.interface))
 
936
            return SocketServer.TCPServer.server_bind(self)
568
937
    def server_activate(self):
569
938
        if self.enabled:
570
 
            return super(type(self), self).server_activate()
 
939
            return SocketServer.TCPServer.server_activate(self)
571
940
    def enable(self):
572
941
        self.enabled = True
 
942
    def handle_ipc(self, source, condition, file_objects={}):
 
943
        condition_names = {
 
944
            gobject.IO_IN: u"IN",   # There is data to read.
 
945
            gobject.IO_OUT: u"OUT", # Data can be written (without
 
946
                                    # blocking).
 
947
            gobject.IO_PRI: u"PRI", # There is urgent data to read.
 
948
            gobject.IO_ERR: u"ERR", # Error condition.
 
949
            gobject.IO_HUP: u"HUP"  # Hung up (the connection has been
 
950
                                    # broken, usually for pipes and
 
951
                                    # sockets).
 
952
            }
 
953
        conditions_string = ' | '.join(name
 
954
                                       for cond, name in
 
955
                                       condition_names.iteritems()
 
956
                                       if cond & condition)
 
957
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
 
958
                     conditions_string)
 
959
        
 
960
        # Turn the pipe file descriptor into a Python file object
 
961
        if source not in file_objects:
 
962
            file_objects[source] = os.fdopen(source, u"r", 1)
 
963
        
 
964
        # Read a line from the file object
 
965
        cmdline = file_objects[source].readline()
 
966
        if not cmdline:             # Empty line means end of file
 
967
            # close the IPC pipe
 
968
            file_objects[source].close()
 
969
            del file_objects[source]
 
970
            
 
971
            # Stop calling this function
 
972
            return False
 
973
        
 
974
        logger.debug(u"IPC command: %r", cmdline)
 
975
        
 
976
        # Parse and act on command
 
977
        cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
 
978
        
 
979
        if cmd == u"NOTFOUND":
 
980
            logger.warning(u"Client not found for fingerprint: %s",
 
981
                           args)
 
982
            if self.use_dbus:
 
983
                # Emit D-Bus signal
 
984
                mandos_dbus_service.ClientNotFound(args)
 
985
        elif cmd == u"INVALID":
 
986
            for client in self.clients:
 
987
                if client.name == args:
 
988
                    logger.warning(u"Client %s is invalid", args)
 
989
                    if self.use_dbus:
 
990
                        # Emit D-Bus signal
 
991
                        client.Rejected()
 
992
                    break
 
993
            else:
 
994
                logger.error(u"Unknown client %s is invalid", args)
 
995
        elif cmd == u"SENDING":
 
996
            for client in self.clients:
 
997
                if client.name == args:
 
998
                    logger.info(u"Sending secret to %s", client.name)
 
999
                    client.checked_ok()
 
1000
                    if self.use_dbus:
 
1001
                        # Emit D-Bus signal
 
1002
                        client.ReceivedSecret()
 
1003
                    break
 
1004
            else:
 
1005
                logger.error(u"Sending secret to unknown client %s",
 
1006
                             args)
 
1007
        else:
 
1008
            logger.error(u"Unknown IPC command: %r", cmdline)
 
1009
        
 
1010
        # Keep calling this function
 
1011
        return True
573
1012
 
574
1013
 
575
1014
def string_to_delta(interval):
576
1015
    """Parse a string and return a datetime.timedelta
577
 
 
578
 
    >>> string_to_delta('7d')
 
1016
    
 
1017
    >>> string_to_delta(u'7d')
579
1018
    datetime.timedelta(7)
580
 
    >>> string_to_delta('60s')
 
1019
    >>> string_to_delta(u'60s')
581
1020
    datetime.timedelta(0, 60)
582
 
    >>> string_to_delta('60m')
 
1021
    >>> string_to_delta(u'60m')
583
1022
    datetime.timedelta(0, 3600)
584
 
    >>> string_to_delta('24h')
 
1023
    >>> string_to_delta(u'24h')
585
1024
    datetime.timedelta(1)
586
1025
    >>> string_to_delta(u'1w')
587
1026
    datetime.timedelta(7)
588
 
    >>> string_to_delta('5m 30s')
 
1027
    >>> string_to_delta(u'5m 30s')
589
1028
    datetime.timedelta(0, 330)
590
1029
    """
591
1030
    timevalue = datetime.timedelta(0)
592
1031
    for s in interval.split():
593
1032
        try:
594
 
            suffix=unicode(s[-1])
595
 
            value=int(s[:-1])
 
1033
            suffix = unicode(s[-1])
 
1034
            value = int(s[:-1])
596
1035
            if suffix == u"d":
597
1036
                delta = datetime.timedelta(value)
598
1037
            elif suffix == u"s":
632
1071
    elif state == avahi.ENTRY_GROUP_FAILURE:
633
1072
        logger.critical(u"Avahi: Error in group state changed %s",
634
1073
                        unicode(error))
635
 
        raise AvahiGroupError("State changed: %s", str(error))
 
1074
        raise AvahiGroupError(u"State changed: %s" % unicode(error))
636
1075
 
637
1076
def if_nametoindex(interface):
638
 
    """Call the C function if_nametoindex(), or equivalent"""
 
1077
    """Call the C function if_nametoindex(), or equivalent
 
1078
    
 
1079
    Note: This function cannot accept a unicode string."""
639
1080
    global if_nametoindex
640
1081
    try:
641
 
        if "ctypes.util" not in sys.modules:
642
 
            import ctypes.util
643
 
        if_nametoindex = ctypes.cdll.LoadLibrary\
644
 
            (ctypes.util.find_library("c")).if_nametoindex
 
1082
        if_nametoindex = (ctypes.cdll.LoadLibrary
 
1083
                          (ctypes.util.find_library(u"c"))
 
1084
                          .if_nametoindex)
645
1085
    except (OSError, AttributeError):
646
 
        if "struct" not in sys.modules:
647
 
            import struct
648
 
        if "fcntl" not in sys.modules:
649
 
            import fcntl
 
1086
        logger.warning(u"Doing if_nametoindex the hard way")
650
1087
        def if_nametoindex(interface):
651
1088
            "Get an interface index the hard way, i.e. using fcntl()"
652
1089
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
653
 
            s = socket.socket()
654
 
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
655
 
                                struct.pack("16s16x", interface))
656
 
            s.close()
657
 
            interface_index = struct.unpack("I", ifreq[16:20])[0]
 
1090
            with closing(socket.socket()) as s:
 
1091
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
 
1092
                                    struct.pack(str(u"16s16x"),
 
1093
                                                interface))
 
1094
            interface_index = struct.unpack(str(u"I"),
 
1095
                                            ifreq[16:20])[0]
658
1096
            return interface_index
659
1097
    return if_nametoindex(interface)
660
1098
 
661
1099
 
662
1100
def daemon(nochdir = False, noclose = False):
663
1101
    """See daemon(3).  Standard BSD Unix function.
 
1102
    
664
1103
    This should really exist as os.daemon, but it doesn't (yet)."""
665
1104
    if os.fork():
666
1105
        sys.exit()
667
1106
    os.setsid()
668
1107
    if not nochdir:
669
 
        os.chdir("/")
 
1108
        os.chdir(u"/")
670
1109
    if os.fork():
671
1110
        sys.exit()
672
1111
    if not noclose:
674
1113
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
675
1114
        if not stat.S_ISCHR(os.fstat(null).st_mode):
676
1115
            raise OSError(errno.ENODEV,
677
 
                          "/dev/null not a character device")
 
1116
                          u"/dev/null not a character device")
678
1117
        os.dup2(null, sys.stdin.fileno())
679
1118
        os.dup2(null, sys.stdout.fileno())
680
1119
        os.dup2(null, sys.stderr.fileno())
683
1122
 
684
1123
 
685
1124
def main():
686
 
    global main_loop_started
687
 
    main_loop_started = False
688
 
    
689
 
    parser = OptionParser(version = "%%prog %s" % version)
690
 
    parser.add_option("-i", "--interface", type="string",
691
 
                      metavar="IF", help="Bind to interface IF")
692
 
    parser.add_option("-a", "--address", type="string",
693
 
                      help="Address to listen for requests on")
694
 
    parser.add_option("-p", "--port", type="int",
695
 
                      help="Port number to receive requests on")
696
 
    parser.add_option("--check", action="store_true", default=False,
697
 
                      help="Run self-test")
698
 
    parser.add_option("--debug", action="store_true",
699
 
                      help="Debug mode; run in foreground and log to"
700
 
                      " terminal")
701
 
    parser.add_option("--priority", type="string", help="GnuTLS"
702
 
                      " priority string (see GnuTLS documentation)")
703
 
    parser.add_option("--servicename", type="string", metavar="NAME",
704
 
                      help="Zeroconf service name")
705
 
    parser.add_option("--configdir", type="string",
706
 
                      default="/etc/mandos", metavar="DIR",
707
 
                      help="Directory to search for configuration"
708
 
                      " files")
709
 
    (options, args) = parser.parse_args()
 
1125
    
 
1126
    ######################################################################
 
1127
    # Parsing of options, both command line and config file
 
1128
    
 
1129
    parser = optparse.OptionParser(version = "%%prog %s" % version)
 
1130
    parser.add_option("-i", u"--interface", type=u"string",
 
1131
                      metavar="IF", help=u"Bind to interface IF")
 
1132
    parser.add_option("-a", u"--address", type=u"string",
 
1133
                      help=u"Address to listen for requests on")
 
1134
    parser.add_option("-p", u"--port", type=u"int",
 
1135
                      help=u"Port number to receive requests on")
 
1136
    parser.add_option("--check", action=u"store_true",
 
1137
                      help=u"Run self-test")
 
1138
    parser.add_option("--debug", action=u"store_true",
 
1139
                      help=u"Debug mode; run in foreground and log to"
 
1140
                      u" terminal")
 
1141
    parser.add_option("--priority", type=u"string", help=u"GnuTLS"
 
1142
                      u" priority string (see GnuTLS documentation)")
 
1143
    parser.add_option("--servicename", type=u"string",
 
1144
                      metavar=u"NAME", help=u"Zeroconf service name")
 
1145
    parser.add_option("--configdir", type=u"string",
 
1146
                      default=u"/etc/mandos", metavar=u"DIR",
 
1147
                      help=u"Directory to search for configuration"
 
1148
                      u" files")
 
1149
    parser.add_option("--no-dbus", action=u"store_false",
 
1150
                      dest=u"use_dbus", help=u"Do not provide D-Bus"
 
1151
                      u" system bus interface")
 
1152
    parser.add_option("--no-ipv6", action=u"store_false",
 
1153
                      dest=u"use_ipv6", help=u"Do not use IPv6")
 
1154
    options = parser.parse_args()[0]
710
1155
    
711
1156
    if options.check:
712
1157
        import doctest
714
1159
        sys.exit()
715
1160
    
716
1161
    # Default values for config file for server-global settings
717
 
    server_defaults = { "interface": "",
718
 
                        "address": "",
719
 
                        "port": "",
720
 
                        "debug": "False",
721
 
                        "priority":
722
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
723
 
                        "servicename": "Mandos",
 
1162
    server_defaults = { u"interface": u"",
 
1163
                        u"address": u"",
 
1164
                        u"port": u"",
 
1165
                        u"debug": u"False",
 
1166
                        u"priority":
 
1167
                        u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
 
1168
                        u"servicename": u"Mandos",
 
1169
                        u"use_dbus": u"True",
 
1170
                        u"use_ipv6": u"True",
724
1171
                        }
725
1172
    
726
1173
    # Parse config file for server-global settings
727
1174
    server_config = ConfigParser.SafeConfigParser(server_defaults)
728
1175
    del server_defaults
729
 
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
 
1176
    server_config.read(os.path.join(options.configdir,
 
1177
                                    u"mandos.conf"))
730
1178
    # Convert the SafeConfigParser object to a dict
731
1179
    server_settings = server_config.defaults()
732
 
    # Use getboolean on the boolean config option
733
 
    server_settings["debug"] = server_config.getboolean\
734
 
                               ("DEFAULT", "debug")
 
1180
    # Use the appropriate methods on the non-string config options
 
1181
    for option in (u"debug", u"use_dbus", u"use_ipv6"):
 
1182
        server_settings[option] = server_config.getboolean(u"DEFAULT",
 
1183
                                                           option)
 
1184
    if server_settings["port"]:
 
1185
        server_settings["port"] = server_config.getint(u"DEFAULT",
 
1186
                                                       u"port")
735
1187
    del server_config
736
1188
    
737
1189
    # Override the settings from the config file with command line
738
1190
    # options, if set.
739
 
    for option in ("interface", "address", "port", "debug",
740
 
                   "priority", "servicename", "configdir"):
 
1191
    for option in (u"interface", u"address", u"port", u"debug",
 
1192
                   u"priority", u"servicename", u"configdir",
 
1193
                   u"use_dbus", u"use_ipv6"):
741
1194
        value = getattr(options, option)
742
1195
        if value is not None:
743
1196
            server_settings[option] = value
744
1197
    del options
 
1198
    # Force all strings to be unicode
 
1199
    for option in server_settings.keys():
 
1200
        if type(server_settings[option]) is str:
 
1201
            server_settings[option] = unicode(server_settings[option])
745
1202
    # Now we have our good server settings in "server_settings"
746
1203
    
747
 
    debug = server_settings["debug"]
 
1204
    ##################################################################
 
1205
    
 
1206
    # For convenience
 
1207
    debug = server_settings[u"debug"]
 
1208
    use_dbus = server_settings[u"use_dbus"]
 
1209
    use_ipv6 = server_settings[u"use_ipv6"]
748
1210
    
749
1211
    if not debug:
750
1212
        syslogger.setLevel(logging.WARNING)
751
1213
        console.setLevel(logging.WARNING)
752
1214
    
753
 
    if server_settings["servicename"] != "Mandos":
754
 
        syslogger.setFormatter(logging.Formatter\
755
 
                               ('Mandos (%s): %%(levelname)s:'
756
 
                                ' %%(message)s'
757
 
                                % server_settings["servicename"]))
 
1215
    if server_settings[u"servicename"] != u"Mandos":
 
1216
        syslogger.setFormatter(logging.Formatter
 
1217
                               (u'Mandos (%s) [%%(process)d]:'
 
1218
                                u' %%(levelname)s: %%(message)s'
 
1219
                                % server_settings[u"servicename"]))
758
1220
    
759
1221
    # Parse config file with clients
760
 
    client_defaults = { "timeout": "1h",
761
 
                        "interval": "5m",
762
 
                        "checker": "fping -q -- %(host)s",
763
 
                        "host": "",
 
1222
    client_defaults = { u"timeout": u"1h",
 
1223
                        u"interval": u"5m",
 
1224
                        u"checker": u"fping -q -- %%(host)s",
 
1225
                        u"host": u"",
764
1226
                        }
765
1227
    client_config = ConfigParser.SafeConfigParser(client_defaults)
766
 
    client_config.read(os.path.join(server_settings["configdir"],
767
 
                                    "clients.conf"))
 
1228
    client_config.read(os.path.join(server_settings[u"configdir"],
 
1229
                                    u"clients.conf"))
 
1230
    
 
1231
    global mandos_dbus_service
 
1232
    mandos_dbus_service = None
768
1233
    
769
1234
    clients = Set()
770
 
    tcp_server = IPv6_TCPServer((server_settings["address"],
771
 
                                 server_settings["port"]),
772
 
                                tcp_handler,
773
 
                                settings=server_settings,
774
 
                                clients=clients)
775
 
    pidfilename = "/var/run/mandos.pid"
 
1235
    tcp_server = IPv6_TCPServer((server_settings[u"address"],
 
1236
                                 server_settings[u"port"]),
 
1237
                                ClientHandler,
 
1238
                                interface=
 
1239
                                server_settings[u"interface"],
 
1240
                                use_ipv6=use_ipv6,
 
1241
                                clients=clients,
 
1242
                                gnutls_priority=
 
1243
                                server_settings[u"priority"],
 
1244
                                use_dbus=use_dbus)
 
1245
    pidfilename = u"/var/run/mandos.pid"
776
1246
    try:
777
 
        pidfile = open(pidfilename, "w")
778
 
    except IOError, error:
779
 
        logger.error("Could not open file %r", pidfilename)
 
1247
        pidfile = open(pidfilename, u"w")
 
1248
    except IOError:
 
1249
        logger.error(u"Could not open file %r", pidfilename)
780
1250
    
781
 
    uid = 65534
782
 
    gid = 65534
783
 
    try:
784
 
        uid = pwd.getpwnam("mandos").pw_uid
785
 
    except KeyError:
786
 
        try:
787
 
            uid = pwd.getpwnam("nobody").pw_uid
788
 
        except KeyError:
789
 
            pass
790
 
    try:
791
 
        gid = pwd.getpwnam("mandos").pw_gid
792
 
    except KeyError:
793
 
        try:
794
 
            gid = pwd.getpwnam("nogroup").pw_gid
795
 
        except KeyError:
796
 
            pass
797
 
    try:
 
1251
    try:
 
1252
        uid = pwd.getpwnam(u"_mandos").pw_uid
 
1253
        gid = pwd.getpwnam(u"_mandos").pw_gid
 
1254
    except KeyError:
 
1255
        try:
 
1256
            uid = pwd.getpwnam(u"mandos").pw_uid
 
1257
            gid = pwd.getpwnam(u"mandos").pw_gid
 
1258
        except KeyError:
 
1259
            try:
 
1260
                uid = pwd.getpwnam(u"nobody").pw_uid
 
1261
                gid = pwd.getpwnam(u"nobody").pw_gid
 
1262
            except KeyError:
 
1263
                uid = 65534
 
1264
                gid = 65534
 
1265
    try:
 
1266
        os.setgid(gid)
798
1267
        os.setuid(uid)
799
 
        os.setgid(gid)
800
1268
    except OSError, error:
801
1269
        if error[0] != errno.EPERM:
802
1270
            raise error
803
1271
    
 
1272
    # Enable all possible GnuTLS debugging
 
1273
    if debug:
 
1274
        # "Use a log level over 10 to enable all debugging options."
 
1275
        # - GnuTLS manual
 
1276
        gnutls.library.functions.gnutls_global_set_log_level(11)
 
1277
        
 
1278
        @gnutls.library.types.gnutls_log_func
 
1279
        def debug_gnutls(level, string):
 
1280
            logger.debug(u"GnuTLS: %s", string[:-1])
 
1281
        
 
1282
        (gnutls.library.functions
 
1283
         .gnutls_global_set_log_function(debug_gnutls))
 
1284
    
804
1285
    global service
805
 
    service = AvahiService(name = server_settings["servicename"],
806
 
                           type = "_mandos._tcp", );
 
1286
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
 
1287
    service = AvahiService(name = server_settings[u"servicename"],
 
1288
                           servicetype = u"_mandos._tcp",
 
1289
                           protocol = protocol)
807
1290
    if server_settings["interface"]:
808
 
        service.interface = if_nametoindex\
809
 
                            (server_settings["interface"])
 
1291
        service.interface = (if_nametoindex
 
1292
                             (str(server_settings[u"interface"])))
810
1293
    
811
1294
    global main_loop
812
1295
    global bus
819
1302
                                           avahi.DBUS_PATH_SERVER),
820
1303
                            avahi.DBUS_INTERFACE_SERVER)
821
1304
    # End of Avahi example code
822
 
    
823
 
    def remove_from_clients(client):
824
 
        clients.remove(client)
825
 
        if not clients:
826
 
            logger.critical(u"No clients left, exiting")
827
 
            sys.exit()
828
 
    
829
 
    clients.update(Set(Client(name = section,
830
 
                              stop_hook = remove_from_clients,
831
 
                              config
832
 
                              = dict(client_config.items(section)))
833
 
                       for section in client_config.sections()))
 
1305
    if use_dbus:
 
1306
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
 
1307
    
 
1308
    client_class = Client
 
1309
    if use_dbus:
 
1310
        client_class = ClientDBus
 
1311
    clients.update(Set(
 
1312
            client_class(name = section,
 
1313
                         config= dict(client_config.items(section)))
 
1314
            for section in client_config.sections()))
834
1315
    if not clients:
835
 
        logger.critical(u"No clients defined")
836
 
        sys.exit(1)
 
1316
        logger.warning(u"No clients defined")
837
1317
    
838
1318
    if debug:
839
1319
        # Redirect stdin so all checkers get /dev/null
848
1328
        daemon()
849
1329
    
850
1330
    try:
851
 
        pid = os.getpid()
852
 
        pidfile.write(str(pid) + "\n")
853
 
        pidfile.close()
 
1331
        with closing(pidfile):
 
1332
            pid = os.getpid()
 
1333
            pidfile.write(str(pid) + "\n")
854
1334
        del pidfile
855
 
    except IOError, err:
 
1335
    except IOError:
856
1336
        logger.error(u"Could not write to file %r with PID %d",
857
1337
                     pidfilename, pid)
858
1338
    except NameError:
871
1351
        
872
1352
        while clients:
873
1353
            client = clients.pop()
874
 
            client.stop_hook = None
875
 
            client.stop()
 
1354
            client.disable_hook = None
 
1355
            client.disable()
876
1356
    
877
1357
    atexit.register(cleanup)
878
1358
    
881
1361
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
882
1362
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
883
1363
    
 
1364
    if use_dbus:
 
1365
        class MandosDBusService(dbus.service.Object):
 
1366
            """A D-Bus proxy object"""
 
1367
            def __init__(self):
 
1368
                dbus.service.Object.__init__(self, bus, u"/")
 
1369
            _interface = u"se.bsnet.fukt.Mandos"
 
1370
            
 
1371
            @dbus.service.signal(_interface, signature=u"oa{sv}")
 
1372
            def ClientAdded(self, objpath, properties):
 
1373
                "D-Bus signal"
 
1374
                pass
 
1375
            
 
1376
            @dbus.service.signal(_interface, signature=u"s")
 
1377
            def ClientNotFound(self, fingerprint):
 
1378
                "D-Bus signal"
 
1379
                pass
 
1380
            
 
1381
            @dbus.service.signal(_interface, signature=u"os")
 
1382
            def ClientRemoved(self, objpath, name):
 
1383
                "D-Bus signal"
 
1384
                pass
 
1385
            
 
1386
            @dbus.service.method(_interface, out_signature=u"ao")
 
1387
            def GetAllClients(self):
 
1388
                "D-Bus method"
 
1389
                return dbus.Array(c.dbus_object_path for c in clients)
 
1390
            
 
1391
            @dbus.service.method(_interface,
 
1392
                                 out_signature=u"a{oa{sv}}")
 
1393
            def GetAllClientsWithProperties(self):
 
1394
                "D-Bus method"
 
1395
                return dbus.Dictionary(
 
1396
                    ((c.dbus_object_path, c.GetAllProperties())
 
1397
                     for c in clients),
 
1398
                    signature=u"oa{sv}")
 
1399
            
 
1400
            @dbus.service.method(_interface, in_signature=u"o")
 
1401
            def RemoveClient(self, object_path):
 
1402
                "D-Bus method"
 
1403
                for c in clients:
 
1404
                    if c.dbus_object_path == object_path:
 
1405
                        clients.remove(c)
 
1406
                        c.remove_from_connection()
 
1407
                        # Don't signal anything except ClientRemoved
 
1408
                        c.disable(signal=False)
 
1409
                        # Emit D-Bus signal
 
1410
                        self.ClientRemoved(object_path, c.name)
 
1411
                        return
 
1412
                raise KeyError
 
1413
            
 
1414
            del _interface
 
1415
        
 
1416
        mandos_dbus_service = MandosDBusService()
 
1417
    
884
1418
    for client in clients:
885
 
        client.start()
 
1419
        if use_dbus:
 
1420
            # Emit D-Bus signal
 
1421
            mandos_dbus_service.ClientAdded(client.dbus_object_path,
 
1422
                                            client.GetAllProperties())
 
1423
        client.enable()
886
1424
    
887
1425
    tcp_server.enable()
888
1426
    tcp_server.server_activate()
889
1427
    
890
1428
    # Find out what port we got
891
1429
    service.port = tcp_server.socket.getsockname()[1]
892
 
    logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
893
 
                u" scope_id %d" % tcp_server.socket.getsockname())
 
1430
    if use_ipv6:
 
1431
        logger.info(u"Now listening on address %r, port %d,"
 
1432
                    " flowinfo %d, scope_id %d"
 
1433
                    % tcp_server.socket.getsockname())
 
1434
    else:                       # IPv4
 
1435
        logger.info(u"Now listening on address %r, port %d"
 
1436
                    % tcp_server.socket.getsockname())
894
1437
    
895
1438
    #service.interface = tcp_server.socket.getsockname()[3]
896
1439
    
897
1440
    try:
898
1441
        # From the Avahi example code
899
 
        server.connect_to_signal("StateChanged", server_state_changed)
 
1442
        server.connect_to_signal(u"StateChanged", server_state_changed)
900
1443
        try:
901
1444
            server_state_changed(server.GetState())
902
1445
        except dbus.exceptions.DBusException, error:
906
1449
        
907
1450
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
908
1451
                             lambda *args, **kwargs:
909
 
                             tcp_server.handle_request\
910
 
                             (*args[2:], **kwargs) or True)
 
1452
                             (tcp_server.handle_request
 
1453
                              (*args[2:], **kwargs) or True))
911
1454
        
912
1455
        logger.debug(u"Starting main loop")
913
 
        main_loop_started = True
914
1456
        main_loop.run()
915
1457
    except AvahiError, error:
916
 
        logger.critical(u"AvahiError: %s" + unicode(error))
 
1458
        logger.critical(u"AvahiError: %s", error)
917
1459
        sys.exit(1)
918
1460
    except KeyboardInterrupt:
919
1461
        if debug:
920
 
            print
 
1462
            print >> sys.stderr
 
1463
        logger.debug(u"Server received KeyboardInterrupt")
 
1464
    logger.debug(u"Server exiting")
921
1465
 
922
1466
if __name__ == '__main__':
923
1467
    main()