/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-11-19 18:31:28 UTC
  • Revision ID: teddy@fukt.bsnet.se-20091119183128-ttstewh61xmtnil1
* Makefile (LINK_FORTIFY_LD): Bug fix: removed "-fPIE".
* mandos-keygen: Bug fix: Fix quoting for the "--password" option.

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