/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 19:36:18 UTC
  • mfrom: (24.1.184 mandos)
  • Revision ID: teddy@fukt.bsnet.se-20110926193618-vtj5c9hena1maixx
Merge from Björn

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))
279
 
    
 
312
    runtime_expansions = ("approval_delay", "approval_duration",
 
313
                          "created", "enabled", "fingerprint",
 
314
                          "host", "interval", "last_checked_ok",
 
315
                          "last_enabled", "name", "timeout")
 
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)
 
327
        return _timedelta_to_milliseconds(self.interval)
287
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
def dbus_service_property(dbus_interface, signature="v",
 
565
                          access="readwrite", byte_arrays=False):
512
566
    """Decorators for marking methods of a DBusObjectWithProperties to
513
567
    become properties on the D-Bus.
514
568
    
521
575
    """
522
576
    # Encoding deeply encoded byte arrays is not supported yet by the
523
577
    # "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)
 
578
    if byte_arrays and signature != "ay":
 
579
        raise ValueError("Byte arrays not supported for non-'ay'"
 
580
                         " signature %r" % signature)
527
581
    def decorator(func):
528
582
        func._dbus_is_property = True
529
583
        func._dbus_interface = dbus_interface
530
584
        func._dbus_signature = signature
531
585
        func._dbus_access = access
532
586
        func._dbus_name = func.__name__
533
 
        if func._dbus_name.endswith(u"_dbus_property"):
 
587
        if func._dbus_name.endswith("_dbus_property"):
534
588
            func._dbus_name = func._dbus_name[:-14]
535
 
        func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
 
589
        func._dbus_get_args_options = {'byte_arrays': byte_arrays }
536
590
        return func
537
591
    return decorator
538
592
 
566
620
    
567
621
    @staticmethod
568
622
    def _is_dbus_property(obj):
569
 
        return getattr(obj, u"_dbus_is_property", False)
 
623
        return getattr(obj, "_dbus_is_property", False)
570
624
    
571
625
    def _get_all_dbus_properties(self):
572
626
        """Returns a generator of (name, attribute) pairs
580
634
        property with the specified name and interface.
581
635
        """
582
636
        for name in (property_name,
583
 
                     property_name + u"_dbus_property"):
 
637
                     property_name + "_dbus_property"):
584
638
            prop = getattr(self, name, None)
585
639
            if (prop is None
586
640
                or not self._is_dbus_property(prop)
590
644
                continue
591
645
            return prop
592
646
        # No such property
593
 
        raise DBusPropertyNotFound(self.dbus_object_path + u":"
594
 
                                   + interface_name + u"."
 
647
        raise DBusPropertyNotFound(self.dbus_object_path + ":"
 
648
                                   + interface_name + "."
595
649
                                   + property_name)
596
650
    
597
 
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
598
 
                         out_signature=u"v")
 
651
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
 
652
                         out_signature="v")
599
653
    def Get(self, interface_name, property_name):
600
654
        """Standard D-Bus property Get() method, see D-Bus standard.
601
655
        """
602
656
        prop = self._get_dbus_property(interface_name, property_name)
603
 
        if prop._dbus_access == u"write":
 
657
        if prop._dbus_access == "write":
604
658
            raise DBusPropertyAccessException(property_name)
605
659
        value = prop()
606
 
        if not hasattr(value, u"variant_level"):
 
660
        if not hasattr(value, "variant_level"):
607
661
            return value
608
662
        return type(value)(value, variant_level=value.variant_level+1)
609
663
    
610
 
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
 
664
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
611
665
    def Set(self, interface_name, property_name, value):
612
666
        """Standard D-Bus property Set() method, see D-Bus standard.
613
667
        """
614
668
        prop = self._get_dbus_property(interface_name, property_name)
615
 
        if prop._dbus_access == u"read":
 
669
        if prop._dbus_access == "read":
616
670
            raise DBusPropertyAccessException(property_name)
617
 
        if prop._dbus_get_args_options[u"byte_arrays"]:
 
671
        if prop._dbus_get_args_options["byte_arrays"]:
618
672
            # The byte_arrays option is not supported yet on
619
673
            # signatures other than "ay".
620
 
            if prop._dbus_signature != u"ay":
 
674
            if prop._dbus_signature != "ay":
621
675
                raise ValueError
622
676
            value = dbus.ByteArray(''.join(unichr(byte)
623
677
                                           for byte in value))
624
678
        prop(value)
625
679
    
626
 
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
627
 
                         out_signature=u"a{sv}")
 
680
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
 
681
                         out_signature="a{sv}")
628
682
    def GetAll(self, interface_name):
629
683
        """Standard D-Bus property GetAll() method, see D-Bus
630
684
        standard.
638
692
                # Interface non-empty but did not match
639
693
                continue
640
694
            # Ignore write-only properties
641
 
            if prop._dbus_access == u"write":
 
695
            if prop._dbus_access == "write":
642
696
                continue
643
697
            value = prop()
644
 
            if not hasattr(value, u"variant_level"):
 
698
            if not hasattr(value, "variant_level"):
645
699
                all[name] = value
646
700
                continue
647
701
            all[name] = type(value)(value, variant_level=
648
702
                                    value.variant_level+1)
649
 
        return dbus.Dictionary(all, signature=u"sv")
 
703
        return dbus.Dictionary(all, signature="sv")
650
704
    
651
705
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
652
 
                         out_signature=u"s",
 
706
                         out_signature="s",
653
707
                         path_keyword='object_path',
654
708
                         connection_keyword='connection')
655
709
    def Introspect(self, object_path, connection):
660
714
        try:
661
715
            document = xml.dom.minidom.parseString(xmlstring)
662
716
            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)
 
717
                e = document.createElement("property")
 
718
                e.setAttribute("name", name)
 
719
                e.setAttribute("type", prop._dbus_signature)
 
720
                e.setAttribute("access", prop._dbus_access)
667
721
                return e
668
 
            for if_tag in document.getElementsByTagName(u"interface"):
 
722
            for if_tag in document.getElementsByTagName("interface"):
669
723
                for tag in (make_tag(document, name, prop)
670
724
                            for name, prop
671
725
                            in self._get_all_dbus_properties()
672
726
                            if prop._dbus_interface
673
 
                            == if_tag.getAttribute(u"name")):
 
727
                            == if_tag.getAttribute("name")):
674
728
                    if_tag.appendChild(tag)
675
729
                # Add the names to the return values for the
676
730
                # "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")
 
731
                if (if_tag.getAttribute("name")
 
732
                    == "org.freedesktop.DBus.Properties"):
 
733
                    for cn in if_tag.getElementsByTagName("method"):
 
734
                        if cn.getAttribute("name") == "Get":
 
735
                            for arg in cn.getElementsByTagName("arg"):
 
736
                                if (arg.getAttribute("direction")
 
737
                                    == "out"):
 
738
                                    arg.setAttribute("name", "value")
 
739
                        elif cn.getAttribute("name") == "GetAll":
 
740
                            for arg in cn.getElementsByTagName("arg"):
 
741
                                if (arg.getAttribute("direction")
 
742
                                    == "out"):
 
743
                                    arg.setAttribute("name", "props")
 
744
            xmlstring = document.toxml("utf-8")
691
745
            document.unlink()
692
746
        except (AttributeError, xml.dom.DOMException,
693
 
                xml.parsers.expat.ExpatError), error:
694
 
            logger.error(u"Failed to override Introspection method",
 
747
                xml.parsers.expat.ExpatError) as error:
 
748
            logger.error("Failed to override Introspection method",
695
749
                         error)
696
750
        return xmlstring
697
751
 
698
752
 
 
753
def datetime_to_dbus (dt, variant_level=0):
 
754
    """Convert a UTC datetime.datetime() to a D-Bus type."""
 
755
    if dt is None:
 
756
        return dbus.String("", variant_level = variant_level)
 
757
    return dbus.String(dt.isoformat(),
 
758
                       variant_level=variant_level)
 
759
 
699
760
class ClientDBus(Client, DBusObjectWithProperties):
700
761
    """A Client class using D-Bus
