/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2016-02-21 13:00:15 UTC
  • Revision ID: teddy@recompile.se-20160221130015-b32z5wnkw9swjg2s
mandos: Remove unused import and an errant space character.

Show diffs side-by-side

added added

removed removed

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