/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: 2015-05-23 20:18:34 UTC
  • mto: This revision was merged to the branch mainline in revision 756.
  • Revision ID: teddy@recompile.se-20150523201834-e89ex4ito93yni8x
mandos: Use multiprocessing module to run checkers.

For a long time, the Mandos server has occasionally logged the message
"ERROR: Child process vanished".  This was never a fatal error, but it
has been annoying and slightly worrying, since a definite cause was
not found.  One potential cause could be the "multiprocessing" and
"subprocess" modules conflicting w.r.t. SIGCHLD.  To avoid this,
change the running of checkers from using subprocess.Popen
asynchronously to instead first create a multiprocessing.Process()
(which is asynchronous) calling a function, and have that function
then call subprocess.call() (which is synchronous).  In this way, the
only thing using any asynchronous subprocesses is the multiprocessing
module.

This makes it necessary to change one small thing in the D-Bus API,
since the subprocesses.call() function does not expose the raw wait(2)
status value.

DBUS-API (CheckerCompleted): Change the second value provided by this
                             D-Bus signal from the raw wait(2) status
                             to the actual terminating signal number.
mandos (subprocess_call_pipe): New function to be called by
                               multiprocessing.Process (starting a
                               separate process).
(Client.last_checker signal): New attribute for signal which
                              terminated last checker.  Like
                              last_checker_status, only not accessible
                              via D-Bus.
(Client.checker_callback): Take new "connection" argument and use it
                           to get returncode; set last_checker_signal.
                           Return False so gobject does not call this
                           callback again.
(Client.start_checker): Start checker using a multiprocessing.Process
                        instead of a subprocess.Popen.
(ClientDBus.checker_callback): Take new "connection" argument.        Call
                               Client.checker_callback early to have
                               it set last_checker_status and
                               last_checker_signal; use those.  Change
                               second value provided to D-Bus signal
                               CheckerCompleted to use
                               last_checker_signal if checker was
                               terminated by signal.
mandos-monitor: Update to reflect DBus API change.
(MandosClientWidget.checker_completed): Take "signal" instead of
                                        "condition" argument.  Use it
                                        accordingly.  Remove dead code
                                        (os.WCOREDUMP case).

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-2014 Teddy Hogeborn
15
 
# Copyright © 2008-2014 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
47
50
import gnutls.library.functions
48
51
import gnutls.library.constants
49
52
import gnutls.library.types
50
 
import ConfigParser as configparser
 
53
try:
 
54
    import ConfigParser as configparser
 
55
except ImportError:
 
56
    import configparser
51
57
import sys
52
58
import re
53
59
import os
62
68
import struct
63
69
import fcntl
64
70
import functools
65
 
import cPickle as pickle
 
71
try:
 
72
    import cPickle as pickle
 
73
except ImportError:
 
74
    import pickle
66
75
import multiprocessing
67
76
import types
68
77
import binascii
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
88
100
    except ImportError:
89
101
        SO_BINDTODEVICE = None
90
102
 
91
 
version = "1.6.3"
 
103
if sys.version_info.major == 2:
 
104
    str = unicode
 
105
 
 
106
version = "1.6.9"
92
107
stored_state_file = "clients.pickle"
93
108
 
94
109
logger = logging.getLogger()
95
 
syslogger = (logging.handlers.SysLogHandler
96
 
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
97
 
              address = str("/dev/log")))
 
110
syslogger = None
98
111
 
99
112
try:
100
 
    if_nametoindex = (ctypes.cdll.LoadLibrary
101
 
                      (ctypes.util.find_library("c"))
102
 
                      .if_nametoindex)
 
113
    if_nametoindex = ctypes.cdll.LoadLibrary(
 
114
        ctypes.util.find_library("c")).if_nametoindex
103
115
except (OSError, AttributeError):
 
116
    
104
117
    def if_nametoindex(interface):
105
118
        "Get an interface index the hard way, i.e. using fcntl()"
106
119
        SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
107
120
        with contextlib.closing(socket.socket()) as s:
108
121
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
109
 
                                struct.pack(str("16s16x"),
110
 
                                            interface))
111
 
        interface_index = struct.unpack(str("I"),
112
 
                                        ifreq[16:20])[0]
 
122
                                struct.pack(b"16s16x", interface))
 
123
        interface_index = struct.unpack("I", ifreq[16:20])[0]
113
124
        return interface_index
114
125
 
115
126
 
116
127
def initlogger(debug, level=logging.WARNING):
117
128
    """init logger and add loglevel"""
118
129
    
 
130
    global syslogger
 
131
    syslogger = (logging.handlers.SysLogHandler(
 
132
        facility = logging.handlers.SysLogHandler.LOG_DAEMON,
 
133
        address = "/dev/log"))
119
134
    syslogger.setFormatter(logging.Formatter
120
135
                           ('Mandos [%(process)d]: %(levelname)s:'
121
136
                            ' %(message)s'))
138
153
 
139
154
class PGPEngine(object):
140
155
    """A simple class for OpenPGP symmetric encryption & decryption"""
 
156
    
141
157
    def __init__(self):
142
158
        self.tempdir = tempfile.mkdtemp(prefix="mandos-")