701
762
    
703
764
    dbus_object_path: dbus.ObjectPath
704
765
    bus: dbus.SystemBus()
705
766
    """
 
767
    
 
768
    runtime_expansions = (Client.runtime_expansions
 
769
                          + ("dbus_object_path",))
 
770
    
706
771
    # dbus.service.Object doesn't use super(), so we can't either.
707
772
    
708
773
    def __init__(self, bus = None, *args, **kwargs):
711
776
        Client.__init__(self, *args, **kwargs)
712
777
        # Only now, when this client is initialized, can it show up on
713
778
        # the D-Bus
 
779
        client_object_name = unicode(self.name).translate(
 
780
            {ord("."): ord("_"),
 
781
             ord("-"): ord("_")})
714
782
        self.dbus_object_path = (dbus.ObjectPath
715
 
                                 (u"/clients/"
716
 
                                  + self.name.replace(u".", u"_")))
 
783
                                 ("/clients/" + client_object_name))
717
784
        DBusObjectWithProperties.__init__(self, self.bus,
718
785
                                          self.dbus_object_path)
719
786
        
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
 
787
    def notifychangeproperty(transform_func,
 
788
                             dbus_name, type_func=lambda x: x,
 
789
                             variant_level=1):
 
790
        """ Modify a variable so that its a property that announce its
 
791
        changes to DBus.
 
792
        transform_fun: Function that takes a value and transform it to
 
793
                       DBus type.
 
794
        dbus_name: DBus name of the variable
 
795
        type_func: Function that transform the value before sending it
 
796
                   to DBus
 
797
        variant_level: DBus variant level. default: 1
 
