/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: 2016-02-21 13:00:15 UTC
  • Revision ID: teddy@recompile.se-20160221130015-b32z5wnkw9swjg2s
mandos: Remove unused import and an errant space character.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python
 
1
#!/usr/bin/python2.7
2
2
# -*- mode: python; coding: utf-8 -*-
3
3
4
4
# Mandos server - give out binary blobs to connecting clients.
11
11
# "AvahiService" class, and some lines in "main".
12
12
13
13
# Everything else is
14
 
# Copyright © 2008-2012 Teddy Hogeborn
15
 
# Copyright © 2008-2012 Björn Påhlsson
 
14
# Copyright © 2008-2015 Teddy Hogeborn
 
15
# Copyright © 2008-2015 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
36
36
 
37
37
from future_builtins import *
38
38
 
39
 
import SocketServer as socketserver
 
39
try:
 
40
    import SocketServer as socketserver
 
41
except ImportError:
 
42
    import socketserver
40
43
import socket
41
44
import argparse
42
45
import datetime
43
46
import errno
44
 
import gnutls.crypto
45
47
import gnutls.connection
46
48
import gnutls.errors
47
49
import gnutls.library.functions
48
50
import gnutls.library.constants
49
51
import gnutls.library.types
50
 
import ConfigParser as configparser
 
52
try:
 
53
    import ConfigParser as configparser
 
54
except ImportError:
 
55
    import configparser
51
56
import sys
52
57
import re
53
58
import os
62
67
import struct
63
68
import fcntl
64
69
import functools
65
 
import cPickle as pickle
 
70
try:
 
71
    import cPickle as pickle
 
72
except ImportError:
 
73
    import pickle
66
74
import multiprocessing
67
75
import types
68
76
import binascii
69
77
import tempfile
70
78
import itertools
71
79
import collections
 
80
import codecs
72
81
 
73
82
import dbus
74
83
import dbus.service
75
 
import gobject
 
84
try:
 
85
    import gobject
 
86
except ImportError:
 
87
    from gi.repository import GObject as gobject
76
88
import avahi
77
89
from dbus.mainloop.glib import DBusGMainLoop
78
90
import ctypes
79
91
import ctypes.util
80
92
import xml.dom.minidom
81
93
import inspect
82
 
import GnuPGInterface
83
94
 
84
95
try:
85
96
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
89
100
    except ImportError:
90
101
        SO_BINDTODEVICE = None
91
102
 
92
 
version = "1.6.0"
 
103
if sys.version_info.major == 2:
 
104
    str = unicode
 
105
 
 
106
version = "1.7.1"
93
107
stored_state_file = "clients.pickle"
94
108
 
95
109
logger = logging.getLogger()
96
 
syslogger = (logging.handlers.SysLogHandler
97
 
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
98
 
              address = str("/dev/log")))
 
110
syslogger = None
99
111
 
100
112
try:
101
 
    if_nametoindex = (ctypes.cdll.LoadLibrary
102
 
                      (ctypes.util.find_library("c"))
103
 
                      .if_nametoindex)
 
113
    if_nametoindex = ctypes.cdll.LoadLibrary(
 
114
        ctypes.util.find_library("c")).if_nametoindex
104
115
except (OSError, AttributeError):
 
116
    
105
117
    def if_nametoindex(interface):
106
118
        "Get an interface index the hard way, i.e. using fcntl()"
107
119
        SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
108
120
        with contextlib.closing(socket.socket()) as s:
109
121
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
110
 
                                struct.pack(str("16s16x"),
111
 
                                            interface))
112
 
        interface_index = struct.unpack(str("I"),
113
 
                                        ifreq[16:20])[0]
 
122
                                struct.pack(b"16s16x", interface))
 
123
        interface_index = struct.unpack("I", ifreq[16:20])[0]
114
124
        return interface_index
115
125
 
116
126
 
117
127
def initlogger(debug, level=logging.WARNING):
118
128
    """init logger and add loglevel"""
119
129
    
 
130
    global syslogger
 
131
    syslogger = (logging.handlers.SysLogHandler(
 
132
        facility = logging.handlers.SysLogHandler.LOG_DAEMON,
 
133
        address = "/dev/log"))
120
134
    syslogger.setFormatter(logging.Formatter
121
135
                           ('Mandos [%(process)d]: %(levelname)s:'
122
136
                            ' %(message)s'))
139
153
 
140
154
class PGPEngine(object):
141
155
    """A simple class for OpenPGP symmetric encryption & decryption"""
 
156
    
142
157
    def __init__(self):
143
 
        self.gnupg = GnuPGInterface.GnuPG()
144
158
        self.tempdir = tempfile.mkdtemp(prefix="mandos-")
145
 
        self.gnupg = GnuPGInterface.GnuPG()
146
 
        self.gnupg.options.meta_interactive = False
147
 
        self.gnupg.options.homedir = self.tempdir
148
 
        self.gnupg.options.extra_args.extend(['--force-mdc',
149
 
                                              '--quiet',
150
 
                                              '--no-use-agent'])
 
159
        self.gnupgargs = ['--batch',
 
160
                          '--home', self.tempdir,
 
161
                          '--force-mdc',
 
162
                          '--quiet',
 
163
                          '--no-use-agent']
151
164
    
152
165
    def __enter__(self):
153
166
        return self
175
188
    def password_encode(self, password):
176
189
        # Passphrase can not be empty and can not contain newlines or
177
190
        # NUL bytes.  So we prefix it and hex encode it.
178
 
        return b"mandos" + binascii.hexlify(password)
 
191
        encoded = b"mandos" + binascii.hexlify(password)
 
192
        if len(encoded) > 2048:
 
193
            # GnuPG can't handle long passwords, so encode differently
 
194
            encoded = (b"mandos" + password.replace(b"\\", b"\\\\")
 
195
                       .replace(b"\n", b"\\n")
 
196
                       .replace(b"\0", b"\\x00"))
 
197
        return encoded
179
198
    
180
199
    def encrypt(self, data, password):
181
 
        self.gnupg.passphrase = self.password_encode(password)
182
 
        with open(os.devnull, "w") as devnull:
183
 
            try:
184
 
                proc = self.gnupg.run(['--symmetric'],
185
 
                                      create_fhs=['stdin', 'stdout'],
186
 
                                      attach_fhs={'stderr': devnull})
187
 
                with contextlib.closing(proc.handles['stdin']) as f:
188
 
                    f.write(data)
189
 
                with contextlib.closing(proc.handles['stdout']) as f:
190
 
                    ciphertext = f.read()
191
 
                proc.wait()
192
 
            except IOError as e:
193
 
                raise PGPError(e)
194
 
        self.gnupg.passphrase = None
 
200
        passphrase = self.password_encode(password)
 
201
        with tempfile.NamedTemporaryFile(
 
202
                dir=self.tempdir) as passfile:
 
203
            passfile.write(passphrase)
 
204
            passfile.flush()
 
205
            proc = subprocess.Popen(['gpg', '--symmetric',
 
206
                                     '--passphrase-file',
 
207
                                     passfile.name]
 
208
                                    + self.gnupgargs,
 
209
                                    stdin = subprocess.PIPE,
 
210
                                    stdout = subprocess.PIPE,
 
211
                                    stderr = subprocess.PIPE)
 
212
            ciphertext, err = proc.communicate(input = data)
 
213
        if proc.returncode != 0:
 
214
            raise PGPError(err)
195
215
        return ciphertext
196
216
    
197
217
    def decrypt(self, data, password):
198
 
        self.gnupg.passphrase = self.password_encode(password)
199
 
        with open(os.devnull, "w") as devnull:
200
 
            try:
201
 
                proc = self.gnupg.run(['--decrypt'],
202
 
                                      create_fhs=['stdin', 'stdout'],
203
 
                                      attach_fhs={'stderr': devnull})
204
 
                with contextlib.closing(proc.handles['stdin']) as f:
205
 
                    f.write(data)
206
 
                with contextlib.closing(proc.handles['stdout']) as f:
207
 
                    decrypted_plaintext = f.read()
208
 
                proc.wait()
209
 
            except IOError as e:
210
 
                raise PGPError(e)
211
 
        self.gnupg.passphrase = None
 
218
        passphrase = self.password_encode(password)
 
219
        with tempfile.NamedTemporaryFile(
 
220
                dir = self.tempdir) as passfile:
 
221
            passfile.write(passphrase)
 
222
            passfile.flush()
 
223
            proc = subprocess.Popen(['gpg', '--decrypt',
 
224
                                     '--passphrase-file',
 
225
                                     passfile.name]
 
226
                                    + self.gnupgargs,
 
227
                                    stdin = subprocess.PIPE,
 
228
                                    stdout = subprocess.PIPE,
 
229
                                    stderr = subprocess.PIPE)
 
230
            decrypted_plaintext, err = proc.communicate(input = data)
 
231
        if proc.returncode != 0:
 
232
            raise PGPError(err)
212
233
        return decrypted_plaintext
213
234
 
214
235
 
215
236
class AvahiError(Exception):
216
237
    def __init__(self, value, *args, **kwargs):
217
238
        self.value = value
218
 
        super(AvahiError, self).__init__(value, *args, **kwargs)
219
 
    def __unicode__(self):
220
 
        return unicode(repr(self.value))
 
239
        return super(AvahiError, self).__init__(value, *args,
 
240
                                                **kwargs)
 
241
 
221
242
 
222
243
class AvahiServiceError(AvahiError):
223
244
    pass
224
245
 
 
246
 
225
247
class AvahiGroupError(AvahiError):
226
248
    pass
227
249
 
247
269
    bus: dbus.SystemBus()
248
270
    """
249
271
    
250
 
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
251
 
                 servicetype = None, port = None, TXT = None,
252
 
                 domain = "", host = "", max_renames = 32768,
253
 
                 protocol = avahi.PROTO_UNSPEC, bus = None):
 
272
    def __init__(self,
 
273
                 interface = avahi.IF_UNSPEC,
 
274
                 name = None,
 
275
                 servicetype = None,
 
276
                 port = None,
 
277
                 TXT = None,
 
278
                 domain = "",
 
279
                 host = "",
 
280
                 max_renames = 32768,
 
281
                 protocol = avahi.PROTO_UNSPEC,
 
282
                 bus = None):
254
283
        self.interface = interface
255
284
        self.name = name
256
285
        self.type = servicetype
266
295
        self.bus = bus
267
296
        self.entry_group_state_changed_match = None
268
297
    
269
 
    def rename(self):
 
298
    def rename(self, remove=True):
270
299
        """Derived from the Avahi example code"""
271
300
        if self.rename_count >= self.max_renames:
272
301
            logger.critical("No suitable Zeroconf service name found"
273
302
                            " after %i retries, exiting.",
274
303
                            self.rename_count)
275
304
            raise AvahiServiceError("Too many renames")
276
 
        self.name = unicode(self.server
277
 
                            .GetAlternativeServiceName(self.name))
 
305
        self.name = str(
 
306
            self.server.GetAlternativeServiceName(self.name))
 
307
        self.rename_count += 1
278
308
        logger.info("Changing Zeroconf service name to %r ...",
279
309
                    self.name)
280
 
        self.remove()
 
310
        if remove:
 
311
            self.remove()
281
312
        try:
282
313
            self.add()
283
314
        except dbus.exceptions.DBusException as error:
284
 
            logger.critical("D-Bus Exception", exc_info=error)
285
 
            self.cleanup()
286
 
            os._exit(1)
287
 
        self.rename_count += 1
 
315
            if (error.get_dbus_name()
 
316
                == "org.freedesktop.Avahi.CollisionError"):
 
317
                logger.info("Local Zeroconf service name collision.")
 
318
                return self.rename(remove=False)
 
319
            else:
 
320
                logger.critical("D-Bus Exception", exc_info=error)
 
321
                self.cleanup()
 
322
                os._exit(1)
288
323
    
289
324
    def remove(self):
290
325
        """Derived from the Avahi example code"""
328
363
            self.rename()
329
364
        elif state == avahi.ENTRY_GROUP_FAILURE:
330
365
            logger.critical("Avahi: Error in group state changed %s",
331
 
                            unicode(error))
332
 
            raise AvahiGroupError("State changed: {0!s}"
333
 
                                  .format(error))
 
366
                            str(error))
 
367
            raise AvahiGroupError("State changed: {!s}".format(error))
334
368
    
335
369
    def cleanup(self):
336
370
        """Derived from the Avahi example code"""
346
380
    def server_state_changed(self, state, error=None):
347
381
        """Derived from the Avahi example code"""
348
382
        logger.debug("Avahi server state change: %i", state)
349
 
        bad_states = { avahi.SERVER_INVALID:
350
 
                           "Zeroconf server invalid",
351
 
                       avahi.SERVER_REGISTERING: None,
352
 
                       avahi.SERVER_COLLISION:
353
 
                           "Zeroconf server name collision",
354
 
                       avahi.SERVER_FAILURE:
355
 
                           "Zeroconf server failure" }
 
383
        bad_states = {
 
384
            avahi.SERVER_INVALID: "Zeroconf server invalid",
 
385
            avahi.SERVER_REGISTERING: None,
 
386
            avahi.SERVER_COLLISION: "Zeroconf server name collision",
 
387
            avahi.SERVER_FAILURE: "Zeroconf server failure",
 
388
        }
356
389
        if state in bad_states:
357
390
            if bad_states[state] is not None:
358
391
                if error is None:
361
394
                    logger.error(bad_states[state] + ": %r", error)
362
395
            self.cleanup()
363
396
        elif state == avahi.SERVER_RUNNING:
364
 
            self.add()
 
397
            try:
 
398
                self.add()
 
399
            except dbus.exceptions.DBusException as error:
 
400
                if (error.get_dbus_name()
 
401
                    == "org.freedesktop.Avahi.CollisionError"):
 
402
                    logger.info("Local Zeroconf service name"
 
403
                                " collision.")
 
404
                    return self.rename(remove=False)
 
405
                else:
 
406
                    logger.critical("D-Bus Exception", exc_info=error)
 
407
                    self.cleanup()
 
408
                    os._exit(1)
365
409
        else:
366
410
            if error is None:
367
411
                logger.debug("Unknown state: %r", state)
377
421
                                    follow_name_owner_changes=True),
378
422
                avahi.DBUS_INTERFACE_SERVER)
379
423
        self.server.connect_to_signal("StateChanged",
380
 
                                 self.server_state_changed)
 
424
                                      self.server_state_changed)
381
425
        self.server_state_changed(self.server.GetState())
382
426
 
383
427
 
384
428
class AvahiServiceToSyslog(AvahiService):
385
 
    def rename(self):
 
429
    def rename(self, *args, **kwargs):
386
430
        """Add the new name to the syslog messages"""
387
 
        ret = AvahiService.rename(self)
388
 
        syslogger.setFormatter(logging.Formatter
389
 
                               ('Mandos ({0}) [%(process)d]:'
390
 
                                ' %(levelname)s: %(message)s'
391
 
                                .format(self.name)))
 
431
        ret = AvahiService.rename(self, *args, **kwargs)
 
432
        syslogger.setFormatter(logging.Formatter(
 
433
            'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
 
434
            .format(self.name)))
392
435
        return ret
393
436
 
394
 
 
395
 
def timedelta_to_milliseconds(td):
396
 
    "Convert a datetime.timedelta() to milliseconds"
397
 
    return ((td.days * 24 * 60 * 60 * 1000)
398
 
            + (td.seconds * 1000)
399
 
            + (td.microseconds // 1000))
400
 
 
 
437
def call_pipe(connection,       # : multiprocessing.Connection
 
438
              func, *args, **kwargs):
 
439
    """This function is meant to be called by multiprocessing.Process
 