143
159
        self.gnupgargs = ['--batch',
182
198
    
183
199
    def encrypt(self, data, password):
184
200
        passphrase = self.password_encode(password)
185
 
        with tempfile.NamedTemporaryFile(dir=self.tempdir
186
 
                                         ) as passfile:
 
201
        with tempfile.NamedTemporaryFile(
 
202
                dir=self.tempdir) as passfile:
187
203
            passfile.write(passphrase)
188
204
            passfile.flush()
189
205
            proc = subprocess.Popen(['gpg', '--symmetric',
200
216
    
201
217
    def decrypt(self, data, password):
202
218
        passphrase = self.password_encode(password)
203
 
        with tempfile.NamedTemporaryFile(dir = self.tempdir
204
 
                                         ) as passfile:
 
219
        with tempfile.NamedTemporaryFile(
 
220
                dir = self.tempdir) as passfile:
205
221
            passfile.write(passphrase)
206
222
            passfile.flush()
207
223
            proc = subprocess.Popen(['gpg', '--decrypt',
211
227
                                    stdin = subprocess.PIPE,
212
228
                                    stdout = subprocess.PIPE,
213
229
                                    stderr = subprocess.PIPE)
214
 
            decrypted_plaintext, err = proc.communicate(input
215
 
                                                        = data)
 
230
            decrypted_plaintext, err = proc.communicate(input = data)
216
231
        if proc.returncode != 0:
217
232
            raise PGPError(err)
218
233
        return decrypted_plaintext
221
236
class AvahiError(Exception):
222
237
    def __init__(self, value, *args, **kwargs):
223
238
        self.value = value
224
 
        super(AvahiError, self).__init__(value, *args, **kwargs)
225
 
    def __unicode__(self):
226
 
        return unicode(repr(self.value))
 
239
        return super(AvahiError, self).__init__(value, *args,
 
240
                                                **kwargs)
 
241
 
227
242
 
228
243
class AvahiServiceError(AvahiError):
229
244
    pass
230
245
 
 
246
 
231
247
class AvahiGroupError(AvahiError):
232
248
    pass
233
249
 
253
269
    bus: dbus.SystemBus()
254
270
    """
255
271
    
256
 
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
257
 
                 servicetype = None, port = None, TXT = None,
258
 
                 domain = "", host = "", max_renames = 32768,
259
 
                 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):
260
283
        self.interface = interface
261
284
        self.name = name
262
285
        self.type = servicetype
272
295
        self.bus = bus
273
296
        self.entry_group_state_changed_match = None
274
297
    
275
 
    def rename(self):
 
298
    def rename(self, remove=True):
276
299
        """Derived from the Avahi example code"""
277
300
        if self.rename_count >= self.max_renames:
278
301
            logger.critical("No suitable Zeroconf service name found"
279
302
                            " after %i retries, exiting.",
280
303
                            self.rename_count)
281
304
            raise AvahiServiceError("Too many renames")
282
 
        self.name = unicode(self.server
283
 
                            .GetAlternativeServiceName(self.name))
 
305
        self.name = str(
 
306
            self.server.GetAlternativeServiceName(self.name))
 
307
        self.rename_count += 1
284
308
        logger.info("Changing Zeroconf service name to %r ...",
285
309
                    self.name)
286
 
        self.remove()
 
310
        if remove:
 
311
            self.remove()
287
312
        try:
288
313
            self.add()
289
314
        except dbus.exceptions.DBusException as error:
290
 
            logger.critical("D-Bus Exception", exc_info=error)
291
 
            self.cleanup()
292
 
            os._exit(1)
293
 
        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)
294
323
    
295
324
    def remove(self):
296
325
        """Derived from the Avahi example code"""
334
363
            self.rename()
335
364
        elif state == avahi.ENTRY_GROUP_FAILURE:
336
365
            logger.critical("Avahi: Error in group state changed %s",
337
 
                            unicode(error))
338
 
            raise AvahiGroupError("State changed: {0!s}"
339
 
                                  .format(error))
 
366
                            str(error))
 
367
            raise AvahiGroupError("State changed: {!s}".format(error))
340
368
    
341
369
    def cleanup(self):
342
370
        """Derived from the Avahi example code"""
352
380
    def server_state_changed(self, state, error=None):
353
381
        """Derived from the Avahi example code"""
354
382
        logger.debug("Avahi server state change: %i", state)
355
 
        bad_states = { avahi.SERVER_INVALID:
356
 
                           "Zeroconf server invalid",
357
 
                       avahi.SERVER_REGISTERING: None,
358
 
                       avahi.SERVER_COLLISION:
359
 
                           "Zeroconf server name collision",
360
 
                       avahi.SERVER_FAILURE:
361
 
                           "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
        }
362
389
        if state in bad_states:
363
390
            if bad_states[state] is not None:
364
391
                if error is None:
383
410
                                    follow_name_owner_changes=True),
384
411
                avahi.DBUS_INTERFACE_SERVER)
385
412
        self.server.connect_to_signal("StateChanged",
386
 
                                 self.server_state_changed)
 
413
                                      self.server_state_changed)
387
414
        self.server_state_changed(self.server.GetState())
388
415
 
389
416
 
390
417
class AvahiServiceToSyslog(AvahiService):
391
 
    def rename(self):
 
418
    def rename(self, *args, **kwargs):
392
419
        """Add the new name to the syslog messages"""
393
 
        ret = AvahiService.rename(self)
394
 
        syslogger.setFormatter(logging.Formatter
395
 
                               ('Mandos ({0}) [%(process)d]:'
396
 
                                ' %(levelname)s: %(message)s'
397
 
                                .format(self.name)))
 
420
        ret = AvahiService.rename(self, *args, **kwargs)
 
421
        syslogger.setFormatter(logging.Formatter(
 
422
            'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
 
423
            .format(self.name)))
398
424
        return ret
399
425
 
400
 
 
401
 
def timedelta_to_milliseconds(td):
402
 
    "Convert a datetime.timedelta() to milliseconds"
403
 
    return ((td.days * 24 * 60 * 60 * 1000)
404
 
            + (td.seconds * 1000)
405
 
            + (td.microseconds // 1000))
406
 
 
 
426
def subprocess_call_pipe(connection, # : multiprocessing.Connection
 
427
                         *args, **kwargs):
 
428
    """This function is meant to be called by multiprocessing.Process
 
429
    
 
430
    This function runs a synchronous subprocess.call(), and writes the
 
431
    resulting return code on the provided multiprocessing.Connection.
 
432
    """
 
433
    connection.send(subprocess.call(*args, **kwargs))
 
434
    connection.close()
407
435
 
408
436
class Client(object):
409
437
    """A representation of a client host served by this server.
436
464
    last_checker_status: integer between 0 and 255 reflecting exit
437
465
                         status of last checker. -1 reflects crashed
438
466
                         checker, -2 means no checker completed yet.
 
467
    last_checker_signal: The signal which killed the last checker, if
 
468
                         last_checker_status is -1
439
469
    last_enabled: datetime.datetime(); (UTC) or None
440
470
    name:       string; from the config file, used in log messages and
441
471
                        D-Bus identifiers
454
484
                          "fingerprint", "host", "interval",
455
485
                          "last_approval_request", "last_checked_ok",
456
486
                          "last_enabled", "name", "timeout")
457
 
    client_defaults = { "timeout": "PT5M",
458
 
                        "extended_timeout": "PT15M",
459
 
                        "interval": "PT2M",
460
 
                        "checker": "fping -q -- %%(host)s",
461
 
                        "host": "",
462
 
                        "approval_delay": "PT0S",
463
 
                        "approval_duration": "PT1S",
464
 
                        "approved_by_default": "True",
465
 
                        "enabled": "True",
466
 
                        }
467
 
    
468
 
    def timeout_milliseconds(self):
469
 
        "Return the 'timeout' attribute in milliseconds"
470
 
        return timedelta_to_milliseconds(self.timeout)
471
 
    
472
 
    def extended_timeout_milliseconds(self):
473
 
        "Return the 'extended_timeout' attribute in milliseconds"
474
 
        return timedelta_to_milliseconds(self.extended_timeout)
475
 
    
476
 
    def interval_milliseconds(self):
477
 
        "Return the 'interval' attribute in milliseconds"
478
 
        return timedelta_to_milliseconds(self.interval)
479
 
    
480
 
    def approval_delay_milliseconds(self):
481
 
        return timedelta_to_milliseconds(self.approval_delay)
 
487
    client_defaults = {
 
488
        "timeout": "PT5M",
 
489
        "extended_timeout": "PT15M",
 
490
        "interval": "PT2M",
 
491
        "checker": "fping -q -- %%(host)s",
 
492
        "host": "",
 
493
        "approval_delay": "PT0S",
 
494
        "approval_duration": "PT1S",
 
495
        "approved_by_default": "True",
 
496
        "enabled": "True",
 
497
    }
482
498
    
483
499
    @staticmethod
484
500
    def config_parser(config):
500
516
            client["enabled"] = config.getboolean(client_name,
501
517
                                                  "enabled")
502
518
            
 
519
            # Uppercase and remove spaces from fingerprint for later
 
520
            # comparison purposes with return value from the
 
521
            # fingerprint() function
503
522
            client["fingerprint"] = (section["fingerprint"].upper()
504
523
                                     .replace(" ", ""))
505
524
            if "secret" in section:
510
529
                          "rb") as secfile:
511
530
                    client["secret"] = secfile.read()
512
531
            else:
513
 
                raise TypeError("No secret or secfile for section {0}"
 
532
                raise TypeError("No secret or secfile for section {}"
514
533
                                .format(section))
515
534
            client["timeout"] = string_to_delta(section["timeout"])
516
535
            client["extended_timeout"] = string_to_delta(
533
552
            server_settings = {}
534
553
        self.server_settings = server_settings
535
554
        # adding all client settings
536
 
        for setting, value in settings.iteritems():
 
555
        for setting, value in settings.items():
537
556
            setattr(self, setting, value)
538
557
        
539
558
        if self.enabled:
547
566
            self.expires = None
548
567
        
549
568
        logger.debug("Creating client %r", self.name)
550
 
        # Uppercase and remove spaces from fingerprint for later
551
 
        # comparison purposes with return value from the fingerprint()
552
 
        # function
553
569
        logger.debug("  Fingerprint: %s", self.fingerprint)
554
570
        self.created = settings.get("created",
555
571
                                    datetime.datetime.utcnow())
562
578
        self.current_checker_command = None
563
579
        self.approved = None
564
580
        self.approvals_pending = 0
565
 
        self.changedstate = (multiprocessing_manager
566
 
                             .Condition(multiprocessing_manager
567
 
                                        .Lock()))
568
 
        self.client_structure = [attr for attr in
569
 
                                 self.__dict__.iterkeys()
 
581
        self.changedstate = multiprocessing_manager.Condition(
 
582
            multiprocessing_manager.Lock())
 
583
        self.client_structure = [attr
 
584
                                 for attr in self.__dict__.iterkeys()
570
585
                                 if not attr.startswith("_")]
571
586
        self.client_structure.append("client_structure")
572
587
        
573
 
        for name, t in inspect.getmembers(type(self),
574
 
                                          lambda obj:
575
 
                                              isinstance(obj,
576
 
                                                         property)):
 
588
        for name, t in inspect.getmembers(
 
589
                type(self), lambda obj: isinstance(obj, property)):
577
590
            if not name.startswith("_"):
578
591
                self.client_structure.append(name)
579
592
    
621
634
        # and every interval from then on.
622
635
        if self.checker_initiator_tag is not None:
623
636
            gobject.source_remove(self.checker_initiator_tag)
624
 
        self.checker_initiator_tag = (gobject.timeout_add
625
 
                                      (self.interval_milliseconds(),
626
 
                                       self.start_checker))
 
637
        self.checker_initiator_tag = gobject.timeout_add(
 
638
            int(self.interval.total_seconds() * 1000),
 
639
            self.start_checker)
627
640
        # Schedule a disable() when 'timeout' has passed
628
641
        if self.disable_initiator_tag is not None:
629
642
            gobject.source_remove(self.disable_initiator_tag)
630
 
        self.disable_initiator_tag = (gobject.timeout_add
631
 
                                   (self.timeout_milliseconds(),
632
 
                                    self.disable))
 
643
        self.disable_initiator_tag = gobject.timeout_add(
 
644
            int(self.timeout.total_seconds() * 1000), self.disable)
633
645
        # Also start a new checker *right now*.
634
646
        self.start_checker()
635
647
    
636
 
    def checker_callback(self, pid, condition, command):
 
648
    def checker_callback(self, source, condition,
 
649
                         (connection, command)):
637
650
        """The checker has completed, so take appropriate actions."""
638
651
        self.checker_callback_tag = None
639
652
        self.checker = None
640
 
        if os.WIFEXITED(condition):
641
 
            self.last_checker_status = os.WEXITSTATUS(condition)
 
653
        # Read return code from connection (see subprocess_call_pipe)
 
654
        returncode = connection.recv()
 
655
        connection.close()
 
656
        
 
657
        if returncode >= 0:
 
658
            self.last_checker_status = returncode
 
659
            self.last_checker_signal = None
642
660
            if self.last_checker_status == 0:
643
661
                logger.info("Checker for %(name)s succeeded",
644
662
                            vars(self))
645
663
                self.checked_ok()
646
664
            else:
647
 
                logger.info("Checker for %(name)s failed",
648
 
                            vars(self))
 
665
                logger.info("Checker for %(name)s failed", vars(self))
649
666
        else:
650
667
            self.last_checker_status = -1
 
668
            self.last_checker_signal = -returncode
651
669
            logger.warning("Checker for %(name)s crashed?",
652
670
                           vars(self))
 
671
        return False
653
672
    
654
673
    def checked_ok(self):
655
674
        """Assert that the client has been seen, alive and well."""
656
675
        self.last_checked_ok = datetime.datetime.utcnow()
657
676
        self.last_checker_status = 0
 
677
        self.last_checker_signal = None
658
678
        self.bump_timeout()
659
679
    
660
680
    def bump_timeout(self, timeout=None):
665
685
            gobject.source_remove(self.disable_initiator_tag)
666
686
            self.disable_initiator_tag = None
667
687
        if getattr(self, "enabled", False):
668
 
            self.disable_initiator_tag = (gobject.timeout_add
669
 
                                          (timedelta_to_milliseconds
670
 
                                           (timeout), self.disable))
 
688
            self.disable_initiator_tag = gobject.timeout_add(
 
689
                int(timeout.total_seconds() * 1000), self.disable)
671
690
            self.expires = datetime.datetime.utcnow() + timeout
672
691
    
673
692
    def need_approval(self):
687
706
        # than 'timeout' for the client to be disabled, which is as it
688
707
        # should be.
689
708
        
690
 
        # If a checker exists, make sure it is not a zombie
691
 
        try:
692
 
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
693
 
        except AttributeError:
694
 
            pass
695
 
        except OSError as error:
696
 
            if error.errno != errno.ECHILD:
697
 
                raise
698
 
        else:
699
 
            if pid:
700
 
                logger.warning("Checker was a zombie")
701
 
                gobject.source_remove(self.checker_callback_tag)
702
 
                self.checker_callback(pid, status,
703
 
                                      self.current_checker_command)
 
709
        if self.checker is not None and not self.checker.is_alive():
 
710
            logger.warning("Checker was not alive; joining")
 
711
            self.checker.join()
 
712
            self.checker = None
704
713
        # Start a new checker if needed
705
714
        if self.checker is None:
706
715
            # Escape attributes for the shell
707
 
            escaped_attrs = dict(
708
 
                (attr, re.escape(unicode(getattr(self, attr))))
709
 
                for attr in
710
 
                self.runtime_expansions)
 
716
            escaped_attrs = {
 
717
                attr: re.escape(str(getattr(self, attr)))
 
718
                for attr in self.runtime_expansions }
711
719
            try:
712
720
                command = self.checker_command % escaped_attrs
713
721
            except TypeError as error:
714
722
                logger.error('Could not format string "%s"',
715
 
                             self.checker_command, exc_info=error)
716
 
                return True # Try again later
 
723
                             self.checker_command,
 
724
                             exc_info=error)
 
725
                return True     # Try again later
717
726
            self.current_checker_command = command
718
 
            try:
719
 
                logger.info("Starting checker %r for %s",
720
 
                            command, self.name)
721
 
                # We don't need to redirect stdout and stderr, since
722
 
                # in normal mode, that is already done by daemon(),
723
 
                # and in debug mode we don't want to.  (Stdin is
724
 
                # always replaced by /dev/null.)
725
 
                # The exception is when not debugging but nevertheless
726
 
                # running in the foreground; use the previously
727
 
                # created wnull.
728
 
                popen_args = {}
729
 
                if (not self.server_settings["debug"]
730
 
                    and self.server_settings["foreground"]):
731
 
                    popen_args.update({"stdout": wnull,
732
 
                                       "stderr": wnull })
733
 
                self.checker = subprocess.Popen(command,
734
 
                                                close_fds=True,
735
 
                                                shell=True, cwd="/",
736
 
                                                **popen_args)
737
 
            except OSError as error:
738
 
                logger.error("Failed to start subprocess",
739
 
                             exc_info=error)
740
 
                return True
741
 
            self.checker_callback_tag = (gobject.child_watch_add
742
 
                                         (self.checker.pid,
743
 
                                          self.checker_callback,
744
 
                                          data=command))
745
 
            # The checker may have completed before the gobject
746
 
            # watch was added.  Check for this.
747
 
            try:
748
 
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
749
 
            except OSError as error:
750
 
                if error.errno == errno.ECHILD:
751
 
                    # This should never happen
752
 
                    logger.error("Child process vanished",
753
 
                                 exc_info=error)
754
 
                    return True
755
 
                raise
756
 
            if pid:
757
 
                gobject.source_remove(self.checker_callback_tag)
758
 
                self.checker_callback(pid, status, command)
 
727
            logger.info("Starting checker %r for %s", command,
 
728
                        self.name)
 
729
            # We don't need to redirect stdout and stderr, since
 
730
            # in normal mode, that is already done by daemon(),
 
731
            # and in debug mode we don't want to.  (Stdin is
 
732
            # always replaced by /dev/null.)
 
733
            # The exception is when not debugging but nevertheless
 
734
            # running in the foreground; use the previously
 
735
            # created wnull.
 
736
            popen_args = { "close_fds": True,
 
737
                           "shell": True,
 
738
                           "cwd": "/" }
 
739
            if (not self.server_settings["debug"]
 
740
                and self.server_settings["foreground"]):
 
741
                popen_args.update({"stdout": wnull,
 
742
                                   "stderr": wnull })
 
743
            pipe = multiprocessing.Pipe(duplex=False)
 
744
            self.checker = multiprocessing.Process(
 
745
                target=subprocess_call_pipe, args=(pipe[1], command),
 
746
                kwargs=popen_args)
 
747
            self.checker.start()
 
748
            self.checker_callback_tag = gobject.io_add_watch(
 
749
                pipe[0].fileno(), gobject.IO_IN,
 
750
                self.checker_callback, (pipe[0], command))
759
751
        # Re-run this periodically if run by gobject.timeout_add
760
752
        return True
761
753
    
767
759
        if getattr(self, "checker", None) is None:
768
760
            return
769
761
        logger.debug("Stopping checker for %(name)s", vars(self))
770
 
        try:
771
 
            self.checker.terminate()
772
 
            #time.sleep(0.5)
773
 
            #if self.checker.poll() is None:
774
 
            #    self.checker.kill()
775
 
        except OSError as error:
776
 
            if error.errno != errno.ESRCH: # No such process
777
 
                raise
 
762
        self.checker.terminate()
778
763
        self.checker = None
779
764
 
780
765
 
781
 
def dbus_service_property(dbus_interface, signature="v",
782
 
                          access="readwrite", byte_arrays=False):
 
766
def dbus_service_property(dbus_interface,
 
767
                          signature="v",
 
768
                          access="readwrite",
 
769
                          byte_arrays=False):
783
770
    """Decorators for marking methods of a DBusObjectWithProperties to
784
771
    become properties on the D-Bus.
785
772
    
794
781
    # "Set" method, so we fail early here:
795
782
    if byte_arrays and signature != "ay":
796
783
        raise ValueError("Byte arrays not supported for non-'ay'"
797
 
                         " signature {0!r}".format(signature))
 
784
                         " signature {!r}".format(signature))
 
785
    
798
786
    def decorator(func):
799
787
        func._dbus_is_property = True
800
788
        func._dbus_interface = dbus_interface
805
793
            func._dbus_name = func._dbus_name[:-14]
806
794
        func._dbus_get_args_options = {'byte_arrays': byte_arrays }
807
795
        return func
 
796
    
808
797
    return decorator
809
798
 
810
799
 
819
808
                "org.freedesktop.DBus.Property.EmitsChangedSignal":
820
809
                    "false"}
821
810
    """
 
811
    
822
812
    def decorator(func):
823
813
        func._dbus_is_interface = True
824
814
        func._dbus_interface = dbus_interface
825
815
        func._dbus_name = dbus_interface
826
816
        return func
 
817
    
827
818
    return decorator
828
819
 
829
820
 
831
822
    """Decorator to annotate D-Bus methods, signals or properties
832
823
    Usage:
833
824
    
 
825
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
 
826
                       "org.freedesktop.DBus.Property."
 
827
                       "EmitsChangedSignal": "false"})
834
828
    @dbus_service_property("org.example.Interface", signature="b",
835
829
                           access="r")
836
 
    @dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
837
 
                        "org.freedesktop.DBus.Property."
838
 
                        "EmitsChangedSignal": "false"})
839
830
    def Property_dbus_property(self):
840
831
        return dbus.Boolean(False)
841
832
    """
 
833
    
842
834
    def decorator(func):
843
835
        func._dbus_annotations = annotations
844
836
        return func
 
837
    
845
838
    return decorator
846
839
 
847
840
 
848
841
class DBusPropertyException(dbus.exceptions.DBusException):
849
842
    """A base class for D-Bus property-related exceptions
850
843
    """
851
 
    def __unicode__(self):
852
 
        return unicode(str(self))
 
844
    pass
853
845
 
854
846
 
855
847
class DBusPropertyAccessException(DBusPropertyException):
879
871
        If called like _is_dbus_thing("method") it returns a function
880
872
        suitable for use as predicate to inspect.getmembers().
881
873
        """
882
 
        return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
 
874
        return lambda obj: getattr(obj, "_dbus_is_{}".format(thing),
883
875
                                   False)
884
876
    
885
877
    def _get_all_dbus_things(self, thing):
886
878
        """Returns a generator of (name, attribute) pairs
887
879
        """
888
 
        return ((getattr(athing.__get__(self), "_dbus_name",
889
 
                         name),
 
880
        return ((getattr(athing.__get__(self), "_dbus_name", name),
890
881
                 athing.__get__(self))
891
882
                for cls in self.__class__.__mro__
892
883
                for name, athing in
893
 
                inspect.getmembers(cls,
894
 
                                   self._is_dbus_thing(thing)))
 
884
                inspect.getmembers(cls, self._is_dbus_thing(thing)))
895
885
    
896
886
    def _get_dbus_property(self, interface_name, property_name):
897
887
        """Returns a bound method if one exists which is a D-Bus
898
888
        property with the specified name and interface.
899
889
        """
900
 
        for cls in  self.__class__.__mro__:
901
 
            for name, value in (inspect.getmembers
902
 
                                (cls,
903
 
                                 self._is_dbus_thing("property"))):
 
890
        for cls in self.__class__.__mro__:
 
891
            for name, value in inspect.getmembers(
 
892
                    cls, self._is_dbus_thing("property")):
904
893
                if (value._dbus_name == property_name
905
894
                    and value._dbus_interface == interface_name):
906
895
                    return value.__get__(self)
907
896
        
908
897
        # No such property
909
 
        raise DBusPropertyNotFound(self.dbus_object_path + ":"
910
 
                                   + interface_name + "."
911
 
                                   + property_name)
 
898
        raise DBusPropertyNotFound("{}:{}.{}".format(
 
899
            self.dbus_object_path, interface_name, property_name))
912
900
    
913
 
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
 
901
    @dbus.service.method(dbus.PROPERTIES_IFACE,
 
902
                         in_signature="ss",
914
903
                         out_signature="v")
915
904
    def Get(self, interface_name, property_name):
916
905
        """Standard D-Bus property Get() method, see D-Bus standard.
935
924
            # signatures other than "ay".
936
925
            if prop._dbus_signature != "ay":
937
926
                raise ValueError("Byte arrays not supported for non-"
938
 
                                 "'ay' signature {0!r}"
 
927
                                 "'ay' signature {!r}"
939
928
                                 .format(prop._dbus_signature))
940
929
            value = dbus.ByteArray(b''.join(chr(byte)
941
930
                                            for byte in value))
942
931
        prop(value)
943
932
    
944
 
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
 
933
    @dbus.service.method(dbus.PROPERTIES_IFACE,
 
934
                         in_signature="s",
945
935
                         out_signature="a{sv}")
946
936
    def GetAll(self, interface_name):
947
937
        """Standard D-Bus property GetAll() method, see D-Bus
962
952
            if not hasattr(value, "variant_level"):
963
953
                properties[name] = value
964
954
                continue
965
 
            properties[name] = type(value)(value, variant_level=
966
 
                                           value.variant_level+1)
 
955
            properties[name] = type(value)(
 
956
                value, variant_level = value.variant_level + 1)
967
957
        return dbus.Dictionary(properties, signature="sv")
968
958
    
 
959
    @dbus.service.signal(dbus.PROPERTIES_IFACE, signature="sa{sv}as")
 
960
    def PropertiesChanged(self, interface_name, changed_properties,
 
961
                          invalidated_properties):
 
962
        """Standard D-Bus PropertiesChanged() signal, see D-Bus
 
963
        standard.
 
964
        """
 
965
        pass
 
966
    
969
967
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
970
968
                         out_signature="s",
971
969
                         path_keyword='object_path',
979
977
                                                   connection)
980
978
        try:
981
979
            document = xml.dom.minidom.parseString(xmlstring)
 
980
            
982
981
            def make_tag(document, name, prop):
983
982
                e = document.createElement("property")
984
983
                e.setAttribute("name", name)
985
984
                e.setAttribute("type", prop._dbus_signature)
986
985
                e.setAttribute("access", prop._dbus_access)
987
986
                return e
 
987
            
988
988
            for if_tag in document.getElementsByTagName("interface"):
989
989
                # Add property tags
990
990
                for tag in (make_tag(document, name, prop)
1002
1002
                            if (name == tag.getAttribute("name")
1003
1003
                                and prop._dbus_interface
1004
1004
                                == if_tag.getAttribute("name")):
1005
 
                                annots.update(getattr
1006
 
                                              (prop,
1007
 
                                               "_dbus_annotations",
1008
 
                                               {}))
1009
 
                        for name, value in annots.iteritems():
 
1005
                                annots.update(getattr(
 
1006
                                    prop, "_dbus_annotations", {}))
 
1007
                        for name, value in annots.items():
1010
1008
                            ann_tag = document.createElement(
1011
1009
                                "annotation")
1012
1010
                            ann_tag.setAttribute("name", name)
1015
1013
                # Add interface annotation tags
1016
1014
                for annotation, value in dict(
1017
1015
                    itertools.chain.from_iterable(
1018
 
                        annotations().iteritems()
1019
 
                        for name, annotations in
1020
 
                        self._get_all_dbus_things("interface")
 
1016
                        annotations().items()
 
1017
                        for name, annotations
 
1018
                        in self._get_all_dbus_things("interface")
1021
1019
                        if name == if_tag.getAttribute("name")
1022
 
                        )).iteritems():
 
1020
                        )).items():
1023
1021
                    ann_tag = document.createElement("annotation")
1024
1022
                    ann_tag.setAttribute("name", annotation)
1025
1023
                    ann_tag.setAttribute("value", value)
1052
1050
    """Convert a UTC datetime.datetime() to a D-Bus type."""
1053
1051
    if dt is None:
1054
1052
        return dbus.String("", variant_level = variant_level)
1055
 
    return dbus.String(dt.isoformat(),
1056
 
                       variant_level=variant_level)
 
1053
    return dbus.String(dt.isoformat(), variant_level=variant_level)
1057
1054
 
1058
1055
 
1059
1056
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1079
1076
    (from DBusObjectWithProperties) and interfaces (from the
1080
1077
    dbus_interface_annotations decorator).
1081
1078
    """
 
1079
    
1082
1080
    def wrapper(cls):
1083
1081
        for orig_interface_name, alt_interface_name in (
1084
 
            alt_interface_names.iteritems()):
 
1082
                alt_interface_names.items()):
1085
1083
            attr = {}
1086
1084
            interface_names = set()
1087
1085
            # Go though all attributes of the class
1089
1087
                # Ignore non-D-Bus attributes, and D-Bus attributes
1090
1088
                # with the wrong interface name
1091
1089
                if (not hasattr(attribute, "_dbus_interface")
1092
 
                    or not attribute._dbus_interface
1093
 
                    .startswith(orig_interface_name)):
 
1090
                    or not attribute._dbus_interface.startswith(
 
1091
                        orig_interface_name)):
1094
1092
                    continue
1095
1093
                # Create an alternate D-Bus interface name based on
1096
1094
                # the current name
1097
 
                alt_interface = (attribute._dbus_interface
1098
 
                                 .replace(orig_interface_name,
1099
 
                                          alt_interface_name))
 
1095
                alt_interface = attribute._dbus_interface.replace(
 
1096
                    orig_interface_name, alt_interface_name)
1100
1097
                interface_names.add(alt_interface)
1101
1098
                # Is this a D-Bus signal?
1102
1099
                if getattr(attribute, "_dbus_is_signal", False):
1103
1100
                    # Extract the original non-method undecorated
1104
1101
                    # function by black magic
1105
1102
                    nonmethod_func = (dict(
1106
 
                            zip(attribute.func_code.co_freevars,
1107
 
                                attribute.__closure__))["func"]
1108
 
                                      .cell_contents)
 
1103
                        zip(attribute.func_code.co_freevars,
 
1104
                            attribute.__closure__))
 
1105
                                      ["func"].cell_contents)
