/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 © 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
 
60
import struct
 
61
import fcntl
58
62
 
59
63
import dbus
 
64
import dbus.service
60
65
import gobject
61
66
import avahi
62
67
from dbus.mainloop.glib import DBusGMainLoop
63
68
import ctypes
64
69
import ctypes.util
65
70
 
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'))
 
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
 
        super(AvahiError, self).__init__()
85
 
    def __str__(self):
86
 
        return repr(self.value)
 
101
        super(AvahiError, self).__init__(value, *args, **kwargs)
 
102
    def __unicode__(self):
 
103
        return unicode(repr(self.value))
87
104
 
88
105
class AvahiServiceError(AvahiError):
89
106
    pass
94
111
 
95
112
class AvahiService(object):
96
113
    """An Avahi (Zeroconf) service.
 
114
    
97
115
    Attributes:
98
116
    interface: integer; avahi.IF_UNSPEC or an interface index.
99
117
               Used to optionally bind to the specified interface.
100
 
    name: string; Example: 'Mandos'
101
 
    type: string; Example: '_mandos._tcp'.
 
118
    name: string; Example: u'Mandos'
 
119
    type: string; Example: u'_mandos._tcp'.
102
120
                  See <http://www.dns-sd.org/ServiceTypes.html>
103
121
    port: integer; what port to announce
104
122
    TXT: list of strings; TXT record for the service
109
127
                  a sensible number of times
110
128
    """
111
129
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
112
 
                 servicetype = None, port = None, TXT = None, domain = "",
113
 
                 host = "", max_renames = 32768):
 
130
                 servicetype = None, port = None, TXT = None,
 
131
                 domain = u"", host = u"", max_renames = 32768,
 
132
                 protocol = avahi.PROTO_UNSPEC):
114
133
        self.interface = interface
115
134
        self.name = name
116
135
        self.type = servicetype
117
136
        self.port = port
118
 
        if TXT is None:
119
 
            self.TXT = []
120
 
        else:
121
 
            self.TXT = TXT
 
137
        self.TXT = TXT if TXT is not None else []
122
138
        self.domain = domain
123
139
        self.host = host
124
140
        self.rename_count = 0
125
141
        self.max_renames = max_renames
 
142
        self.protocol = protocol
126
143
    def rename(self):
127
144
        """Derived from the Avahi example code"""
128
145
        if self.rename_count >= self.max_renames:
129
146
            logger.critical(u"No suitable Zeroconf service name found"
130
147
                            u" after %i retries, exiting.",
131
148
                            self.rename_count)
132
 
            raise AvahiServiceError("Too many renames")
 
149
            raise AvahiServiceError(u"Too many renames")
133
150
        self.name = server.GetAlternativeServiceName(self.name)
134
151
        logger.info(u"Changing Zeroconf service name to %r ...",
135
 
                    str(self.name))
