/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 06:47:28 UTC
  • Revision ID: teddy@fukt.bsnet.se-20090416064728-c3d36mvgxo5q9aoh
* mandos: Import "SocketServer" as "socketserver" and "ConfigParser"
          as "configparser".  All users changed.

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