/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Björn Påhlsson
  • Date: 2011-10-02 13:45:45 UTC
  • mto: This revision was merged to the branch mainline in revision 505.
  • Revision ID: belorn@fukt.bsnet.se-20111002134545-oytmfbl15r8lsm6p
working transition code for going between se.bsnet.fukt to se.recompile

Show diffs side-by-side

added added

removed removed

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