798
        """
 
799
        real_value = [None,]
 
800
        def setter(self, value):
 
801
            old_value = real_value[0]
 
802
            real_value[0] = value
 
803
            if hasattr(self, "dbus_object_path"):
 
804
                if type_func(old_value) != type_func(real_value[0]):
 
805
                    dbus_value = transform_func(type_func(real_value[0]),
 
806
                                                variant_level)
 
807
                    self.PropertyChanged(dbus.String(dbus_name),
 
808
                                         dbus_value)
 
809
 
 
810
        return property(lambda self: real_value[0], setter)
 
811
 
 
812
 
 
813
    expires = notifychangeproperty(datetime_to_dbus, "Expires")
 
814
    approvals_pending = notifychangeproperty(dbus.Boolean,
 
815
                                             "ApprovalPending",
 
816
                                             type_func = bool)
 
817
    enabled = notifychangeproperty(dbus.Boolean, "Enabled")
 
818
    last_enabled = notifychangeproperty(datetime_to_dbus,
 
819
                                        "LastEnabled")
 
820
    checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
 
821
                                   type_func = lambda checker: checker is not None)
 
822
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
 
823
                                           "LastCheckedOK")
 
824
    last_approval_request = notifychangeproperty(datetime_to_dbus,
 
825
                                                 "LastApprovalRequest")
 
826
    approved_by_default = notifychangeproperty(dbus.Boolean,
 
827
                                               "ApprovedByDefault")
 
828
    approval_delay = notifychangeproperty(dbus.UInt16, "ApprovalDelay",
 
829
                                          type_func = _timedelta_to_milliseconds)
 
830
    approval_duration = notifychangeproperty(dbus.UInt16, "ApprovalDuration",
 
831
                                             type_func = _timedelta_to_milliseconds)
 
832
    host = notifychangeproperty(dbus.String, "Host")
 
833
    timeout = notifychangeproperty(dbus.UInt16, "Timeout",
 
834
                                   type_func = _timedelta_to_milliseconds)
 
835
    extended_timeout = notifychangeproperty(dbus.UInt16, "ExtendedTimeout",
 
836
                                            type_func = _timedelta_to_milliseconds)
 
837
    interval = notifychangeproperty(dbus.UInt16, "Interval",
 
838
                                    type_func = _timedelta_to_milliseconds)
 
839
    checker_command = notifychangeproperty(dbus.String, "Checker")
 
840
    
 
841
    del notifychangeproperty
763
842
    
764
843
    def __del__(self, *args, **kwargs):
765
844
        try:
766
845
            self.remove_from_connection()
767
846
        except LookupError:
768
847
            pass
769
 
        if hasattr(DBusObjectWithProperties, u"__del__"):
 
848
        if hasattr(DBusObjectWithProperties, "__del__"):
770
849
            DBusObjectWithProperties.__del__(self, *args, **kwargs)
771
850
        Client.__del__(self, *args, **kwargs)
772
851
    
774
853
                         *args, **kwargs):
775
854
        self.checker_callback_tag = None
776
855
        self.checker = None
777
 
        # Emit D-Bus signal
778
 
        self.PropertyChanged(dbus.String(u"CheckerRunning"),
779
 
                             dbus.Boolean(False, variant_level=1))
780
856
        if os.WIFEXITED(condition):
781
857
            exitstatus = os.WEXITSTATUS(condition)
782
858
            # Emit D-Bus signal
791
867
        
792
868
        return Client.checker_callback(self, pid, condition, command,
793
869
                                       *args, **kwargs)
794
 
    
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
 
    
 
870
 
804
871
    def start_checker(self, *args, **kwargs):
805
872
        old_checker = self.checker
806
873
        if self.checker is not None:
813
880
            and old_checker_pid != self.checker.pid):
814
881
            # Emit D-Bus signal
815
882
            self.CheckerStarted(self.current_checker_command)
816
 
            self.PropertyChanged(
817
 
                dbus.String(u"CheckerRunning"),
818
 
                dbus.Boolean(True, variant_level=1))
819
883
        return r
820
884
    
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
885
    def _reset_approved(self):
831
886
        self._approved = None
832
887
        return False
834
889
    def approve(self, value=True):
835
890
        self.send_changedstate()
836
891
        self._approved = value
837
 
        gobject.timeout_add(self._timedelta_to_milliseconds
 
892
        gobject.timeout_add(_timedelta_to_milliseconds
838
893
                            (self.approval_duration),
839
894
                            self._reset_approved)
840
895
    
841
896
    
842
897
    ## D-Bus methods, signals & properties
843
 
    _interface = u"se.bsnet.fukt.Mandos.Client"
 
898
    _interface = "se.bsnet.fukt.Mandos.Client"
844
899
    
845
900
    ## Signals
846
901
    
847
902
    # CheckerCompleted - signal
848
 
    @dbus.service.signal(_interface, signature=u"nxs")
 
903
    @dbus.service.signal(_interface, signature="nxs")
849
904
    def CheckerCompleted(self, exitcode, waitstatus, command):
850
905
        "D-Bus signal"
851
906
        pass
852
907
    
853
908
    # CheckerStarted - signal
854
 
    @dbus.service.signal(_interface, signature=u"s")
 
909
    @dbus.service.signal(_interface, signature="s")
855
910
    def CheckerStarted(self, command):
856
911
        "D-Bus signal"
857
912
        pass
858
913
    
859
914
    # PropertyChanged - signal
860
 
    @dbus.service.signal(_interface, signature=u"sv")
 
915
    @dbus.service.signal(_interface, signature="sv")
861
916
    def PropertyChanged(self, property, value):
862
917
        "D-Bus signal"
863
918
        pass
872
927
        pass
873
928
    
874
929
    # Rejected - signal
875
 
    @dbus.service.signal(_interface, signature=u"s")
 
930
    @dbus.service.signal(_interface, signature="s")
876
931
    def Rejected(self, reason):
877
932
        "D-Bus signal"
878
933
        pass
879
934
    
880
935
    # NeedApproval - signal
881
 
    @dbus.service.signal(_interface, signature=u"tb")
 
936
    @dbus.service.signal(_interface, signature="tb")
882
937
    def NeedApproval(self, timeout, default):
883
938
        "D-Bus signal"
884
 
        pass
 
939
        return self.need_approval()
885
940
    
886
941
    ## Methods
887
 
 
 
942
    
888
943
    # Approve - method
889
 
    @dbus.service.method(_interface, in_signature=u"b")
 
944
    @dbus.service.method(_interface, in_signature="b")
890
945
    def Approve(self, value):
891
946
        self.approve(value)
892
 
 
 
947
    
893
948
    # CheckedOK - method
894
949
    @dbus.service.method(_interface)
895
950
    def CheckedOK(self):
896
 
        return self.checked_ok()
 
951
        self.checked_ok()
897
952
    
898
953
    # Enable - method
899
954
    @dbus.service.method(_interface)
921
976
    ## Properties
922
977
    
923
978
    # ApprovalPending - property
924
 
    @dbus_service_property(_interface, signature=u"b", access=u"read")
 
979
    @dbus_service_property(_interface, signature="b", access="read")
925
980
    def ApprovalPending_dbus_property(self):
926
981
        return dbus.Boolean(bool(self.approvals_pending))
927
982
    
928
983
    # ApprovedByDefault - property
929
 
    @dbus_service_property(_interface, signature=u"b",
930
 
                           access=u"readwrite")
 
984
    @dbus_service_property(_interface, signature="b",
 
985
                           access="readwrite")
931
986
    def ApprovedByDefault_dbus_property(self, value=None):
932
987
        if value is None:       # get
933
988
            return dbus.Boolean(self.approved_by_default)
934
989
        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
990
    
939
991
    # ApprovalDelay - property
940
 
    @dbus_service_property(_interface, signature=u"t",
941
 
                           access=u"readwrite")
 
992
    @dbus_service_property(_interface, signature="t",
 
993
                           access="readwrite")
942
994
    def ApprovalDelay_dbus_property(self, value=None):
943
995
        if value is None:       # get
944
996
            return dbus.UInt64(self.approval_delay_milliseconds())
945
997
        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
998
    
950
999
    # ApprovalDuration - property
951
 
    @dbus_service_property(_interface, signature=u"t",
952
 
                           access=u"readwrite")
 
1000
    @dbus_service_property(_interface, signature="t",
 
1001
                           access="readwrite")
953
1002
    def ApprovalDuration_dbus_property(self, value=None):
954
1003
        if value is None:       # get
955
 
            return dbus.UInt64(self._timedelta_to_milliseconds(
 
1004
            return dbus.UInt64(_timedelta_to_milliseconds(
956
1005
                    self.approval_duration))
957
1006
        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
1007
    
962
1008
    # Name - property
963
 
    @dbus_service_property(_interface, signature=u"s", access=u"read")
 
1009
    @dbus_service_property(_interface, signature="s", access="read")
964
1010
    def Name_dbus_property(self):
965
1011
        return dbus.String(self.name)
966
1012
    
967
1013
    # Fingerprint - property
968
 
    @dbus_service_property(_interface, signature=u"s", access=u"read")
 
1014
    @dbus_service_property(_interface, signature="s", access="read")
969
1015
    def Fingerprint_dbus_property(self):
970
1016
        return dbus.String(self.fingerprint)
971
1017
    
972
1018
    # Host - property
973
 
    @dbus_service_property(_interface, signature=u"s",
974
 
                           access=u"readwrite")
 
1019
    @dbus_service_property(_interface, signature="s",
 
1020
                           access="readwrite")
975
1021
    def Host_dbus_property(self, value=None):
976
1022
        if value is None:       # get
977
1023
            return dbus.String(self.host)
978
1024
        self.host = value
979
 
        # Emit D-Bus signal
980
 
        self.PropertyChanged(dbus.String(u"Host"),
981
 
                             dbus.String(value, variant_level=1))
982
1025
    
983
1026
    # Created - property
984
 
    @dbus_service_property(_interface, signature=u"s", access=u"read")
 
1027
    @dbus_service_property(_interface, signature="s", access="read")
985
1028
    def Created_dbus_property(self):
986
 
        return dbus.String(self._datetime_to_dbus(self.created))
 
1029
        return dbus.String(datetime_to_dbus(self.created))
987
1030
    
988
1031
    # LastEnabled - property
989
 
    @dbus_service_property(_interface, signature=u"s", access=u"read")
 
1032
    @dbus_service_property(_interface, signature="s", access="read")
990
1033
    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))
 
1034
        return datetime_to_dbus(self.last_enabled)
994
1035
    
995
1036
    # Enabled - property
996
 
    @dbus_service_property(_interface, signature=u"b",
997
 
                           access=u"readwrite")
 
1037
    @dbus_service_property(_interface, signature="b",
 
1038
                           access="readwrite")
998
1039
    def Enabled_dbus_property(self, value=None):
999
1040
        if value is None:       # get
1000
1041
            return dbus.Boolean(self.enabled)
1004
1045
            self.disable()
1005
1046
    
1006
1047
    # LastCheckedOK - property
1007
 
    @dbus_service_property(_interface, signature=u"s",
1008
 
                           access=u"readwrite")
 
1048
    @dbus_service_property(_interface, signature="s",
 
1049
                           access="readwrite")
1009
1050
    def LastCheckedOK_dbus_property(self, value=None):
1010
1051
        if value is not None:
1011
1052
            self.checked_ok()
1012
1053
            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))
 
1054
        return datetime_to_dbus(self.last_checked_ok)
 
1055
    
 
1056
    # Expires - property
 
1057
    @dbus_service_property(_interface, signature="s", access="read")
 
1058
    def Expires_dbus_property(self):
 
1059
        return datetime_to_dbus(self.expires)
 
1060
    
 
1061
    # LastApprovalRequest - property
 
1062
    @dbus_service_property(_interface, signature="s", access="read")
 
1063
    def LastApprovalRequest_dbus_property(self):
 
1064
        return datetime_to_dbus(self.last_approval_request)
1017
1065
    
1018
1066
    # Timeout - property
1019
 
    @dbus_service_property(_interface, signature=u"t",
1020
 
                           access=u"readwrite")
 
1067
    @dbus_service_property(_interface, signature="t",
 
1068
                           access="readwrite")
1021
1069
    def Timeout_dbus_property(self, value=None):
1022
1070
        if value is None:       # get
1023
1071
            return dbus.UInt64(self.timeout_milliseconds())
1024
1072
        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:
 
1073
        if getattr(self, "disable_initiator_tag", None) is None:
1029
1074
            return
1030
1075
        # Reschedule timeout
1031
1076
        gobject.source_remove(self.disable_initiator_tag)
1032
1077
        self.disable_initiator_tag = None
 
1078
        self.expires = None
1033
1079
        time_to_die = (self.
1034
1080
                       _timedelta_to_milliseconds((self
1035
1081
                                                   .last_checked_ok
1040
1086
            # The timeout has passed
1041
1087
            self.disable()
1042
1088
        else:
 
1089
            self.expires = (datetime.datetime.utcnow()
 
1090
                            + datetime.timedelta(milliseconds = time_to_die))
1043
1091
            self.disable_initiator_tag = (gobject.timeout_add
1044
1092
                                          (time_to_die, self.disable))
1045
 
    
 
1093
 
 
1094
    # ExtendedTimeout - property
 
1095
    @dbus_service_property(_interface, signature="t",
 
1096
                           access="readwrite")
 
1097
    def ExtendedTimeout_dbus_property(self, value=None):
 
1098
        if value is None:       # get
 
1099
            return dbus.UInt64(self.extended_timeout_milliseconds())
 
1100
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
 
1101
 
1046
1102
    # Interval - property
1047
 
    @dbus_service_property(_interface, signature=u"t",
1048
 
                           access=u"readwrite")
 
1103
    @dbus_service_property(_interface, signature="t",
 
1104
                           access="readwrite")
1049
1105
    def Interval_dbus_property(self, value=None):
1050
1106
        if value is None:       # get
1051
1107
            return dbus.UInt64(self.interval_milliseconds())
1052
1108
        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:
 
1109
        if getattr(self, "checker_initiator_tag", None) is None:
1057
1110
            return
1058
1111
        # Reschedule checker run
1059
1112
        gobject.source_remove(self.checker_initiator_tag)
1062
1115
        self.start_checker()    # Start one now, too
1063
1116
 
1064
1117
    # Checker - property
1065
 
    @dbus_service_property(_interface, signature=u"s",
1066
 
                           access=u"readwrite")
 
1118
    @dbus_service_property(_interface, signature="s",
 
1119
                           access="readwrite")
1067
1120
    def Checker_dbus_property(self, value=None):
1068
1121
        if value is None:       # get
1069
1122
            return dbus.String(self.checker_command)
1070
1123
        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
1124
    
1076
1125
    # CheckerRunning - property
1077
 
    @dbus_service_property(_interface, signature=u"b",
1078
 
                           access=u"readwrite")
 
1126
    @dbus_service_property(_interface, signature="b",
 
1127
                           access="readwrite")
1079
1128
    def CheckerRunning_dbus_property(self, value=None):
1080
1129
        if value is None:       # get
1081
1130
            return dbus.Boolean(self.checker is not None)
1085
1134
            self.stop_checker()
1086
1135
    
1087
1136
    # ObjectPath - property
1088
 
    @dbus_service_property(_interface, signature=u"o", access=u"read")
 
1137
    @dbus_service_property(_interface, signature="o", access="read")
1089
1138
    def ObjectPath_dbus_property(self):
1090
1139
        return self.dbus_object_path # is already a dbus.ObjectPath
1091
1140
    
1092
1141
    # Secret = property
1093
 
    @dbus_service_property(_interface, signature=u"ay",
1094
 
                           access=u"write", byte_arrays=True)
 
1142
    @dbus_service_property(_interface, signature="ay",
 
1143
                           access="write", byte_arrays=True)
1095
1144
    def Secret_dbus_property(self, value):
1096
1145
        self.secret = str(value)
1097
1146
    
1132
1181
    
1133
1182
    def handle(self):
1134
1183
        with contextlib.closing(self.server.child_pipe) as child_pipe:
1135
 
            logger.info(u"TCP connection from: %s",
 
1184
            logger.info("TCP connection from: %s",
1136
1185
                        unicode(self.client_address))
1137
 
            logger.debug(u"Pipe FD: %d",
 
1186
            logger.debug("Pipe FD: %d",
1138
1187
                         self.server.child_pipe.fileno())
1139
1188
 
1140
1189
            session = (gnutls.connection
1147
1196
            # no X.509 keys are added to it.  Therefore, we can use it
1148
1197
            # here despite using OpenPGP certificates.
1149
1198
 
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"))
 
1199
            #priority = ':'.join(("NONE", "+VERS-TLS1.1",
 
1200
            #                      "+AES-256-CBC", "+SHA1",
 
1201
            #                      "+COMP-NULL", "+CTYPE-OPENPGP",
 
1202
            #                      "+DHE-DSS"))
1154
1203
            # Use a fallback default, since this MUST be set.
1155
1204
            priority = self.server.gnutls_priority
1156
1205
            if priority is None:
1157
 
                priority = u"NORMAL"
 
1206
                priority = "NORMAL"
1158
1207
            (gnutls.library.functions
1159
1208
             .gnutls_priority_set_direct(session._c_object,
1160
1209
                                         priority, None))
1162
1211
            # Start communication using the Mandos protocol
1163
1212
            # Get protocol number
1164
1213
            line = self.request.makefile().readline()
1165
 
            logger.debug(u"Protocol version: %r", line)
 
1214
            logger.debug("Protocol version: %r", line)
1166
1215
            try:
1167
1216
                if int(line.strip().split()[0]) > 1:
1168
1217
                    raise RuntimeError
1169
 
            except (ValueError, IndexError, RuntimeError), error:
1170
 
                logger.error(u"Unknown protocol version: %s", error)
 
1218
            except (ValueError, IndexError, RuntimeError) as error:
 
1219
                logger.error("Unknown protocol version: %s", error)
1171
1220
                return
1172
1221
 
1173
1222
            # Start GnuTLS connection
1174
1223
            try:
1175
1224
                session.handshake()
1176
 
            except gnutls.errors.GNUTLSError, error:
1177
 
                logger.warning(u"Handshake failed: %s", error)
 
1225
            except gnutls.errors.GNUTLSError as error:
 
1226
                logger.warning("Handshake failed: %s", error)
1178
1227
                # Do not run session.bye() here: the session is not
1179
1228
                # established.  Just abandon the request.
1180
1229
                return
1181
 
            logger.debug(u"Handshake succeeded")
 
1230
            logger.debug("Handshake succeeded")
1182
1231
 
1183
1232
            approval_required = False
1184
1233
            try:
1185
1234
                try:
1186
1235
                    fpr = self.fingerprint(self.peer_certificate
1187
1236
                                           (session))
1188
 
                except (TypeError, gnutls.errors.GNUTLSError), error:
1189
 
                    logger.warning(u"Bad certificate: %s", error)
 
1237
                except (TypeError,
 
1238
                        gnutls.errors.GNUTLSError) as error:
 
1239
                    logger.warning("Bad certificate: %s", error)
1190
1240
                    return
1191
 
                logger.debug(u"Fingerprint: %s", fpr)
 
1241
                logger.debug("Fingerprint: %s", fpr)
1192
1242
 
1193
1243
                try:
1194
1244
                    client = ProxyClient(child_pipe, fpr,
1203
1253
                
1204
1254
                while True:
1205
1255
                    if not client.enabled:
1206
 
                        logger.warning(u"Client %s is disabled",
 
1256
                        logger.info("Client %s is disabled",
1207
1257
                                       client.name)
1208
1258
                        if self.server.use_dbus:
1209
1259
                            # Emit D-Bus signal
1214
1264
                        #We are approved or approval is disabled
1215
1265
                        break
1216
1266
                    elif client._approved is None:
1217
 
                        logger.info(u"Client %s needs approval",
 
1267
                        logger.info("Client %s needs approval",
1218
1268
                                    client.name)
1219
1269
                        if self.server.use_dbus:
1220
1270
                            # Emit D-Bus signal
1222
1272
                                client.approval_delay_milliseconds(),
1223
1273
                                client.approved_by_default)
1224
1274
                    else:
1225
 
                        logger.warning(u"Client %s was not approved",
 
1275
                        logger.warning("Client %s was not approved",
1226
1276
                                       client.name)
1227
1277
                        if self.server.use_dbus:
1228
1278
                            # Emit D-Bus signal
1254
1304
                while sent_size < len(client.secret):
1255
1305
                    try:
1256
1306
                        sent = session.send(client.secret[sent_size:])
1257
 
                    except (gnutls.errors.GNUTLSError), error:
 
1307
                    except gnutls.errors.GNUTLSError as error:
1258
1308
                        logger.warning("gnutls send failed")
1259
1309
                        return
1260
 
                    logger.debug(u"Sent: %d, remaining: %d",
 
1310
                    logger.debug("Sent: %d, remaining: %d",
1261
1311
                                 sent, len(client.secret)
1262
1312
                                 - (sent_size + sent))
1263
1313
                    sent_size += sent
1264
1314
 
1265
 
                logger.info(u"Sending secret to %s", client.name)
 
1315
                logger.info("Sending secret to %s", client.name)
1266
1316
                # bump the timeout as if seen
1267
 
                client.checked_ok()
 
1317
                client.checked_ok(client.extended_timeout)
1268
1318
                if self.server.use_dbus:
1269
1319
                    # Emit D-Bus signal
1270
1320
                    client.GotSecret()
1274
1324
                    client.approvals_pending -= 1
1275
1325
                try:
1276
1326
                    session.bye()
1277
 
                except (gnutls.errors.GNUTLSError), error:
 
1327
                except gnutls.errors.GNUTLSError as error:
1278
1328
                    logger.warning("GnuTLS bye failed")
1279
1329
    
1280
1330
    @staticmethod
1291
1341
                     .gnutls_certificate_get_peers
1292
1342
                     (session._c_object, ctypes.byref(list_size)))
1293
1343
        if not bool(cert_list) and list_size.value != 0:
1294
 
            raise gnutls.errors.GNUTLSError(u"error getting peer"
1295
 
                                            u" certificate")
 
1344
            raise gnutls.errors.GNUTLSError("error getting peer"
 
1345
                                            " certificate")
1296
1346
        if list_size.value == 0:
1297
1347
            return None
1298
1348
        cert = cert_list[0]
1324
1374
        if crtverify.value != 0:
1325
1375
            gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1326
1376
            raise (gnutls.errors.CertificateSecurityError
1327
 
                   (u"Verify failed"))
 
1377
                   ("Verify failed"))
1328
1378
        # New buffer for the fingerprint
1329
1379
        buf = ctypes.create_string_buffer(20)
1330
1380
        buf_len = ctypes.c_size_t()
1337
1387
        # Convert the buffer to a Python bytestring
1338
1388
        fpr = ctypes.string_at(buf, buf_len.value)
1339
1389
        # Convert the bytestring to hexadecimal notation
1340
 
        hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
 
1390
        hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
1341
1391
        return hex_fpr
1342
1392
 
1343
1393
 
1371
1421
 
1372
1422
    def add_pipe(self, parent_pipe):
1373
1423
        """Dummy function; override as necessary"""
1374
 
        pass
 
1424
        raise NotImplementedError
1375
1425
 
1376
1426
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1377
1427
                     socketserver.TCPServer, object):
1395
1445
        bind to an address or port if they were not specified."""
