/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

Changed ForkingMixIn in favor of multiprocessing
Added approval functionallity

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