1109
1106
                    # Create a new, but exactly alike, function
1110
1107
                    # object, and decorate it to be a new D-Bus signal
1111
1108
                    # with the alternate D-Bus interface name
1112
 
                    new_function = (dbus.service.signal
1113
 
                                    (alt_interface,
1114
 
                                     attribute._dbus_signature)
 
1109
                    new_function = (dbus.service.signal(
 
1110
                        alt_interface, attribute._dbus_signature)
1115
1111
                                    (types.FunctionType(
1116
 
                                nonmethod_func.func_code,
1117
 
                                nonmethod_func.func_globals,
1118
 
                                nonmethod_func.func_name,
1119
 
                                nonmethod_func.func_defaults,
1120
 
                                nonmethod_func.func_closure)))
 
1112
                                        nonmethod_func.func_code,
 
1113
                                        nonmethod_func.func_globals,
 
1114
                                        nonmethod_func.func_name,
 
1115
                                        nonmethod_func.func_defaults,
 
1116
                                        nonmethod_func.func_closure)))
1121
1117
                    # Copy annotations, if any
1122
1118
                    try:
1123
 
                        new_function._dbus_annotations = (
1124
 
                            dict(attribute._dbus_annotations))
 
1119
                        new_function._dbus_annotations = dict(
 
1120
                            attribute._dbus_annotations)