136
 
        syslogger.setFormatter(logging.Formatter\
137
 
                               ('Mandos (%s): %%(levelname)s:'
138
 
                               ' %%(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))
139
157
        self.remove()
140
158
        self.add()
141
159
        self.rename_count += 1
147
165
        """Derived from the Avahi example code"""
148
166
        global group
149
167
        if group is None:
150
 
            group = dbus.Interface\
151
 
                    (bus.get_object(avahi.DBUS_NAME,
 
168
            group = dbus.Interface(bus.get_object
 
169
                                   (avahi.DBUS_NAME,
152
170
                                    server.EntryGroupNew()),
153
 
                     avahi.DBUS_INTERFACE_ENTRY_GROUP)
 
171
                                   avahi.DBUS_INTERFACE_ENTRY_GROUP)
154
172
            group.connect_to_signal('StateChanged',
155
173
                                    entry_group_state_changed)
156
174
        logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
157
175
                     service.name, service.type)
158
176
        group.AddService(
159
177
                self.interface,         # interface
160
 
                avahi.PROTO_INET6,      # protocol
 
178
                self.protocol,          # protocol
161
179
                dbus.UInt32(0),         # flags
162
180
                self.name, self.type,
163
181
                self.domain, self.host,
170
188
# End of Avahi example code
171
189
 
172
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
 
173
196
class Client(object):
174
197
    """A representation of a client host served by this server.
 
198
    
175
199
    Attributes:
176
 
    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
177
202
    fingerprint: string (40 or 32 hexadecimal digits); used to
178
203
                 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.
 
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.
190
217
    checker_initiator_tag: a gobject event source tag, or None
191
 
    stop_initiator_tag:    - '' -
 
218
    disable_initiator_tag:    - '' -
192
219
    checker_callback_tag:  - '' -
193
220
    checker_command: string; External command which is run to check if
194
221
                     client lives.  %() expansions are done at
195
222
                     runtime with vars(self) as dict, so that for
196
223
                     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: - '' -
 
224
    current_checker_command: string; current running checker_command
202
225
    """
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):
 
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):
227
243
        """Note: the 'checker' key in 'config' sets the
228
244
        'checker_command' attribute and *not* the 'checker'
229
245
        attribute."""
 
246
        self.name = name
230
247
        if config is None:
231
248
            config = {}
232
 
        self.name = name
233
249
        logger.debug(u"Creating client %r", self.name)
234
250
        # Uppercase and remove spaces from fingerprint for later
235
251
        # comparison purposes with return value from the fingerprint()
236
252
        # function
237
 
        self.fingerprint = config["fingerprint"].upper()\
238
 
                           .replace(u" ", u"")
 
253
        self.fingerprint = (config[u"fingerprint"].upper()
 
254
                            .replace(u" ", u""))
239
255
        logger.debug(u"  Fingerprint: %s", self.fingerprint)
240
 
        if "secret" in config:
241
 
            self.secret = config["secret"].decode(u"base64")
242
 
        elif "secfile" in config:
243
 
            secfile = open(os.path.expanduser(os.path.expandvars
244
 
                                              (config["secfile"])))
245
 
            self.secret = secfile.read()
246
 
            secfile.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()
247
263
        else:
248
264
            raise TypeError(u"No secret or secfile for client %s"
249
265
                            % self.name)
250
 
        self.host = config.get("host", "")
251
 
        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
252
270
        self.last_checked_ok = None
253
 
        self.timeout = string_to_delta(config["timeout"])
254
 
        self.interval = string_to_delta(config["interval"])
255
 
        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
256
274
        self.checker = None
257
275
        self.checker_initiator_tag = None
258
 
        self.stop_initiator_tag = None
 
276
        self.disable_initiator_tag = None
259
277
        self.checker_callback_tag = None
260
 
        self.check_command = config["checker"]
261
 
    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):
262
283
        """Start this client's checker and timeout hooks"""
 
284
        self.last_enabled = datetime.datetime.utcnow()
263
285
        # Schedule a new checker to be started an 'interval' from now,
264
286
        # and every interval from then on.
265
 
        self.checker_initiator_tag = gobject.timeout_add\
266
 
                                     (self._interval_milliseconds,
267
 
                                      self.start_checker)
 
287
        self.checker_initiator_tag = (gobject.timeout_add
 
288
                                      (self.interval_milliseconds(),
 
289
                                       self.start_checker))
268
290
        # Also start a new checker *right now*.
269
291
        self.start_checker()
270
 
        # Schedule a stop() when 'timeout' has passed
271
 
        self.stop_initiator_tag = gobject.timeout_add\
272
 
                                  (self._timeout_milliseconds,
273
 
                                   self.stop)
274
 
    def stop(self):
275
 
        """Stop this client.
276
 
        The possibility that a client might be restarted is left open,
277
 
        but not currently used."""
278
 
        # If this client doesn't have a secret, it is already stopped.
279
 
        if hasattr(self, "secret") and self.secret:
280
 
            logger.info(u"Stopping client %s", self.name)
281
 
            self.secret = None
282
 
        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):
283
301
            return False
284
 
        if getattr(self, "stop_initiator_tag", False):
285
 
            gobject.source_remove(self.stop_initiator_tag)
286
 
            self.stop_initiator_tag = None
287
 
        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):
288
307
            gobject.source_remove(self.checker_initiator_tag)
289
308
            self.checker_initiator_tag = None
290
309
        self.stop_checker()
291
 
        if self.stop_hook:
292
 
            self.stop_hook(self)
 
310
        if self.disable_hook:
 
311
            self.disable_hook(self)
 
312
        self.enabled = False