1396
1446
        if self.interface is not None:
1397
1447
            if SO_BINDTODEVICE is None:
1398
 
                logger.error(u"SO_BINDTODEVICE does not exist;"
1399
 
                             u" cannot bind to interface %s",
 
1448
                logger.error("SO_BINDTODEVICE does not exist;"
 
1449
                             " cannot bind to interface %s",
1400
1450
                             self.interface)
1401
1451
            else:
1402
1452
                try:
1403
1453
                    self.socket.setsockopt(socket.SOL_SOCKET,
1404
1454
                                           SO_BINDTODEVICE,
1405
1455
                                           str(self.interface
1406
 
                                               + u'\0'))
1407
 
                except socket.error, error:
 
1456
                                               + '\0'))
 
1457
                except socket.error as error:
1408
1458
                    if error[0] == errno.EPERM:
1409
 
                        logger.error(u"No permission to"
1410
 
                                     u" bind to interface %s",
 
1459
                        logger.error("No permission to"
 
1460
                                     " bind to interface %s",
1411
1461
                                     self.interface)
1412
1462
                    elif error[0] == errno.ENOPROTOOPT:
1413
 
                        logger.error(u"SO_BINDTODEVICE not available;"
1414
 
                                     u" cannot bind to interface %s",
 
1463
                        logger.error("SO_BINDTODEVICE not available;"
 
1464
                                     " cannot bind to interface %s",
1415
1465
                                     self.interface)
1416
1466
                    else:
1417
1467
                        raise
1419
1469
        if self.server_address[0] or self.server_address[1]:
1420
1470
            if not self.server_address[0]:
1421
1471
                if self.address_family == socket.AF_INET6:
1422
 
                    any_address = u"::" # in6addr_any
 
1472
                    any_address = "::" # in6addr_any
1423
1473
                else:
1424
1474
                    any_address = socket.INADDR_ANY
1425
1475
                self.server_address = (any_address,
1474
1524
    def handle_ipc(self, source, condition, parent_pipe=None,
1475
1525
                   client_object=None):
1476
1526
        condition_names = {
1477
 
            gobject.IO_IN: u"IN",   # There is data to read.
1478
 
            gobject.IO_OUT: u"OUT", # Data can be written (without
 
1527
            gobject.IO_IN: "IN",   # There is data to read.
 
1528
            gobject.IO_OUT: "OUT", # Data can be written (without
1479
1529
                                    # 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
 
1530
            gobject.IO_PRI: "PRI", # There is urgent data to read.
 
1531
            gobject.IO_ERR: "ERR", # Error condition.
 
1532
            gobject.IO_HUP: "HUP"  # Hung up (the connection has been
1483
1533
                                    # broken, usually for pipes and
1484
1534
                                    # sockets).
1485
1535
            }
1504
1554
                    client = c
1505
1555
                    break
1506
1556
            else:
1507
 
                logger.warning(u"Client not found for fingerprint: %s, ad"
1508
 
                               u"dress: %s", fpr, address)
 
1557
                logger.info("Client not found for fingerprint: %s, ad"
 
1558
                            "dress: %s", fpr, address)
1509
1559
                if self.use_dbus:
1510
1560
                    # Emit D-Bus signal
1511
 
                    mandos_dbus_service.ClientNotFound(fpr, address)
 
1561
                    mandos_dbus_service.ClientNotFound(fpr, address[0])
1512
1562
                parent_pipe.send(False)
1513
1563
                return False
1514
1564
            
1545
1595
def string_to_delta(interval):
1546
1596
    """Parse a string and return a datetime.timedelta
1547
1597
    
1548
 
    >>> string_to_delta(u'7d')
 
1598
    >>> string_to_delta('7d')
1549
1599
    datetime.timedelta(7)
1550
 
    >>> string_to_delta(u'60s')
 
1600
    >>> string_to_delta('60s')
1551
1601
    datetime.timedelta(0, 60)
1552
 
    >>> string_to_delta(u'60m')
 
1602
    >>> string_to_delta('60m')
1553
1603
    datetime.timedelta(0, 3600)
1554
 
    >>> string_to_delta(u'24h')
 
1604
    >>> string_to_delta('24h')
1555
1605
    datetime.timedelta(1)
1556
 
    >>> string_to_delta(u'1w')
 
1606
    >>> string_to_delta('1w')
1557
1607
    datetime.timedelta(7)
1558
 
    >>> string_to_delta(u'5m 30s')
 
1608
    >>> string_to_delta('5m 30s')
1559
1609
    datetime.timedelta(0, 330)
1560
1610
    """
1561
1611
    timevalue = datetime.timedelta(0)
1563
1613
        try:
1564
1614
            suffix = unicode(s[-1])
1565
1615
            value = int(s[:-1])
1566
 
            if suffix == u"d":
 
1616
            if suffix == "d":
1567
1617
                delta = datetime.timedelta(value)
1568
 
            elif suffix == u"s":
 
1618
            elif suffix == "s":
1569
1619
                delta = datetime.timedelta(0, value)
1570
 
            elif suffix == u"m":
 
1620
            elif suffix == "m":
1571
1621
                delta = datetime.timedelta(0, 0, 0, 0, value)
1572
 
            elif suffix == u"h":
 
1622
            elif suffix == "h":
1573
1623
                delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1574
 
            elif suffix == u"w":
 
1624
            elif suffix == "w":
1575
1625
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1576
1626
            else:
1577
 
                raise ValueError(u"Unknown suffix %r" % suffix)
1578
 
        except (ValueError, IndexError), e:
1579
 
            raise ValueError(e.message)
 
1627
                raise ValueError("Unknown suffix %r" % suffix)
 
1628
        except (ValueError, IndexError) as e:
 
1629
            raise ValueError(*(e.args))
1580
1630
        timevalue += delta
1581
1631
    return timevalue
1582
1632
 
1588
1638
    global if_nametoindex
1589
1639
    try:
1590
1640
        if_nametoindex = (ctypes.cdll.LoadLibrary
1591
 
                          (ctypes.util.find_library(u"c"))
 
1641
                          (ctypes.util.find_library("c"))
1592
1642
                          .if_nametoindex)
1593
1643
    except (OSError, AttributeError):
1594
 
        logger.warning(u"Doing if_nametoindex the hard way")
 
1644
        logger.warning("Doing if_nametoindex the hard way")
1595
1645
        def if_nametoindex(interface):
1596
1646
            "Get an interface index the hard way, i.e. using fcntl()"
1597
1647
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1598
1648
            with contextlib.closing(socket.socket()) as s:
1599
1649
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1600
 
                                    struct.pack(str(u"16s16x"),
 
1650
                                    struct.pack(str("16s16x"),
1601
1651
                                                interface))
1602
 
            interface_index = struct.unpack(str(u"I"),
 
1652
            interface_index = struct.unpack(str("I"),
1603
1653
                                            ifreq[16:20])[0]
1604
1654
            return interface_index
1605
1655
    return if_nametoindex(interface)
1613
1663
        sys.exit()
1614
1664
    os.setsid()
1615
1665
    if not nochdir:
1616
 
        os.chdir(u"/")
 
1666
        os.chdir("/")
1617
1667
    if os.fork():
1618
1668
        sys.exit()
1619
1669
    if not noclose:
1621
1671
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1622
1672
        if not stat.S_ISCHR(os.fstat(null).st_mode):
1623
1673
            raise OSError(errno.ENODEV,
1624
 
                          u"%s not a character device"
 
1674
                          "%s not a character device"
1625
1675
                          % os.path.devnull)
1626
1676
        os.dup2(null, sys.stdin.fileno())
1627
1677
        os.dup2(null, sys.stdout.fileno())
1635
1685
    ##################################################################
1636
1686
    # Parsing of options, both command line and config file
1637
1687
    
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]
 
1688
    parser = argparse.ArgumentParser()
 
1689
    parser.add_argument("-v", "--version", action="version",
 
1690
                        version = "%%(prog)s %s" % version,
 
1691
                        help="show version number and exit")
 
1692
    parser.add_argument("-i", "--interface", metavar="IF",
 
1693
                        help="Bind to interface IF")
 
1694
    parser.add_argument("-a", "--address",
 
1695
                        help="Address to listen for requests on")
 
1696
    parser.add_argument("-p", "--port", type=int,
 
1697
                        help="Port number to receive requests on")
 
1698
    parser.add_argument("--check", action="store_true",
 
1699
                        help="Run self-test")
 
1700
    parser.add_argument("--debug", action="store_true",
 
1701
                        help="Debug mode; run in foreground and log"
 
1702
                        " to terminal")
 
1703
    parser.add_argument("--debuglevel", metavar="LEVEL",
 
1704
                        help="Debug level for stdout output")
 
1705
    parser.add_argument("--priority", help="GnuTLS"
 
1706
                        " priority string (see GnuTLS documentation)")
 
1707
    parser.add_argument("--servicename",
 
1708
                        metavar="NAME", help="Zeroconf service name")
 
1709
    parser.add_argument("--configdir",
 
1710
                        default="/etc/mandos", metavar="DIR",
 
1711
                        help="Directory to search for configuration"
 
1712
                        " files")
 
1713
    parser.add_argument("--no-dbus", action="store_false",
 
1714
                        dest="use_dbus", help="Do not provide D-Bus"
 
1715
                        " system bus interface")
 
1716
    parser.add_argument("--no-ipv6", action="store_false",
 
1717
                        dest="use_ipv6", help="Do not use IPv6")
 
1718
    options = parser.parse_args()
1666
1719
    
1667
1720
    if options.check:
1668
1721
        import doctest
1670
1723
        sys.exit()
1671
1724
    
1672
1725
    # 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"",
 
1726
    server_defaults = { "interface": "",
 
1727
                        "address": "",
 
1728
                        "port": "",
 
1729
                        "debug": "False",
 
1730
                        "priority":
 
1731
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
 
1732
                        "servicename": "Mandos",
 
1733
                        "use_dbus": "True",
 
1734
                        "use_ipv6": "True",
 
1735
                        "debuglevel": "",
1683
1736
                        }
1684
1737
    
1685
1738
    # Parse config file for server-global settings
1686
1739
    server_config = configparser.SafeConfigParser(server_defaults)
1687
1740
    del server_defaults
1688
1741
    server_config.read(os.path.join(options.configdir,
1689
 
                                    u"mandos.conf"))
 
1742
                                    "mandos.conf"))
1690
1743
    # Convert the SafeConfigParser object to a dict
1691
1744
    server_settings = server_config.defaults()
1692
1745
    # 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",
 
1746
    for option in ("debug", "use_dbus", "use_ipv6"):
 
1747
        server_settings[option] = server_config.getboolean("DEFAULT",
1695
1748
                                                           option)
1696
1749
    if server_settings["port"]:
1697
 
        server_settings["port"] = server_config.getint(u"DEFAULT",
1698
 
                                                       u"port")
 
1750
        server_settings["port"] = server_config.getint("DEFAULT",
 
1751
                                                       "port")
1699
1752
    del server_config
1700
1753
    
1701
1754
    # Override the settings from the config file with command line
1702
1755
    # 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"):
 
1756
    for option in ("interface", "address", "port", "debug",
 
1757
                   "priority", "servicename", "configdir",
 
1758
                   "use_dbus", "use_ipv6", "debuglevel"):
1706
1759
        value = getattr(options, option)
1707
1760
        if value is not None:
1708
1761
            server_settings[option] = value
1716
1769
    ##################################################################
1717
1770
    
1718
1771
    # 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"]
 
1772
    debug = server_settings["debug"]
 
1773
    debuglevel = server_settings["debuglevel"]
 
1774
    use_dbus = server_settings["use_dbus"]
 
1775
    use_ipv6 = server_settings["use_ipv6"]
1723
1776
 
1724
 
    if server_settings[u"servicename"] != u"Mandos":
 
1777
    if server_settings["servicename"] != "Mandos":
1725
1778
        syslogger.setFormatter(logging.Formatter
1726
 
                               (u'Mandos (%s) [%%(process)d]:'
1727
 
                                u' %%(levelname)s: %%(message)s'
1728
 
                                % server_settings[u"servicename"]))
 
1779
                               ('Mandos (%s) [%%(process)d]:'
 
1780
                                ' %%(levelname)s: %%(message)s'
 
1781
                                % server_settings["servicename"]))