440
    
 
441
    This function runs func(*args, **kwargs), and writes the resulting
 
442
    return value on the provided multiprocessing.Connection.
 
443
    """
 
444
    connection.send(func(*args, **kwargs))
 
445
    connection.close()
401
446
 
402
447
class Client(object):
403
448
    """A representation of a client host served by this server.
430
475
    last_checker_status: integer between 0 and 255 reflecting exit
431
476
                         status of last checker. -1 reflects crashed
432
477
                         checker, -2 means no checker completed yet.
 
478
    last_checker_signal: The signal which killed the last checker, if
 
479
                         last_checker_status is -1
433
480
    last_enabled: datetime.datetime(); (UTC) or None
434
481
    name:       string; from the config file, used in log messages and
435
482
                        D-Bus identifiers
440
487
    runtime_expansions: Allowed attributes for runtime expansion.
441
488
    expires:    datetime.datetime(); time (UTC) when a client will be
442
489
                disabled, or None
 
490
    server_settings: The server_settings dict from main()
443
491
    """
444
492
    
445
493
    runtime_expansions = ("approval_delay", "approval_duration",
447
495
                          "fingerprint", "host", "interval",
448
496
                          "last_approval_request", "last_checked_ok",
449
497
                          "last_enabled", "name", "timeout")
450
 
    client_defaults = { "timeout": "PT5M",
451
 
                        "extended_timeout": "PT15M",
452
 
                        "interval": "PT2M",
453
 
                        "checker": "fping -q -- %%(host)s",
454
 
                        "host": "",
455
 
                        "approval_delay": "PT0S",
456
 
                        "approval_duration": "PT1S",
457
 
                        "approved_by_default": "True",
458
 
                        "enabled": "True",
459
 
                        }
460
 
    
461
 
    def timeout_milliseconds(self):
462
 
        "Return the 'timeout' attribute in milliseconds"
463
 
        return timedelta_to_milliseconds(self.timeout)
464
 
    
465
 
    def extended_timeout_milliseconds(self):
466
 
        "Return the 'extended_timeout' attribute in milliseconds"
467
 
        return timedelta_to_milliseconds(self.extended_timeout)
468
 
    
469
 
    def interval_milliseconds(self):
470
 
        "Return the 'interval' attribute in milliseconds"
471
 
        return timedelta_to_milliseconds(self.interval)
472
 
    
473
 
    def approval_delay_milliseconds(self):
474
 
        return timedelta_to_milliseconds(self.approval_delay)
 
498
    client_defaults = {
 
499
        "timeout": "PT5M",
 
500
        "extended_timeout": "PT15M",
 
501
        "interval": "PT2M",
 
502
        "checker": "fping -q -- %%(host)s",
 
503
        "host": "",
 
504
        "approval_delay": "PT0S",
 
505
        "approval_duration": "PT1S",
 
506
        "approved_by_default": "True",
 
507
        "enabled": "True",
 
508
    }
475
509
    
476
510
    @staticmethod
477
511
    def config_parser(config):
493
527
            client["enabled"] = config.getboolean(client_name,
494
528
                                                  "enabled")
495
529
            
 
530
            # Uppercase and remove spaces from fingerprint for later
 
531
            # comparison purposes with return value from the
 
532
            # fingerprint() function
496
533
            client["fingerprint"] = (section["fingerprint"].upper()
497
534
                                     .replace(" ", ""))
498
535
            if "secret" in section:
503
540
                          "rb") as secfile:
504
541
                    client["secret"] = secfile.read()
505
542
            else:
506
 
                raise TypeError("No secret or secfile for section {0}"
 
543
                raise TypeError("No secret or secfile for section {}"
507
544
                                .format(section))
508
545
            client["timeout"] = string_to_delta(section["timeout"])
509
546
            client["extended_timeout"] = string_to_delta(
520
557
        
521
558
        return settings
522
559
    
523
 
    def __init__(self, settings, name = None):
 
560
    def __init__(self, settings, name = None, server_settings=None):
524
561
        self.name = name
 
562
        if server_settings is None:
 
563
            server_settings = {}
 
564
        self.server_settings = server_settings
525
565
        # adding all client settings
526
 
        for setting, value in settings.iteritems():
 
566
        for setting, value in settings.items():
527
567
            setattr(self, setting, value)
528
568
        
529
569
        if self.enabled:
537
577
            self.expires = None
538
578
        
539
579
        logger.debug("Creating client %r", self.name)
540
 
        # Uppercase and remove spaces from fingerprint for later
541
 
        # comparison purposes with return value from the fingerprint()
542
 
        # function
543
580
        logger.debug("  Fingerprint: %s", self.fingerprint)
544
581
        self.created = settings.get("created",
545
582
                                    datetime.datetime.utcnow())
552
589
        self.current_checker_command = None
553
590
        self.approved = None
554
591
        self.approvals_pending = 0
555
 
        self.changedstate = (multiprocessing_manager
556
 
                             .Condition(multiprocessing_manager
557
 
                                        .Lock()))
558
 
        self.client_structure = [attr for attr in
559
 
                                 self.__dict__.iterkeys()
 
592
        self.changedstate = multiprocessing_manager.Condition(
 
593
            multiprocessing_manager.Lock())
 
594
        self.client_structure = [attr
 
595
                                 for attr in self.__dict__.iterkeys()
560
596
                                 if not attr.startswith("_")]
561
597
        self.client_structure.append("client_structure")
562
598
        
563
 
        for name, t in inspect.getmembers(type(self),
564
 
                                          lambda obj:
565
 
                                              isinstance(obj,
566
 
                                                         property)):
 
599
        for name, t in inspect.getmembers(
 
600
                type(self), lambda obj: isinstance(obj, property)):
567
601
            if not name.startswith("_"):
568
602
                self.client_structure.append(name)
569
603
    
611
645
        # and every interval from then on.
612
646
        if self.checker_initiator_tag is not None:
613
647
            gobject.source_remove(self.checker_initiator_tag)
614
 
        self.checker_initiator_tag = (gobject.timeout_add
615
 
                                      (self.interval_milliseconds(),
616
 
                                       self.start_checker))
 
648
        self.checker_initiator_tag = gobject.timeout_add(
 
649
            int(self.interval.total_seconds() * 1000),
 
650
            self.start_checker)
617
651
        # Schedule a disable() when 'timeout' has passed
618
652
        if self.disable_initiator_tag is not None:
619
653
            gobject.source_remove(self.disable_initiator_tag)
620
 
        self.disable_initiator_tag = (gobject.timeout_add
621
 
                                   (self.timeout_milliseconds(),
622
 
                                    self.disable))
 
654
        self.disable_initiator_tag = gobject.timeout_add(
 
655
            int(self.timeout.total_seconds() * 1000), self.disable)
623
656
        # Also start a new checker *right now*.
624
657
        self.start_checker()
625
658
    
626
 
    def checker_callback(self, pid, condition, command):
 
659
    def checker_callback(self, source, condition, connection,
 
660
                         command):
627
661
        """The checker has completed, so take appropriate actions."""
628
662
        self.checker_callback_tag = None
629
663
        self.checker = None
630
 
        if os.WIFEXITED(condition):
631
 
            self.last_checker_status = os.WEXITSTATUS(condition)
 
664
        # Read return code from connection (see call_pipe)
 
665
        returncode = connection.recv()
 
666
        connection.close()
 
667
        
 
668
        if returncode >= 0:
 
669
            self.last_checker_status = returncode
 
670
            self.last_checker_signal = None
632
671
            if self.last_checker_status == 0:
633
672
                logger.info("Checker for %(name)s succeeded",
634
673
                            vars(self))
635
674
                self.checked_ok()
636
675
            else:
637
 
                logger.info("Checker for %(name)s failed",
638
 
                            vars(self))
 
676
                logger.info("Checker for %(name)s failed", vars(self))
639
677
        else:
640
678
            self.last_checker_status = -1
 
679
            self.last_checker_signal = -returncode
641
680
            logger.warning("Checker for %(name)s crashed?",
642
681
                           vars(self))
 
682
        return False
643
683
    
644
684
    def checked_ok(self):
645
685
        """Assert that the client has been seen, alive and well."""
646
686
        self.last_checked_ok = datetime.datetime.utcnow()
647
687
        self.last_checker_status = 0
 
688
        self.last_checker_signal = None
648
689
        self.bump_timeout()
649
690
    
650
691
    def bump_timeout(self, timeout=None):
655
696
            gobject.source_remove(self.disable_initiator_tag)
656
697
            self.disable_initiator_tag = None
657
698
        if getattr(self, "enabled", False):
658
 
            self.disable_initiator_tag = (gobject.timeout_add
659
 
                                          (timedelta_to_milliseconds
660
 
                                           (timeout), self.disable))
 
699
            self.disable_initiator_tag = gobject.timeout_add(
 
700
                int(timeout.total_seconds() * 1000), self.disable)
661
701
            self.expires = datetime.datetime.utcnow() + timeout
662
702
    
663
703
    def need_approval(self):
677
717
        # than 'timeout' for the client to be disabled, which is as it
678
718
        # should be.
679
719
        
680
 
        # If a checker exists, make sure it is not a zombie
681
 
        try:
682
 
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
683
 
        except (AttributeError, OSError) as error:
684
 
            if (isinstance(error, OSError)
685
 
                and error.errno != errno.ECHILD):
686
 
                raise error
687
 
        else:
688
 
            if pid:
689
 
                logger.warning("Checker was a zombie")
690
 
                gobject.source_remove(self.checker_callback_tag)
691
 
                self.checker_callback(pid, status,
692
 
                                      self.current_checker_command)
 
720
        if self.checker is not None and not self.checker.is_alive():
 
721
            logger.warning("Checker was not alive; joining")
 
722
            self.checker.join()
 
723
            self.checker = None
693
724
        # Start a new checker if needed
694
725
        if self.checker is None:
695
726
            # Escape attributes for the shell
696
 
            escaped_attrs = dict(
697
 
                (attr, re.escape(unicode(getattr(self, attr))))
698
 
                for attr in
699
 
                self.runtime_expansions)
 
727
            escaped_attrs = {
 
728
                attr: re.escape(str(getattr(self, attr)))
 
729
                for attr in self.runtime_expansions }
700
730
            try:
701
731
                command = self.checker_command % escaped_attrs
702
732
            except TypeError as error:
703
733
                logger.error('Could not format string "%s"',
704
 
                             self.checker_command, exc_info=error)
705
 
                return True # Try again later
 
734
                             self.checker_command,
 
735
                             exc_info=error)
 
736
                return True     # Try again later
706
737
            self.current_checker_command = command
707
 
            try:
708
 
                logger.info("Starting checker %r for %s",
709
 
                            command, self.name)
710
 
                # We don't need to redirect stdout and stderr, since
711
 
                # in normal mode, that is already done by daemon(),
712
 
                # and in debug mode we don't want to.  (Stdin is
713
 
                # always replaced by /dev/null.)
714
 
                self.checker = subprocess.Popen(command,
715
 
                                                close_fds=True,
716
 
                                                shell=True, cwd="/")
717
 
            except OSError as error:
718
 
                logger.error("Failed to start subprocess",
719
 
                             exc_info=error)
720
 
                return True
721
 
            self.checker_callback_tag = (gobject.child_watch_add
722
 
                                         (self.checker.pid,
723
 
                                          self.checker_callback,
724
 
                                          data=command))
725
 
            # The checker may have completed before the gobject
726
 
            # watch was added.  Check for this.
727
 
            try:
728
 
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
729
 
            except OSError as error:
730
 
                if error.errno == errno.ECHILD:
731
 
                    # This should never happen
732
 
                    logger.error("Child process vanished",
733
 
                                 exc_info=error)
734
 
                    return True
735
 
                raise
736
 
            if pid:
737
 
                gobject.source_remove(self.checker_callback_tag)
738
 
                self.checker_callback(pid, status, command)
 
738
            logger.info("Starting checker %r for %s", command,
 
739
                        self.name)
 
740
            # We don't need to redirect stdout and stderr, since
 
741
            # in normal mode, that is already done by daemon(),
 
742
            # and in debug mode we don't want to.  (Stdin is
 
743
            # always replaced by /dev/null.)
 
744
            # The exception is when not debugging but nevertheless
 
745
            # running in the foreground; use the previously
 
746
            # created wnull.
 
747
            popen_args = { "close_fds": True,
 
748
                           "shell": True,
 
749
                           "cwd": "/" }
 
750
            if (not self.server_settings["debug"]
 
751
                and self.server_settings["foreground"]):
 
752
                popen_args.update({"stdout": wnull,
 
753
                                   "stderr": wnull })
 
754
            pipe = multiprocessing.Pipe(duplex = False)
 
755
            self.checker = multiprocessing.Process(
 
756
                target = call_pipe,
 
757
                args = (pipe[1], subprocess.call, command),
 
758
                kwargs = popen_args)
 
759
            self.checker.start()
 
760
            self.checker_callback_tag = gobject.io_add_watch(
 
761
                pipe[0].fileno(), gobject.IO_IN,
 
762
                self.checker_callback, pipe[0], command)
739
763
        # Re-run this periodically if run by gobject.timeout_add
740
764
        return True
741
765
    
747
771
        if getattr(self, "checker", None) is None:
748
772
            return
749
773
        logger.debug("Stopping checker for %(name)s", vars(self))
750
 
        try:
751
 
            self.checker.terminate()
752
 
            #time.sleep(0.5)
753
 
            #if self.checker.poll() is None:
754
 
            #    self.checker.kill()
755
 
        except OSError as error:
756
 
            if error.errno != errno.ESRCH: # No such process
757
 
                raise
 
774
        self.checker.terminate()
758
775
        self.checker = None
759
776
 
760
777
 
761
 
def dbus_service_property(dbus_interface, signature="v",
762
 
                          access="readwrite", byte_arrays=False):
 
778
def dbus_service_property(dbus_interface,
 
779
                          signature="v",
 
780
                          access="readwrite",
 
781
                          byte_arrays=False):
763
782
    """Decorators for marking methods of a DBusObjectWithProperties to
764
783
    become properties on the D-Bus.
765
784
    
774
793
    # "Set" method, so we fail early here:
775
794
    if byte_arrays and signature != "ay":
776
795
        raise ValueError("Byte arrays not supported for non-'ay'"
777
 
                         " signature {0!r}".format(signature))
 
796
                         " signature {!r}".format(signature))
 
797
    
778
798
    def decorator(func):
779
799
        func._dbus_is_property = True
780
800
        func._dbus_interface = dbus_interface
785
805
            func._dbus_name = func._dbus_name[:-14]
786
806
        func._dbus_get_args_options = {'byte_arrays': byte_arrays }
787
807
        return func
 
808
    
788
809
    return decorator
789
810
 
790
811
 
799
820
                "org.freedesktop.DBus.Property.EmitsChangedSignal":
800
821
                    "false"}
801
822
    """
 
823
    
802
824
    def decorator(func):
803
825
        func._dbus_is_interface = True
804
826
        func._dbus_interface = dbus_interface
805
827
        func._dbus_name = dbus_interface
806
828
        return func
 
829
    
807
830
    return decorator
808
831
 
809
832
 
811
834
    """Decorator to annotate D-Bus methods, signals or properties
812
835
    Usage:
813
836
    
 
837
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
 
838
                       "org.freedesktop.DBus.Property."
 
