/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-14 14:56:46 UTC
  • Revision ID: teddy@fukt.bsnet.se-20090414145646-i6x4knw2f6cde3td
* debian/mandos-client.README.Debian: Added text about non-usability
                                      of pseudo-network interfaces.

Show diffs side-by-side

added added

removed removed

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