/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-01 03:37:45 UTC
  • Revision ID: teddy@fukt.bsnet.se-20090401033745-c89k6bij5opdm1rk
* mandos (ClientDBus.__del__): Bug fix: Correct mispasted code, and do
                               not try to call
                               dbus.service.Object.__del__() if it
                               does not exist.
  (ClientDBus.start_checker): Simplify logic.

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