839
                       "EmitsChangedSignal": "false"})
814
840
    @dbus_service_property("org.example.Interface", signature="b",
815
841
                           access="r")
816
 
    @dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
817
 
                        "org.freedesktop.DBus.Property."
818
 
                        "EmitsChangedSignal": "false"})
819
842
    def Property_dbus_property(self):
820
843
        return dbus.Boolean(False)
 
844
    
 
845
    See also the DBusObjectWithAnnotations class.
821
846
    """
 
847
    
822
848
    def decorator(func):
823
849
        func._dbus_annotations = annotations
824
850
        return func
 
851
    
825
852
    return decorator
826
853
 
827
854
 
828
855
class DBusPropertyException(dbus.exceptions.DBusException):
829
856
    """A base class for D-Bus property-related exceptions
830
857
    """
831
 
    def __unicode__(self):
832
 
        return unicode(str(self))
 
858
    pass
833
859
 
834
860
 
835
861
class DBusPropertyAccessException(DBusPropertyException):
844
870
    pass
845
871
 
846
872
 
847
 
class DBusObjectWithProperties(dbus.service.Object):
848
 
    """A D-Bus object with properties.
 
873
class DBusObjectWithAnnotations(dbus.service.Object):
 
874
    """A D-Bus object with annotations.
849
875
    
850
 
    Classes inheriting from this can use the dbus_service_property
851
 
    decorator to expose methods as D-Bus properties.  It exposes the
852
 
    standard Get(), Set(), and GetAll() methods on the D-Bus.
 
876
    Classes inheriting from this can use the dbus_annotations
 
877
    decorator to add annotations to methods or signals.
853
878
    """
854
879
    
855
880
    @staticmethod
859
884
        If called like _is_dbus_thing("method") it returns a function
860
885
        suitable for use as predicate to inspect.getmembers().
861
886
        """
862
 
        return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
 
887
        return lambda obj: getattr(obj, "_dbus_is_{}".format(thing),
863
888
                                   False)
864
889
    
865
890
    def _get_all_dbus_things(self, thing):
866
891
        """Returns a generator of (name, attribute) pairs
867
892
        """
868
 
        return ((getattr(athing.__get__(self), "_dbus_name",
869
 
                         name),
 
893
        return ((getattr(athing.__get__(self), "_dbus_name", name),
870
894
                 athing.__get__(self))
871
895
                for cls in self.__class__.__mro__
872
896
                for name, athing in
873
 
                inspect.getmembers(cls,
874
 
                                   self._is_dbus_thing(thing)))
 
897
                inspect.getmembers(cls, self._is_dbus_thing(thing)))
 
898
    
 
899
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
 
900
                         out_signature = "s",
 
901
                         path_keyword = 'object_path',
 
902
                         connection_keyword = 'connection')
 
903
    def Introspect(self, object_path, connection):
 
904
        """Overloading of standard D-Bus method.
 
905
        
 
906
        Inserts annotation tags on methods and signals.
 
907
        """
 
908
        xmlstring = dbus.service.Object.Introspect(self, object_path,
 
909
                                                   connection)
 
910
        try:
 
911
            document = xml.dom.minidom.parseString(xmlstring)
 
912
            
 
913
            for if_tag in document.getElementsByTagName("interface"):
 
914
                # Add annotation tags
 
915
                for typ in ("method", "signal"):
 
916
                    for tag in if_tag.getElementsByTagName(typ):
 
917
                        annots = dict()
 
918
                        for name, prop in (self.
 
919
                                           _get_all_dbus_things(typ)):
 
920
                            if (name == tag.getAttribute("name")
 
921
                                and prop._dbus_interface
 
922
                                == if_tag.getAttribute("name")):
 
923
                                annots.update(getattr(
 
924
                                    prop, "_dbus_annotations", {}))
 
925
                        for name, value in annots.items():
 
926
                            ann_tag = document.createElement(
 
927
                                "annotation")
 
928
                            ann_tag.setAttribute("name", name)
 
929
                            ann_tag.setAttribute("value", value)
 
930
                            tag.appendChild(ann_tag)
 
931
                # Add interface annotation tags
 
932
                for annotation, value in dict(
 
933
                    itertools.chain.from_iterable(
 
934
                        annotations().items()
 
935
                        for name, annotations
 
936
                        in self._get_all_dbus_things("interface")
 
937
                        if name == if_tag.getAttribute("name")
 
938
                        )).items():
 
939
                    ann_tag = document.createElement("annotation")
 
940
                    ann_tag.setAttribute("name", annotation)
 
941
                    ann_tag.setAttribute("value", value)
 
942
                    if_tag.appendChild(ann_tag)
 
943
                # Fix argument name for the Introspect method itself
 
944
                if (if_tag.getAttribute("name")
 
945
                                == dbus.INTROSPECTABLE_IFACE):
 
946
                    for cn in if_tag.getElementsByTagName("method"):
 
947
                        if cn.getAttribute("name") == "Introspect":
 
948
                            for arg in cn.getElementsByTagName("arg"):
 
949
                                if (arg.getAttribute("direction")
 
950
                                    == "out"):
 
951
                                    arg.setAttribute("name",
 
952
                                                     "xml_data")
 
953
            xmlstring = document.toxml("utf-8")
 
954
            document.unlink()
 
955
        except (AttributeError, xml.dom.DOMException,
 
956
                xml.parsers.expat.ExpatError) as error:
 
957
            logger.error("Failed to override Introspection method",
 
958
                         exc_info=error)
 
959
        return xmlstring
 
960
 
 
961
 
 
962
class DBusObjectWithProperties(DBusObjectWithAnnotations):
 
963
    """A D-Bus object with properties.
 
964
    
 
965
    Classes inheriting from this can use the dbus_service_property
 
966
    decorator to expose methods as D-Bus properties.  It exposes the
 
967
    standard Get(), Set(), and GetAll() methods on the D-Bus.
 
968
    """
875
969
    
876
970
    def _get_dbus_property(self, interface_name, property_name):
877
971
        """Returns a bound method if one exists which is a D-Bus
878
972
        property with the specified name and interface.
879
973
        """
880
 
        for cls in  self.__class__.__mro__:
881
 
            for name, value in (inspect.getmembers
882
 
                                (cls,
883
 
                                 self._is_dbus_thing("property"))):
 
974
        for cls in self.__class__.__mro__:
 
975
            for name, value in inspect.getmembers(
 
976
                    cls, self._is_dbus_thing("property")):
884
977
                if (value._dbus_name == property_name
885
978
                    and value._dbus_interface == interface_name):
886
979
                    return value.__get__(self)
887
980
        
888
981
        # No such property
889
 
        raise DBusPropertyNotFound(self.dbus_object_path + ":"
890
 
                                   + interface_name + "."
891
 
                                   + property_name)
892
 
    
893
 
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
 
982
        raise DBusPropertyNotFound("{}:{}.{}".format(
 
983
            self.dbus_object_path, interface_name, property_name))
 
984
    
 
985
    @classmethod
 
986
    def _get_all_interface_names(cls):
 
987
        """Get a sequence of all interfaces supported by an object"""
 
988
        return (name for name in set(getattr(getattr(x, attr),
 
989
                                             "_dbus_interface", None)
 
990
                                     for x in (inspect.getmro(cls))
 
991
                                     for attr in dir(x))
 
992
                if name is not None)
 
993
    
 
994
    @dbus.service.method(dbus.PROPERTIES_IFACE,
 
995
                         in_signature="ss",
894
996
                         out_signature="v")
895
997
    def Get(self, interface_name, property_name):
896
998
        """Standard D-Bus property Get() method, see D-Bus standard.
914
1016
            # The byte_arrays option is not supported yet on
915
1017
            # signatures other than "ay".
916
1018
            if prop._dbus_signature != "ay":
917
 
                raise ValueError
 
1019
                raise ValueError("Byte arrays not supported for non-"
 
1020
                                 "'ay' signature {!r}"
 
1021
                                 .format(prop._dbus_signature))
918
1022
            value = dbus.ByteArray(b''.join(chr(byte)
919
1023
                                            for byte in value))
920
1024
        prop(value)
921
1025
    
922
 
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
 
1026
    @dbus.service.method(dbus.PROPERTIES_IFACE,
 
1027
                         in_signature="s",
923
1028
                         out_signature="a{sv}")
924
1029
    def GetAll(self, interface_name):
925
1030
        """Standard D-Bus property GetAll() method, see D-Bus
940
1045
            if not hasattr(value, "variant_level"):
941
1046
                properties[name] = value
942
1047
                continue
943
 
            properties[name] = type(value)(value, variant_level=
944
 
                                           value.variant_level+1)
 
1048
            properties[name] = type(value)(
 
1049
                value, variant_level = value.variant_level + 1)
945
1050
        return dbus.Dictionary(properties, signature="sv")
946
1051
    
 
1052
    @dbus.service.signal(dbus.PROPERTIES_IFACE, signature="sa{sv}as")
 
1053
    def PropertiesChanged(self, interface_name, changed_properties,
 
1054
                          invalidated_properties):
 
1055
        """Standard D-Bus PropertiesChanged() signal, see D-Bus
 
1056
        standard.
 
1057
        """
 
1058
        pass
 
1059
    
947
1060
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
948
1061
                         out_signature="s",
949
1062
                         path_keyword='object_path',
953
1066
        
954
1067
        Inserts property tags and interface annotation tags.
955
1068
        """
956
 
        xmlstring = dbus.service.Object.Introspect(self, object_path,
957
 
                                                   connection)
 
1069
        xmlstring = DBusObjectWithAnnotations.Introspect(self,
 
1070
                                                         object_path,
 
1071
                                                         connection)
958
1072
        try:
959
1073
            document = xml.dom.minidom.parseString(xmlstring)
 
1074
            
960
1075
            def make_tag(document, name, prop):
961
1076
                e = document.createElement("property")
962
1077
                e.setAttribute("name", name)
963
1078
                e.setAttribute("type", prop._dbus_signature)
964
1079
                e.setAttribute("access", prop._dbus_access)
965
1080
                return e
 
1081
            
966
1082
            for if_tag in document.getElementsByTagName("interface"):
967
1083
                # Add property tags
968
1084
                for tag in (make_tag(document, name, prop)
971
1087
                            if prop._dbus_interface
972
1088
                            == if_tag.getAttribute("name")):
973
1089
                    if_tag.appendChild(tag)
974
 
                # Add annotation tags
975
 
                for typ in ("method", "signal", "property"):
976
 
                    for tag in if_tag.getElementsByTagName(typ):
977
 
                        annots = dict()
978
 
                        for name, prop in (self.
979
 
                                           _get_all_dbus_things(typ)):
980
 
                            if (name == tag.getAttribute("name")
981
 
                                and prop._dbus_interface
982
 
                                == if_tag.getAttribute("name")):
983
 
                                annots.update(getattr
984
 
                                              (prop,
985
 
                                               "_dbus_annotations",
986
 
                                               {}))
987
 
                        for name, value in annots.iteritems():
988
 
                            ann_tag = document.createElement(
989
 
                                "annotation")
990
 
                            ann_tag.setAttribute("name", name)
991
 
                            ann_tag.setAttribute("value", value)
992
 
                            tag.appendChild(ann_tag)
993
 
                # Add interface annotation tags
994
 
                for annotation, value in dict(
995
 
                    itertools.chain.from_iterable(
996
 
                        annotations().iteritems()
997
 
                        for name, annotations in
998
 
                        self._get_all_dbus_things("interface")
999
 
                        if name == if_tag.getAttribute("name")
1000
 
                        )).iteritems():
1001
 
                    ann_tag = document.createElement("annotation")
1002
 
                    ann_tag.setAttribute("name", annotation)
1003
 
                    ann_tag.setAttribute("value", value)
1004
 
                    if_tag.appendChild(ann_tag)
 
1090
                # Add annotation tags for properties
 
1091
                for tag in if_tag.getElementsByTagName("property"):
 
1092
                    annots = dict()
 
1093
                    for name, prop in self._get_all_dbus_things(
 
1094
                            "property"):
 
1095
                        if (name == tag.getAttribute("name")
 
1096
                            and prop._dbus_interface
 
1097
                            == if_tag.getAttribute("name")):
 
1098
                            annots.update(getattr(
 
1099
                                prop, "_dbus_annotations", {}))
 
1100
                    for name, value in annots.items():
 
1101
                        ann_tag = document.createElement(
 
1102
                            "annotation")
 
1103
                        ann_tag.setAttribute("name", name)
 
1104
                        ann_tag.setAttribute("value", value)
 
1105
                        tag.appendChild(ann_tag)
1005
1106
                # Add the names to the return values for the
1006
1107
                # "org.freedesktop.DBus.Properties" methods
1007
1108
                if (if_tag.getAttribute("name")
1025
1126
                         exc_info=error)
1026
1127
        return xmlstring
1027
1128
 
 
1129
try:
 
1130
    dbus.OBJECT_MANAGER_IFACE
 
1131
except AttributeError:
 
1132
    dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
 
1133
 
 
1134
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
 
1135
    """A D-Bus object with an ObjectManager.
 
1136
    
 
1137
    Classes inheriting from this exposes the standard
 
1138
    GetManagedObjects call and the InterfacesAdded and
 
1139
    InterfacesRemoved signals on the standard
 
1140
    "org.freedesktop.DBus.ObjectManager" interface.
 
1141
    
 
1142
    Note: No signals are sent automatically; they must be sent
 
1143
    manually.
 
1144
    """
 
1145
    @dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
 
1146
                         out_signature = "a{oa{sa{sv}}}")
 
1147
    def GetManagedObjects(self):
 
1148
        """This function must be overridden"""
 
1149
        raise NotImplementedError()
 
1150
    
 
1151
    @dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
 
1152
                         signature = "oa{sa{sv}}")
 
1153
    def InterfacesAdded(self, object_path, interfaces_and_properties):
 
1154
        pass
 
1155
    
 
1156
    @dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
 
1157
    def InterfacesRemoved(self, object_path, interfaces):
 
1158
        pass
 
1159
    
 
1160
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
 
1161
                         out_signature = "s",
 
1162
                         path_keyword = 'object_path',
 
1163
                         connection_keyword = 'connection')
 
1164
    def Introspect(self, object_path, connection):
 
1165
        """Overloading of standard D-Bus method.
 
1166
        
 
1167
        Override return argument name of GetManagedObjects to be
 
1168
        "objpath_interfaces_and_properties"
 
1169
        """
 
1170
        xmlstring = DBusObjectWithAnnotations.Introspect(self,
 
1171
                                                         object_path,
 
1172
                                                         connection)
 
1173
        try:
 
1174
            document = xml.dom.minidom.parseString(xmlstring)
 
1175
            
 
1176
            for if_tag in document.getElementsByTagName("interface"):
 
1177
                # Fix argument name for the GetManagedObjects method
 
1178
                if (if_tag.getAttribute("name")
 
1179
                                == dbus.OBJECT_MANAGER_IFACE):
 
1180
                    for cn in if_tag.getElementsByTagName("method"):
 
1181
                        if (cn.getAttribute("name")
 
1182
                            == "GetManagedObjects"):
 
1183
                            for arg in cn.getElementsByTagName("arg"):
 
1184
                                if (arg.getAttribute("direction")
 
1185
                                    == "out"):
 
1186
                                    arg.setAttribute(
 
1187
                                        "name",
 
1188
                                        "objpath_interfaces"
 
1189
                                        "_and_properties")
 
1190
            xmlstring = document.toxml("utf-8")
 