1729
1782
    
1730
1783
    # 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",
 
1784
    client_defaults = { "timeout": "5m",
 
1785
                        "extended_timeout": "15m",
 
1786
                        "interval": "2m",
 
1787
                        "checker": "fping -q -- %%(host)s",
 
1788
                        "host": "",
 
1789
                        "approval_delay": "0s",
 
1790
                        "approval_duration": "1s",
1737
1791
                        }
1738
1792
    client_config = configparser.SafeConfigParser(client_defaults)
1739
 
    client_config.read(os.path.join(server_settings[u"configdir"],
1740
 
                                    u"clients.conf"))
 
1793
    client_config.read(os.path.join(server_settings["configdir"],
 
1794
                                    "clients.conf"))
1741
1795
    
1742
1796
    global mandos_dbus_service
1743
1797
    mandos_dbus_service = None
1744
1798
    
1745
 
    tcp_server = MandosServer((server_settings[u"address"],
1746
 
                               server_settings[u"port"]),
 
1799
    tcp_server = MandosServer((server_settings["address"],
 
1800
                               server_settings["port"]),
1747
1801
                              ClientHandler,
1748
 
                              interface=(server_settings[u"interface"]
 
1802
                              interface=(server_settings["interface"]
1749
1803
                                         or None),
1750
1804
                              use_ipv6=use_ipv6,
1751
1805
                              gnutls_priority=
1752
 
                              server_settings[u"priority"],
 
1806
                              server_settings["priority"],
1753
1807
                              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)
 
1808
    if not debug:
 
1809
        pidfilename = "/var/run/mandos.pid"
 
1810
        try:
 
1811
            pidfile = open(pidfilename, "w")
 
1812
        except IOError:
 
1813
            logger.error("Could not open file %r", pidfilename)
1759
1814
    
1760
1815
    try:
1761
 
        uid = pwd.getpwnam(u"_mandos").pw_uid
1762
 
        gid = pwd.getpwnam(u"_mandos").pw_gid
 
1816
        uid = pwd.getpwnam("_mandos").pw_uid
 
1817
        gid = pwd.getpwnam("_mandos").pw_gid
1763
1818
    except KeyError:
1764
1819
        try:
1765
 
            uid = pwd.getpwnam(u"mandos").pw_uid
1766
 
            gid = pwd.getpwnam(u"mandos").pw_gid
 
1820
            uid = pwd.getpwnam("mandos").pw_uid
 
1821
            gid = pwd.getpwnam("mandos").pw_gid
1767
1822
        except KeyError:
1768
1823
            try:
1769
 
                uid = pwd.getpwnam(u"nobody").pw_uid
1770
 
                gid = pwd.getpwnam(u"nobody").pw_gid
 
1824
                uid = pwd.getpwnam("nobody").pw_uid
 
1825
                gid = pwd.getpwnam("nobody").pw_gid
1771
1826
            except KeyError:
1772
1827
                uid = 65534
1773
1828
                gid = 65534
1774
1829
    try:
1775
1830
        os.setgid(gid)
1776
1831
        os.setuid(uid)
1777
 
    except OSError, error:
 
1832
    except OSError as error:
1778
1833
        if error[0] != errno.EPERM:
1779
1834
            raise error
1780
1835
    
1795
1850
        
1796
1851
        @gnutls.library.types.gnutls_log_func
1797
1852
        def debug_gnutls(level, string):
1798
 
            logger.debug(u"GnuTLS: %s", string[:-1])
 
1853
            logger.debug("GnuTLS: %s", string[:-1])
1799
1854
        
1800
1855
        (gnutls.library.functions
1801
1856
         .gnutls_global_set_log_function(debug_gnutls))
1809
1864
        # No console logging
1810
1865
        logger.removeHandler(console)
1811
1866
    
 
1867
    # Need to fork before connecting to D-Bus
 
1868
    if not debug:
 
1869
        # Close all input and output, do double fork, etc.
 
1870
        daemon()
1812
1871
    
1813
1872
    global main_loop
1814
1873
    # From the Avahi example code
1818
1877
    # End of Avahi example code
1819
1878
    if use_dbus:
1820
1879
        try:
1821
 
            bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
 
1880
            bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
1822
1881
                                            bus, do_not_queue=True)
1823
 
        except dbus.exceptions.NameExistsException, e:
1824
 
            logger.error(unicode(e) + u", disabling D-Bus")
 
1882
        except dbus.exceptions.NameExistsException as e:
 
1883
            logger.error(unicode(e) + ", disabling D-Bus")
1825
1884
            use_dbus = False
1826
 
            server_settings[u"use_dbus"] = False
 
1885
            server_settings["use_dbus"] = False
1827
1886
            tcp_server.use_dbus = False
1828
1887
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1829
 
    service = AvahiService(name = server_settings[u"servicename"],
1830
 
                           servicetype = u"_mandos._tcp",
 
1888
    service = AvahiService(name = server_settings["servicename"],
 
1889
                           servicetype = "_mandos._tcp",
1831
1890
                           protocol = protocol, bus = bus)
1832
1891
    if server_settings["interface"]:
1833
1892
        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
 
        
 
1893
                             (str(server_settings["interface"])))
 
1894
    
1840
1895
    global multiprocessing_manager
1841
1896
    multiprocessing_manager = multiprocessing.Manager()
1842
1897
    
1861
1916
                        client_config, section)))