293
313
        # Do not run this again if called by a gobject.timeout_add
294
314
        return False
 
315
    
295
316
    def __del__(self):
296
 
        self.stop_hook = None
297
 
        self.stop()
298
 
    def checker_callback(self, pid, condition):
 
317
        self.disable_hook = None
 
318
        self.disable()
 
319
    
 
320
    def checker_callback(self, pid, condition, command):
299
321
        """The checker has completed, so take appropriate actions."""
300
 
        now = datetime.datetime.now()
301
322
        self.checker_callback_tag = None
302
323
        self.checker = None
303
 
        if os.WIFEXITED(condition) \
304
 
               and (os.WEXITSTATUS(condition) == 0):
305
 
            logger.info(u"Checker for %(name)s succeeded",
306
 
                        vars(self))
307
 
            self.last_checked_ok = now
308
 
            gobject.source_remove(self.stop_initiator_tag)
309
 
            self.stop_initiator_tag = gobject.timeout_add\
310
 
                                      (self._timeout_milliseconds,
311
 
                                       self.stop)
312
 
        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:
313
334
            logger.warning(u"Checker for %(name)s crashed?",
314
335
                           vars(self))
315
 
        else:
316
 
            logger.info(u"Checker for %(name)s failed",
317
 
                        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
    
318
349
    def start_checker(self):
319
350
        """Start a new checker subprocess if one is not running.
 
351
        
320
352
        If a checker already exists, leave it running and do
321
353
        nothing."""
322
354
        # The reason for not killing a running checker is that if we
327
359
        # checkers alone, the checker would have to take more time
328
360
        # than 'timeout' for the client to be declared invalid, which
329
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
330
372
        if self.checker is None:
331
373
            try:
332
 
                # In case check_command has exactly one % operator
333
 
                command = self.check_command % self.host
 
374
                # In case checker_command has exactly one % operator
 
375
                command = self.checker_command % self.host
334
376
            except TypeError:
335
377
                # Escape attributes for the shell
336
 
                escaped_attrs = dict((key, re.escape(str(val)))
 
378
                escaped_attrs = dict((key,
 
379
                                      re.escape(unicode(str(val),
 
380
                                                        errors=
 
381
                                                        u'replace')))
337
382
                                     for key, val in
338
383
                                     vars(self).iteritems())
339
384
                try:
340
 
                    command = self.check_command % escaped_attrs
 
385
                    command = self.checker_command % escaped_attrs
341
386
                except TypeError, error:
342
387
                    logger.error(u'Could not format string "%s":'
343
 
                                 u' %s', self.check_command, error)
 
388
                                 u' %s', self.checker_command, error)
344
389
                    return True # Try again later
 
390
            self.current_checker_command = command
345
391
            try:
346
392
                logger.info(u"Starting checker %r for %s",
347
393
                            command, self.name)
351
397
                # always replaced by /dev/null.)
352
398
                self.checker = subprocess.Popen(command,
353
399
                                                close_fds=True,
354
 
                                                shell=True, cwd="/")
355
 
                self.checker_callback_tag = gobject.child_watch_add\
356
 
                                            (self.checker.pid,
357
 
                                             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)
358
411
            except OSError, error:
359
412
                logger.error(u"Failed to start subprocess: %s",
360
413
                             error)
361
414
        # Re-run this periodically if run by gobject.timeout_add
362
415
        return True
 
416
    
363
417
    def stop_checker(self):
364
418
        """Force the checker process, if any, to stop."""
365
419
        if self.checker_callback_tag:
366
420
            gobject.source_remove(self.checker_callback_tag)
367
421
            self.checker_callback_tag = None
368
 
        if getattr(self, "checker", None) is None:
 
422
        if getattr(self, u"checker", None) is None:
369
423
            return
370
424
        logger.debug(u"Stopping checker for %(name)s", vars(self))
371
425
        try:
377
431
            if error.errno != errno.ESRCH: # No such process
378
432
                raise
379
433
        self.checker = None
 
434
    
380
435
    def still_valid(self):
381
436
        """Has the timeout not yet passed for this client?"""
382
 
        now = datetime.datetime.now()
 
437
        if not getattr(self, u"enabled", False):
 
438
            return False
 
439
        now = datetime.datetime.utcnow()
383
440
        if self.last_checked_ok is None:
384
441
            return now < (self.created + self.timeout)
385
442
        else:
386
443
            return now < (self.last_checked_ok + self.timeout)
387
444
 
388
445
 
389
 
def peer_certificate(session):
390
 
    "Return the peer's OpenPGP certificate as a bytestring"
391
 
    # If not an OpenPGP certificate...
392
 
    if gnutls.library.functions.gnutls_certificate_type_get\
393
 
            (session._c_object) \
394
 
           != gnutls.library.constants.GNUTLS_CRT_OPENPGP:
395
 
        # ...do the normal thing
396
 
        return session.peer_certificate
397
 
    list_size = ctypes.c_uint()
398
 
    cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
399
 
        (session._c_object, ctypes.byref(list_size))
400
 
    if list_size.value == 0:
401
 
        return None
402
 
    cert = cert_list[0]
403
 
    return ctypes.string_at(cert.data, cert.size)
404
 
 
405
 
 
406
 
def fingerprint(openpgp):
407
 
    "Convert an OpenPGP bytestring to a hexdigit fingerprint string"
408
 
    # New GnuTLS "datum" with the OpenPGP public key
409
 
    datum = gnutls.library.types.gnutls_datum_t\
410
 
        (ctypes.cast(ctypes.c_char_p(openpgp),
411
 
                     ctypes.POINTER(ctypes.c_ubyte)),
412
 
         ctypes.c_uint(len(openpgp)))
413
 
    # New empty GnuTLS certificate
414
 
    crt = gnutls.library.types.gnutls_openpgp_crt_t()
415
 
    gnutls.library.functions.gnutls_openpgp_crt_init\
416
 
        (ctypes.byref(crt))
417
 
    # Import the OpenPGP public key into the certificate
418
 
    gnutls.library.functions.gnutls_openpgp_crt_import\
419
 
                    (crt, ctypes.byref(datum),
420
 
                     gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
421
 
    # Verify the self signature in the key
422
 
    crtverify = ctypes.c_uint()
423
 
    gnutls.library.functions.gnutls_openpgp_crt_verify_self\
424
 
        (crt, 0, ctypes.byref(crtverify))
425
 
    if crtverify.value != 0:
426
 
        gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
427
 
        raise gnutls.errors.CertificateSecurityError("Verify failed")
428
 
    # New buffer for the fingerprint
429
 
    buf = ctypes.create_string_buffer(20)
430
 
    buf_len = ctypes.c_size_t()
431
 
    # Get the fingerprint from the certificate into the buffer
432
 
    gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
433
 
        (crt, ctypes.byref(buf), ctypes.byref(buf_len))
434
 
    # Deinit the certificate
435
 
    gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
436
 
    # Convert the buffer to a Python bytestring
437
 
    fpr = ctypes.string_at(buf, buf_len.value)
438
 
    # Convert the bytestring to hexadecimal notation
439
 
    hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
440
 
    return hex_fpr
441
 
 
442
 
 
443
 
class TCP_handler(SocketServer.BaseRequestHandler, object):
444
 
    """A TCP request handler class.
445
 
    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.
446
709
    Note: This will run in its own forked process."""
447
710
    
448
711
    def handle(self):
449
712
        logger.info(u"TCP connection from: %s",
450
 
                     unicode(self.client_address))
451
 
        session = gnutls.connection.ClientSession\
452
 
                  (self.request, gnutls.connection.X509Credentials())
453
 
        
454
 
        line = self.request.makefile().readline()
455
 
        logger.debug(u"Protocol version: %r", line)
456
 
        try:
457
 
            if int(line.strip().split()[0]) > 1:
458
 
                raise RuntimeError
459
 
        except (ValueError, IndexError, RuntimeError), error:
460
 
            logger.error(u"Unknown protocol version: %s", error)
461
 
            return
462
 
        
463
 
        # Note: gnutls.connection.X509Credentials is really a generic
464
 
        # GnuTLS certificate credentials object so long as no X.509
465
 
        # keys are added to it.  Therefore, we can use it here despite
466
 
        # using OpenPGP certificates.
467
 
        
468
 
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
469
 
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
470
 
        #                "+DHE-DSS"))
471
 
        priority = "NORMAL"             # Fallback default, since this
472
 
                                        # MUST be set.
473
 
        if self.server.settings["priority"]:
474
 
            priority = self.server.settings["priority"]
475
 
        gnutls.library.functions.gnutls_priority_set_direct\
476
 
            (session._c_object, priority, None)
477
 
        
478
 
        try:
479
 
            session.handshake()
480
 
        except gnutls.errors.GNUTLSError, error:
481
 
            logger.warning(u"Handshake failed: %s", error)
482
 
            # Do not run session.bye() here: the session is not
483
 
            # established.  Just abandon the request.
484
 
            return
485
 
        try:
486
 
            fpr = fingerprint(peer_certificate(session))
487
 
        except (TypeError, gnutls.errors.GNUTLSError), error:
488
 
            logger.warning(u"Bad certificate: %s", error)
489
 
            session.bye()
490
 
            return
491
 
        logger.debug(u"Fingerprint: %s", fpr)
492
 
        client = None
493
 
        for c in self.server.clients:
494
 
            if c.fingerprint == fpr:
495
 
                client = c
496
 
                break
497
 
        if not client:
498
 
            logger.warning(u"Client not found for fingerprint: %s",
499
 
                           fpr)
500
 
            session.bye()
501
 
            return
502
 
        # Have to check if client.still_valid(), since it is possible
503
 
        # that the client timed out while establishing the GnuTLS
504
 
        # session.
505
 
        if not client.still_valid():
506
 
            logger.warning(u"Client %(name)s is invalid",
507
 
                           vars(client))
508
 
            session.bye()
509
 
            return
510
 
        sent_size = 0
511
 
        while sent_size < len(client.secret):
512
 
            sent = session.send(client.secret[sent_size:])
513
 
            logger.debug(u"Sent: %d, remaining: %d",
514
 
                         sent, len(client.secret)
515
 
                         - (sent_size + sent))
516
 
            sent_size += sent
517
 
        session.bye()
518
 
 
519
 
 
520
 
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
521
 
    """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
    
522
881
    Attributes:
523
 
        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
        ----
524
886
        clients:        Set() of Client objects
525
 
        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
526
889
    """
527
 
    address_family = socket.AF_INET6
528
 
    def __init__(self, *args, **kwargs):
529
 
        if "settings" in kwargs:
530
 
            self.settings = kwargs["settings"]
531
 
            del kwargs["settings"]
532
 
        if "clients" in kwargs:
533
 
            self.clients = kwargs["clients"]
534
 
            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):
535
893
        self.enabled = False
536
 
        super(IPv6_TCPServer, 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)
537
902
    def server_bind(self):
538
903
        """This overrides the normal server_bind() function
539
904
        to bind to an interface if one was specified, and also NOT to
540
905
        bind to an address or port if they were not specified."""
541
 
        if self.settings["interface"]:
542
 
            # 25 is from /usr/include/asm-i486/socket.h
543
 
            SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
 
906
        if self.interface is not None:
544
907
            try:
545
908
                self.socket.setsockopt(socket.SOL_SOCKET,
546
909
                                       SO_BINDTODEVICE,
547
 
                                       self.settings["interface"])
 
910
                                       str(self.interface + u'\0'))