1125
1121
                    except AttributeError:
1126
1122
                        pass
1127
1123
                    # Define a creator of a function to call both the
1132
1128
                        """This function is a scope container to pass
1133
1129
                        func1 and func2 to the "call_both" function
1134
1130
                        outside of its arguments"""
 
1131
                        
1135
1132
                        def call_both(*args, **kwargs):
1136
1133
                            """This function will emit two D-Bus
1137
1134
                            signals by calling func1 and func2"""
1138
1135
                            func1(*args, **kwargs)
1139
1136
                            func2(*args, **kwargs)
 
1137
                        
1140
1138
                        return call_both
1141
1139
                    # Create the "call_both" function and add it to
1142
1140
                    # the class
1147
1145
                    # object.  Decorate it to be a new D-Bus method
1148
1146
                    # with the alternate D-Bus interface name.  Add it
1149
1147
                    # to the class.
1150
 
                    attr[attrname] = (dbus.service.method
1151
 
                                      (alt_interface,
1152
 
                                       attribute._dbus_in_signature,
1153
 
                                       attribute._dbus_out_signature)
1154
 
                                      (types.FunctionType
1155
 
                                       (attribute.func_code,
1156
 
                                        attribute.func_globals,
1157
 
                                        attribute.func_name,
1158
 
                                        attribute.func_defaults,
1159
 
                                        attribute.func_closure)))
 
1148
                    attr[attrname] = (
 
1149
                        dbus.service.method(
 
1150
                            alt_interface,
 
1151
                            attribute._dbus_in_signature,
 
1152
                            attribute._dbus_out_signature)
 
1153
                        (types.FunctionType(attribute.func_code,
 
1154
                                            attribute.func_globals,
 
1155
                                            attribute.func_name,
 
1156
                                            attribute.func_defaults,
 
1157
                                            attribute.func_closure)))
1160
1158
                    # Copy annotations, if any
1161
1159
                    try:
1162
 
                        attr[attrname]._dbus_annotations = (
1163
 
                            dict(attribute._dbus_annotations))
 
1160
                        attr[attrname]._dbus_annotations = dict(
 
1161
                            attribute._dbus_annotations)
1164
1162
                    except AttributeError:
1165
1163
                        pass
1166
1164
                # Is this a D-Bus property?
1169
1167
                    # object, and decorate it to be a new D-Bus
1170
1168
                    # property with the alternate D-Bus interface
1171
1169
                    # name.  Add it to the class.
1172
 
                    attr[attrname] = (dbus_service_property
1173
 
                                      (alt_interface,
1174
 
                                       attribute._dbus_signature,
1175
 
                                       attribute._dbus_access,
1176
 
                                       attribute
1177
 
                                       ._dbus_get_args_options
1178
 
                                       ["byte_arrays"])
1179
 
                                      (types.FunctionType
1180
 
                                       (attribute.func_code,
1181
 
                                        attribute.func_globals,
1182
 
                                        attribute.func_name,
1183
 
                                        attribute.func_defaults,
1184
 
                                        attribute.func_closure)))
 
1170
                    attr[attrname] = (dbus_service_property(
 
1171
                        alt_interface, attribute._dbus_signature,
 
1172
                        attribute._dbus_access,
 
1173
                        attribute._dbus_get_args_options
 
1174
                        ["byte_arrays"])
 
1175
                                      (types.FunctionType(
 
1176
                                          attribute.func_code,
 
1177
                                          attribute.func_globals,
 
1178
                                          attribute.func_name,
 
1179
                                          attribute.func_defaults,
 
1180
                                          attribute.func_closure)))
1185
1181
                    # Copy annotations, if any
1186
1182
                    try:
1187
 
                        attr[attrname]._dbus_annotations = (
1188
 
                            dict(attribute._dbus_annotations))
 
1183
                        attr[attrname]._dbus_annotations = dict(
 
1184
                            attribute._dbus_annotations)
1189
1185
                    except AttributeError:
1190
1186
                        pass
1191
1187
                # Is this a D-Bus interface?
1194
1190
                    # object.  Decorate it to be a new D-Bus interface
1195
1191
                    # with the alternate D-Bus interface name.  Add it
1196
1192
                    # to the class.
1197
 
                    attr[attrname] = (dbus_interface_annotations
1198
 
                                      (alt_interface)
1199
 
                                      (types.FunctionType
1200
 
                                       (attribute.func_code,
1201
 
                                        attribute.func_globals,
1202
 
                                        attribute.func_name,
1203
 
                                        attribute.func_defaults,
1204
 
                                        attribute.func_closure)))
 
1193
                    attr[attrname] = (
 
1194
                        dbus_interface_annotations(alt_interface)
 
1195
                        (types.FunctionType(attribute.func_code,
 
1196
                                            attribute.func_globals,
 
1197
                                            attribute.func_name,
 
1198
                                            attribute.func_defaults,
 
1199
                                            attribute.func_closure)))
1205
1200
            if deprecate:
1206
1201
                # Deprecate all alternate interfaces
1207
 
                iname="_AlternateDBusNames_interface_annotation{0}"
 
1202
                iname="_AlternateDBusNames_interface_annotation{}"
1208
1203
                for interface_name in interface_names:
 
1204
                    
1209
1205
                    @dbus_interface_annotations(interface_name)
1210
1206
                    def func(self):
1211
1207
                        return { "org.freedesktop.DBus.Deprecated":
1212
 
                                     "true" }
 
1208
                                 "true" }
1213
1209
                    # Find an unused name
1214
1210
                    for aname in (iname.format(i)
1215
1211
                                  for i in itertools.count()):
1219
1215
            if interface_names:
1220
1216
                # Replace the class with a new subclass of it with
1221
1217
                # methods, signals, etc. as created above.
1222
 
                cls = type(b"{0}Alternate".format(cls.__name__),
1223
 
                           (cls,), attr)
 
1218
                cls = type(b"{}Alternate".format(cls.__name__),
 
1219
                           (cls, ), attr)
1224
1220
        return cls
 
1221
    
1225
1222
    return wrapper
1226
1223
 
1227
1224
 
1228
1225
@alternate_dbus_interfaces({"se.recompile.Mandos":
1229
 
                                "se.bsnet.fukt.Mandos"})
 
1226
                            "se.bsnet.fukt.Mandos"})
1230
1227
class ClientDBus(Client, DBusObjectWithProperties):
1231
1228
    """A Client class using D-Bus
1232
1229
    
1236
1233
    """
1237
1234
    
1238
1235
    runtime_expansions = (Client.runtime_expansions
1239
 
                          + ("dbus_object_path",))
 
1236
                          + ("dbus_object_path", ))
 
1237
    
 
1238
    _interface = "se.recompile.Mandos.Client"
1240
1239
    
1241
1240
    # dbus.service.Object doesn't use super(), so we can't either.
1242
1241
    
1245
1244
        Client.__init__(self, *args, **kwargs)
1246
1245
        # Only now, when this client is initialized, can it show up on
1247
1246
        # the D-Bus
1248
 
        client_object_name = unicode(self.name).translate(
 
1247
        client_object_name = str(self.name).translate(
1249
1248
            {ord("."): ord("_"),
1250
1249
             ord("-"): ord("_")})
1251
 
        self.dbus_object_path = (dbus.ObjectPath
1252
 
                                 ("/clients/" + client_object_name))
 
1250
        self.dbus_object_path = dbus.ObjectPath(
 
1251
            "/clients/" + client_object_name)
1253
1252
        DBusObjectWithProperties.__init__(self, self.bus,
1254
1253
                                          self.dbus_object_path)
1255
1254
    
1256
 
    def notifychangeproperty(transform_func,
1257
 
                             dbus_name, type_func=lambda x: x,
1258
 
                             variant_level=1):
 
1255
    def notifychangeproperty(transform_func, dbus_name,
 
1256
                             type_func=lambda x: x,
 
1257
                             variant_level=1,
 
1258
                             invalidate_only=False,
 
1259
                             _interface=_interface):
1259
1260
        """ Modify a variable so that it's a property which announces
1260
1261
        its changes to DBus.
1261
1262
        
1266
1267
                   to the D-Bus.  Default: no transform
1267
1268
        variant_level: D-Bus variant level.  Default: 1
1268
1269
        """
1269
 
        attrname = "_{0}".format(dbus_name)
 
1270
        attrname = "_{}".format(dbus_name)
 
1271
        
1270
1272
        def setter(self, value):
1271
1273
            if hasattr(self, "dbus_object_path"):
1272
1274
                if (not hasattr(self, attrname) or
1273
1275
                    type_func(getattr(self, attrname, None))
1274
1276
                    != type_func(value)):
1275
 
                    dbus_value = transform_func(type_func(value),
1276
 
                                                variant_level
1277
 
                                                =variant_level)
1278
 
                    self.PropertyChanged(dbus.String(dbus_name),
1279
 
                                         dbus_value)
 
1277
                    if invalidate_only:
 
1278
                        self.PropertiesChanged(
 
1279
                            _interface, dbus.Dictionary(),
 
1280
                            dbus.Array((dbus_name, )))
 
1281
                    else:
 
1282
                        dbus_value = transform_func(
 
1283
                            type_func(value),
 
1284
                            variant_level = variant_level)
 
1285
                        self.PropertyChanged(dbus.String(dbus_name),
 
1286
                                             dbus_value)
 
1287
                        self.PropertiesChanged(
 
1288
                            _interface,
 
1289
                            dbus.Dictionary({ dbus.String(dbus_name):
 
1290
                                              dbus_value }),
 
1291
                            dbus.Array())
1280
1292
            setattr(self, attrname, value)
1281
1293
        
1282
1294
        return property(lambda self: getattr(self, attrname), setter)
1288
1300
    enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1289
1301
    last_enabled = notifychangeproperty(datetime_to_dbus,
1290
1302
                                        "LastEnabled")
1291
 
    checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1292
 
                                   type_func = lambda checker:
1293
 
                                       checker is not None)
 
1303
    checker = notifychangeproperty(
 
1304
        dbus.Boolean, "CheckerRunning",
 
1305
        type_func = lambda checker: checker is not None)
1294
1306
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
1295
1307
                                           "LastCheckedOK")
1296
1308
    last_checker_status = notifychangeproperty(dbus.Int16,
1299
1311
        datetime_to_dbus, "LastApprovalRequest")
1300
1312
    approved_by_default = notifychangeproperty(dbus.Boolean,
1301
1313
                                               "ApprovedByDefault")
1302
 
    approval_delay = notifychangeproperty(dbus.UInt64,
1303
 
                                          "ApprovalDelay",
1304
 
                                          type_func =
1305
 
                                          timedelta_to_milliseconds)
 
1314
    approval_delay = notifychangeproperty(
 
1315
        dbus.UInt64, "ApprovalDelay",
 
1316
        type_func = lambda td: td.total_seconds() * 1000)
1306
1317
    approval_duration = notifychangeproperty(
1307
1318
        dbus.UInt64, "ApprovalDuration",
1308
 
        type_func = timedelta_to_milliseconds)
 
1319
        type_func = lambda td: td.total_seconds() * 1000)
1309
1320
    host = notifychangeproperty(dbus.String, "Host")
1310
 
    timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1311
 
                                   type_func =
1312
 
                                   timedelta_to_milliseconds)
 
1321
    timeout = notifychangeproperty(
 
1322
        dbus.UInt64, "Timeout",
 
1323
        type_func = lambda td: td.total_seconds() * 1000)
1313
1324
    extended_timeout = notifychangeproperty(
1314
1325
        dbus.UInt64, "ExtendedTimeout",
1315
 
        type_func = timedelta_to_milliseconds)
1316
 
    interval = notifychangeproperty(dbus.UInt64,
1317
 
                                    "Interval",
1318
 
                                    type_func =
1319
 
                                    timedelta_to_milliseconds)
 
1326
        type_func = lambda td: td.total_seconds() * 1000)
 
1327
    interval = notifychangeproperty(
 
1328
        dbus.UInt64, "Interval",
 
1329
        type_func = lambda td: td.total_seconds() * 1000)