1862
1917
            for section in client_config.sections()))
1863
1918
    if not tcp_server.clients:
1864
 
        logger.warning(u"No clients defined")
 
1919
        logger.warning("No clients defined")
1865
1920
        
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
1921
    if not debug:
 
1922
        try:
 
1923
            with pidfile:
 
1924
                pid = os.getpid()
 
1925
                pidfile.write(str(pid) + "\n".encode("utf-8"))
 
1926
            del pidfile
 
1927
        except IOError:
 
1928
            logger.error("Could not write to file %r with PID %d",
 
1929
                         pidfilename, pid)
 
1930
        except NameError:
 
1931
            # "pidfile" was never created
 
1932
            pass
 
1933
        del pidfilename
 
1934
        
1880
1935
        signal.signal(signal.SIGINT, signal.SIG_IGN)
 
1936
 
1881
1937
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1882
1938
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1883
1939
    
1885
1941
        class MandosDBusService(dbus.service.Object):
1886
1942
            """A D-Bus proxy object"""
1887
1943
            def __init__(self):
1888
 
                dbus.service.Object.__init__(self, bus, u"/")
1889
 
            _interface = u"se.bsnet.fukt.Mandos"
 
1944
                dbus.service.Object.__init__(self, bus, "/")
 
1945
            _interface = "se.bsnet.fukt.Mandos"