1191
            document.unlink()
 
1192
        except (AttributeError, xml.dom.DOMException,
 
1193
                xml.parsers.expat.ExpatError) as error:
 
1194
            logger.error("Failed to override Introspection method",
 
1195
                         exc_info = error)
 
1196
        return xmlstring
1028
1197
 
1029
1198
def datetime_to_dbus(dt, variant_level=0):
1030
1199
    """Convert a UTC datetime.datetime() to a D-Bus type."""
1031
1200
    if dt is None:
1032
1201
        return dbus.String("", variant_level = variant_level)
1033
 
    return dbus.String(dt.isoformat(),
1034
 
                       variant_level=variant_level)
 
1202
    return dbus.String(dt.isoformat(), variant_level=variant_level)
1035
1203
 
1036
1204
 
1037
1205
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1057
1225
    (from DBusObjectWithProperties) and interfaces (from the
1058
1226
    dbus_interface_annotations decorator).
1059
1227
    """
 
1228
    
1060
1229
    def wrapper(cls):
1061
1230
        for orig_interface_name, alt_interface_name in (
1062
 
            alt_interface_names.iteritems()):
 
1231
                alt_interface_names.items()):
1063
1232
            attr = {}
1064
1233
            interface_names = set()
1065
1234
            # Go though all attributes of the class
1067
1236
                # Ignore non-D-Bus attributes, and D-Bus attributes
1068
1237
                # with the wrong interface name
1069
1238
                if (not hasattr(attribute, "_dbus_interface")
1070
 
                    or not attribute._dbus_interface
1071
 
                    .startswith(orig_interface_name)):
 
1239
                    or not attribute._dbus_interface.startswith(
 
1240
                        orig_interface_name)):
1072
1241
                    continue
1073
1242
                # Create an alternate D-Bus interface name based on
1074
1243
                # the current name
1075
 
                alt_interface = (attribute._dbus_interface
1076
 
                                 .replace(orig_interface_name,
1077
 
                                          alt_interface_name))
 
1244
                alt_interface = attribute._dbus_interface.replace(
 
1245
                    orig_interface_name, alt_interface_name)
1078
1246
                interface_names.add(alt_interface)
1079
1247
                # Is this a D-Bus signal?
1080
1248
                if getattr(attribute, "_dbus_is_signal", False):
1081
 
                    # Extract the original non-method undecorated
1082
 
                    # function by black magic
1083
 
                    nonmethod_func = (dict(
 
1249
                    if sys.version_info.major == 2:
 
1250
                        # Extract the original non-method undecorated
 
1251
                        # function by black magic
 
1252
                        nonmethod_func = (dict(
1084
1253
                            zip(attribute.func_code.co_freevars,
1085
 
                                attribute.__closure__))["func"]
1086
 
                                      .cell_contents)
 
1254
                                attribute.__closure__))
 
1255
                                          ["func"].cell_contents)
 
1256
                    else:
 
1257
                        nonmethod_func = attribute
1087
1258
                    # Create a new, but exactly alike, function
1088
1259
                    # object, and decorate it to be a new D-Bus signal
1089
1260
                    # with the alternate D-Bus interface name
1090
 
                    new_function = (dbus.service.signal
1091
 
                                    (alt_interface,
1092
 
                                     attribute._dbus_signature)
1093
 
                                    (types.FunctionType(
1094
 
                                nonmethod_func.func_code,
1095
 
                                nonmethod_func.func_globals,
1096
 
                                nonmethod_func.func_name,
1097
 
                                nonmethod_func.func_defaults,
1098
 
                                nonmethod_func.func_closure)))
 
1261
                    if sys.version_info.major == 2:
 
1262
                        new_function = types.FunctionType(
 
1263
                            nonmethod_func.func_code,
 
1264
                            nonmethod_func.func_globals,
 
1265
                            nonmethod_func.func_name,
 
1266
                            nonmethod_func.func_defaults,
 
1267
                            nonmethod_func.func_closure)
 
1268
                    else:
 
1269
                        new_function = types.FunctionType(
 
1270
                            nonmethod_func.__code__,
 
1271
                            nonmethod_func.__globals__,
 
1272
                            nonmethod_func.__name__,
 
1273
                            nonmethod_func.__defaults__,
 
1274
                            nonmethod_func.__closure__)
 
1275
                    new_function = (dbus.service.signal(
 
1276
                        alt_interface,
 
1277
                        attribute._dbus_signature)(new_function))
1099
1278
                    # Copy annotations, if any
1100
1279
                    try:
1101
 
                        new_function._dbus_annotations = (
1102
 
                            dict(attribute._dbus_annotations))
 
1280
                        new_function._dbus_annotations = dict(
 
1281
                            attribute._dbus_annotations)
1103
1282
                    except AttributeError:
1104
1283
                        pass
1105
1284
                    # Define a creator of a function to call both the
1110
1289
                        """This function is a scope container to pass
1111
1290
                        func1 and func2 to the "call_both" function
1112
1291
                        outside of its arguments"""
 
1292
                        
 
1293
                        @functools.wraps(func2)
1113
1294
                        def call_both(*args, **kwargs):
1114
1295
                            """This function will emit two D-Bus
1115
1296
                            signals by calling func1 and func2"""
1116
1297
                            func1(*args, **kwargs)
1117
1298
                            func2(*args, **kwargs)
 
1299
                        # Make wrapper function look like a D-Bus signal
 
1300
                        for name, attr in inspect.getmembers(func2):
 
1301
                            if name.startswith("_dbus_"):
 
1302
                                setattr(call_both, name, attr)
 
1303
                        
1118
1304
                        return call_both
1119
1305
                    # Create the "call_both" function and add it to
1120
1306
                    # the class
1125
1311
                    # object.  Decorate it to be a new D-Bus method
1126
1312
                    # with the alternate D-Bus interface name.  Add it
1127
1313
                    # to the class.
1128
 
                    attr[attrname] = (dbus.service.method
1129
 
                                      (alt_interface,
1130
 
                                       attribute._dbus_in_signature,
1131
 
                                       attribute._dbus_out_signature)
1132
 
                                      (types.FunctionType
1133
 
                                       (attribute.func_code,
1134
 
                                        attribute.func_globals,
1135
 
                                        attribute.func_name,
1136
 
                                        attribute.func_defaults,
1137
 
                                        attribute.func_closure)))
 
1314
                    attr[attrname] = (
 
1315
                        dbus.service.method(
 
1316
                            alt_interface,
 
1317
                            attribute._dbus_in_signature,
 
1318
                            attribute._dbus_out_signature)
 
1319
                        (types.FunctionType(attribute.func_code,
 
1320
                                            attribute.func_globals,
 
1321
                                            attribute.func_name,
 
1322
                                            attribute.func_defaults,
 
1323
                                            attribute.func_closure)))
1138
1324
                    # Copy annotations, if any
1139
1325
                    try:
1140
 
                        attr[attrname]._dbus_annotations = (
1141
 
                            dict(attribute._dbus_annotations))
 
1326
                        attr[attrname]._dbus_annotations = dict(
 
1327
                            attribute._dbus_annotations)
1142
1328
                    except AttributeError:
1143
1329
                        pass
1144
1330
                # Is this a D-Bus property?
1147
1333
                    # object, and decorate it to be a new D-Bus
1148
1334
                    # property with the alternate D-Bus interface
1149
1335
                    # name.  Add it to the class.
1150
 
                    attr[attrname] = (dbus_service_property
1151
 
                                      (alt_interface,
1152
 
                                       attribute._dbus_signature,
1153
 
                                       attribute._dbus_access,
1154
 
                                       attribute
1155
 
                                       ._dbus_get_args_options
1156
 
                                       ["byte_arrays"])
1157
 
                                      (types.FunctionType
1158
 
                                       (attribute.func_code,
1159
 
                                        attribute.func_globals,
1160
 
                                        attribute.func_name,
1161
 
                                        attribute.func_defaults,
1162
 
                                        attribute.func_closure)))
 
1336
                    attr[attrname] = (dbus_service_property(
 
1337
                        alt_interface, attribute._dbus_signature,
 
1338
                        attribute._dbus_access,
 
1339
                        attribute._dbus_get_args_options
 
1340
                        ["byte_arrays"])
 
1341
                                      (types.FunctionType(
 
1342
                                          attribute.func_code,
 
1343
                                          attribute.func_globals,
 
1344
                                          attribute.func_name,
 
1345
                                          attribute.func_defaults,
 
1346
                                          attribute.func_closure)))
1163
1347
                    # Copy annotations, if any
1164
1348
                    try:
1165
 
                        attr[attrname]._dbus_annotations = (
1166
 
                            dict(attribute._dbus_annotations))
 
1349
                        attr[attrname]._dbus_annotations = dict(
 
1350
                            attribute._dbus_annotations)
1167
1351
                    except AttributeError:
1168
1352
                        pass
1169
1353
                # Is this a D-Bus interface?
1172
1356
                    # object.  Decorate it to be a new D-Bus interface
1173
1357
                    # with the alternate D-Bus interface name.  Add it
1174
1358
                    # to the class.
1175
 
                    attr[attrname] = (dbus_interface_annotations
1176
 
                                      (alt_interface)
1177
 
                                      (types.FunctionType
1178
 
                                       (attribute.func_code,
1179
 
                                        attribute.func_globals,
1180
 
                                        attribute.func_name,
1181
 
                                        attribute.func_defaults,
1182
 
                                        attribute.func_closure)))
 
1359
                    attr[attrname] = (
 
1360
                        dbus_interface_annotations(alt_interface)
 
1361
                        (types.FunctionType(attribute.func_code,
 
1362
                                            attribute.func_globals,
 
1363
                                            attribute.func_name,
 
1364
                                            attribute.func_defaults,
 
1365
                                            attribute.func_closure)))
1183
1366
            if deprecate:
1184
1367
                # Deprecate all alternate interfaces
1185
 
                iname="_AlternateDBusNames_interface_annotation{0}"
 
1368
                iname="_AlternateDBusNames_interface_annotation{}"
1186
1369
                for interface_name in interface_names:
 
1370
                    
1187
1371
                    @dbus_interface_annotations(interface_name)
1188
1372
                    def func(self):
1189
1373
                        return { "org.freedesktop.DBus.Deprecated":
1190
 
                                     "true" }
 
1374
                                 "true" }
1191
1375
                    # Find an unused name
1192
1376
                    for aname in (iname.format(i)
1193
1377
                                  for i in itertools.count()):
1197
1381
            if interface_names:
1198
1382
                # Replace the class with a new subclass of it with
1199
1383
                # methods, signals, etc. as created above.
1200
 
                cls = type(b"{0}Alternate".format(cls.__name__),
1201
 
                           (cls,), attr)
 
1384
                cls = type(b"{}Alternate".format(cls.__name__),
 
1385
                           (cls, ), attr)
1202
1386
        return cls
 
1387
    
1203
1388
    return wrapper
1204
1389
 
1205
1390
 
1206
1391
@alternate_dbus_interfaces({"se.recompile.Mandos":
1207
 
                                "se.bsnet.fukt.Mandos"})
 
1392
                            "se.bsnet.fukt.Mandos"})
1208
1393
class ClientDBus(Client, DBusObjectWithProperties):
1209
1394
    """A Client class using D-Bus
1210
1395
    
1214
1399
    """
1215
1400
    
1216
1401
    runtime_expansions = (Client.runtime_expansions
1217
 
                          + ("dbus_object_path",))
 
1402
                          + ("dbus_object_path", ))
 
1403
    
 
1404
    _interface = "se.recompile.Mandos.Client"
1218
1405
    
1219
1406
    # dbus.service.Object doesn't use super(), so we can't either.
1220
1407
    
1223
1410
        Client.__init__(self, *args, **kwargs)
1224
1411
        # Only now, when this client is initialized, can it show up on
1225
1412
        # the D-Bus
1226
 
        client_object_name = unicode(self.name).translate(
 
1413
        client_object_name = str(self.name).translate(
1227
1414
            {ord("."): ord("_"),
1228
1415
             ord("-"): ord("_")})
1229
 
        self.dbus_object_path = (dbus.ObjectPath
1230
 
                                 ("/clients/" + client_object_name))
 
1416
        self.dbus_object_path = dbus.ObjectPath(
 
1417
            "/clients/" + client_object_name)
1231
1418
        DBusObjectWithProperties.__init__(self, self.bus,
1232
1419
                                          self.dbus_object_path)
1233
1420
    
1234
 
    def notifychangeproperty(transform_func,
1235
 
                             dbus_name, type_func=lambda x: x,
1236
 
                             variant_level=1):
 
1421
    def notifychangeproperty(transform_func, dbus_name,
 
1422
                             type_func=lambda x: x,
 
1423
                             variant_level=1,
 
1424
                             invalidate_only=False,
 
1425
                             _interface=_interface):
1237
1426
        """ Modify a variable so that it's a property which announces
1238
1427
        its changes to DBus.
1239
1428
        
1244
1433
                   to the D-Bus.  Default: no transform
1245
1434
        variant_level: D-Bus variant level.  Default: 1