548
911
            except socket.error, error:
549
912
                if error[0] == errno.EPERM:
550
913
                    logger.error(u"No permission to"
551
914
                                 u" bind to interface %s",
552
 
                                 self.settings["interface"])
 
915
                                 self.interface)
553
916
                else:
554
 
                    raise error
 
917
                    raise
555
918
        # Only bind(2) the socket if we really need to.
556
919
        if self.server_address[0] or self.server_address[1]:
557
920
            if not self.server_address[0]:
558
 
                in6addr_any = "::"
559
 
                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,
560
926
                                       self.server_address[1])
561
927
            elif not self.server_address[1]:
562
928
                self.server_address = (self.server_address[0],
563
929
                                       0)
564
 
#                 if self.settings["interface"]:
 
930
#                 if self.interface:
565
931
#                     self.server_address = (self.server_address[0],
566
932
#                                            0, # port
567
933
#                                            0, # flowinfo
568
934
#                                            if_nametoindex
569
 
#                                            (self.settings
570
 
#                                             ["interface"]))
571
 
            return super(IPv6_TCPServer, self).server_bind()
 
935
#                                            (self.interface))
 
936
            return SocketServer.TCPServer.server_bind(self)
572
937
    def server_activate(self):
573
938
        if self.enabled:
574
 
            return super(IPv6_TCPServer, self).server_activate()
 
