/mandos/release

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

« back to all changes in this revision

Viewing changes to mandos

* mandos (peer_certificate): Handle NULL pointer from
                             "gnutls_certificate_get_peers" slightly
                             better.
  (TCP_handler.handle): Added some extra debug output.

  (MandosServer.GetAllClients,
  MandosServer.GetAllClientsWithProperties,
  MandosServer.RemoveClient): Added doc string.

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