/mandos/release

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Björn Påhlsson
  • Date: 2011-10-15 16:48:03 UTC
  • Revision ID: belorn@fukt.bsnet.se-20111015164803-61q3hzrv91d042mb
Tags: version-1.4.1-1
* Makefile (version): Changed to "1.4.1".
* NEWS (Version 1.4.1): New entry.
* debian/changelog (1.4.1-1): - '' -

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