1320
1330
    checker_command = notifychangeproperty(dbus.String, "Checker")
 
1331
    secret = notifychangeproperty(dbus.ByteArray, "Secret",
 
1332
                                  invalidate_only=True)
1321
1333
    
1322
1334
    del notifychangeproperty
1323
1335
    
1330
1342
            DBusObjectWithProperties.__del__(self, *args, **kwargs)
1331
1343
        Client.__del__(self, *args, **kwargs)
1332
1344
    
1333
 
    def checker_callback(self, pid, condition, command,
1334
 
                         *args, **kwargs):
1335
 
        self.checker_callback_tag = None
1336
 
        self.checker = None
1337
 
        if os.WIFEXITED(condition):
1338
 
            exitstatus = os.WEXITSTATUS(condition)
 
1345
    def checker_callback(self, source, condition,
 
1346
                         (connection, command), *args, **kwargs):
 
1347
        ret = Client.checker_callback(self, source, condition,
 
1348
                                      (connection, command), *args,
 
1349
                                      **kwargs)
 
1350
        exitstatus = self.last_checker_status
 
1351
        if exitstatus >= 0:
1339
1352
            # Emit D-Bus signal
1340
1353
            self.CheckerCompleted(dbus.Int16(exitstatus),
1341
 
                                  dbus.Int64(condition),
 
1354
                                  dbus.Int64(0),
1342
1355
                                  dbus.String(command))
1343
1356
        else:
1344
1357
            # Emit D-Bus signal
1345
1358
            self.CheckerCompleted(dbus.Int16(-1),
1346
 
                                  dbus.Int64(condition),
 
1359
                                  dbus.Int64(
 
1360
                                      self.last_checker_signal),
1347
1361
                                  dbus.String(command))
1348
 
        
1349
 
        return Client.checker_callback(self, pid, condition, command,
1350
 
                                       *args, **kwargs)
 
1362
        return ret
1351
1363
    
1352
1364
    def start_checker(self, *args, **kwargs):
1353
 
        old_checker = self.checker
1354
 
        if self.checker is not None:
1355
 
            old_checker_pid = self.checker.pid
1356
 
        else:
1357
 
            old_checker_pid = None
 
1365
        old_checker_pid = getattr(self.checker, "pid", None)
1358
1366
        r = Client.start_checker(self, *args, **kwargs)
1359
1367
        # Only if new checker process was started
1360
1368
        if (self.checker is not None
1369
1377
    
1370
1378
    def approve(self, value=True):
1371
1379
        self.approved = value
1372
 
        gobject.timeout_add(timedelta_to_milliseconds
1373
 
                            (self.approval_duration),
1374
 
                            self._reset_approved)
 
1380
        gobject.timeout_add(int(self.approval_duration.total_seconds()
 
1381
                                * 1000), self._reset_approved)
1375
1382
        self.send_changedstate()
1376
1383
    
1377
1384
    ## D-Bus methods, signals & properties
1378
 
    _interface = "se.recompile.Mandos.Client"
1379
1385
    
1380
1386
    ## Interfaces
1381
1387
    
1382
 
    @dbus_interface_annotations(_interface)
1383
 
    def _foo(self):
1384
 
        return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
1385
 
                     "false"}
1386
 
    
1387
1388
    ## Signals
1388
1389
    
1389
1390
    # CheckerCompleted - signal
1399
1400
        pass
1400
1401
    
1401
1402
    # PropertyChanged - signal
 
1403
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1402
1404
    @dbus.service.signal(_interface, signature="sv")
1403
1405
    def PropertyChanged(self, property, value):
1404
1406
        "D-Bus signal"
1468
1470
        return dbus.Boolean(bool(self.approvals_pending))
1469
1471
    
1470
1472
    # ApprovedByDefault - property