939
            return SocketServer.TCPServer.server_activate(self)
575
940
    def enable(self):
576
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
577
1012
 
578
1013
 
579
1014
def string_to_delta(interval):
580
1015
    """Parse a string and return a datetime.timedelta
581
 
 
582
 
    >>> string_to_delta('7d')
 
1016
    
 
1017
    >>> string_to_delta(u'7d')
583
1018
    datetime.timedelta(7)
584
 
    >>> string_to_delta('60s')
 
1019
    >>> string_to_delta(u'60s')
585
1020
    datetime.timedelta(0, 60)
586
 
    >>> string_to_delta('60m')
 
1021
    >>> string_to_delta(u'60m')
587
1022
    datetime.timedelta(0, 3600)
588
 
    >>> string_to_delta('24h')
 
1023
    >>> string_to_delta(u'24h')
589
1024
    datetime.timedelta(1)
590
1025
    >>> string_to_delta(u'1w')
591
1026
    datetime.timedelta(7)
592
 
    >>> string_to_delta('5m 30s')
 
1027
    >>> string_to_delta(u'5m 30s')
593
1028
    datetime.timedelta(0, 330)
594
1029
    """
595
1030
    timevalue = datetime.timedelta(0)
636
1071
    elif state == avahi.ENTRY_GROUP_FAILURE:
