/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

Tags: version-1.0.13-1
* Makefile (version): Changed to "1.0.13".
* NEWS (Version 1.0.13): New entry.
* debian/changelog (1.0.13-1): - '' -

Show diffs side-by-side

added added

removed removed

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