/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2011-09-26 21:06:14 UTC
  • mto: This revision was merged to the branch mainline in revision 503.
  • Revision ID: teddy@fukt.bsnet.se-20110926210614-tvxkpb6xfhkq3gc3
* mandos: White space fixes only.

Show diffs side-by-side

added added

removed removed

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