637
1072
        logger.critical(u"Avahi: Error in group state changed %s",
638
1073
                        unicode(error))
639
 
        raise AvahiGroupError("State changed: %s", str(error))
 
1074
        raise AvahiGroupError(u"State changed: %s" % unicode(error))
640
1075
 
641
1076
def if_nametoindex(interface):
642
 
    """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."""
643
1080
    global if_nametoindex
644
1081
    try:
645
 
        if_nametoindex = ctypes.cdll.LoadLibrary\
646
 
            (ctypes.util.find_library("c")).if_nametoindex
 
1082
        if_nametoindex = (ctypes.cdll.LoadLibrary
 
1083
                          (ctypes.util.find_library(u"c"))
 
1084
                          .if_nametoindex)
647
1085
    except (OSError, AttributeError):
648
 
        if "struct" not in sys.modules:
649
 
            import struct
650
 
        if "fcntl" not in sys.modules:
651
 
            import fcntl
 
1086
        logger.warning(u"Doing if_nametoindex the hard way")
652
1087
        def if_nametoindex(interface):
653
1088
            "Get an interface index the hard way, i.e. using fcntl()"
654
1089
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
655
 
            s = socket.socket()
656
 
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
657
 
                                struct.pack("16s16x", interface))
658
 
            s.close()
659
 
            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]
660
1096
            return interface_index
661
1097
    return if_nametoindex(interface)
662
1098
 
663
1099
 
664
1100
def daemon(nochdir = False, noclose = False):
665
1101
    """See daemon(3).  Standard BSD Unix function.
 
1102
    
666
1103
    This should really exist as os.daemon, but it doesn't (yet)."""
667
1104
    if os.fork():
668
1105
        sys.exit()
669
1106
    os.setsid()
670
1107
    if not nochdir:
671
 
        os.chdir("/")
 
1108
        os.chdir(u"/")
672
1109
    if os.fork():
673
1110
        sys.exit()
674
1111
    if not noclose:
676
1113
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
677
1114
        if not stat.S_ISCHR(os.fstat(null).st_mode):
678
1115
            raise OSError(errno.ENODEV,
679
 
                          "/dev/null not a character device")
 
1116
                          u"/dev/null not a character device")
680
1117
        os.dup2(null, sys.stdin.fileno())
681
1118
        os.dup2(null, sys.stdout.fileno())
682
1119
        os.dup2(null, sys.stderr.fileno())
685
1122
 
686
1123
 
687
1124
def main():
688
 
    parser = OptionParser(version = "%%prog %s" % version)
