/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos

Change "fukt.bsnet.se" to "recompile.se" throughout.

* README: - '' -
* debian/control: - '' -
* debian/copyright: - '' -
* debian/mandos-client.README.Debian: - '' - and some rewriting.
* debian/mandos.README.Debian: - '' -
* debian/watch: Change "fukt.bsnet.se" to "recompile.se".
* init.d-mandos: - '' -
* intro.xml: - '' -
* mandos: - '' -
* mandos-clients.conf.xml: - '' -
* mandos-ctl: - '' -
* mandos-ctl.xml: - '' -
* mandos-keygen: - '' -
* mandos-keygen.xml: - '' -
* mandos-monitor: - '' -
* mandos-monitor.xml: - '' -
* mandos.conf.xml: - '' -
* mandos.lsm: - '' -
* mandos.xml: - '' -
* plugin-runner.c: - '' -
* plugin-runner.xml: - '' -
* plugins.d/askpass-fifo.c: - '' -
* plugins.d/askpass-fifo.xml: - '' -
* plugins.d/mandos-client.c: - '' -
* plugins.d/mandos-client.xml: - '' -
* plugins.d/password-prompt.c: - '' -
* plugins.d/password-prompt.xml: - '' -
* plugins.d/plymouth.c: - '' -
* plugins.d/plymouth.xml: - '' -
* plugins.d/splashy.c: - '' -
* plugins.d/splashy.xml: - '' -
* plugins.d/usplash.c: - '' -
* plugins.d/usplash.xml: - '' -

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