1890
1946
            
1891
 
            @dbus.service.signal(_interface, signature=u"o")
 
1947
            @dbus.service.signal(_interface, signature="o")
1892
1948
            def ClientAdded(self, objpath):
1893
1949
                "D-Bus signal"
1894
1950
                pass
1895
1951
            
1896
 
            @dbus.service.signal(_interface, signature=u"ss")
 
1952
            @dbus.service.signal(_interface, signature="ss")
1897
1953
            def ClientNotFound(self, fingerprint, address):
1898
1954
                "D-Bus signal"
1899
1955
                pass
1900
1956
            
1901
 
            @dbus.service.signal(_interface, signature=u"os")
 
1957
            @dbus.service.signal(_interface, signature="os")
1902
1958
            def ClientRemoved(self, objpath, name):
1903
1959
                "D-Bus signal"
1904
1960
                pass
1905
1961
            
1906
 
            @dbus.service.method(_interface, out_signature=u"ao")
 
1962
            @dbus.service.method(_interface, out_signature="ao")
1907
1963
            def GetAllClients(self):
1908
1964
                "D-Bus method"
1909
1965
                return dbus.Array(c.dbus_object_path
1910
1966
                                  for c in tcp_server.clients)
1911
1967
            
1912
1968
            @dbus.service.method(_interface,
1913
 
                                 out_signature=u"a{oa{sv}}")
 
