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