1246
1435
        """
1247
 
        attrname = "_{0}".format(dbus_name)
 
1436
        attrname = "_{}".format(dbus_name)
 
1437
        
1248
1438
        def setter(self, value):
1249
1439
            if hasattr(self, "dbus_object_path"):
1250
1440
                if (not hasattr(self, attrname) or
1251
1441
                    type_func(getattr(self, attrname, None))
1252
1442
                    != type_func(value)):
1253
 
                    dbus_value = transform_func(type_func(value),
1254
 
                                                variant_level
1255
 
                                                =variant_level)
1256
 
                    self.PropertyChanged(dbus.String(dbus_name),
1257
 
                                         dbus_value)
 
1443
                    if invalidate_only:
 
1444
                        self.PropertiesChanged(
 
1445
                            _interface, dbus.Dictionary(),
 
1446
                            dbus.Array((dbus_name, )))
 
1447
                    else:
 
1448
                        dbus_value = transform_func(
 
1449
                            type_func(value),
 
1450
                            variant_level = variant_level)
 
1451
                        self.PropertyChanged(dbus.String(dbus_name),
 
1452
                                             dbus_value)
 
1453
                        self.PropertiesChanged(
 
1454
                            _interface,
 
1455
                            dbus.Dictionary({ dbus.String(dbus_name):
 
1456
                                              dbus_value }),
 
1457
                            dbus.Array())
1258
1458
            setattr(self, attrname, value)
1259
1459
        
1260
1460
        return property(lambda self: getattr(self, attrname), setter)
1266
1466
    enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1267
1467
    last_enabled = notifychangeproperty(datetime_to_dbus,
1268
1468
                                        "LastEnabled")
1269
 
    checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1270
 
                                   type_func = lambda checker:
1271
 
                                       checker is not None)
 
1469
    checker = notifychangeproperty(
 
1470
        dbus.Boolean, "CheckerRunning",
 
1471
        type_func = lambda checker: checker is not None)
1272
1472
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
1273
1473
                                           "LastCheckedOK")
1274
1474
    last_checker_status = notifychangeproperty(dbus.Int16,
1277
1477
        datetime_to_dbus, "LastApprovalRequest")
1278
1478
    approved_by_default = notifychangeproperty(dbus.Boolean,
1279
1479
                                               "ApprovedByDefault")
1280
 
    approval_delay = notifychangeproperty(dbus.UInt64,
1281
 
                                          "ApprovalDelay",
1282
 
                                          type_func =
1283
 
                                          timedelta_to_milliseconds)
 
1480
    approval_delay = notifychangeproperty(
 
1481
        dbus.UInt64, "ApprovalDelay",
 
1482
        type_func = lambda td: td.total_seconds() * 1000)
1284
1483
    approval_duration = notifychangeproperty(
1285
1484
        dbus.UInt64, "ApprovalDuration",
1286
 
        type_func = timedelta_to_milliseconds)
 
1485
        type_func = lambda td: td.total_seconds() * 1000)
1287
1486
    host = notifychangeproperty(dbus.String, "Host")
1288
 
    timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1289
 
                                   type_func =
1290
 
                                   timedelta_to_milliseconds)
 
1487
    timeout = notifychangeproperty(
 
1488
        dbus.UInt64, "Timeout",
 
1489
        type_func = lambda td: td.total_seconds() * 1000)
1291
1490
    extended_timeout = notifychangeproperty(
1292
1491
        dbus.UInt64, "ExtendedTimeout",
1293
 
        type_func = timedelta_to_milliseconds)
1294
 
    interval = notifychangeproperty(dbus.UInt64,
1295
 
                                    "Interval",
1296
 
                                    type_func =
1297
 
                                    timedelta_to_milliseconds)
 
1492
        type_func = lambda td: td.total_seconds() * 1000)
 
1493
    interval = notifychangeproperty(
 
1494
        dbus.UInt64, "Interval",
 
1495
        type_func = lambda td: td.total_seconds() * 1000)
1298
1496
    checker_command = notifychangeproperty(dbus.String, "Checker")
 
1497
    secret = notifychangeproperty(dbus.ByteArray, "Secret",
 
1498
                                  invalidate_only=True)
1299
1499
    
1300
1500
    del notifychangeproperty
1301
1501
    
1308
1508
            DBusObjectWithProperties.__del__(self, *args, **kwargs)
1309
1509
        Client.__del__(self, *args, **kwargs)
1310
1510
    
1311
 
    def checker_callback(self, pid, condition, command,
1312
 
                         *args, **kwargs):
1313
 
        self.checker_callback_tag = None
1314
 
        self.checker = None
1315
 
        if os.WIFEXITED(condition):
1316
 
            exitstatus = os.WEXITSTATUS(condition)
 
1511
    def checker_callback(self, source, condition,
 
1512
                         connection, command, *args, **kwargs):
 
1513
        ret = Client.checker_callback(self, source, condition,
 
1514
                                      connection, command, *args,
 
1515
                                      **kwargs)
 
1516
        exitstatus = self.last_checker_status
 
1517
        if exitstatus >= 0:
1317
1518
            # Emit D-Bus signal
1318
1519
            self.CheckerCompleted(dbus.Int16(exitstatus),
1319
 
                                  dbus.Int64(condition),
 
1520
                                  # This is specific to GNU libC
 
1521
                                  dbus.Int64(exitstatus << 8),
1320
1522
                                  dbus.String(command))
1321
1523
        else:
1322
1524
            # Emit D-Bus signal
1323
1525
            self.CheckerCompleted(dbus.Int16(-1),
1324
 
                                  dbus.Int64(condition),
 
1526
                                  dbus.Int64(
 
1527
                                      # This is specific to GNU libC
 
1528
                                      (exitstatus << 8)
 
1529
                                      | self.last_checker_signal),
1325
1530
                                  dbus.String(command))
1326
 
        
1327
 
        return Client.checker_callback(self, pid, condition, command,
1328
 
                                       *args, **kwargs)
 
1531
        return ret
1329
1532
    
1330
1533
    def start_checker(self, *args, **kwargs):
1331
 
        old_checker = self.checker
1332
 
        if self.checker is not None:
1333
 
            old_checker_pid = self.checker.pid
1334
 
        else:
1335
 
            old_checker_pid = None
 
1534
        old_checker_pid = getattr(self.checker, "pid", None)
1336
1535
        r = Client.start_checker(self, *args, **kwargs)
1337
1536
        # Only if new checker process was started
1338
1537
        if (self.checker is not None
1347
1546
    
1348
1547
    def approve(self, value=True):
1349
1548
        self.approved = value
1350
 
        gobject.timeout_add(timedelta_to_milliseconds
1351
 
                            (self.approval_duration),
1352
 
                            self._reset_approved)
 
1549
        gobject.timeout_add(int(self.approval_duration.total_seconds()
 
1550
                                * 1000), self._reset_approved)
1353
1551
        self.send_changedstate()
1354
1552
    
1355
1553
    ## D-Bus methods, signals & properties
1356
 
    _interface = "se.recompile.Mandos.Client"
1357
1554
    
1358
1555
    ## Interfaces
1359
1556
    
1360
 
    @dbus_interface_annotations(_interface)
1361
 
    def _foo(self):
1362
 
        return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
1363
 
                     "false"}
1364
 
    
1365
1557
    ## Signals
1366
1558
    
1367
1559
    # CheckerCompleted - signal
1377
1569
        pass
1378
1570
    
1379
1571
    # PropertyChanged - signal
 
1572
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1380
1573
    @dbus.service.signal(_interface, signature="sv")
1381
1574
    def PropertyChanged(self, property, value):
1382
1575
        "D-Bus signal"
1416
1609
        self.checked_ok()
1417
1610
    
1418
1611
    # Enable - method
 
1612
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1419
1613
    @dbus.service.method(_interface)
1420
1614
    def Enable(self):
1421
1615
        "D-Bus method"
1422
1616
        self.enable()
1423
1617
    
1424
1618
    # StartChecker - method
 
1619
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1425
1620
    @dbus.service.method(_interface)
1426
1621
    def StartChecker(self):
1427
1622
        "D-Bus method"
1428
1623
        self.start_checker()
1429
1624
    
1430
1625
    # Disable - method
 
1626
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1431
1627
    @dbus.service.method(_interface)
1432
1628
    def Disable(self):
1433
1629
        "D-Bus method"
1434
1630
        self.disable()
1435
1631
    
1436
1632
    # StopChecker - method
 
1633
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1437
1634
    @dbus.service.method(_interface)
1438
1635
    def StopChecker(self):
1439
1636
        self.stop_checker()
1446
1643
        return dbus.Boolean(bool(self.approvals_pending))
1447
1644
    
1448
1645
    # ApprovedByDefault - property
1449
 
    @dbus_service_property(_interface, signature="b",
 
1646
    @dbus_service_property(_interface,
 
1647
                           signature="b",
1450
1648
                           access="readwrite")
1451
1649
    def ApprovedByDefault_dbus_property(self, value=None):
1452
1650
        if value is None:       # get
1454
1652
        self.approved_by_default = bool(value)
1455
1653
    
1456
1654
    # ApprovalDelay - property
1457
 
    @dbus_service_property(_interface, signature="t",
 
1655
    @dbus_service_property(_interface,
 
1656
                           signature="t",
1458
1657
                           access="readwrite")
1459
1658
    def ApprovalDelay_dbus_property(self, value=None):
1460
1659
        if value is None:       # get
1461
 
            return dbus.UInt64(self.approval_delay_milliseconds())
 
1660
            return dbus.UInt64(self.approval_delay.total_seconds()
 
1661
                               * 1000)
1462
1662
        self.approval_delay = datetime.timedelta(0, 0, 0, value)
1463
1663
    
1464
1664
    # ApprovalDuration - property
1465
 
    @dbus_service_property(_interface, signature="t",
 
1665
    @dbus_service_property(_interface,
 
1666
                           signature="t",
1466
1667
                           access="readwrite")
1467
1668
    def ApprovalDuration_dbus_property(self, value=None):
1468
1669
        if value is None:       # get
1469
 
            return dbus.UInt64(timedelta_to_milliseconds(
1470
 
                    self.approval_duration))
 
1670
            return dbus.UInt64(self.approval_duration.total_seconds()
 
1671
                               * 1000)
1471
1672
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
1472
1673
    
1473
1674
    # Name - property
 
1675
    @dbus_annotations(
 
1676
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1474
1677
    @dbus_service_property(_interface, signature="s", access="read")
1475
1678
    def Name_dbus_property(self):
1476
1679
        return dbus.String(self.name)
1477
1680
    
1478
1681
    # Fingerprint - property
 
1682
    @dbus_annotations(
 
1683
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1479
1684
    @dbus_service_property(_interface, signature="s", access="read")
1480
1685
    def Fingerprint_dbus_property(self):
1481
1686
        return dbus.String(self.fingerprint)
1482
1687
    
1483
1688
    # Host - property
1484
 
    @dbus_service_property(_interface, signature="s",
 
1689
    @dbus_service_property(_interface,
 
1690
                           signature="s",
1485
1691
                           access="readwrite")
1486
1692
    def Host_dbus_property(self, value=None):
1487
1693
        if value is None:       # get
1488
1694
            return dbus.String(self.host)
1489
 
        self.host = unicode(value)
 
1695
        self.host = str(value)
1490
1696
    
1491
1697
    # Created - property
 
1698
    @dbus_annotations(
 
1699
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1492
1700
    @dbus_service_property(_interface, signature="s", access="read")
1493
1701
    def Created_dbus_property(self):
1494
1702
        return datetime_to_dbus(self.created)
1499
1707
        return datetime_to_dbus(self.last_enabled)
1500
1708
    
1501
1709
    # Enabled - property
1502
 
    @dbus_service_property(_interface, signature="b",
 
1710
    @dbus_service_property(_interface,
 
1711
                           signature="b",
1503
1712
                           access="readwrite")
1504
1713
    def Enabled_dbus_property(self, value=None):
1505
1714
        if value is None:       # get
1510
1719
            self.disable()
1511
1720
    
1512
1721
    # LastCheckedOK - property
1513
 
    @dbus_service_property(_interface, signature="s",
 
1722
    @dbus_service_property(_interface,
 
1723
                           signature="s",
1514
1724
                           access="readwrite")
1515
1725
    def LastCheckedOK_dbus_property(self, value=None):
1516
1726
        if value is not None:
1519
1729
        return datetime_to_dbus(self.last_checked_ok)
1520
1730
    
1521
1731
    # LastCheckerStatus - property
1522
 
    @dbus_service_property(_interface, signature="n",
1523
 
                           access="read")
 
1732
    @dbus_service_property(_interface, signature="n", access="read")
1524
1733
    def LastCheckerStatus_dbus_property(self):
1525
1734
        return dbus.Int16(self.last_checker_status)
1526
1735
    
1535
1744
        return datetime_to_dbus(self.last_approval_request)
1536
1745
    
1537
1746
    # Timeout - property
1538
 
    @dbus_service_property(_interface, signature="t",
 
1747
    @dbus_service_property(_interface,
 
1748
                           signature="t",
1539
1749
                           access="readwrite")
1540
1750
    def Timeout_dbus_property(self, value=None):
1541
1751
        if value is None:       # get
1542
 
            return dbus.UInt64(self.timeout_milliseconds())
 
1752
            return dbus.UInt64(self.timeout.total_seconds() * 1000)
1543
1753
        old_timeout = self.timeout
1544
1754
        self.timeout = datetime.timedelta(0, 0, 0, value)
1545
1755
        # Reschedule disabling
1554
1764
                    is None):
1555
1765
                    return
1556
1766
                gobject.source_remove(self.disable_initiator_tag)
1557
 
                self.disable_initiator_tag = (
1558
 
                    gobject.timeout_add(
1559
 
                        timedelta_to_milliseconds(self.expires - now),
1560
 
                        self.disable))
 
1767
                self.disable_initiator_tag = gobject.timeout_add(
 
1768
                    int((self.expires - now).total_seconds() * 1000),
 
1769
                    self.disable)
1561
1770
    
1562
1771
    # ExtendedTimeout - property
1563
 
    @dbus_service_property(_interface, signature="t",
 
1772
    @dbus_service_property(_interface,
 
1773
                           signature="t",
1564
1774
                           access="readwrite")
1565
1775
    def ExtendedTimeout_dbus_property(self, value=None):
1566
1776
        if value is None:       # get
1567
 
            return dbus.UInt64(self.extended_timeout_milliseconds())
 
1777
            return dbus.UInt64(self.extended_timeout.total_seconds()
 
1778
                               * 1000)
1568
1779
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1569
1780
    
1570
1781
    # Interval - property
1571
 
    @dbus_service_property(_interface, signature="t",
 
1782
    @dbus_service_property(_interface,
 
1783
                           signature="t",
1572
1784
                           access="readwrite")
1573
1785
    def Interval_dbus_property(self, value=None):
1574
1786
        if value is None:       # get
1575
 
            return dbus.UInt64(self.interval_milliseconds())
 
1787
            return dbus.UInt64(self.interval.total_seconds() * 1000)
1576
1788
        self.interval = datetime.timedelta(0, 0, 0, value)
1577
1789
        if getattr(self, "checker_initiator_tag", None) is None:
1578
1790
            return
1579
1791
        if self.enabled:
1580
1792
            # Reschedule checker run
1581
1793
            gobject.source_remove(self.checker_initiator_tag)
1582
 
            self.checker_initiator_tag = (gobject.timeout_add
1583
 
                                          (value, self.start_checker))
1584
 
            self.start_checker()    # Start one now, too
 
1794
            self.checker_initiator_tag = gobject.timeout_add(
 
1795
                value, self.start_checker)
 
1796
            self.start_checker() # Start one now, too
1585
1797
    
1586
1798
    # Checker - property
1587
 
    @dbus_service_property(_interface, signature="s",
 
1799
    @dbus_service_property(_interface,
 
1800
                           signature="s",
1588
1801
                           access="readwrite")
1589
1802
    def Checker_dbus_property(self, value=None):
1590
1803
        if value is None:       # get
1591
1804
            return dbus.String(self.checker_command)
1592
 
        self.checker_command = unicode(value)
 
1805
        self.checker_command = str(value)
1593
1806
    
1594
1807
    # CheckerRunning - property
1595
 
    @dbus_service_property(_interface, signature="b",
 
1808
    @dbus_service_property(_interface,
 
1809
                           signature="b",
1596
1810
                           access="readwrite")
1597
1811
    def CheckerRunning_dbus_property(self, value=None):
1598
1812
        if value is None:       # get
1603
1817
            self.stop_checker()
1604
1818
    
1605
1819
    # ObjectPath - property
 
1820
    @dbus_annotations(
 
1821
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const",
 
1822
         "org.freedesktop.DBus.Deprecated": "true"})
1606
1823
    @dbus_service_property(_interface, signature="o", access="read")
1607
1824
    def ObjectPath_dbus_property(self):
1608
1825
        return self.dbus_object_path # is already a dbus.ObjectPath
1609
1826
    
1610
1827
    # Secret = property
1611
 
    @dbus_service_property(_interface, signature="ay",
1612
 
                           access="write", byte_arrays=True)
 
1828
    @dbus_annotations(
 
1829
        {"org.freedesktop.DBus.Property.EmitsChangedSignal":
 
1830
         "invalidates"})
 
1831
    @dbus_service_property(_interface,
 
1832
                           signature="ay",
 
1833
                           access="write",
 
1834
                           byte_arrays=True)
1613
1835
    def Secret_dbus_property(self, value):
1614
 
        self.secret = str(value)
 
1836
        self.secret = bytes(value)
1615
1837
    
1616
1838
    del _interface
1617
1839
 
1621
1843
        self._pipe = child_pipe
1622
1844
        self._pipe.send(('init', fpr, address))
1623
1845
        if not self._pipe.recv():
1624
 
            raise KeyError()
 
1846
            raise KeyError(fpr)
1625
1847
    
1626
1848
    def __getattribute__(self, name):
1627
1849
        if name == '_pipe':
1631
1853
        if data[0] == 'data':
1632
1854
            return data[1]
1633
1855
        if data[0] == 'function':
 
1856
            
1634
1857
            def func(*args, **kwargs):
1635
1858
                self._pipe.send(('funcall', name, args, kwargs))
1636
1859
                return self._pipe.recv()[1]
 
1860
            
1637
1861
            return func
1638
1862
    
1639
1863
    def __setattr__(self, name, value):
1651
1875
    def handle(self):
1652
1876
        with contextlib.closing(self.server.child_pipe) as child_pipe:
1653
1877
            logger.info("TCP connection from: %s",
1654
 
                        unicode(self.client_address))
 
1878
                        str(self.client_address))
1655
1879
            logger.debug("Pipe FD: %d",
1656
1880
                         self.server.child_pipe.fileno())
1657
1881
            
1658
 
            session = (gnutls.connection
1659
 
                       .ClientSession(self.request,
1660
 
                                      gnutls.connection
1661
 
                                      .X509Credentials()))
 
1882
            session = gnutls.connection.ClientSession(
 
1883
                self.request, gnutls.connection.X509Credentials())
1662
1884
            
1663
1885
            # Note: gnutls.connection.X509Credentials is really a
1664
1886
            # generic GnuTLS certificate credentials object so long as
1673
1895
            priority = self.server.gnutls_priority
1674
1896
            if priority is None:
1675
1897
                priority = "NORMAL"
1676
 
            (gnutls.library.functions
1677
 
             .gnutls_priority_set_direct(session._c_object,
1678
 
                                         priority, None))
 
1898
            gnutls.library.functions.gnutls_priority_set_direct(
 
1899
                session._c_object, priority, None)
1679
1900
            
1680
1901
            # Start communication using the Mandos protocol
1681
1902
            # Get protocol number
1683
1904
            logger.debug("Protocol version: %r", line)
1684
1905
            try:
1685
1906
                if int(line.strip().split()[0]) > 1:
1686
 
                    raise RuntimeError
 
1907
                    raise RuntimeError(line)
1687
1908
            except (ValueError, IndexError, RuntimeError) as error:
1688
1909
                logger.error("Unknown protocol version: %s", error)
1689
1910
                return
1701
1922
            approval_required = False
1702
1923
            try:
1703
1924
                try:
1704
 
                    fpr = self.fingerprint(self.peer_certificate
1705
 
                                           (session))
 
1925
                    fpr = self.fingerprint(
 
1926
                        self.peer_certificate(session))
1706
1927
                except (TypeError,
1707
1928
                        gnutls.errors.GNUTLSError) as error:
1708
1929
                    logger.warning("Bad certificate: %s", error)
1723
1944
                while True:
1724
1945
                    if not client.enabled:
1725
1946
                        logger.info("Client %s is disabled",
1726
 
                                       client.name)
 
1947
                                    client.name)
1727
1948
                        if self.server.use_dbus:
1728
1949
                            # Emit D-Bus signal
1729
1950
                            client.Rejected("Disabled")
1738
1959
                        if self.server.use_dbus:
1739
1960
                            # Emit D-Bus signal
1740
1961
                            client.NeedApproval(
1741
 
                                client.approval_delay_milliseconds(),
1742
 
                                client.approved_by_default)
 
1962
                                client.approval_delay.total_seconds()
 
1963
                                * 1000, client.approved_by_default)
1743
1964
                    else:
1744
1965
                        logger.warning("Client %s was not approved",
1745
1966
                                       client.name)
1751
1972
                    #wait until timeout or approved
1752
1973
                    time = datetime.datetime.now()
1753
1974
                    client.changedstate.acquire()
1754
 
                    client.changedstate.wait(
1755
 
                        float(timedelta_to_milliseconds(delay)
1756
 
                              / 1000))
 
1975
                    client.changedstate.wait(delay.total_seconds())
1757
1976
                    client.changedstate.release()
1758
1977
                    time2 = datetime.datetime.now()
1759
1978
                    if (time2 - time) >= delay:
1778
1997
                        logger.warning("gnutls send failed",
1779
1998
                                       exc_info=error)
1780
1999
                        return
1781
 
                    logger.debug("Sent: %d, remaining: %d",
1782
 
                                 sent, len(client.secret)
1783
 
                                 - (sent_size + sent))
 
2000
                    logger.debug("Sent: %d, remaining: %d", sent,
 
2001
                                 len(client.secret) - (sent_size
 
2002
                                                       + sent))
1784
2003
                    sent_size += sent
1785
2004
                
1786
2005
                logger.info("Sending secret to %s", client.name)
1803
2022
    def peer_certificate(session):
1804
2023
        "Return the peer's OpenPGP certificate as a bytestring"
1805
2024
        # If not an OpenPGP certificate...
1806
 
        if (gnutls.library.functions
1807
 
            .gnutls_certificate_type_get(session._c_object)
 
2025
        if (gnutls.library.functions.gnutls_certificate_type_get(
 
2026
                session._c_object)
1808
2027
            != gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1809
2028
            # ...do the normal thing
1810
2029
            return session.peer_certificate
1824
2043
    def fingerprint(openpgp):
1825
2044
        "Convert an OpenPGP bytestring to a hexdigit fingerprint"
1826
2045
        # New GnuTLS "datum" with the OpenPGP public key
1827
 
        datum = (gnutls.library.types
1828
 
                 .gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1829
 
                                             ctypes.POINTER
1830
 
                                             (ctypes.c_ubyte)),
1831
 
                                 ctypes.c_uint(len(openpgp))))
 
2046
        datum = gnutls.library.types.gnutls_datum_t(
 
2047
            ctypes.cast(ctypes.c_char_p(openpgp),
 
2048
                        ctypes.POINTER(ctypes.c_ubyte)),
 
2049
            ctypes.c_uint(len(openpgp)))
1832
2050
        # New empty GnuTLS certificate
1833
2051
        crt = gnutls.library.types.gnutls_openpgp_crt_t()
1834
 
        (gnutls.library.functions
1835
 
         .gnutls_openpgp_crt_init(ctypes.byref(crt)))
 
2052
        gnutls.library.functions.gnutls_openpgp_crt_init(
 
2053
            ctypes.byref(crt))
1836
2054
        # Import the OpenPGP public key into the certificate
1837
 
        (gnutls.library.functions
1838
 
         .gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1839
 
                                    gnutls.library.constants
1840
 
                                    .GNUTLS_OPENPGP_FMT_RAW))
 
2055
        gnutls.library.functions.gnutls_openpgp_crt_import(
 
2056
            crt, ctypes.byref(datum),
 
2057
            gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
1841
2058
        # Verify the self signature in the key
1842
2059
        crtverify = ctypes.c_uint()
1843
 
        (gnutls.library.functions
1844
 
         .gnutls_openpgp_crt_verify_self(crt, 0,
1845
 
                                         ctypes.byref(crtverify)))
 
2060
        gnutls.library.functions.gnutls_openpgp_crt_verify_self(
 
2061
            crt, 0, ctypes.byref(crtverify))
1846
2062
        if crtverify.value != 0:
1847
2063
            gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1848
 
            raise (gnutls.errors.CertificateSecurityError
1849
 
                   ("Verify failed"))
 
2064
            raise gnutls.errors.CertificateSecurityError(
 
2065
                "Verify failed")
1850
2066
        # New buffer for the fingerprint
1851
2067
        buf = ctypes.create_string_buffer(20)
1852
2068
        buf_len = ctypes.c_size_t()
1853
2069
        # Get the fingerprint from the certificate into the buffer
1854
 
        (gnutls.library.functions
1855
 
         .gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1856
 
                                             ctypes.byref(buf_len)))
 
2070
        gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint(
 
2071
            crt, ctypes.byref(buf), ctypes.byref(buf_len))
1857
2072
        # Deinit the certificate
1858
2073
        gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1859
2074
        # Convert the buffer to a Python bytestring
1865
2080
 
1866
2081
class MultiprocessingMixIn(object):
1867
2082
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
 
2083
    
1868
2084
    def sub_process_main(self, request, address):
1869
2085
        try:
1870
2086
            self.finish_request(request, address)
1882
2098
 
1883
2099
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1884
2100
    """ adds a pipe to the MixIn """
 
2101
    
1885
2102
    def process_request(self, request, client_address):
1886
2103
        """Overrides and wraps the original process_request().