1969
                                 out_signature="a{oa{sv}}")
1914
1970
            def GetAllClientsWithProperties(self):
1915
1971
                "D-Bus method"
1916
1972
                return dbus.Dictionary(
1917
 
                    ((c.dbus_object_path, c.GetAll(u""))
 
1973
                    ((c.dbus_object_path, c.GetAll(""))
1918
1974
                     for c in tcp_server.clients),
1919
 
                    signature=u"oa{sv}")
 
1975
                    signature="oa{sv}")
1920
1976
            
1921
 
            @dbus.service.method(_interface, in_signature=u"o")
 
1977
            @dbus.service.method(_interface, in_signature="o")
1922
1978
            def RemoveClient(self, object_path):
1923
1979
                "D-Bus method"
1924
1980
                for c in tcp_server.clients:
1966
2022
    # Find out what port we got
1967
2023
    service.port = tcp_server.socket.getsockname()[1]
1968
2024
    if use_ipv6:
1969
 
        logger.info(u"Now listening on address %r, port %d,"
 
2025
        logger.info("Now listening on address %r, port %d,"
1970
2026
                    " flowinfo %d, scope_id %d"
1971
2027
                    % tcp_server.socket.getsockname())
1972
2028
    else:                       # IPv4
1973
 
        logger.info(u"Now listening on address %r, port %d"
 
2029
        logger.info("Now listening on address %r, port %d"
1974
2030
                    % tcp_server.socket.getsockname())
1975
2031
    
1976
2032
    #service.interface = tcp_server.socket.getsockname()[3]
1979
2035
        # From the Avahi example code
1980
2036
        try:
1981
2037
            service.activate()
1982
 
        except dbus.exceptions.DBusException, error:
1983
 
            logger.critical(u"DBusException: %s", error)
 
2038
        except dbus.exceptions.DBusException as error:
 
2039
            logger.critical("DBusException: %s", error)
1984
2040
            cleanup()
1985
2041
            sys.exit(1)
1986
2042
        # End of Avahi example code
1990
2046
                             (tcp_server.handle_request
1991
2047
                              (*args[2:], **kwargs) or True))
1992
2048
        
1993
 
        logger.debug(u"Starting main loop")
 
2049
        logger.debug("Starting main loop")
1994
2050
        main_loop.run()
1995
 
    except AvahiError, error:
1996
 
        logger.critical(u"AvahiError: %s", error)
 
2051
    except AvahiError as error:
 
2052
        logger.critical("AvahiError: %s", error)
1997
2053
        cleanup()
1998
2054
        sys.exit(1)
1999
2055
    except KeyboardInterrupt:
2000
2056
        if debug:
2001
 
            print >> sys.stderr
2002
 
        logger.debug(u"Server received KeyboardInterrupt")
2003
 
    logger.debug(u"Server exiting")
 
2057
            print("", file=sys.stderr)
 
2058
        logger.debug("Server received KeyboardInterrupt")
 
2059
    logger.debug("Server exiting")
2004
2060
    # Must run before the D-Bus bus name gets deregistered
2005
2061
    cleanup()
2006
2062