1471
 
    @dbus_service_property(_interface, signature="b",
 
1473
    @dbus_service_property(_interface,
 
1474
                           signature="b",
1472
1475
                           access="readwrite")
1473
1476
    def ApprovedByDefault_dbus_property(self, value=None):
1474
1477
        if value is None:       # get
1476
1479
        self.approved_by_default = bool(value)
1477
1480
    
1478
1481
    # ApprovalDelay - property
1479
 
    @dbus_service_property(_interface, signature="t",
 
1482
    @dbus_service_property(_interface,
 
1483
                           signature="t",
1480
1484
                           access="readwrite")
1481
1485
    def ApprovalDelay_dbus_property(self, value=None):
1482
1486
        if value is None:       # get
1483
 
            return dbus.UInt64(self.approval_delay_milliseconds())
 
1487
            return dbus.UInt64(self.approval_delay.total_seconds()
 
1488
                               * 1000)
1484
1489
        self.approval_delay = datetime.timedelta(0, 0, 0, value)
1485
1490
    
1486
1491
    # ApprovalDuration - property
1487
 
    @dbus_service_property(_interface, signature="t",
 
1492
    @dbus_service_property(_interface,
 
1493
                           signature="t",
1488
1494
                           access="readwrite")
1489
1495
    def ApprovalDuration_dbus_property(self, value=None):
1490
1496
        if value is None:       # get
1491
 
            return dbus.UInt64(timedelta_to_milliseconds(
1492
 
                    self.approval_duration))
 
1497
            return dbus.UInt64(self.approval_duration.total_seconds()
 
1498
                               * 1000)
1493
1499
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
1494
1500
    
1495
1501
    # Name - property
1503
1509
        return dbus.String(self.fingerprint)
1504
1510
    
1505
1511
    # Host - property
1506
 
    @dbus_service_property(_interface, signature="s",
 
1512
    @dbus_service_property(_interface,
 
1513
                           signature="s",
1507
1514
                           access="readwrite")
1508
1515
    def Host_dbus_property(self, value=None):
1509
1516
        if value is None:       # get
1510
1517
            return dbus.String(self.host)
1511
 
        self.host = unicode(value)
 
1518
        self.host = str(value)
1512
1519
    
1513
1520
    # Created - property
1514
1521
    @dbus_service_property(_interface, signature="s", access="read")
1521
1528
        return datetime_to_dbus(self.last_enabled)
1522
1529
    
1523
1530
    # Enabled - property
1524
 
    @dbus_service_property(_interface, signature="b",
 
1531
    @dbus_service_property(_interface,
 
1532
                           signature="b",
1525
1533
                           access="readwrite")
1526
1534
    def Enabled_dbus_property(self, value=None):
1527
1535
        if value is None:       # get
1532
1540
            self.disable()
1533
1541
    
1534
1542
    # LastCheckedOK - property
1535
 
    @dbus_service_property(_interface, signature="s",
 
1543
    @dbus_service_property(_interface,
 
1544
                           signature="s",
1536
1545
                           access="readwrite")
1537
1546
    def LastCheckedOK_dbus_property(self, value=None):
1538
1547
        if value is not None:
1541
1550
        return datetime_to_dbus(self.last_checked_ok)
1542
1551
    
1543
1552
    # LastCheckerStatus - property
1544
 
    @dbus_service_property(_interface, signature="n",
1545
 
                           access="read")
 
1553
    @dbus_service_property(_interface, signature="n", access="read")
1546
1554
    def LastCheckerStatus_dbus_property(self):
1547
1555
        return dbus.Int16(self.last_checker_status)
1548
1556
    
1557
1565
        return datetime_to_dbus(self.last_approval_request)
1558
1566
    
1559
1567
    # Timeout - property
1560
 
    @dbus_service_property(_interface, signature="t",
 
1568
    @dbus_service_property(_interface,
 
1569
                           signature="t",
1561
1570
                           access="readwrite")
1562
1571
    def Timeout_dbus_property(self, value=None):
1563
1572
        if value is None:       # get
1564
 
            return dbus.UInt64(self.timeout_milliseconds())
 
1573
            return dbus.UInt64(self.timeout.total_seconds() * 1000)
1565
1574
        old_timeout = self.timeout
1566
1575
        self.timeout = datetime.timedelta(0, 0, 0, value)
1567
1576
        # Reschedule disabling
1576
1585
                    is None):
1577
1586
                    return
1578
1587
                gobject.source_remove(self.disable_initiator_tag)
1579
 
                self.disable_initiator_tag = (
1580
 
                    gobject.timeout_add(
1581
 
                        timedelta_to_milliseconds(self.expires - now),
1582
 
                        self.disable))
 
1588
                self.disable_initiator_tag = gobject.timeout_add(
 
1589
                    int((self.expires - now).total_seconds() * 1000),
 
1590
                    self.disable)
1583
1591
    
1584
1592
    # ExtendedTimeout - property
1585
 
    @dbus_service_property(_interface, signature="t",
 
1593
    @dbus_service_property(_interface,
 
1594
                           signature="t",
1586
1595
                           access="readwrite")
1587
1596
    def ExtendedTimeout_dbus_property(self, value=None):
1588
1597
        if value is None:       # get
1589
 
            return dbus.UInt64(self.extended_timeout_milliseconds())
 
1598
            return dbus.UInt64(self.extended_timeout.total_seconds()
 
1599
                               * 1000)
1590
1600
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1591
1601
    
1592
1602
    # Interval - property
1593
 
    @dbus_service_property(_interface, signature="t",
 
1603
    @dbus_service_property(_interface,
 
1604
                           signature="t",
1594
1605
                           access="readwrite")
1595
1606
    def Interval_dbus_property(self, value=None):
1596
1607
        if value is None:       # get
1597
 
            return dbus.UInt64(self.interval_milliseconds())
 
1608
            return dbus.UInt64(self.interval.total_seconds() * 1000)
1598
1609
        self.interval = datetime.timedelta(0, 0, 0, value)
1599
1610
        if getattr(self, "checker_initiator_tag", None) is None:
1600
1611
            return
1601
1612
        if self.enabled:
1602
1613
            # Reschedule checker run
1603
1614
            gobject.source_remove(self.checker_initiator_tag)
1604
 
            self.checker_initiator_tag = (gobject.timeout_add
1605
 
                                          (value, self.start_checker))
1606
 
            self.start_checker()    # Start one now, too
 
1615
            self.checker_initiator_tag = gobject.timeout_add(
 
1616
                value, self.start_checker)
 
1617
            self.start_checker() # Start one now, too
1607
1618
    
1608
1619
    # Checker - property
1609
 
    @dbus_service_property(_interface, signature="s",
 
1620
    @dbus_service_property(_interface,
 
1621
                           signature="s",
1610
1622
                           access="readwrite")
1611
1623
    def Checker_dbus_property(self, value=None):
1612
1624
        if value is None:       # get
1613
1625
            return dbus.String(self.checker_command)
1614
 
        self.checker_command = unicode(value)
 
1626
        self.checker_command = str(value)
1615
1627
    
1616
1628
    # CheckerRunning - property
1617
 
    @dbus_service_property(_interface, signature="b",
 
1629
    @dbus_service_property(_interface,
 
1630
                           signature="b",
1618
1631
                           access="readwrite")
1619
1632
    def CheckerRunning_dbus_property(self, value=None):
1620
1633
        if value is None:       # get
1630
1643
        return self.dbus_object_path # is already a dbus.ObjectPath
1631
1644
    
1632
1645
    # Secret = property
1633
 
    @dbus_service_property(_interface, signature="ay",
1634
 
                           access="write", byte_arrays=True)
 
1646
    @dbus_service_property(_interface,
 
1647
                           signature="ay",
 
1648
                           access="write",
 
1649
                           byte_arrays=True)
1635
1650
    def Secret_dbus_property(self, value):
1636
 
        self.secret = str(value)
 
1651
        self.secret = bytes(value)
1637
1652
    
1638
1653
    del _interface
1639
1654
 
1653
1668
        if data[0] == 'data':
1654
1669
            return data[1]
1655
1670
        if data[0] == 'function':
 
1671
            
1656
1672
            def func(*args, **kwargs):
1657
1673
                self._pipe.send(('funcall', name, args, kwargs))
1658
1674
                return self._pipe.recv()[1]
 
1675
            
1659
1676
            return func
1660
1677
    
1661
1678
    def __setattr__(self, name, value):
1673
1690
    def handle(self):
1674
1691
        with contextlib.closing(self.server.child_pipe) as child_pipe:
1675
1692
            logger.info("TCP connection from: %s",
1676
 
                        unicode(self.client_address))
 
1693
                        str(self.client_address))
1677
1694
            logger.debug("Pipe FD: %d",
1678
1695
                         self.server.child_pipe.fileno())
1679
1696
            
1680
 
            session = (gnutls.connection
1681
 
                       .ClientSession(self.request,
1682
 
                                      gnutls.connection
1683
 
                                      .X509Credentials()))
 
1697
            session = gnutls.connection.ClientSession(
 
1698
                self.request, gnutls.connection .X509Credentials())
1684
1699
            
1685
1700
            # Note: gnutls.connection.X509Credentials is really a
1686
1701
            # generic GnuTLS certificate credentials object so long as
1695
1710
            priority = self.server.gnutls_priority
1696
1711
            if priority is None:
1697
1712
                priority = "NORMAL"
1698
 
            (gnutls.library.functions
1699
 
             .gnutls_priority_set_direct(session._c_object,
1700
 
                                         priority, None))
 
1713
            gnutls.library.functions.gnutls_priority_set_direct(
 
1714
                session._c_object, priority, None)
1701
1715
            
1702
1716
            # Start communication using the Mandos protocol
1703
1717
            # Get protocol number
1723
1737
            approval_required = False
1724
1738
            try:
1725
1739
                try:
1726
 
                    fpr = self.fingerprint(self.peer_certificate
1727
 
                                           (session))
 
1740
                    fpr = self.fingerprint(
 
1741
                        self.peer_certificate(session))
1728
1742
                except (TypeError,
1729
1743
                        gnutls.errors.GNUTLSError) as error:
1730
1744
                    logger.warning("Bad certificate: %s", error)
1745
1759
                while True:
1746
1760
                    if not client.enabled:
1747
1761
                        logger.info("Client %s is disabled",
1748
 
                                       client.name)
 
1762
                                    client.name)
1749
1763
                        if self.server.use_dbus:
1750
1764
                            # Emit D-Bus signal
1751
1765
                            client.Rejected("Disabled")
1760
1774
                        if self.server.use_dbus:
1761
1775
                            # Emit D-Bus signal
1762
1776
                            client.NeedApproval(
1763
 
                                client.approval_delay_milliseconds(),
1764
 
                                client.approved_by_default)
 
1777
                                client.approval_delay.total_seconds()
 
1778
                                * 1000, client.approved_by_default)
1765
1779
                    else:
1766
1780
                        logger.warning("Client %s was not approved",
1767
1781
                                       client.name)
1773
1787
                    #wait until timeout or approved
1774
1788
                    time = datetime.datetime.now()
1775
1789
                    client.changedstate.acquire()
1776
 
                    client.changedstate.wait(
1777
 
                        float(timedelta_to_milliseconds(delay)
1778
 
                              / 1000))
 
1790
                    client.changedstate.wait(delay.total_seconds())
1779
1791
                    client.changedstate.release()
1780
1792
                    time2 = datetime.datetime.now()
1781
1793
                    if (time2 - time) >= delay:
1800
1812
                        logger.warning("gnutls send failed",
1801
1813
                                       exc_info=error)
1802
1814
                        return
1803
 
                    logger.debug("Sent: %d, remaining: %d",
1804
 
                                 sent, len(client.secret)
1805
 
                                 - (sent_size + sent))
 
1815
                    logger.debug("Sent: %d, remaining: %d", sent,
 
1816
                                 len(client.secret) - (sent_size
 
1817
                                                       + sent))
1806
1818
                    sent_size += sent
1807
1819
                
1808
1820
                logger.info("Sending secret to %s", client.name)
1825
1837
    def peer_certificate(session):
1826
1838
        "Return the peer's OpenPGP certificate as a bytestring"
1827
1839
        # If not an OpenPGP certificate...
1828
 
        if (gnutls.library.functions
1829
 
            .gnutls_certificate_type_get(session._c_object)
 
1840
        if (gnutls.library.functions.gnutls_certificate_type_get(
 
1841
                session._c_object)
1830
1842
            != gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1831
1843
            # ...do the normal thing
1832
1844
            return session.peer_certificate
1846
1858
    def fingerprint(openpgp):
1847
1859
        "Convert an OpenPGP bytestring to a hexdigit fingerprint"
1848
1860
        # New GnuTLS "datum" with the OpenPGP public key
1849
 
        datum = (gnutls.library.types
1850
 
                 .gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1851
 
                                             ctypes.POINTER
1852
 
                                             (ctypes.c_ubyte)),
1853
 
                                 ctypes.c_uint(len(openpgp))))
 
1861
        datum = gnutls.library.types.gnutls_datum_t(
 
1862
            ctypes.cast(ctypes.c_char_p(openpgp),
 
1863
                        ctypes.POINTER(ctypes.c_ubyte)),
 
1864
            ctypes.c_uint(len(openpgp)))
1854
1865
        # New empty GnuTLS certificate
1855
1866
        crt = gnutls.library.types.gnutls_openpgp_crt_t()
1856
 
        (gnutls.library.functions
1857
 
         .gnutls_openpgp_crt_init(ctypes.byref(crt)))
 
1867
        gnutls.library.functions.gnutls_openpgp_crt_init(
 
1868
            ctypes.byref(crt))
1858
1869
        # Import the OpenPGP public key into the certificate
1859
 
        (gnutls.library.functions
1860
 
         .gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1861
 
                                    gnutls.library.constants
1862
 
                                    .GNUTLS_OPENPGP_FMT_RAW))
 
1870
        gnutls.library.functions.gnutls_openpgp_crt_import(
 
1871
            crt, ctypes.byref(datum),
 
1872
            gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
1863
1873
        # Verify the self signature in the key
1864
1874
        crtverify = ctypes.c_uint()
1865
 
        (gnutls.library.functions
1866
 
         .gnutls_openpgp_crt_verify_self(crt, 0,
1867
 
                                         ctypes.byref(crtverify)))
 
1875
        gnutls.library.functions.gnutls_openpgp_crt_verify_self(
 
1876
            crt, 0, ctypes.byref(crtverify))
1868
1877
        if crtverify.value != 0:
1869
1878
            gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1870
 
            raise (gnutls.errors.CertificateSecurityError
1871
 
                   ("Verify failed"))
 
1879
            raise gnutls.errors.CertificateSecurityError(
 
1880
                "Verify failed")
1872
1881
        # New buffer for the fingerprint
1873
1882
        buf = ctypes.create_string_buffer(20)
1874
1883
        buf_len = ctypes.c_size_t()
1875
1884
        # Get the fingerprint from the certificate into the buffer
1876
 
        (gnutls.library.functions
1877
 
         .gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1878
 
                                             ctypes.byref(buf_len)))
 
1885
        gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint(
 
1886
            crt, ctypes.byref(buf), ctypes.byref(buf_len))
1879
1887
        # Deinit the certificate
1880
1888
        gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1881
1889
        # Convert the buffer to a Python bytestring
1887
1895
 
1888
1896
class MultiprocessingMixIn(object):
1889
1897
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
 
1898
    
1890
1899
    def sub_process_main(self, request, address):
1891
1900
        try:
1892
1901
            self.finish_request(request, address)
1904
1913
 
1905
1914
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1906
1915
    """ adds a pipe to the MixIn """
 
1916
    
1907
1917
    def process_request(self, request, client_address):
1908
1918
        """Overrides and wraps the original process_request().
1909
1919
        
1930
1940
        interface:      None or a network interface name (string)
1931
1941
        use_ipv6:       Boolean; to use IPv6 or not
1932
1942
    """
 
1943
    
1933
1944
    def __init__(self, server_address, RequestHandlerClass,
1934
 
                 interface=None, use_ipv6=True, socketfd=None):
 
1945
                 interface=None,
 
1946
                 use_ipv6=True,
 
1947
                 socketfd=None):
1935
1948
        """If socketfd is set, use that file descriptor instead of
1936
1949
        creating a new one with socket.socket().
1937
1950
        """
1978
1991
                             self.interface)
1979
1992
            else:
1980
1993
                try:
1981
 
                    self.socket.setsockopt(socket.SOL_SOCKET,
1982
 
                                           SO_BINDTODEVICE,
1983
 
                                           str(self.interface + '\0'))
 
1994
                    self.socket.setsockopt(
 
1995
                        socket.SOL_SOCKET, SO_BINDTODEVICE,
 
1996
                        (self.interface + "\0").encode("utf-8"))
1984
1997
                except socket.error as error:
1985
1998
                    if error.errno == errno.EPERM:
1986
1999
                        logger.error("No permission to bind to"
2004
2017
                self.server_address = (any_address,
2005
2018
                                       self.server_address[1])
2006
2019
            elif not self.server_address[1]:
2007
 
                self.server_address = (self.server_address[0],
2008
 
                                       0)
 
2020
                self.server_address = (self.server_address[0], 0)
2009
2021
#                 if self.interface:
2010
2022
#                     self.server_address = (self.server_address[0],
2011
2023
#                                            0, # port
2025
2037
    
2026
2038
    Assumes a gobject.MainLoop event loop.
2027
2039
    """
 
2040
    
2028
2041
    def __init__(self, server_address, RequestHandlerClass,
2029
 
                 interface=None, use_ipv6=True, clients=None,
2030
 
                 gnutls_priority=None, use_dbus=True, socketfd=None):
 
2042
                 interface=None,
 
2043
                 use_ipv6=True,
 
2044
                 clients=None,
 
2045
                 gnutls_priority=None,
 
2046
                 use_dbus=True,
 
2047
                 socketfd=None):
2031
2048
        self.enabled = False
2032
2049
        self.clients = clients
2033
2050
        if self.clients is None:
2039
2056
                                interface = interface,
2040
2057
                                use_ipv6 = use_ipv6,
2041
2058
                                socketfd = socketfd)
 
2059
    
2042
2060
    def server_activate(self):
2043
2061
        if self.enabled:
2044
2062
            return socketserver.TCPServer.server_activate(self)
2048
2066
    
2049
2067
    def add_pipe(self, parent_pipe, proc):
2050
2068
        # Call "handle_ipc" for both data and EOF events
2051
 
        gobject.io_add_watch(parent_pipe.fileno(),
2052
 
                             gobject.IO_IN | gobject.IO_HUP,
2053
 
                             functools.partial(self.handle_ipc,
2054
 
                                               parent_pipe =
2055
 
                                               parent_pipe,
2056
 
                                               proc = proc))
 
2069
        gobject.io_add_watch(
 
2070
            parent_pipe.fileno(),
 
2071
            gobject.IO_IN | gobject.IO_HUP,
 
2072
            functools.partial(self.handle_ipc,
 
2073
                              parent_pipe = parent_pipe,
 
2074
                              proc = proc))
2057
2075
    
2058
 
    def handle_ipc(self, source, condition, parent_pipe=None,
2059
 
                   proc = None, client_object=None):
 
2076
    def handle_ipc(self, source, condition,
 
2077
                   parent_pipe=None,
 
2078
                   proc = None,
 
2079
                   client_object=None):
2060
2080
        # error, or the other end of multiprocessing.Pipe has closed
2061
2081
        if condition & (gobject.IO_ERR | gobject.IO_HUP):
2062
2082
            # Wait for other process to exit
2085
2105
                parent_pipe.send(False)
2086
2106
                return False
2087
2107
            
2088
 
            gobject.io_add_watch(parent_pipe.fileno(),
2089
 
                                 gobject.IO_IN | gobject.IO_HUP,
2090
 
                                 functools.partial(self.handle_ipc,
2091
 
                                                   parent_pipe =
2092
 
                                                   parent_pipe,
2093
 
                                                   proc = proc,
2094
 
                                                   client_object =
2095
 
                                                   client))
 
2108
            gobject.io_add_watch(
 
2109
                parent_pipe.fileno(),
 
2110
                gobject.IO_IN | gobject.IO_HUP,
 
2111
                functools.partial(self.handle_ipc,
 
2112
                                  parent_pipe = parent_pipe,
 
2113
                                  proc = proc,
 
2114
                                  client_object = client))
2096
2115
            parent_pipe.send(True)
2097
2116
            # remove the old hook in favor of the new above hook on
2098
2117
            # same fileno
2104
2123
            
2105
2124
            parent_pipe.send(('data', getattr(client_object,
2106
2125
                                              funcname)(*args,
2107
 
                                                         **kwargs)))
 
2126
                                                        **kwargs)))
2108
2127
        
2109
2128
        if command == 'getattr':
2110
2129
            attrname = request[1]
2111
2130
            if callable(client_object.__getattribute__(attrname)):
2112
 
                parent_pipe.send(('function',))
 
2131
                parent_pipe.send(('function', ))
2113
2132
            else:
2114
 
                parent_pipe.send(('data', client_object
2115
 
                                  .__getattribute__(attrname)))
 
2133
                parent_pipe.send((
 
2134
                    'data', client_object.__getattribute__(attrname)))
2116
2135
        
2117
2136
        if command == 'setattr':
2118
2137
            attrname = request[1]
2158
2177
                                              # None
2159
2178
                                    "followers")) # Tokens valid after
2160
2179
                                                  # this token
 
2180
    Token = collections.namedtuple("Token", (
 
2181
        "regexp",  # To match token; if "value" is not None, must have
 
2182
                   # a "group" containing digits
 
2183
        "value",   # datetime.timedelta or None
 
2184
        "followers"))           # Tokens valid after this token
2161
2185
    # RFC 3339 "duration" tokens, syntax, and semantics; taken from
2162
2186
    # the "duration" ABNF definition in RFC 3339, Appendix A.
2163
2187
    token_end = Token(re.compile(r"$"), None, frozenset())
2164
2188
    token_second = Token(re.compile(r"(\d+)S"),
2165
2189
                         datetime.timedelta(seconds=1),
2166
 
                         frozenset((token_end,)))
 
2190
                         frozenset((token_end, )))
2167
2191
    token_minute = Token(re.compile(r"(\d+)M"),
2168
2192
                         datetime.timedelta(minutes=1),
2169
2193
                         frozenset((token_second, token_end)))
2185
2209
                       frozenset((token_month, token_end)))
2186
2210
    token_week = Token(re.compile(r"(\d+)W"),
2187
2211
                       datetime.timedelta(weeks=1),
2188
 
                       frozenset((token_end,)))
 
2212
                       frozenset((token_end, )))
2189
2213
    token_duration = Token(re.compile(r"P"), None,
2190
2214
                           frozenset((token_year, token_month,
2191
2215
                                      token_day, token_time,
2192
 
                                      token_week))),
 
2216
                                      token_week)))
2193
2217
    # Define starting values
2194
2218
    value = datetime.timedelta() # Value so far
2195
2219
    found_token = None
2196
 
    followers = frozenset(token_duration,) # Following valid tokens
 
2220
    followers = frozenset((token_duration,)) # Following valid tokens
2197
2221
    s = duration                # String left to parse
2198
2222
    # Loop until end token is found
2199
2223
    while found_token is not token_end:
2246
2270
    timevalue = datetime.timedelta(0)
2247
2271
    for s in interval.split():
2248
2272
        try:
2249
 
            suffix = unicode(s[-1])
 
2273
            suffix = s[-1]
2250
2274
            value = int(s[:-1])
2251
2275
            if suffix == "d":
2252
2276
                delta = datetime.timedelta(value)
2259
2283
            elif suffix == "w":
2260
2284
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
2261
2285
            else:
2262
 
                raise ValueError("Unknown suffix {0!r}"
2263
 
                                 .format(suffix))
 
2286
                raise ValueError("Unknown suffix {!r}".format(suffix))
2264
2287
        except IndexError as e:
2265
2288
            raise ValueError(*(e.args))
2266
2289
        timevalue += delta
2283
2306
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2284
2307
        if not stat.S_ISCHR(os.fstat(null).st_mode):
2285
2308
            raise OSError(errno.ENODEV,
2286
 
                          "{0} not a character device"
 
2309
                          "{} not a character device"
2287
2310
                          .format(os.devnull))
2288
2311
        os.dup2(null, sys.stdin.fileno())
2289
2312
        os.dup2(null, sys.stdout.fileno())
2299
2322
    
2300
2323
    parser = argparse.ArgumentParser()
2301
2324
    parser.add_argument("-v", "--version", action="version",
2302
 
                        version = "%(prog)s {0}".format(version),
 
2325
                        version = "%(prog)s {}".format(version),
2303
2326
                        help="show version number and exit")
2304
2327
    parser.add_argument("-i", "--interface", metavar="IF",
2305
2328
                        help="Bind to interface IF")
2338
2361
                        help="Directory to save/restore state in")
2339
2362
    parser.add_argument("--foreground", action="store_true",
2340
2363
                        help="Run in foreground", default=None)
 
2364
    parser.add_argument("--no-zeroconf", action="store_false",
 
2365
                        dest="zeroconf", help="Do not use Zeroconf",
 
2366
                        default=None)
2341
2367
    
2342
2368
    options = parser.parse_args()
2343
2369
    
2352
2378
                        "port": "",
2353
2379
                        "debug": "False",
2354
2380
                        "priority":
2355
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
 
2381
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
 
2382
                        ":+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
2356
2383
                        "servicename": "Mandos",
2357
2384
                        "use_dbus": "True",
2358
2385
                        "use_ipv6": "True",
2361
2388
                        "socket": "",
2362
2389
                        "statedir": "/var/lib/mandos",
2363
2390
                        "foreground": "False",
2364
 
                        }
 
2391
                        "zeroconf": "True",
 
2392
                    }
2365
2393
    
2366
2394
    # Parse config file for server-global settings
2367
2395
    server_config = configparser.SafeConfigParser(server_defaults)
2368
2396
    del server_defaults
2369
 
    server_config.read(os.path.join(options.configdir,
2370
 
                                    "mandos.conf"))
 
2397
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
2371
2398
    # Convert the SafeConfigParser object to a dict
2372
2399
    server_settings = server_config.defaults()
2373
2400
    # Use the appropriate methods on the non-string config options
2391
2418
    # Override the settings from the config file with command line
2392
2419
    # options, if set.
2393
2420
    for option in ("interface", "address", "port", "debug",
2394
 
                   "priority", "servicename", "configdir",
2395
 
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
2396
 
                   "statedir", "socket", "foreground"):
 
2421
                   "priority", "servicename", "configdir", "use_dbus",
 
2422
                   "use_ipv6", "debuglevel", "restore", "statedir",
 
2423
                   "socket", "foreground", "zeroconf"):
2397
2424
        value = getattr(options, option)
2398
2425
        if value is not None:
2399
2426
            server_settings[option] = value
2400
2427
    del options
2401
2428
    # Force all strings to be unicode
2402
2429
    for option in server_settings.keys():
2403
 
        if type(server_settings[option]) is str:
2404
 
            server_settings[option] = unicode(server_settings[option])
 
2430
        if isinstance(server_settings[option], bytes):
 
2431
            server_settings[option] = (server_settings[option]
 
2432
                                       .decode("utf-8"))
2405
2433
    # Force all boolean options to be boolean
2406
2434
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
2407
 
                   "foreground"):
 
2435
                   "foreground", "zeroconf"):
2408
2436
        server_settings[option] = bool(server_settings[option])
2409
2437
    # Debug implies foreground
2410
2438
    if server_settings["debug"]:
2413
2441
    
2414
2442
    ##################################################################
2415
2443
    
 
2444
    if (not server_settings["zeroconf"]
 
2445
        and not (server_settings["port"]
 
2446
                 or server_settings["socket"] != "")):
 
2447
        parser.error("Needs port or socket to work without Zeroconf")
 
2448
    
2416
2449
    # For convenience
2417
2450
    debug = server_settings["debug"]
2418
2451
    debuglevel = server_settings["debuglevel"]
2421
2454
    stored_state_path = os.path.join(server_settings["statedir"],
2422
2455
                                     stored_state_file)
2423
2456
    foreground = server_settings["foreground"]
 
2457
    zeroconf = server_settings["zeroconf"]
2424
2458
    
2425
2459
    if debug:
2426
2460
        initlogger(debug, logging.DEBUG)
2432
2466
            initlogger(debug, level)
2433
2467
    
2434
2468
    if server_settings["servicename"] != "Mandos":
2435
 
        syslogger.setFormatter(logging.Formatter
2436
 
                               ('Mandos ({0}) [%(process)d]:'
2437
 
                                ' %(levelname)s: %(message)s'
2438
 
                                .format(server_settings
2439
 
                                        ["servicename"])))
 
2469
        syslogger.setFormatter(
 
2470
            logging.Formatter('Mandos ({}) [%(process)d]:'
 
2471
                              ' %(levelname)s: %(message)s'.format(
 
2472
                                  server_settings["servicename"])))
2440
2473
    
2441
2474
    # Parse config file with clients
2442
2475
    client_config = configparser.SafeConfigParser(Client
2447
2480
    global mandos_dbus_service
2448
2481
    mandos_dbus_service = None
2449
2482
    
2450
 
    tcp_server = MandosServer((server_settings["address"],
2451
 
                               server_settings["port"]),
2452
 
                              ClientHandler,
2453
 
                              interface=(server_settings["interface"]
2454
 
                                         or None),
2455
 
                              use_ipv6=use_ipv6,
2456
 
                              gnutls_priority=
2457
 
                              server_settings["priority"],
2458
 
                              use_dbus=use_dbus,
2459
 
                              socketfd=(server_settings["socket"]
2460
 
                                        or None))
 
2483
    socketfd = None
 
2484
    if server_settings["socket"] != "":
 
2485
        socketfd = server_settings["socket"]
 
2486
    tcp_server = MandosServer(
 
2487
        (server_settings["address"], server_settings["port"]),
 
2488
        ClientHandler,
 
2489
        interface=(server_settings["interface"] or None),
 
2490
        use_ipv6=use_ipv6,
 
2491
        gnutls_priority=server_settings["priority"],
 
2492
        use_dbus=use_dbus,
 
2493
        socketfd=socketfd)
2461
2494
    if not foreground:
2462
2495
        pidfilename = "/run/mandos.pid"
2463
2496
        if not os.path.isdir("/run/."):
2497
2530
        def debug_gnutls(level, string):
2498
2531
            logger.debug("GnuTLS: %s", string[:-1])
2499
2532
        
2500
 
        (gnutls.library.functions
2501
 
         .gnutls_global_set_log_function(debug_gnutls))
 
2533
        gnutls.library.functions.gnutls_global_set_log_function(
 
2534
            debug_gnutls)
2502
2535
        
2503
2536
        # Redirect stdin so all checkers get /dev/null
2504
2537
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2524
2557
    if use_dbus:
2525
2558
        try:
2526
2559
            bus_name = dbus.service.BusName("se.recompile.Mandos",
2527
 
                                            bus, do_not_queue=True)
2528
 
            old_bus_name = (dbus.service.BusName
2529
 
                            ("se.bsnet.fukt.Mandos", bus,
2530
 
                             do_not_queue=True))
 
2560
                                            bus,
 
2561
                                            do_not_queue=True)
 
2562
            old_bus_name = dbus.service.BusName(
 
2563
                "se.bsnet.fukt.Mandos", bus,
 
2564
                do_not_queue=True)
2531
2565
        except dbus.exceptions.NameExistsException as e:
2532
2566
            logger.error("Disabling D-Bus:", exc_info=e)
2533
2567
            use_dbus = False
2534
2568
            server_settings["use_dbus"] = False
2535
2569
            tcp_server.use_dbus = False
2536
 
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2537
 
    service = AvahiServiceToSyslog(name =
2538
 
                                   server_settings["servicename"],
2539
 
                                   servicetype = "_mandos._tcp",
2540
 
                                   protocol = protocol, bus = bus)
2541
 
    if server_settings["interface"]:
2542
 
        service.interface = (if_nametoindex
2543
 
                             (str(server_settings["interface"])))
 
2570
    if zeroconf:
 
2571
        protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
 
2572
        service = AvahiServiceToSyslog(
 
2573
            name = server_settings["servicename"],
 
2574
            servicetype = "_mandos._tcp",
 
2575
            protocol = protocol,
 
2576
            bus = bus)
 
2577
        if server_settings["interface"]:
 
2578
            service.interface = if_nametoindex(
 
2579
                server_settings["interface"].encode("utf-8"))
2544
2580
    
2545
2581
    global multiprocessing_manager
2546
2582
    multiprocessing_manager = multiprocessing.Manager()
2565
2601
    if server_settings["restore"]:
2566
2602
        try:
2567
2603
            with open(stored_state_path, "rb") as stored_state:
2568
 
                clients_data, old_client_settings = (pickle.load
2569
 
                                                     (stored_state))
 
2604
                clients_data, old_client_settings = pickle.load(
 
2605
                    stored_state)
2570
2606
            os.remove(stored_state_path)
2571
2607
        except IOError as e:
2572
2608
            if e.errno == errno.ENOENT:
2573
 
                logger.warning("Could not load persistent state: {0}"
2574
 
                                .format(os.strerror(e.errno)))
 
2609
                logger.warning("Could not load persistent state:"
 
2610
                               " {}".format(os.strerror(e.errno)))
2575
2611
            else:
2576
2612
                logger.critical("Could not load persistent state:",
2577
2613
                                exc_info=e)
2578
2614
                raise
2579
2615
        except EOFError as e:
2580
2616
            logger.warning("Could not load persistent state: "
2581
 
                           "EOFError:", exc_info=e)
 
2617
                           "EOFError:",
 
2618
                           exc_info=e)
2582
2619
    
2583
2620
    with PGPEngine() as pgp:
2584
 
        for client_name, client in clients_data.iteritems():
 
2621
        for client_name, client in clients_data.items():
2585
2622
            # Skip removed clients
2586
2623
            if client_name not in client_settings:
2587
2624
                continue
2596
2633
                    # For each value in new config, check if it
2597
2634
                    # differs from the old config value (Except for
2598
2635
                    # the "secret" attribute)
2599
 
                    if (name != "secret" and
2600
 
                        value != old_client_settings[client_name]
2601
 
                        [name]):
 
2636
                    if (name != "secret"
 
2637
                        and (value !=
 
2638
                             old_client_settings[client_name][name])):
2602
2639
                        client[name] = value
2603
2640
                except KeyError:
2604
2641
                    pass
2605
2642
            
2606
2643
            # Clients who has passed its expire date can still be
2607
 
            # enabled if its last checker was successful.  Clients
 
2644
            # enabled if its last checker was successful.  A Client
2608
2645
            # whose checker succeeded before we stored its state is
2609
2646
            # assumed to have successfully run all checkers during
2610
2647
            # downtime.
2612
2649
                if datetime.datetime.utcnow() >= client["expires"]:
2613
2650
                    if not client["last_checked_ok"]:
2614
2651
                        logger.warning(
2615
 
                            "disabling client {0} - Client never "
2616
 
                            "performed a successful checker"
2617
 
                            .format(client_name))
 
2652
                            "disabling client {} - Client never "
 
2653
                            "performed a successful checker".format(
 
2654
                                client_name))
2618
2655
                        client["enabled"] = False
2619
2656
                    elif client["last_checker_status"] != 0:
2620
2657
                        logger.warning(
2621
 
                            "disabling client {0} - Client "
2622
 
                            "last checker failed with error code {1}"
2623
 
                            .format(client_name,
2624
 
                                    client["last_checker_status"]))
 
2658
                            "disabling client {} - Client last"
 
2659
                            " checker failed with error code"
 
2660
                            " {}".format(
 
2661
                                client_name,
 
2662
                                client["last_checker_status"]))
2625
2663
                        client["enabled"] = False
2626
2664
                    else:
2627
 
                        client["expires"] = (datetime.datetime
2628
 
                                             .utcnow()
2629
 
                                             + client["timeout"])
 
2665
                        client["expires"] = (
 
2666
                            datetime.datetime.utcnow()
 
2667
                            + client["timeout"])
2630
2668
                        logger.debug("Last checker succeeded,"
2631
 
                                     " keeping {0} enabled"
2632
 
                                     .format(client_name))
 
2669
                                     " keeping {} enabled".format(
 
2670
                                         client_name))
2633
2671
            try:
2634
 
                client["secret"] = (
2635
 
                    pgp.decrypt(client["encrypted_secret"],
2636
 
                                client_settings[client_name]
2637
 
                                ["secret"]))
 
2672
                client["secret"] = pgp.decrypt(
 
2673
                    client["encrypted_secret"],
 
2674
                    client_settings[client_name]["secret"])
2638
2675
            except PGPError:
2639
2676
                # If decryption fails, we use secret from new settings
2640
 
                logger.debug("Failed to decrypt {0} old secret"
2641
 
                             .format(client_name))
2642
 
                client["secret"] = (
2643
 
                    client_settings[client_name]["secret"])
 
2677
                logger.debug("Failed to decrypt {} old secret".format(
 
2678
                    client_name))
 
2679
                client["secret"] = (client_settings[client_name]
 
2680
                                    ["secret"])
2644
2681
    
2645
2682
    # Add/remove clients based on new changes made to config
2646
2683
    for client_name in (set(old_client_settings)
2651
2688
        clients_data[client_name] = client_settings[client_name]
2652
2689
    
2653
2690
    # Create all client objects
2654
 
    for client_name, client in clients_data.iteritems():
 
2691
    for client_name, client in clients_data.items():
2655
2692
        tcp_server.clients[client_name] = client_class(
2656
 
            name = client_name, settings = client,
 
2693
            name = client_name,
 
2694
            settings = client,
2657
2695
            server_settings = server_settings)
2658
2696
    
2659
2697
    if not tcp_server.clients:
2664
2702
            try:
2665
2703
                with pidfile:
2666
2704
                    pid = os.getpid()
2667
 
                    pidfile.write(str(pid) + "\n".encode("utf-8"))
 
2705
                    pidfile.write("{}\n".format(pid).encode("utf-8"))
2668
2706
            except IOError:
2669
2707
                logger.error("Could not write to file %r with PID %d",
2670
2708
                             pidfilename, pid)
2675
2713
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2676
2714
    
2677
2715
    if use_dbus:
2678
 
        @alternate_dbus_interfaces({"se.recompile.Mandos":
2679
 
                                        "se.bsnet.fukt.Mandos"})
 
2716
        
 
2717
        @alternate_dbus_interfaces(
 
2718
            { "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
2680
2719
        class MandosDBusService(DBusObjectWithProperties):
2681
2720
            """A D-Bus proxy object"""
 
2721
            
2682
2722
            def __init__(self):
2683
2723
                dbus.service.Object.__init__(self, bus, "/")
 
2724
            
2684
2725
            _interface = "se.recompile.Mandos"
2685
2726
            
2686
2727
            @dbus_interface_annotations(_interface)
2687
2728
            def _foo(self):
2688
 
                return { "org.freedesktop.DBus.Property"
2689
 
                         ".EmitsChangedSignal":
2690
 
                             "false"}
 
2729
                return {
 
2730
                    "org.freedesktop.DBus.Property.EmitsChangedSignal":
 
2731
                    "false" }
2691
2732
            
2692
2733
            @dbus.service.signal(_interface, signature="o")
2693
2734
            def ClientAdded(self, objpath):
2707
2748
            @dbus.service.method(_interface, out_signature="ao")
2708
2749
            def GetAllClients(self):
2709
2750
                "D-Bus method"
2710
 
                return dbus.Array(c.dbus_object_path
2711
 
                                  for c in
 
2751
                return dbus.Array(c.dbus_object_path for c in
2712
2752
                                  tcp_server.clients.itervalues())
2713
2753
            
2714
2754
            @dbus.service.method(_interface,
2716
2756
            def GetAllClientsWithProperties(self):
2717
2757
                "D-Bus method"
2718
2758
                return dbus.Dictionary(
2719
 
                    ((c.dbus_object_path, c.GetAll(""))
2720
 
                     for c in tcp_server.clients.itervalues()),
 
2759
                    { c.dbus_object_path: c.GetAll("")
 
2760
                      for c in tcp_server.clients.itervalues() },
2721
2761
                    signature="oa{sv}")
2722
2762
            
2723
2763
            @dbus.service.method(_interface, in_signature="o")
2740
2780
    
2741
2781
    def cleanup():
2742
2782
        "Cleanup function; run on exit"
2743
 
        service.cleanup()
 
2783
        if zeroconf:
 
2784
            service.cleanup()
2744
2785
        
2745
2786
        multiprocessing.active_children()
2746
2787
        wnull.close()
2760
2801
                
2761
2802
                # A list of attributes that can not be pickled
2762
2803
                # + secret.
2763
 
                exclude = set(("bus", "changedstate", "secret",
2764
 
                               "checker", "server_settings"))
2765
 
                for name, typ in (inspect.getmembers
2766
 
                                  (dbus.service.Object)):
 
2804
                exclude = { "bus", "changedstate", "secret",
 
2805
                            "checker", "server_settings" }
 
2806
                for name, typ in inspect.getmembers(dbus.service
 
2807
                                                    .Object):
2767
2808
                    exclude.add(name)
2768
2809
                
2769
2810
                client_dict["encrypted_secret"] = (client
2776
2817
                del client_settings[client.name]["secret"]
2777
2818
        
2778
2819
        try:
2779
 
            with (tempfile.NamedTemporaryFile
2780
 
                  (mode='wb', suffix=".pickle", prefix='clients-',
2781
 
                   dir=os.path.dirname(stored_state_path),
2782
 
                   delete=False)) as stored_state:
 
2820
            with tempfile.NamedTemporaryFile(
 
2821
                    mode='wb',
 
2822
                    suffix=".pickle",
 
2823
                    prefix='clients-',
 
2824
                    dir=os.path.dirname(stored_state_path),
 
2825
                    delete=False) as stored_state:
2783
2826
                pickle.dump((clients, client_settings), stored_state)
2784
 
                tempname=stored_state.name
 
2827
                tempname = stored_state.name
2785
2828
            os.rename(tempname, stored_state_path)
2786
2829
        except (IOError, OSError) as e:
2787
2830
            if not debug:
2790
2833
                except NameError:
2791
2834
                    pass
2792
2835
            if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
2793
 
                logger.warning("Could not save persistent state: {0}"
 
2836
                logger.warning("Could not save persistent state: {}"
2794
2837
                               .format(os.strerror(e.errno)))
2795
2838
            else:
2796
2839
                logger.warning("Could not save persistent state:",
2806
2849
            client.disable(quiet=True)
2807
2850
            if use_dbus:
2808
2851
                # Emit D-Bus signal
2809
 
                mandos_dbus_service.ClientRemoved(client
2810
 
                                                  .dbus_object_path,
2811
 
                                                  client.name)
 
2852
                mandos_dbus_service.ClientRemoved(
 
2853
                    client.dbus_object_path, client.name)
2812
2854
        client_settings.clear()
2813
2855
    
2814
2856
    atexit.register(cleanup)
2825
2867
    tcp_server.server_activate()
2826
2868
    
2827
2869
    # Find out what port we got
2828
 
    service.port = tcp_server.socket.getsockname()[1]
 
2870
    if zeroconf:
 
2871
        service.port = tcp_server.socket.getsockname()[1]
2829
2872
    if use_ipv6:
2830
2873
        logger.info("Now listening on address %r, port %d,"
2831
2874
                    " flowinfo %d, scope_id %d",
2837
2880
    #service.interface = tcp_server.socket.getsockname()[3]
2838
2881
    
2839
2882
    try:
2840
 
        # From the Avahi example code
2841
 
        try:
2842
 
            service.activate()
2843
 
        except dbus.exceptions.DBusException as error:
2844
 
            logger.critical("D-Bus Exception", exc_info=error)
2845
 
            cleanup()
2846
 
            sys.exit(1)
2847
 
        # End of Avahi example code
 
2883
        if zeroconf:
 
2884
            # From the Avahi example code
 
2885
            try:
 
2886
                service.activate()
 
2887
            except dbus.exceptions.DBusException as error:
 
2888
                logger.critical("D-Bus Exception", exc_info=error)
 
2889
                cleanup()
 
2890
                sys.exit(1)
 
2891
            # End of Avahi example code
2848
2892
        
2849
2893
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2850
2894
                             lambda *args, **kwargs:
2865
2909
    # Must run before the D-Bus bus name gets deregistered
2866
2910
    cleanup()
2867
2911
 
 
2912
 
2868
2913
if __name__ == '__main__':
2869
2914
    main()