689
 
    parser.add_option("-i", "--interface", type="string",
690
 
                      metavar="IF", help="Bind to interface IF")
691
 
    parser.add_option("-a", "--address", type="string",
692
 
                      help="Address to listen for requests on")
693
 
    parser.add_option("-p", "--port", type="int",
694
 
                      help="Port number to receive requests on")
695
 
    parser.add_option("--check", action="store_true", default=False,
696
 
                      help="Run self-test")
697
 
    parser.add_option("--debug", action="store_true",
698
 
                      help="Debug mode; run in foreground and log to"
699
 
                      " terminal")
700
 
    parser.add_option("--priority", type="string", help="GnuTLS"
701
 
                      " priority string (see GnuTLS documentation)")
702
 
    parser.add_option("--servicename", type="string", metavar="NAME",
703
 
                      help="Zeroconf service name")
704
 
    parser.add_option("--configdir", type="string",
705
 
                      default="/etc/mandos", metavar="DIR",
706
 
                      help="Directory to search for configuration"
707
 
                      " files")
 
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")
708
1154
    options = parser.parse_args()[0]
709
1155
    
710
1156
    if options.check:
713
1159
        sys.exit()
714
1160
    
715
1161
    # Default values for config file for server-global settings
716
 
    server_defaults = { "interface": "",
717
 
                        "address": "",
718
 
                        "port": "",
719
 
                        "debug": "False",
720
 
                        "priority":
721
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
722
 
                        "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",
723
1171
                        }
724
1172
    
725
1173
    # Parse config file for server-global settings
726
1174
    server_config = ConfigParser.SafeConfigParser(server_defaults)
727
1175
    del server_defaults
728
 
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
 
1176
    server_config.read(os.path.join(options.configdir,
 
1177
                                    u"mandos.conf"))
729
1178
    # Convert the SafeConfigParser object to a dict
730
1179
    server_settings = server_config.defaults()
731
 
    # Use getboolean on the boolean config option
732
 
    server_settings["debug"] = server_config.getboolean\
733
 
                               ("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")
734
1187
    del server_config
735
1188
    
736
1189
    # Override the settings from the config file with command line
737
1190
    # options, if set.
738
 
    for option in ("interface", "address", "port", "debug",
739
 
                   "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"):
740
1194
        value = getattr(options, option)
741
1195
        if value is not None:
742
1196
            server_settings[option] = value
743
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])
744
1202
    # Now we have our good server settings in "server_settings"
745
1203
    
746
 
    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"]
747
1210
    
748
1211
    if not debug:
749
1212
        syslogger.setLevel(logging.WARNING)
750
1213
        console.setLevel(logging.WARNING)
751
1214
    
752
 
    if server_settings["servicename"] != "Mandos":