1887
2104
        
1896
2113
    
1897
2114
    def add_pipe(self, parent_pipe, proc):
1898
2115
        """Dummy function; override as necessary"""
1899
 
        raise NotImplementedError
 
2116
        raise NotImplementedError()
1900
2117
 
1901
2118
 
1902
2119
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1908
2125
        interface:      None or a network interface name (string)
1909
2126
        use_ipv6:       Boolean; to use IPv6 or not
1910
2127
    """
 
2128
    
1911
2129
    def __init__(self, server_address, RequestHandlerClass,
1912
 
                 interface=None, use_ipv6=True, socketfd=None):
 
2130
                 interface=None,
 
2131
                 use_ipv6=True,
 
2132
                 socketfd=None):
1913
2133
        """If socketfd is set, use that file descriptor instead of
1914
2134
        creating a new one with socket.socket().
1915
2135
        """
1956
2176
                             self.interface)
1957
2177
            else:
1958
2178
                try:
1959
 
                    self.socket.setsockopt(socket.SOL_SOCKET,
1960
 
                                           SO_BINDTODEVICE,
1961
 
                                           str(self.interface + '\0'))
 
2179
                    self.socket.setsockopt(
 
2180
                        socket.SOL_SOCKET, SO_BINDTODEVICE,
 
2181
                        (self.interface + "\0").encode("utf-8"))
1962
2182
                except socket.error as error:
1963
2183
                    if error.errno == errno.EPERM:
1964
2184
                        logger.error("No permission to bind to"
1978
2198
                if self.address_family == socket.AF_INET6:
1979
2199
                    any_address = "::" # in6addr_any
1980
2200
                else:
1981
 
                    any_address = socket.INADDR_ANY
 
2201
                    any_address = "0.0.0.0" # INADDR_ANY
1982
2202
                self.server_address = (any_address,
1983
2203
                                       self.server_address[1])
1984
2204
            elif not self.server_address[1]:
1985
 
                self.server_address = (self.server_address[0],
1986
 
                                       0)
 
2205
                self.server_address = (self.server_address[0], 0)
1987
2206
#                 if self.interface:
1988
2207
#                     self.server_address = (self.server_address[0],
1989
2208
#                                            0, # port
2003
2222
    
2004
2223
    Assumes a gobject.MainLoop event loop.
2005
2224
    """
 
2225
    
2006
2226
    def __init__(self, server_address, RequestHandlerClass,
2007
 
                 interface=None, use_ipv6=True, clients=None,
2008
 
                 gnutls_priority=None, use_dbus=True, socketfd=None):
 
2227
                 interface=None,
 
2228
                 use_ipv6=True,
 
2229
                 clients=None,
 
2230
                 gnutls_priority=None,
 
2231
                 use_dbus=True,
 
2232
                 socketfd=None):
2009
2233
        self.enabled = False
2010
2234
        self.clients = clients
2011
2235
        if self.clients is None:
2017
2241
                                interface = interface,
2018
2242
                                use_ipv6 = use_ipv6,
2019
2243
                                socketfd = socketfd)
 
2244
    
2020
2245
    def server_activate(self):
2021
2246
        if self.enabled:
2022
2247
            return socketserver.TCPServer.server_activate(self)
2026
2251
    
2027
2252
    def add_pipe(self, parent_pipe, proc):
2028
2253
        # Call "handle_ipc" for both data and EOF events
2029
 
        gobject.io_add_watch(parent_pipe.fileno(),
2030
 
                             gobject.IO_IN | gobject.IO_HUP,
2031
 
                             functools.partial(self.handle_ipc,
2032
 
                                               parent_pipe =
2033
 
                                               parent_pipe,
2034
 
                                               proc = proc))
 
2254
        gobject.io_add_watch(
 
2255
            parent_pipe.fileno(),
 
2256
            gobject.IO_IN | gobject.IO_HUP,
 
2257
            functools.partial(self.handle_ipc,
 
2258
                              parent_pipe = parent_pipe,
 
2259
                              proc = proc))
2035
2260
    
2036
 
    def handle_ipc(self, source, condition, parent_pipe=None,
2037
 
                   proc = None, client_object=None):
 
2261
    def handle_ipc(self, source, condition,
 
2262
                   parent_pipe=None,
 
2263
                   proc = None,
 
2264
                   client_object=None):
2038
2265
        # error, or the other end of multiprocessing.Pipe has closed
2039
2266
        if condition & (gobject.IO_ERR | gobject.IO_HUP):
2040
2267
            # Wait for other process to exit
2063
2290
                parent_pipe.send(False)
2064
2291
                return False
2065
2292
            
2066
 
            gobject.io_add_watch(parent_pipe.fileno(),
2067
 
                                 gobject.IO_IN | gobject.IO_HUP,
2068
 
                                 functools.partial(self.handle_ipc,
2069
 
                                                   parent_pipe =
2070
 
                                                   parent_pipe,
2071
 
                                                   proc = proc,
2072
 
                                                   client_object =
2073
 
                                                   client))
 
2293
            gobject.io_add_watch(
 
2294
                parent_pipe.fileno(),
 
2295
                gobject.IO_IN | gobject.IO_HUP,
 
2296
                functools.partial(self.handle_ipc,
 
2297
                                  parent_pipe = parent_pipe,
 
2298
                                  proc = proc,
 
2299
                                  client_object = client))
2074
2300
            parent_pipe.send(True)
2075
2301
            # remove the old hook in favor of the new above hook on
2076
2302
            # same fileno
2082
2308
            
2083
2309
            parent_pipe.send(('data', getattr(client_object,
2084
2310
                                              funcname)(*args,
2085
 
                                                         **kwargs)))
 
2311
                                                        **kwargs)))
2086
2312
        
2087
2313
        if command == 'getattr':
2088
2314
            attrname = request[1]
2089
 
            if callable(client_object.__getattribute__(attrname)):
2090
 
                parent_pipe.send(('function',))
 
2315
            if isinstance(client_object.__getattribute__(attrname),
 
2316
                          collections.Callable):
 
2317
                parent_pipe.send(('function', ))
2091
2318
            else:
2092
 
                parent_pipe.send(('data', client_object
2093
 
                                  .__getattribute__(attrname)))
 
2319
                parent_pipe.send((
 
2320
                    'data', client_object.__getattribute__(attrname)))
2094
2321
        
2095
2322
        if command == 'setattr':
2096
2323
            attrname = request[1]
2127
2354
    # avoid excessive use of external libraries.
2128
2355
    
2129
2356
    # New type for defining tokens, syntax, and semantics all-in-one
2130
 
    Token = collections.namedtuple("Token",
2131
 
                                   ("regexp", # To match token; if
2132
 
                                              # "value" is not None,
2133
 
                                              # must have a "group"
2134
 
                                              # containing digits
2135
 
                                    "value",  # datetime.timedelta or
2136
 
                                              # None
2137
 
                                    "followers")) # Tokens valid after
2138
 
                                                  # this token
 
2357
    Token = collections.namedtuple("Token", (
 
2358
        "regexp",  # To match token; if "value" is not None, must have
 
2359
                   # a "group" containing digits
 
2360
        "value",   # datetime.timedelta or None
 
2361
        "followers"))           # Tokens valid after this token
