/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2009-04-16 06:47:28 UTC
  • Revision ID: teddy@fukt.bsnet.se-20090416064728-c3d36mvgxo5q9aoh
* mandos: Import "SocketServer" as "socketserver" and "ConfigParser"
          as "configparser".  All users changed.

Show diffs side-by-side

added added

removed removed

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