753
 
        syslogger.setFormatter(logging.Formatter\
754
 
                               ('Mandos (%s): %%(levelname)s:'
755
 
                                ' %%(message)s'
756
 
                                % 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"]))
757
1220
    
758
1221
    # Parse config file with clients
759
 
    client_defaults = { "timeout": "1h",
760
 
                        "interval": "5m",
761
 
                        "checker": "fping -q -- %(host)s",
762
 
                        "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"",
763
1226
                        }
764
1227
    client_config = ConfigParser.SafeConfigParser(client_defaults)
765
 
    client_config.read(os.path.join(server_settings["configdir"],
766
 
                                    "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
767
1233
    
768
1234
    clients = Set()
769
 
    tcp_server = IPv6_TCPServer((server_settings["address"],
770
 
                                 server_settings["port"]),
771
 
                                TCP_handler,
772
 
                                settings=server_settings,
773
 
                                clients=clients)
774
 
    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"
775
1246
    try:
776
 
        pidfile = open(pidfilename, "w")
777
 
    except IOError, error:
778
 
        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)
779
1250
    
780
 
    uid = 65534
781
 
    gid = 65534
782
 
    try:
783
 
        uid = pwd.getpwnam("mandos").pw_uid
784
 
    except KeyError:
785
 
        try:
786
 
            uid = pwd.getpwnam("nobody").pw_uid
787
 
        except KeyError:
788
 
            pass
789
 
    try:
790
 
        gid = pwd.getpwnam("mandos").pw_gid
791
 
    except KeyError:
792
 
        try:
793
 
            gid = pwd.getpwnam("nogroup").pw_gid
794
 
        except KeyError:
795
 
            pass
796
 
    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)
797
1267
        os.setuid(uid)
798
 
        os.setgid(gid)
799
1268
    except OSError, error:
800
1269
        if error[0] != errno.EPERM:
801
1270
            raise error
802
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
    
803
1285
    global service
804
 
    service = AvahiService(name = server_settings["servicename"],
805
 
                           servicetype = "_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)
806
1290
    if server_settings["interface"]:
807
 
        service.interface = if_nametoindex\
808
 
                            (server_settings["interface"])
 
1291
        service.interface = (if_nametoindex
 
1292
                             (str(server_settings[u"interface"])))
809
1293
    
810
1294
    global main_loop
811
1295
    global bus
818
1302
                                           avahi.DBUS_PATH_SERVER),
819
1303
                            avahi.DBUS_INTERFACE_SERVER)
820
1304
    # End of Avahi example code
821
 
    
822
 
    def remove_from_clients(client):
823
 
        clients.remove(client)
824
 
        if not clients:
825
 
            logger.critical(u"No clients left, exiting")
826
 
            sys.exit()
827
 
    
828
 
    clients.update(Set(Client(name = section,
829
 
                              stop_hook = remove_from_clients,
830
 
                              config
831
 
                              = dict(client_config.items(section)))
832
 
                       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()))
833
1315
    if not clients:
834
 
        logger.critical(u"No clients defined")
835
 
        sys.exit(1)
 
1316
        logger.warning(u"No clients defined")
836
1317
    
837
1318
    if debug:
838
1319
        # Redirect stdin so all checkers get /dev/null
847
1328
        daemon()
848
1329
    
849
1330
    try:
850
 
        pid = os.getpid()
851
 
        pidfile.write(str(pid) + "\n")
852
 
        pidfile.close()
 
1331
        with closing(pidfile):
 
1332
            pid = os.getpid()
 
1333
            pidfile.write(str(pid) + "\n")
853
1334
        del pidfile
854
1335
    except IOError:
855
1336
        logger.error(u"Could not write to file %r with PID %d",
870
1351
        
871
1352
        while clients:
872
1353
            client = clients.pop()
873
 
            client.stop_hook = None
874
 
            client.stop()
 
1354
            client.disable_hook = None
 
1355
            client.disable()
875
1356
    
876
1357
    atexit.register(cleanup)
877
1358
    
880
1361
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
881
1362
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
882
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
    
883
1418
    for client in clients:
884
 
        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()
885
1424
    
886
1425
    tcp_server.enable()
887
1426
    tcp_server.server_activate()
888
1427
    
889
1428
    # Find out what port we got
890
1429
    service.port = tcp_server.socket.getsockname()[1]
891
 
    logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
892
 
                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())
893
1437
    
894
1438
    #service.interface = tcp_server.socket.getsockname()[3]
895
1439
    
896
1440
    try:
897
1441
        # From the Avahi example code
898
 
        server.connect_to_signal("StateChanged", server_state_changed)
 
1442
        server.connect_to_signal(u"StateChanged", server_state_changed)
899
1443
        try:
900
1444
            server_state_changed(server.GetState())
901
1445
        except dbus.exceptions.DBusException, error:
905
1449
        
906
1450
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
907
1451
                             lambda *args, **kwargs:
908
 
                             tcp_server.handle_request\
909
 
                             (*args[2:], **kwargs) or True)
 
1452
                             (tcp_server.handle_request
 
1453
                              (*args[2:], **kwargs) or True))
910
1454
        
911
1455
        logger.debug(u"Starting main loop")
912
1456
        main_loop.run()
913
1457
    except AvahiError, error:
914
 
        logger.critical(u"AvahiError: %s" + unicode(error))
 
1458
        logger.critical(u"AvahiError: %s", error)
915
1459
        sys.exit(1)
916
1460
    except KeyboardInterrupt:
917
1461
        if debug:
918
 
            print
 
1462
            print >> sys.stderr
 
1463
        logger.debug(u"Server received KeyboardInterrupt")
 
1464
    logger.debug(u"Server exiting")
919
1465
 
920
1466
if __name__ == '__main__':
921
1467
    main()