2139
2362
    # RFC 3339 "duration" tokens, syntax, and semantics; taken from
2140
2363
    # the "duration" ABNF definition in RFC 3339, Appendix A.
2141
2364
    token_end = Token(re.compile(r"$"), None, frozenset())
2142
2365
    token_second = Token(re.compile(r"(\d+)S"),
2143
2366
                         datetime.timedelta(seconds=1),
2144
 
                         frozenset((token_end,)))
 
2367
                         frozenset((token_end, )))
2145
2368
    token_minute = Token(re.compile(r"(\d+)M"),
2146
2369
                         datetime.timedelta(minutes=1),
2147
2370
                         frozenset((token_second, token_end)))
2163
2386
                       frozenset((token_month, token_end)))
2164
2387
    token_week = Token(re.compile(r"(\d+)W"),
2165
2388
                       datetime.timedelta(weeks=1),
2166
 
                       frozenset((token_end,)))
 
2389
                       frozenset((token_end, )))
2167
2390
    token_duration = Token(re.compile(r"P"), None,
2168
2391
                           frozenset((token_year, token_month,
2169
2392
                                      token_day, token_time,
2170
 
                                      token_week))),
 
2393
                                      token_week)))
2171
2394
    # Define starting values
2172
2395
    value = datetime.timedelta() # Value so far
2173
2396
    found_token = None
2174
 
    followers = frozenset(token_duration,) # Following valid tokens
 
2397
    followers = frozenset((token_duration, )) # Following valid tokens
2175
2398
    s = duration                # String left to parse
2176
2399
    # Loop until end token is found
2177
2400
    while found_token is not token_end:
2194
2417
                break
2195
2418
        else:
2196
2419
            # No currently valid tokens were found
2197
 
            raise ValueError("Invalid RFC 3339 duration")
 
2420
            raise ValueError("Invalid RFC 3339 duration: {!r}"
 
2421
                             .format(duration))
2198
2422
    # End token found
2199
2423
    return value
2200
2424
 
2224
2448
    timevalue = datetime.timedelta(0)
2225
2449
    for s in interval.split():
2226
2450
        try:
2227
 
            suffix = unicode(s[-1])
 
2451
            suffix = s[-1]
2228
2452
            value = int(s[:-1])
2229
2453
            if suffix == "d":
2230
2454
                delta = datetime.timedelta(value)
2237
2461
            elif suffix == "w":
2238
2462
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
2239
2463
            else:
2240
 
                raise ValueError("Unknown suffix {0!r}"
2241
 
                                 .format(suffix))
2242
 
        except (ValueError, IndexError) as e:
 
2464
                raise ValueError("Unknown suffix {!r}".format(suffix))
 
2465
        except IndexError as e:
2243
2466
            raise ValueError(*(e.args))
2244
2467
        timevalue += delta
2245
2468
    return timevalue
2261
2484
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2262
2485
        if not stat.S_ISCHR(os.fstat(null).st_mode):
2263
2486
            raise OSError(errno.ENODEV,
2264
 
                          "{0} not a character device"
 
2487
                          "{} not a character device"
2265
2488
                          .format(os.devnull))
2266
2489
        os.dup2(null, sys.stdin.fileno())
2267
2490
        os.dup2(null, sys.stdout.fileno())
2277
2500
    
2278
2501
    parser = argparse.ArgumentParser()
2279
2502
    parser.add_argument("-v", "--version", action="version",
2280
 
                        version = "%(prog)s {0}".format(version),
 
2503
                        version = "%(prog)s {}".format(version),
2281
2504
                        help="show version number and exit")
2282
2505
    parser.add_argument("-i", "--interface", metavar="IF",
2283
2506
                        help="Bind to interface IF")
2289
2512
                        help="Run self-test")
2290
2513
    parser.add_argument("--debug", action="store_true",
2291
2514
                        help="Debug mode; run in foreground and log"
2292
 
                        " to terminal")
 
2515
                        " to terminal", default=None)
2293
2516
    parser.add_argument("--debuglevel", metavar="LEVEL",
2294
2517
                        help="Debug level for stdout output")
2295
2518
    parser.add_argument("--priority", help="GnuTLS"
2302
2525
                        " files")
2303
2526
    parser.add_argument("--no-dbus", action="store_false",
2304
2527
                        dest="use_dbus", help="Do not provide D-Bus"
2305
 
                        " system bus interface")
 
2528
                        " system bus interface", default=None)
2306
2529
    parser.add_argument("--no-ipv6", action="store_false",
2307
 
                        dest="use_ipv6", help="Do not use IPv6")
 
2530
                        dest="use_ipv6", help="Do not use IPv6",
 
2531
                        default=None)
2308
2532
    parser.add_argument("--no-restore", action="store_false",
2309
2533
                        dest="restore", help="Do not restore stored"
2310
 
                        " state")
 
2534
                        " state", default=None)
2311
2535
    parser.add_argument("--socket", type=int,
2312
2536
                        help="Specify a file descriptor to a network"
2313
2537
                        " socket to use instead of creating one")
2314
2538
    parser.add_argument("--statedir", metavar="DIR",
2315
2539
                        help="Directory to save/restore state in")
2316
2540
    parser.add_argument("--foreground", action="store_true",
2317
 
                        help="Run in foreground")
 
2541
                        help="Run in foreground", default=None)
 
2542
    parser.add_argument("--no-zeroconf", action="store_false",
 
2543
                        dest="zeroconf", help="Do not use Zeroconf",
 
2544
                        default=None)
2318
2545
    
2319
2546
    options = parser.parse_args()
2320
2547
    
2321
2548
    if options.check:
2322
2549
        import doctest
2323
 
        doctest.testmod()
2324
 
        sys.exit()
 
2550
        fail_count, test_count = doctest.testmod()
 
2551
        sys.exit(os.EX_OK if fail_count == 0 else 1)
2325
2552
    
2326
2553
    # Default values for config file for server-global settings
2327
2554
    server_defaults = { "interface": "",
2329
2556
                        "port": "",
2330
2557
                        "debug": "False",
2331
2558
                        "priority":
2332
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
 
2559
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
 
2560
                        ":+SIGN-DSA-SHA256",
2333
2561
                        "servicename": "Mandos",
2334
2562
                        "use_dbus": "True",
2335
2563
                        "use_ipv6": "True",
2338
2566
                        "socket": "",
2339
2567
                        "statedir": "/var/lib/mandos",
2340
2568
                        "foreground": "False",
2341
 
                        }
 
2569
                        "zeroconf": "True",
 
2570
                    }
2342
2571
    
2343
2572
    # Parse config file for server-global settings
2344
2573
    server_config = configparser.SafeConfigParser(server_defaults)
2345
2574
    del server_defaults
2346
 
    server_config.read(os.path.join(options.configdir,
2347
 
                                    "mandos.conf"))
 
2575
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
2348
2576
    # Convert the SafeConfigParser object to a dict
2349
2577
    server_settings = server_config.defaults()
2350
2578
    # Use the appropriate methods on the non-string config options
2368
2596
    # Override the settings from the config file with command line
2369
2597
    # options, if set.
2370
2598
    for option in ("interface", "address", "port", "debug",
2371
 
                   "priority", "servicename", "configdir",
2372
 
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
2373
 
                   "statedir", "socket", "foreground"):
 
2599
                   "priority", "servicename", "configdir", "use_dbus",
 
2600
                   "use_ipv6", "debuglevel", "restore", "statedir",
 
2601
                   "socket", "foreground", "zeroconf"):
2374
2602
        value = getattr(options, option)
2375
2603
        if value is not None:
2376
2604
            server_settings[option] = value
2377
2605
    del options
2378
2606
    # Force all strings to be unicode
2379
2607
    for option in server_settings.keys():
2380
 
        if type(server_settings[option]) is str:
2381
 
            server_settings[option] = unicode(server_settings[option])
 
2608
        if isinstance(server_settings[option], bytes):
 
2609
            server_settings[option] = (server_settings[option]
 
2610
                                       .decode("utf-8"))
 
2611
    # Force all boolean options to be boolean
 
2612
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
 
2613
                   "foreground", "zeroconf"):
 
2614
        server_settings[option] = bool(server_settings[option])
2382
2615
    # Debug implies foreground
2383
2616
    if server_settings["debug"]:
2384
2617
        server_settings["foreground"] = True
2386
2619
    
2387
2620
    ##################################################################
2388
2621
    
 
2622
    if (not server_settings["zeroconf"]
 
2623
        and not (server_settings["port"]
 
2624
                 or server_settings["socket"] != "")):
 
2625
        parser.error("Needs port or socket to work without Zeroconf")
 
2626
    
2389
2627
    # For convenience
2390
2628
    debug = server_settings["debug"]
2391
2629
    debuglevel = server_settings["debuglevel"]
2394
2632
    stored_state_path = os.path.join(server_settings["statedir"],
2395
2633
                                     stored_state_file)
2396
2634
    foreground = server_settings["foreground"]
 
2635
    zeroconf = server_settings["zeroconf"]
2397
2636
    
2398
2637
    if debug:
2399
2638
        initlogger(debug, logging.DEBUG)
2405
2644
            initlogger(debug, level)
2406
2645
    
2407
2646
    if server_settings["servicename"] != "Mandos":
2408
 
        syslogger.setFormatter(logging.Formatter
2409
 
                               ('Mandos ({0}) [%(process)d]:'
2410
 
                                ' %(levelname)s: %(message)s'
2411
 
                                .format(server_settings
2412
 
                                        ["servicename"])))
 
2647
        syslogger.setFormatter(
 
2648
            logging.Formatter('Mandos ({}) [%(process)d]:'
 
2649
                              ' %(levelname)s: %(message)s'.format(
 
2650
                                  server_settings["servicename"])))
2413
2651
    
2414
2652
    # Parse config file with clients
2415
2653
    client_config = configparser.SafeConfigParser(Client
2420
2658
    global mandos_dbus_service
2421
2659
    mandos_dbus_service = None
2422
2660
    
2423
 
    tcp_server = MandosServer((server_settings["address"],
2424
 
                               server_settings["port"]),
2425
 
                              ClientHandler,
2426
 
                              interface=(server_settings["interface"]
2427
 
                                         or None),
2428
 
                              use_ipv6=use_ipv6,
2429
 
                              gnutls_priority=
2430
 
                              server_settings["priority"],
2431
 
                              use_dbus=use_dbus,
2432
 
                              socketfd=(server_settings["socket"]
2433
 
                                        or None))
 
2661
    socketfd = None
 
2662
    if server_settings["socket"] != "":
 
2663
        socketfd = server_settings["socket"]
 
2664
    tcp_server = MandosServer(
 
2665
        (server_settings["address"], server_settings["port"]),
 
2666
        ClientHandler,
 
2667
        interface=(server_settings["interface"] or None),
 
2668
        use_ipv6=use_ipv6,
 
2669
        gnutls_priority=server_settings["priority"],
 
2670
        use_dbus=use_dbus,
 
2671
        socketfd=socketfd)
2434
2672
    if not foreground:
2435
 
        pidfilename = "/var/run/mandos.pid"
 
2673
        pidfilename = "/run/mandos.pid"
 
2674
        if not os.path.isdir("/run/."):
 
2675
            pidfilename = "/var/run/mandos.pid"
2436
2676
        pidfile = None
2437
2677
        try:
2438
 
            pidfile = open(pidfilename, "w")
 
2678
            pidfile = codecs.open(pidfilename, "w", encoding="utf-8")
2439
2679
        except IOError as e:
2440
2680
            logger.error("Could not open file %r", pidfilename,
2441
2681
                         exc_info=e)
2455
2695
        os.setuid(uid)
2456
2696
    except OSError as error:
2457
2697
        if error.errno != errno.EPERM:
2458
 
            raise error
 
2698
            raise
2459
2699
    
2460
2700
    if debug:
2461
2701
        # Enable all possible GnuTLS debugging
2468
2708
        def debug_gnutls(level, string):
2469
2709
            logger.debug("GnuTLS: %s", string[:-1])
2470
2710
        
2471
 
        (gnutls.library.functions
2472
 
         .gnutls_global_set_log_function(debug_gnutls))
 
2711
        gnutls.library.functions.gnutls_global_set_log_function(
 
2712
            debug_gnutls)
2473
2713
        
2474
2714
        # Redirect stdin so all checkers get /dev/null
2475
2715
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2495
2735
    if use_dbus:
2496
2736
        try:
2497
2737
            bus_name = dbus.service.BusName("se.recompile.Mandos",
2498
 
                                            bus, do_not_queue=True)
2499
 
            old_bus_name = (dbus.service.BusName
2500
 
                            ("se.bsnet.fukt.Mandos", bus,
2501
 
                             do_not_queue=True))
2502
 
        except dbus.exceptions.NameExistsException as e:
 
2738
                                            bus,
 
2739
                                            do_not_queue=True)
 
2740
            old_bus_name = dbus.service.BusName(
 
2741
                "se.bsnet.fukt.Mandos", bus,
 
2742
                do_not_queue=True)
 
2743
        except dbus.exceptions.DBusException as e:
2503
2744
            logger.error("Disabling D-Bus:", exc_info=e)
2504
2745
            use_dbus = False
2505
2746
            server_settings["use_dbus"] = False
2506
2747
            tcp_server.use_dbus = False
2507
 
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2508
 
    service = AvahiServiceToSyslog(name =
2509
 
                                   server_settings["servicename"],
2510
 
                                   servicetype = "_mandos._tcp",
2511
 
                                   protocol = protocol, bus = bus)
2512
 
    if server_settings["interface"]:
2513
 
        service.interface = (if_nametoindex
2514
 
                             (str(server_settings["interface"])))
 
2748
    if zeroconf:
 
2749
        protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
 
2750
        service = AvahiServiceToSyslog(
 
2751
            name = server_settings["servicename"],
 
2752
            servicetype = "_mandos._tcp",
 
2753
            protocol = protocol,
 
2754
            bus = bus)
 
2755
        if server_settings["interface"]:
 
2756
            service.interface = if_nametoindex(
 
2757
                server_settings["interface"].encode("utf-8"))
2515
2758
    
2516
2759
    global multiprocessing_manager
2517
2760
    multiprocessing_manager = multiprocessing.Manager()
2524
2767
    old_client_settings = {}
2525
2768
    clients_data = {}
2526
2769
    
 
2770
    # This is used to redirect stdout and stderr for checker processes
 
2771
    global wnull
 
2772
    wnull = open(os.devnull, "w") # A writable /dev/null
 
2773
    # Only used if server is running in foreground but not in debug
 
2774
    # mode
 
2775
    if debug or not foreground:
 
2776
        wnull.close()
 
2777
    
2527
2778
    # Get client data and settings from last running state.
2528
2779
    if server_settings["restore"]:
2529
2780
        try:
2530
2781
            with open(stored_state_path, "rb") as stored_state:
2531
 
                clients_data, old_client_settings = (pickle.load
2532
 
                                                     (stored_state))
 
2782
                clients_data, old_client_settings = pickle.load(
 
2783
                    stored_state)
2533
2784
            os.remove(stored_state_path)
2534
2785
        except IOError as e:
2535
2786
            if e.errno == errno.ENOENT:
2536
 
                logger.warning("Could not load persistent state: {0}"
2537
 
                                .format(os.strerror(e.errno)))
 
2787
                logger.warning("Could not load persistent state:"
 
2788
                               " {}".format(os.strerror(e.errno)))
2538
2789
            else:
2539
2790
                logger.critical("Could not load persistent state:",
2540
2791
                                exc_info=e)
2541
2792
                raise
2542
2793
        except EOFError as e:
2543
2794
            logger.warning("Could not load persistent state: "
2544
 
                           "EOFError:", exc_info=e)
 
2795
                           "EOFError:",
 
2796
                           exc_info=e)
2545
2797
    
2546
2798
    with PGPEngine() as pgp:
2547
 
        for client_name, client in clients_data.iteritems():
 
2799
        for client_name, client in clients_data.items():
 
2800
            # Skip removed clients
 
2801
            if client_name not in client_settings:
 
2802
                continue
 
2803
            
2548
2804
            # Decide which value to use after restoring saved state.
2549
2805
            # We have three different values: Old config file,
2550
2806
            # new config file, and saved state.
2555
2811
                    # For each value in new config, check if it
2556
2812
                    # differs from the old config value (Except for
2557
2813
                    # the "secret" attribute)
2558
 
                    if (name != "secret" and
2559
 
                        value != old_client_settings[client_name]
2560
 
                        [name]):
 
2814
                    if (name != "secret"
 
2815
                        and (value !=
 
2816
                             old_client_settings[client_name][name])):
2561
2817
                        client[name] = value
2562
2818
                except KeyError:
2563
2819
                    pass
2564
2820
            
2565
2821
            # Clients who has passed its expire date can still be
2566
 
            # enabled if its last checker was successful.  Clients
 
2822
            # enabled if its last checker was successful.  A Client
2567
2823
            # whose checker succeeded before we stored its state is
2568
2824
            # assumed to have successfully run all checkers during
2569
2825
            # downtime.
2571
2827
                if datetime.datetime.utcnow() >= client["expires"]:
2572
2828
                    if not client["last_checked_ok"]:
2573
2829
                        logger.warning(
2574
 
                            "disabling client {0} - Client never "
2575
 
                            "performed a successful checker"
2576
 
                            .format(client_name))
 
2830
                            "disabling client {} - Client never "
 
2831
                            "performed a successful checker".format(
 
2832
                                client_name))
2577
2833
                        client["enabled"] = False
2578
2834
                    elif client["last_checker_status"] != 0:
2579
2835
                        logger.warning(
2580
 
                            "disabling client {0} - Client "
2581
 
                            "last checker failed with error code {1}"
2582
 
                            .format(client_name,
2583
 
                                    client["last_checker_status"]))
 
2836
                            "disabling client {} - Client last"
 
2837
                            " checker failed with error code"
 
2838
                            " {}".format(
 
2839
                                client_name,
 
2840
                                client["last_checker_status"]))
2584
2841
                        client["enabled"] = False
2585
2842
                    else:
2586
 
                        client["expires"] = (datetime.datetime
2587
 
                                             .utcnow()
2588
 
                                             + client["timeout"])
 
2843
                        client["expires"] = (
 
2844
                            datetime.datetime.utcnow()
 
2845
                            + client["timeout"])
2589
2846
                        logger.debug("Last checker succeeded,"
2590
 
                                     " keeping {0} enabled"
2591
 
                                     .format(client_name))
 
2847
                                     " keeping {} enabled".format(
 
2848
                                         client_name))
2592
2849
            try:
2593
 
                client["secret"] = (
2594
 
                    pgp.decrypt(client["encrypted_secret"],
2595
 
                                client_settings[client_name]
2596
 
                                ["secret"]))
 
2850
                client["secret"] = pgp.decrypt(
 
2851
                    client["encrypted_secret"],
 
2852
                    client_settings[client_name]["secret"])
2597
2853
            except PGPError:
2598
2854
                # If decryption fails, we use secret from new settings
2599
 
                logger.debug("Failed to decrypt {0} old secret"
2600
 
                             .format(client_name))
2601
 
                client["secret"] = (
2602
 
                    client_settings[client_name]["secret"])
 
2855
                logger.debug("Failed to decrypt {} old secret".format(
 
2856
                    client_name))
 
2857
                client["secret"] = (client_settings[client_name]
 
2858
                                    ["secret"])
2603
2859
    
2604
2860
    # Add/remove clients based on new changes made to config
2605
2861
    for client_name in (set(old_client_settings)
2610
2866
        clients_data[client_name] = client_settings[client_name]
2611
2867
    
2612
2868
    # Create all client objects
2613
 
    for client_name, client in clients_data.iteritems():
 
2869
    for client_name, client in clients_data.items():
2614
2870
        tcp_server.clients[client_name] = client_class(
2615
 
            name = client_name, settings = client)
 
2871
            name = client_name,
 
2872
            settings = client,
 
2873
            server_settings = server_settings)
2616
2874
    
2617
2875
    if not tcp_server.clients:
2618
2876
        logger.warning("No clients defined")
2619
2877
    
2620
2878
    if not foreground:
2621
2879
        if pidfile is not None:
 
2880
            pid = os.getpid()
2622
2881
            try:
2623
2882
                with pidfile:
2624
 
                    pid = os.getpid()
2625
 
                    pidfile.write(str(pid) + "\n".encode("utf-8"))
 
2883
                    print(pid, file=pidfile)
2626
2884
            except IOError:
2627
2885
                logger.error("Could not write to file %r with PID %d",
2628
2886
                             pidfilename, pid)
2633
2891
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2634
2892
    
2635
2893
    if use_dbus:
2636
 
        @alternate_dbus_interfaces({"se.recompile.Mandos":
2637
 
                                        "se.bsnet.fukt.Mandos"})
2638
 
        class MandosDBusService(DBusObjectWithProperties):
 
2894
        
 
2895
        @alternate_dbus_interfaces(
 
2896
            { "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
 
2897
        class MandosDBusService(DBusObjectWithObjectManager):
2639
2898
            """A D-Bus proxy object"""
 
2899
            
2640
2900
            def __init__(self):
2641
2901
                dbus.service.Object.__init__(self, bus, "/")
 
2902
            
2642
2903
            _interface = "se.recompile.Mandos"
2643
2904
            
2644
 
            @dbus_interface_annotations(_interface)
2645
 
            def _foo(self):
2646
 
                return { "org.freedesktop.DBus.Property"
2647
 
                         ".EmitsChangedSignal":
2648
 
                             "false"}
2649
 
            
2650
2905
            @dbus.service.signal(_interface, signature="o")
2651
2906
            def ClientAdded(self, objpath):
2652
2907
                "D-Bus signal"
2657
2912
                "D-Bus signal"
2658
2913
                pass
2659
2914
            
 
2915
            @dbus_annotations({"org.freedesktop.DBus.Deprecated":
 
2916
                               "true"})
2660
2917
            @dbus.service.signal(_interface, signature="os")
2661
2918
            def ClientRemoved(self, objpath, name):
2662
2919
                "D-Bus signal"
2663
2920
                pass
2664
2921
            
 
2922
            @dbus_annotations({"org.freedesktop.DBus.Deprecated":
 
2923
                               "true"})
2665
2924
            @dbus.service.method(_interface, out_signature="ao")
2666
2925
            def GetAllClients(self):
2667
2926
                "D-Bus method"
2668
 
                return dbus.Array(c.dbus_object_path
2669
 
                                  for c in
 
2927
                return dbus.Array(c.dbus_object_path for c in
2670
2928
                                  tcp_server.clients.itervalues())
2671
2929
            
 
2930
            @dbus_annotations({"org.freedesktop.DBus.Deprecated":
 
2931
                               "true"})
2672
2932
            @dbus.service.method(_interface,
2673
2933
                                 out_signature="a{oa{sv}}")
2674
2934
            def GetAllClientsWithProperties(self):
2675
2935
                "D-Bus method"
2676
2936
                return dbus.Dictionary(
2677
 
                    ((c.dbus_object_path, c.GetAll(""))
2678
 
                     for c in tcp_server.clients.itervalues()),
 
2937
                    { c.dbus_object_path: c.GetAll(
 
2938
                        "se.recompile.Mandos.Client")
 
2939
                      for c in tcp_server.clients.itervalues() },
2679
2940
                    signature="oa{sv}")
2680
2941
            
2681
2942
            @dbus.service.method(_interface, in_signature="o")
2685
2946
                    if c.dbus_object_path == object_path:
2686
2947
                        del tcp_server.clients[c.name]
2687
2948
                        c.remove_from_connection()
2688
 
                        # Don't signal anything except ClientRemoved
 
2949
                        # Don't signal the disabling
2689
2950
                        c.disable(quiet=True)
2690
 
                        # Emit D-Bus signal
2691
 
                        self.ClientRemoved(object_path, c.name)
 
2951
                        # Emit D-Bus signal for removal
 
2952
                        self.client_removed_signal(c)
2692
2953
                        return
2693
2954
                raise KeyError(object_path)
2694
2955
            
2695
2956
            del _interface
 
2957
            
 
2958
            @dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
 
2959
                                 out_signature = "a{oa{sa{sv}}}")
 
2960
            def GetManagedObjects(self):
 
2961
                """D-Bus method"""
 
2962
                return dbus.Dictionary(
 
2963
                    { client.dbus_object_path:
 
2964
                      dbus.Dictionary(
 
2965
                          { interface: client.GetAll(interface)
 
2966
                            for interface in
 
2967
                                 client._get_all_interface_names()})
 
2968
                      for client in tcp_server.clients.values()})
 
2969
            
 
2970
            def client_added_signal(self, client):
 
2971
                """Send the new standard signal and the old signal"""
 
2972
                if use_dbus:
 
2973
                    # New standard signal
 
2974
                    self.InterfacesAdded(
 
2975
                        client.dbus_object_path,
 
2976
                        dbus.Dictionary(
 
2977
                            { interface: client.GetAll(interface)
 
2978
                              for interface in
 
2979
                              client._get_all_interface_names()}))
 
2980
                    # Old signal
 
2981
                    self.ClientAdded(client.dbus_object_path)
 
2982
            
 
2983
            def client_removed_signal(self, client):
 
2984
                """Send the new standard signal and the old signal"""
 
2985
                if use_dbus:
 
2986
                    # New standard signal
 
2987
                    self.InterfacesRemoved(
 
2988
                        client.dbus_object_path,
 
2989
                        client._get_all_interface_names())
 
2990
                    # Old signal
 
2991
                    self.ClientRemoved(client.dbus_object_path,
 
2992
                                       client.name)
2696
2993
        
2697
2994
        mandos_dbus_service = MandosDBusService()
2698
2995
    
2699
2996
    def cleanup():
2700
2997
        "Cleanup function; run on exit"
2701
 
        service.cleanup()
 
2998
        if zeroconf:
 
2999
            service.cleanup()
2702
3000
        
2703
3001
        multiprocessing.active_children()
 
3002
        wnull.close()
2704
3003
        if not (tcp_server.clients or client_settings):
2705
3004
            return
2706
3005
        
2717
3016
                
2718
3017
                # A list of attributes that can not be pickled
2719
3018
                # + secret.
2720
 
                exclude = set(("bus", "changedstate", "secret",
2721
 
                               "checker"))
2722
 
                for name, typ in (inspect.getmembers
2723
 
                                  (dbus.service.Object)):
 
3019
                exclude = { "bus", "changedstate", "secret",
 
3020
                            "checker", "server_settings" }
 
3021
                for name, typ in inspect.getmembers(dbus.service
 
3022
                                                    .Object):
2724
3023
                    exclude.add(name)
2725
3024
                
2726
3025
                client_dict["encrypted_secret"] = (client
2733
3032
                del client_settings[client.name]["secret"]
2734
3033
        
2735
3034
        try:
2736
 
            with (tempfile.NamedTemporaryFile
2737
 
                  (mode='wb', suffix=".pickle", prefix='clients-',
2738
 
                   dir=os.path.dirname(stored_state_path),
2739
 
                   delete=False)) as stored_state:
 
3035
            with tempfile.NamedTemporaryFile(
 
3036
                    mode='wb',
 
3037
                    suffix=".pickle",
 
3038
                    prefix='clients-',
 
3039
                    dir=os.path.dirname(stored_state_path),
 
3040
                    delete=False) as stored_state:
2740
3041
                pickle.dump((clients, client_settings), stored_state)
2741
 
                tempname=stored_state.name
 
3042
                tempname = stored_state.name
2742
3043
            os.rename(tempname, stored_state_path)
2743
3044
        except (IOError, OSError) as e:
2744
3045
            if not debug:
2747
3048
                except NameError:
2748
3049
                    pass
2749
3050
            if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
2750
 
                logger.warning("Could not save persistent state: {0}"
 
3051
                logger.warning("Could not save persistent state: {}"
2751
3052
                               .format(os.strerror(e.errno)))
2752
3053
            else:
2753
3054
                logger.warning("Could not save persistent state:",
2754
3055
                               exc_info=e)
2755
 
                raise e
 
3056
                raise
2756
3057
        
2757
3058
        # Delete all clients, and settings from config
2758
3059
        while tcp_server.clients:
2759
3060
            name, client = tcp_server.clients.popitem()
2760
3061
            if use_dbus:
2761
3062
                client.remove_from_connection()
2762
 
            # Don't signal anything except ClientRemoved
 
3063
            # Don't signal the disabling
2763
3064
            client.disable(quiet=True)
 
3065
            # Emit D-Bus signal for removal
2764
3066
            if use_dbus:
2765
 
                # Emit D-Bus signal
2766
 
                mandos_dbus_service.ClientRemoved(client
2767
 
                                                  .dbus_object_path,
2768
 
                                                  client.name)
 
3067
                mandos_dbus_service.client_removed_signal(client)
2769
3068
        client_settings.clear()
2770
3069
    
2771
3070
    atexit.register(cleanup)
2772
3071
    
2773
3072
    for client in tcp_server.clients.itervalues():
2774
3073
        if use_dbus:
2775
 
            # Emit D-Bus signal
2776
 
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
 
3074
            # Emit D-Bus signal for adding
 
3075
            mandos_dbus_service.client_added_signal(client)
2777
3076
        # Need to initiate checking of clients
2778
3077
        if client.enabled:
2779
3078
            client.init_checker()
2782
3081
    tcp_server.server_activate()
2783
3082
    
2784
3083
    # Find out what port we got
2785
 
    service.port = tcp_server.socket.getsockname()[1]
 
3084
    if zeroconf:
 
3085
        service.port = tcp_server.socket.getsockname()[1]
2786
3086
    if use_ipv6:
2787
3087
        logger.info("Now listening on address %r, port %d,"
2788
3088
                    " flowinfo %d, scope_id %d",
2794
3094
    #service.interface = tcp_server.socket.getsockname()[3]
2795
3095
    
2796
3096
    try:
2797
 
        # From the Avahi example code
2798
 
        try:
2799
 
            service.activate()
2800
 
        except dbus.exceptions.DBusException as error:
2801
 
            logger.critical("D-Bus Exception", exc_info=error)
2802
 
            cleanup()
2803
 
            sys.exit(1)
2804
 
        # End of Avahi example code
 
3097
        if zeroconf:
 
3098
            # From the Avahi example code
 
3099
            try:
 
3100
                service.activate()
 
3101
            except dbus.exceptions.DBusException as error:
 
3102
                logger.critical("D-Bus Exception", exc_info=error)
 
3103
                cleanup()
 
3104
                sys.exit(1)
 
3105
            # End of Avahi example code
2805
3106
        
2806
3107
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2807
3108
                             lambda *args, **kwargs:
2822
3123
    # Must run before the D-Bus bus name gets deregistered
2823
3124
    cleanup()
2824
3125
 
 
3126
 
2825
3127
if __name__ == '__main__':
2826
3128
    main()