/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: 2012-06-23 00:58:49 UTC
  • Revision ID: teddy@recompile.se-20120623005849-02wj82cng433rt2k
* clients.conf: Convert all time intervals to new RFC 3339 syntax.
* mandos: All client options for time intervals now take an RFC 3339
          duration.
  (rfc3339_duration_to_delta): New function.
  (string_to_delta): Try rfc3339_duration_to_delta first.
* mandos-clients.conf.xml (OPTIONS/timeout): Document new format.
  (EXAMPLE): Update to new interval format.
  (SEE ALSO): Reference RFC 3339.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python2.7
 
1
#!/usr/bin/python
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-2015 Teddy Hogeborn
15
 
# Copyright © 2008-2015 Björn Påhlsson
 
14
# Copyright © 2008-2012 Teddy Hogeborn
 
15
# Copyright © 2008-2012 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
 
try:
40
 
    import SocketServer as socketserver
41
 
except ImportError:
42
 
    import socketserver
 
39
import SocketServer as socketserver
43
40
import socket
44
41
import argparse
45
42
import datetime
50
47
import gnutls.library.functions
51
48
import gnutls.library.constants
52
49
import gnutls.library.types
53
 
try:
54
 
    import ConfigParser as configparser
55
 
except ImportError:
56
 
    import configparser
 
50
import ConfigParser as configparser
57
51
import sys
58
52
import re
59
53
import os
68
62
import struct
69
63
import fcntl
70
64
import functools
71
 
try:
72
 
    import cPickle as pickle
73
 
except ImportError:
74
 
    import pickle
 
65
import cPickle as pickle
75
66
import multiprocessing
76
67
import types
77
68
import binascii
81
72
 
82
73
import dbus
83
74
import dbus.service
84
 
try:
85
 
    import gobject
86
 
except ImportError:
87
 
    from gi.repository import GObject as gobject
 
75
import gobject
88
76
import avahi
89
77
from dbus.mainloop.glib import DBusGMainLoop
90
78
import ctypes
91
79
import ctypes.util
92
80
import xml.dom.minidom
93
81
import inspect
 
82
import GnuPGInterface
94
83
 
95
84
try:
96
85
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
100
89
    except ImportError:
101
90
        SO_BINDTODEVICE = None
102
91
 
103
 
if sys.version_info.major == 2:
104
 
    str = unicode
105
 
 
106
 
version = "1.6.9"
 
92
version = "1.6.0"
107
93
stored_state_file = "clients.pickle"
108
94
 
109
95
logger = logging.getLogger()
110
 
syslogger = None
 
96
syslogger = (logging.handlers.SysLogHandler
 
97
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
 
98
              address = str("/dev/log")))
111
99
 
112
100
try:
113
 
    if_nametoindex = ctypes.cdll.LoadLibrary(
114
 
        ctypes.util.find_library("c")).if_nametoindex
 
101
    if_nametoindex = (ctypes.cdll.LoadLibrary
 
102
                      (ctypes.util.find_library("c"))
 
103
                      .if_nametoindex)
115
104
except (OSError, AttributeError):
116
 
    
117
105
    def if_nametoindex(interface):
118
106
        "Get an interface index the hard way, i.e. using fcntl()"
119
107
        SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
120
108
        with contextlib.closing(socket.socket()) as s:
121
109
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
122
 
                                struct.pack(b"16s16x", interface))
123
 
        interface_index = struct.unpack("I", ifreq[16:20])[0]
 
110
                                struct.pack(str("16s16x"),
 
111
                                            interface))
 
112
        interface_index = struct.unpack(str("I"),
 
113
                                        ifreq[16:20])[0]
124
114
        return interface_index
125
115
 
126
116
 
127
117
def initlogger(debug, level=logging.WARNING):
128
118
    """init logger and add loglevel"""
129
119
    
130
 
    global syslogger
131
 
    syslogger = (logging.handlers.SysLogHandler(
132
 
        facility = logging.handlers.SysLogHandler.LOG_DAEMON,
133
 
        address = "/dev/log"))
134
120
    syslogger.setFormatter(logging.Formatter
135
121
                           ('Mandos [%(process)d]: %(levelname)s:'
136
122
                            ' %(message)s'))
153
139
 
154
140
class PGPEngine(object):
155
141
    """A simple class for OpenPGP symmetric encryption & decryption"""
156
 
    
157
142
    def __init__(self):
 
143
        self.gnupg = GnuPGInterface.GnuPG()
158
144
        self.tempdir = tempfile.mkdtemp(prefix="mandos-")
159
 
        self.gnupgargs = ['--batch',
160
 
                          '--home', self.tempdir,
161
 
                          '--force-mdc',
162
 
                          '--quiet',
163
 
                          '--no-use-agent']
 
145
        self.gnupg = GnuPGInterface.GnuPG()
 
146
        self.gnupg.options.meta_interactive = False
 
147
        self.gnupg.options.homedir = self.tempdir
 
148
        self.gnupg.options.extra_args.extend(['--force-mdc',
 
149
                                              '--quiet',
 
150
                                              '--no-use-agent'])
164
151
    
165
152
    def __enter__(self):
166
153
        return self
188
175
    def password_encode(self, password):
189
176
        # Passphrase can not be empty and can not contain newlines or
190
177
        # NUL bytes.  So we prefix it and hex encode it.
191
 
        encoded = b"mandos" + binascii.hexlify(password)
192
 
        if len(encoded) > 2048:
193
 
            # GnuPG can't handle long passwords, so encode differently
194
 
            encoded = (b"mandos" + password.replace(b"\\", b"\\\\")
195
 
                       .replace(b"\n", b"\\n")
196
 
                       .replace(b"\0", b"\\x00"))
197
 
        return encoded
 
178
        return b"mandos" + binascii.hexlify(password)
198
179
    
199
180
    def encrypt(self, data, password):
200
 
        passphrase = self.password_encode(password)
201
 
        with tempfile.NamedTemporaryFile(
202
 
                dir=self.tempdir) as passfile:
203
 
            passfile.write(passphrase)
204
 
            passfile.flush()
205
 
            proc = subprocess.Popen(['gpg', '--symmetric',
206
 
                                     '--passphrase-file',
207
 
                                     passfile.name]
208
 
                                    + self.gnupgargs,
209
 
                                    stdin = subprocess.PIPE,
210
 
                                    stdout = subprocess.PIPE,
211
 
                                    stderr = subprocess.PIPE)
212
 
            ciphertext, err = proc.communicate(input = data)
213
 
        if proc.returncode != 0:
214
 
            raise PGPError(err)
 
181
        self.gnupg.passphrase = self.password_encode(password)
 
182
        with open(os.devnull, "w") as devnull:
 
183
            try:
 
184
                proc = self.gnupg.run(['--symmetric'],
 
185
                                      create_fhs=['stdin', 'stdout'],
 
186
                                      attach_fhs={'stderr': devnull})
 
187
                with contextlib.closing(proc.handles['stdin']) as f:
 
188
                    f.write(data)
 
189
                with contextlib.closing(proc.handles['stdout']) as f:
 
190
                    ciphertext = f.read()
 
191
                proc.wait()
 
192
            except IOError as e:
 
193
                raise PGPError(e)
 
194
        self.gnupg.passphrase = None
215
195
        return ciphertext
216
196
    
217
197
    def decrypt(self, data, password):
218
 
        passphrase = self.password_encode(password)
219
 
        with tempfile.NamedTemporaryFile(
220
 
                dir = self.tempdir) as passfile:
221
 
            passfile.write(passphrase)
222
 
            passfile.flush()
223
 
            proc = subprocess.Popen(['gpg', '--decrypt',
224
 
                                     '--passphrase-file',
225
 
                                     passfile.name]
226
 
                                    + self.gnupgargs,
227
 
                                    stdin = subprocess.PIPE,
228
 
                                    stdout = subprocess.PIPE,
229
 
                                    stderr = subprocess.PIPE)
230
 
            decrypted_plaintext, err = proc.communicate(input = data)
231
 
        if proc.returncode != 0:
232
 
            raise PGPError(err)
 
198
        self.gnupg.passphrase = self.password_encode(password)
 
199
        with open(os.devnull, "w") as devnull:
 
200
            try:
 
201
                proc = self.gnupg.run(['--decrypt'],
 
202
                                      create_fhs=['stdin', 'stdout'],
 
203
                                      attach_fhs={'stderr': devnull})
 
204
                with contextlib.closing(proc.handles['stdin']) as f:
 
205
                    f.write(data)
 
206
                with contextlib.closing(proc.handles['stdout']) as f:
 
207
                    decrypted_plaintext = f.read()
 
208
                proc.wait()
 
209
            except IOError as e:
 
210
                raise PGPError(e)
 
211
        self.gnupg.passphrase = None
233
212
        return decrypted_plaintext
234
213
 
235
214
 
236
215
class AvahiError(Exception):
237
216
    def __init__(self, value, *args, **kwargs):
238
217
        self.value = value
239
 
        return super(AvahiError, self).__init__(value, *args,
240
 
                                                **kwargs)
241
 
 
 
218
        super(AvahiError, self).__init__(value, *args, **kwargs)
 
219
    def __unicode__(self):
 
220
        return unicode(repr(self.value))
242
221
 
243
222
class AvahiServiceError(AvahiError):
244
223
    pass
245
224
 
246
 
 
247
225
class AvahiGroupError(AvahiError):
248
226
    pass
249
227
 
256
234
               Used to optionally bind to the specified interface.
257
235
    name: string; Example: 'Mandos'
258
236
    type: string; Example: '_mandos._tcp'.
259
 
     See <https://www.iana.org/assignments/service-names-port-numbers>
 
237
                  See <http://www.dns-sd.org/ServiceTypes.html>
260
238
    port: integer; what port to announce
261
239
    TXT: list of strings; TXT record for the service
262
240
    domain: string; Domain to publish on, default to .local if empty.
269
247
    bus: dbus.SystemBus()
270
248
    """
271
249
    
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):
 
250
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
 
251
                 servicetype = None, port = None, TXT = None,
 
252
                 domain = "", host = "", max_renames = 32768,
 
253
                 protocol = avahi.PROTO_UNSPEC, bus = None):
283
254
        self.interface = interface
284
255
        self.name = name
285
256
        self.type = servicetype
295
266
        self.bus = bus
296
267
        self.entry_group_state_changed_match = None
297
268
    
298
 
    def rename(self, remove=True):
 
269
    def rename(self):
299
270
        """Derived from the Avahi example code"""
300
271
        if self.rename_count >= self.max_renames:
301
272
            logger.critical("No suitable Zeroconf service name found"
302
273
                            " after %i retries, exiting.",
303
274
                            self.rename_count)
304
275
            raise AvahiServiceError("Too many renames")
305
 
        self.name = str(
306
 
            self.server.GetAlternativeServiceName(self.name))
307
 
        self.rename_count += 1
 
276
        self.name = unicode(self.server
 
277
                            .GetAlternativeServiceName(self.name))
308
278
        logger.info("Changing Zeroconf service name to %r ...",
309
279
                    self.name)
310
 
        if remove:
311
 
            self.remove()
 
280
        self.remove()
312
281
        try:
313
282
            self.add()
314
283
        except dbus.exceptions.DBusException as error:
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)
 
284
            logger.critical("D-Bus Exception", exc_info=error)
 
285
            self.cleanup()
 
286
            os._exit(1)
 
287
        self.rename_count += 1
323
288
    
324
289
    def remove(self):
325
290
        """Derived from the Avahi example code"""
363
328
            self.rename()
364
329
        elif state == avahi.ENTRY_GROUP_FAILURE:
365
330
            logger.critical("Avahi: Error in group state changed %s",
366
 
                            str(error))
367
 
            raise AvahiGroupError("State changed: {!s}".format(error))
 
331
                            unicode(error))
 
332
            raise AvahiGroupError("State changed: {0!s}"
 
333
                                  .format(error))
368
334
    
369
335
    def cleanup(self):
370
336
        """Derived from the Avahi example code"""
380
346
    def server_state_changed(self, state, error=None):
381
347
        """Derived from the Avahi example code"""
382
348
        logger.debug("Avahi server state change: %i", state)
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
 
        }
 
349
        bad_states = { avahi.SERVER_INVALID:
 
350
                           "Zeroconf server invalid",
 
351
                       avahi.SERVER_REGISTERING: None,
 
352
                       avahi.SERVER_COLLISION:
 
353
                           "Zeroconf server name collision",
 
354
                       avahi.SERVER_FAILURE:
 
355
                           "Zeroconf server failure" }
389
356
        if state in bad_states:
390
357
            if bad_states[state] is not None:
391
358
                if error is None:
410
377
                                    follow_name_owner_changes=True),
411
378
                avahi.DBUS_INTERFACE_SERVER)
412
379
        self.server.connect_to_signal("StateChanged",
413
 
                                      self.server_state_changed)
 
380
                                 self.server_state_changed)
414
381
        self.server_state_changed(self.server.GetState())
415
382
 
416
383
 
417
384
class AvahiServiceToSyslog(AvahiService):
418
 
    def rename(self, *args, **kwargs):
 
385
    def rename(self):
419
386
        """Add the new name to the syslog messages"""
420
 
        ret = AvahiService.rename(self, *args, **kwargs)
421
 
        syslogger.setFormatter(logging.Formatter(
422
 
            'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
423
 
            .format(self.name)))
 
387
        ret = AvahiService.rename(self)
 
388
        syslogger.setFormatter(logging.Formatter
 
389
                               ('Mandos ({0}) [%(process)d]:'
 
390
                                ' %(levelname)s: %(message)s'
 
391
                                .format(self.name)))
424
392
        return ret
425
393
 
426
394
 
 
395
def timedelta_to_milliseconds(td):
 
396
    "Convert a datetime.timedelta() to milliseconds"
 
397
    return ((td.days * 24 * 60 * 60 * 1000)
 
398
            + (td.seconds * 1000)
 
399
            + (td.microseconds // 1000))
 
400
 
 
401
 
427
402
class Client(object):
428
403
    """A representation of a client host served by this server.
429
404
    
465
440
    runtime_expansions: Allowed attributes for runtime expansion.
466
441
    expires:    datetime.datetime(); time (UTC) when a client will be
467
442
                disabled, or None
468
 
    server_settings: The server_settings dict from main()
469
443
    """
470
444
    
471
445
    runtime_expansions = ("approval_delay", "approval_duration",
473
447
                          "fingerprint", "host", "interval",
474
448
                          "last_approval_request", "last_checked_ok",
475
449
                          "last_enabled", "name", "timeout")
476
 
    client_defaults = {
477
 
        "timeout": "PT5M",
478
 
        "extended_timeout": "PT15M",
479
 
        "interval": "PT2M",
480
 
        "checker": "fping -q -- %%(host)s",
481
 
        "host": "",
482
 
        "approval_delay": "PT0S",
483
 
        "approval_duration": "PT1S",
484
 
        "approved_by_default": "True",
485
 
        "enabled": "True",
486
 
    }
 
450
    client_defaults = { "timeout": "PT5M",
 
451
                        "extended_timeout": "PT15M",
 
452
                        "interval": "PT2M",
 
453
                        "checker": "fping -q -- %%(host)s",
 
454
                        "host": "",
 
455
                        "approval_delay": "PT0S",
 
456
                        "approval_duration": "PT1S",
 
457
                        "approved_by_default": "True",
 
458
                        "enabled": "True",
 
459
                        }
 
460
    
 
461
    def timeout_milliseconds(self):
 
462
        "Return the 'timeout' attribute in milliseconds"
 
463
        return timedelta_to_milliseconds(self.timeout)
 
464
    
 
465
    def extended_timeout_milliseconds(self):
 
466
        "Return the 'extended_timeout' attribute in milliseconds"
 
467
        return timedelta_to_milliseconds(self.extended_timeout)
 
468
    
 
469
    def interval_milliseconds(self):
 
470
        "Return the 'interval' attribute in milliseconds"
 
471
        return timedelta_to_milliseconds(self.interval)
 
472
    
 
473
    def approval_delay_milliseconds(self):
 
474
        return timedelta_to_milliseconds(self.approval_delay)
487
475
    
488
476
    @staticmethod
489
477
    def config_parser(config):
505
493
            client["enabled"] = config.getboolean(client_name,
506
494
                                                  "enabled")
507
495
            
508
 
            # Uppercase and remove spaces from fingerprint for later
509
 
            # comparison purposes with return value from the
510
 
            # fingerprint() function
511
496
            client["fingerprint"] = (section["fingerprint"].upper()
512
497
                                     .replace(" ", ""))
513
498
            if "secret" in section:
518
503
                          "rb") as secfile:
519
504
                    client["secret"] = secfile.read()
520
505
            else:
521
 
                raise TypeError("No secret or secfile for section {}"
 
506
                raise TypeError("No secret or secfile for section {0}"
522
507
                                .format(section))
523
508
            client["timeout"] = string_to_delta(section["timeout"])
524
509
            client["extended_timeout"] = string_to_delta(
535
520
        
536
521
        return settings
537
522
    
538
 
    def __init__(self, settings, name = None, server_settings=None):
 
523
    def __init__(self, settings, name = None):
539
524
        self.name = name
540
 
        if server_settings is None:
541
 
            server_settings = {}
542
 
        self.server_settings = server_settings
543
525
        # adding all client settings
544
 
        for setting, value in settings.items():
 
526
        for setting, value in settings.iteritems():
545
527
            setattr(self, setting, value)
546
528
        
547
529
        if self.enabled:
555
537
            self.expires = None
556
538
        
557
539
        logger.debug("Creating client %r", self.name)
 
540
        # Uppercase and remove spaces from fingerprint for later
 
541
        # comparison purposes with return value from the fingerprint()
 
542
        # function
558
543
        logger.debug("  Fingerprint: %s", self.fingerprint)
559
544
        self.created = settings.get("created",
560
545
                                    datetime.datetime.utcnow())
567
552
        self.current_checker_command = None
568
553
        self.approved = None
569
554
        self.approvals_pending = 0
570
 
        self.changedstate = multiprocessing_manager.Condition(
571
 
            multiprocessing_manager.Lock())
572
 
        self.client_structure = [attr
573
 
                                 for attr in self.__dict__.iterkeys()
 
555
        self.changedstate = (multiprocessing_manager
 
556
                             .Condition(multiprocessing_manager
 
557
                                        .Lock()))
 
558
        self.client_structure = [attr for attr in
 
559
                                 self.__dict__.iterkeys()
574
560
                                 if not attr.startswith("_")]
575
561
        self.client_structure.append("client_structure")
576
562
        
577
 
        for name, t in inspect.getmembers(
578
 
                type(self), lambda obj: isinstance(obj, property)):
 
563
        for name, t in inspect.getmembers(type(self),
 
564
                                          lambda obj:
 
565
                                              isinstance(obj,
 
566
                                                         property)):
579
567
            if not name.startswith("_"):
580
568
                self.client_structure.append(name)
581
569
    
623
611
        # and every interval from then on.
624
612
        if self.checker_initiator_tag is not None:
625
613
            gobject.source_remove(self.checker_initiator_tag)
626
 
        self.checker_initiator_tag = gobject.timeout_add(
627
 
            int(self.interval.total_seconds() * 1000),
628
 
            self.start_checker)
 
614
        self.checker_initiator_tag = (gobject.timeout_add
 
615
                                      (self.interval_milliseconds(),
 
616
                                       self.start_checker))
629
617
        # Schedule a disable() when 'timeout' has passed
630
618
        if self.disable_initiator_tag is not None:
631
619
            gobject.source_remove(self.disable_initiator_tag)
632
 
        self.disable_initiator_tag = gobject.timeout_add(
633
 
            int(self.timeout.total_seconds() * 1000), self.disable)
 
620
        self.disable_initiator_tag = (gobject.timeout_add
 
621
                                   (self.timeout_milliseconds(),
 
622
                                    self.disable))
634
623
        # Also start a new checker *right now*.
635
624
        self.start_checker()
636
625
    
645
634
                            vars(self))
646
635
                self.checked_ok()
647
636
            else:
648
 
                logger.info("Checker for %(name)s failed", vars(self))
 
637
                logger.info("Checker for %(name)s failed",
 
638
                            vars(self))
649
639
        else:
650
640
            self.last_checker_status = -1
651
641
            logger.warning("Checker for %(name)s crashed?",
665
655
            gobject.source_remove(self.disable_initiator_tag)
666
656
            self.disable_initiator_tag = None
667
657
        if getattr(self, "enabled", False):
668
 
            self.disable_initiator_tag = gobject.timeout_add(
669
 
                int(timeout.total_seconds() * 1000), self.disable)
 
658
            self.disable_initiator_tag = (gobject.timeout_add
 
659
                                          (timedelta_to_milliseconds
 
660
                                           (timeout), self.disable))
670
661
            self.expires = datetime.datetime.utcnow() + timeout
671
662
    
672
663
    def need_approval(self):
689
680
        # If a checker exists, make sure it is not a zombie
690
681
        try:
691
682
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
692
 
        except AttributeError:
693
 
            pass
694
 
        except OSError as error:
695
 
            if error.errno != errno.ECHILD:
696
 
                raise
 
683
        except (AttributeError, OSError) as error:
 
684
            if (isinstance(error, OSError)
 
685
                and error.errno != errno.ECHILD):
 
686
                raise error
697
687
        else:
698
688
            if pid:
699
689
                logger.warning("Checker was a zombie")
703
693
        # Start a new checker if needed
704
694
        if self.checker is None:
705
695
            # Escape attributes for the shell
706
 
            escaped_attrs = {
707
 
                attr: re.escape(str(getattr(self, attr)))
708
 
                for attr in self.runtime_expansions }
 
696
            escaped_attrs = dict(
 
697
                (attr, re.escape(unicode(getattr(self, attr))))
 
698
                for attr in
 
699
                self.runtime_expansions)
709
700
            try:
710
701
                command = self.checker_command % escaped_attrs
711
702
            except TypeError as error:
712
703
                logger.error('Could not format string "%s"',
713
 
                             self.checker_command,
714
 
                             exc_info=error)
715
 
                return True     # Try again later
 
704
                             self.checker_command, exc_info=error)
 
705
                return True # Try again later
716
706
            self.current_checker_command = command
717
707
            try:
718
 
                logger.info("Starting checker %r for %s", command,
719
 
                            self.name)
 
708
                logger.info("Starting checker %r for %s",
 
709
                            command, self.name)
720
710
                # We don't need to redirect stdout and stderr, since
721
711
                # in normal mode, that is already done by daemon(),
722
712
                # and in debug mode we don't want to.  (Stdin is
723
713
                # always replaced by /dev/null.)
724
 
                # The exception is when not debugging but nevertheless
725
 
                # running in the foreground; use the previously
726
 
                # created wnull.
727
 
                popen_args = {}
728
 
                if (not self.server_settings["debug"]
729
 
                    and self.server_settings["foreground"]):
730
 
                    popen_args.update({"stdout": wnull,
731
 
                                       "stderr": wnull })
732
714
                self.checker = subprocess.Popen(command,
733
715
                                                close_fds=True,
734
 
                                                shell=True,
735
 
                                                cwd="/",
736
 
                                                **popen_args)
 
716
                                                shell=True, cwd="/")
737
717
            except OSError as error:
738
718
                logger.error("Failed to start subprocess",
739
719
                             exc_info=error)
740
 
                return True
741
 
            self.checker_callback_tag = gobject.child_watch_add(
742
 
                self.checker.pid, self.checker_callback, data=command)
 
720
            self.checker_callback_tag = (gobject.child_watch_add
 
721
                                         (self.checker.pid,
 
722
                                          self.checker_callback,
 
723
                                          data=command))
743
724
            # The checker may have completed before the gobject
744
725
            # watch was added.  Check for this.
745
 
            try:
746
 
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
747
 
            except OSError as error:
748
 
                if error.errno == errno.ECHILD:
749
 
                    # This should never happen
750
 
                    logger.error("Child process vanished",
751
 
                                 exc_info=error)
752
 
                    return True
753
 
                raise
 
726
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
754
727
            if pid:
755
728
                gobject.source_remove(self.checker_callback_tag)
756
729
                self.checker_callback(pid, status, command)
776
749
        self.checker = None
777
750
 
778
751
 
779
 
def dbus_service_property(dbus_interface,
780
 
                          signature="v",
781
 
                          access="readwrite",
782
 
                          byte_arrays=False):
 
752
def dbus_service_property(dbus_interface, signature="v",
 
753
                          access="readwrite", byte_arrays=False):
783
754
    """Decorators for marking methods of a DBusObjectWithProperties to
784
755
    become properties on the D-Bus.
785
756
    
794
765
    # "Set" method, so we fail early here:
795
766
    if byte_arrays and signature != "ay":
796
767
        raise ValueError("Byte arrays not supported for non-'ay'"
797
 
                         " signature {!r}".format(signature))
798
 
    
 
768
                         " signature {0!r}".format(signature))
799
769
    def decorator(func):
800
770
        func._dbus_is_property = True
801
771
        func._dbus_interface = dbus_interface
806
776
            func._dbus_name = func._dbus_name[:-14]
807
777
        func._dbus_get_args_options = {'byte_arrays': byte_arrays }
808
778
        return func
809
 
    
810
779
    return decorator
811
780
 
812
781
 
821
790
                "org.freedesktop.DBus.Property.EmitsChangedSignal":
822
791
                    "false"}
823
792
    """
824
 
    
825
793
    def decorator(func):
826
794
        func._dbus_is_interface = True
827
795
        func._dbus_interface = dbus_interface
828
796
        func._dbus_name = dbus_interface
829
797
        return func
830
 
    
831
798
    return decorator
832
799
 
833
800
 
835
802
    """Decorator to annotate D-Bus methods, signals or properties
836
803
    Usage:
837
804
    
838
 
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
839
 
                       "org.freedesktop.DBus.Property."
840
 
                       "EmitsChangedSignal": "false"})
841
805
    @dbus_service_property("org.example.Interface", signature="b",
842
806
                           access="r")
 
807
    @dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
 
808
                        "org.freedesktop.DBus.Property."
 
809
                        "EmitsChangedSignal": "false"})
843
810
    def Property_dbus_property(self):
844
811
        return dbus.Boolean(False)
845
812
    """
846
 
    
847
813
    def decorator(func):
848
814
        func._dbus_annotations = annotations
849
815
        return func
850
 
    
851
816
    return decorator
852
817
 
853
818
 
854
819
class DBusPropertyException(dbus.exceptions.DBusException):
855
820
    """A base class for D-Bus property-related exceptions
856
821
    """
857
 
    pass
 
822
    def __unicode__(self):
 
823
        return unicode(str(self))
858
824
 
859
825
 
860
826
class DBusPropertyAccessException(DBusPropertyException):
884
850
        If called like _is_dbus_thing("method") it returns a function
885
851
        suitable for use as predicate to inspect.getmembers().
886
852
        """
887
 
        return lambda obj: getattr(obj, "_dbus_is_{}".format(thing),
 
853
        return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
888
854
                                   False)
889
855
    
890
856
    def _get_all_dbus_things(self, thing):
891
857
        """Returns a generator of (name, attribute) pairs
892
858
        """
893
 
        return ((getattr(athing.__get__(self), "_dbus_name", name),
 
859
        return ((getattr(athing.__get__(self), "_dbus_name",
 
860
                         name),
894
861
                 athing.__get__(self))
895
862
                for cls in self.__class__.__mro__
896
863
                for name, athing in
897
 
                inspect.getmembers(cls, self._is_dbus_thing(thing)))
 
864
                inspect.getmembers(cls,
 
865
                                   self._is_dbus_thing(thing)))
898
866
    
899
867
    def _get_dbus_property(self, interface_name, property_name):
900
868
        """Returns a bound method if one exists which is a D-Bus
901
869
        property with the specified name and interface.
902
870
        """
903
 
        for cls in self.__class__.__mro__:
904
 
            for name, value in inspect.getmembers(
905
 
                    cls, self._is_dbus_thing("property")):
 
871
        for cls in  self.__class__.__mro__:
 
872
            for name, value in (inspect.getmembers
 
873
                                (cls,
 
874
                                 self._is_dbus_thing("property"))):
906
875
                if (value._dbus_name == property_name
907
876
                    and value._dbus_interface == interface_name):
908
877
                    return value.__get__(self)
909
878
        
910
879
        # No such property
911
 
        raise DBusPropertyNotFound("{}:{}.{}".format(
912
 
            self.dbus_object_path, interface_name, property_name))
 
880
        raise DBusPropertyNotFound(self.dbus_object_path + ":"
 
881
                                   + interface_name + "."
 
882
                                   + property_name)
913
883
    
914
 
    @dbus.service.method(dbus.PROPERTIES_IFACE,
915
 
                         in_signature="ss",
 
884
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
916
885
                         out_signature="v")
917
886
    def Get(self, interface_name, property_name):
918
887
        """Standard D-Bus property Get() method, see D-Bus standard.
936
905
            # The byte_arrays option is not supported yet on
937
906
            # signatures other than "ay".
938
907
            if prop._dbus_signature != "ay":
939
 
                raise ValueError("Byte arrays not supported for non-"
940
 
                                 "'ay' signature {!r}"
941
 
                                 .format(prop._dbus_signature))
 
908
                raise ValueError
942
909
            value = dbus.ByteArray(b''.join(chr(byte)
943
910
                                            for byte in value))
944
911
        prop(value)
945
912
    
946
 
    @dbus.service.method(dbus.PROPERTIES_IFACE,
947
 
                         in_signature="s",
 
913
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
948
914
                         out_signature="a{sv}")
949
915
    def GetAll(self, interface_name):
950
916
        """Standard D-Bus property GetAll() method, see D-Bus
965
931
            if not hasattr(value, "variant_level"):
966
932
                properties[name] = value
967
933
                continue
968
 
            properties[name] = type(value)(
969
 
                value, variant_level = value.variant_level + 1)
 
934
            properties[name] = type(value)(value, variant_level=
 
935
                                           value.variant_level+1)
970
936
        return dbus.Dictionary(properties, signature="sv")
971
937
    
972
 
    @dbus.service.signal(dbus.PROPERTIES_IFACE, signature="sa{sv}as")
973
 
    def PropertiesChanged(self, interface_name, changed_properties,
974
 
                          invalidated_properties):
975
 
        """Standard D-Bus PropertiesChanged() signal, see D-Bus
976
 
        standard.
977
 
        """
978
 
        pass
979
 
    
980
938
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
981
939
                         out_signature="s",
982
940
                         path_keyword='object_path',
990
948
                                                   connection)
991
949
        try:
992
950
            document = xml.dom.minidom.parseString(xmlstring)
993
 
            
994
951
            def make_tag(document, name, prop):
995
952
                e = document.createElement("property")
996
953
                e.setAttribute("name", name)
997
954
                e.setAttribute("type", prop._dbus_signature)
998
955
                e.setAttribute("access", prop._dbus_access)
999
956
                return e
1000
 
            
1001
957
            for if_tag in document.getElementsByTagName("interface"):
1002
958
                # Add property tags
1003
959
                for tag in (make_tag(document, name, prop)
1015
971
                            if (name == tag.getAttribute("name")
1016
972
                                and prop._dbus_interface
1017
973
                                == if_tag.getAttribute("name")):
1018
 
                                annots.update(getattr(
1019
 
                                    prop, "_dbus_annotations", {}))
1020
 
                        for name, value in annots.items():
 
974
                                annots.update(getattr
 
975
                                              (prop,
 
976
                                               "_dbus_annotations",
 
977
                                               {}))
 
978
                        for name, value in annots.iteritems():
1021
979
                            ann_tag = document.createElement(
1022
980
                                "annotation")
1023
981
                            ann_tag.setAttribute("name", name)
1026
984
                # Add interface annotation tags
1027
985
                for annotation, value in dict(
1028
986
                    itertools.chain.from_iterable(
1029
 
                        annotations().items()
1030
 
                        for name, annotations
1031
 
                        in self._get_all_dbus_things("interface")
 
987
                        annotations().iteritems()
 
988
                        for name, annotations in
 
989
                        self._get_all_dbus_things("interface")
1032
990
                        if name == if_tag.getAttribute("name")
1033
 
                        )).items():
 
991
                        )).iteritems():
1034
992
                    ann_tag = document.createElement("annotation")
1035
993
                    ann_tag.setAttribute("name", annotation)
1036
994
                    ann_tag.setAttribute("value", value)
1063
1021
    """Convert a UTC datetime.datetime() to a D-Bus type."""
1064
1022
    if dt is None:
1065
1023
        return dbus.String("", variant_level = variant_level)
1066
 
    return dbus.String(dt.isoformat(), variant_level=variant_level)
 
1024
    return dbus.String(dt.isoformat(),
 
1025
                       variant_level=variant_level)
1067
1026
 
1068
1027
 
1069
1028
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1089
1048
    (from DBusObjectWithProperties) and interfaces (from the
1090
1049
    dbus_interface_annotations decorator).
1091
1050
    """
1092
 
    
1093
1051
    def wrapper(cls):
1094
1052
        for orig_interface_name, alt_interface_name in (
1095
 
                alt_interface_names.items()):
 
1053
            alt_interface_names.iteritems()):
1096
1054
            attr = {}
1097
1055
            interface_names = set()
1098
1056
            # Go though all attributes of the class
1100
1058
                # Ignore non-D-Bus attributes, and D-Bus attributes
1101
1059
                # with the wrong interface name
1102
1060
                if (not hasattr(attribute, "_dbus_interface")
1103
 
                    or not attribute._dbus_interface.startswith(
1104
 
                        orig_interface_name)):
 
1061
                    or not attribute._dbus_interface
 
1062
                    .startswith(orig_interface_name)):
1105
1063
                    continue
1106
1064
                # Create an alternate D-Bus interface name based on
1107
1065
                # the current name
1108
 
                alt_interface = attribute._dbus_interface.replace(
1109
 
                    orig_interface_name, alt_interface_name)
 
1066
                alt_interface = (attribute._dbus_interface
 
1067
                                 .replace(orig_interface_name,
 
1068
                                          alt_interface_name))
1110
1069
                interface_names.add(alt_interface)
1111
1070
                # Is this a D-Bus signal?
1112
1071
                if getattr(attribute, "_dbus_is_signal", False):
1113
 
                    # Extract the original non-method undecorated
1114
 
                    # function by black magic
 
1072
                    # Extract the original non-method function by
 
1073
                    # black magic
1115
1074
                    nonmethod_func = (dict(
1116
 
                        zip(attribute.func_code.co_freevars,
1117
 
                            attribute.__closure__))
1118
 
                                      ["func"].cell_contents)
 
1075
                            zip(attribute.func_code.co_freevars,
 
1076
                                attribute.__closure__))["func"]
 
1077
                                      .cell_contents)
1119
1078
                    # Create a new, but exactly alike, function
1120
1079
                    # object, and decorate it to be a new D-Bus signal
1121
1080
                    # with the alternate D-Bus interface name
1122
 
                    new_function = (dbus.service.signal(
1123
 
                        alt_interface, attribute._dbus_signature)
 
1081
                    new_function = (dbus.service.signal
 
1082
                                    (alt_interface,
 
1083
                                     attribute._dbus_signature)
1124
1084
                                    (types.FunctionType(
1125
 
                                        nonmethod_func.func_code,
1126
 
                                        nonmethod_func.func_globals,
1127
 
                                        nonmethod_func.func_name,
1128
 
                                        nonmethod_func.func_defaults,
1129
 
                                        nonmethod_func.func_closure)))
 
1085
                                nonmethod_func.func_code,
 
1086
                                nonmethod_func.func_globals,
 
1087
                                nonmethod_func.func_name,
 
1088
                                nonmethod_func.func_defaults,
 
1089
                                nonmethod_func.func_closure)))
1130
1090
                    # Copy annotations, if any
1131
1091
                    try:
1132
 
                        new_function._dbus_annotations = dict(
1133
 
                            attribute._dbus_annotations)
 
1092
                        new_function._dbus_annotations = (
 
1093
                            dict(attribute._dbus_annotations))
1134
1094
                    except AttributeError:
1135
1095
                        pass
1136
1096
                    # Define a creator of a function to call both the
1141
1101
                        """This function is a scope container to pass
1142
1102
                        func1 and func2 to the "call_both" function
1143
1103
                        outside of its arguments"""
1144
 
                        
1145
1104
                        def call_both(*args, **kwargs):
1146
1105
                            """This function will emit two D-Bus
1147
1106
                            signals by calling func1 and func2"""
1148
1107
                            func1(*args, **kwargs)
1149
1108
                            func2(*args, **kwargs)
1150
 
                        
1151
1109
                        return call_both
1152
1110
                    # Create the "call_both" function and add it to
1153
1111
                    # the class
1158
1116
                    # object.  Decorate it to be a new D-Bus method
1159
1117
                    # with the alternate D-Bus interface name.  Add it
1160
1118
                    # to the class.
1161
 
                    attr[attrname] = (
1162
 
                        dbus.service.method(
1163
 
                            alt_interface,
1164
 
                            attribute._dbus_in_signature,
1165
 
                            attribute._dbus_out_signature)
1166
 
                        (types.FunctionType(attribute.func_code,
1167
 
                                            attribute.func_globals,
1168
 
                                            attribute.func_name,
1169
 
                                            attribute.func_defaults,
1170
 
                                            attribute.func_closure)))
 
1119
                    attr[attrname] = (dbus.service.method
 
1120
                                      (alt_interface,
 
1121
                                       attribute._dbus_in_signature,
 
1122
                                       attribute._dbus_out_signature)
 
1123
                                      (types.FunctionType
 
1124
                                       (attribute.func_code,
 
1125
                                        attribute.func_globals,
 
1126
                                        attribute.func_name,
 
1127
                                        attribute.func_defaults,
 
1128
                                        attribute.func_closure)))
1171
1129
                    # Copy annotations, if any
1172
1130
                    try:
1173
 
                        attr[attrname]._dbus_annotations = dict(
1174
 
                            attribute._dbus_annotations)
 
1131
                        attr[attrname]._dbus_annotations = (
 
1132
                            dict(attribute._dbus_annotations))
1175
1133
                    except AttributeError:
1176
1134
                        pass
1177
1135
                # Is this a D-Bus property?
1180
1138
                    # object, and decorate it to be a new D-Bus
1181
1139
                    # property with the alternate D-Bus interface
1182
1140
                    # name.  Add it to the class.
1183
 
                    attr[attrname] = (dbus_service_property(
1184
 
                        alt_interface, attribute._dbus_signature,
1185
 
                        attribute._dbus_access,
1186
 
                        attribute._dbus_get_args_options
1187
 
                        ["byte_arrays"])
1188
 
                                      (types.FunctionType(
1189
 
                                          attribute.func_code,
1190
 
                                          attribute.func_globals,
1191
 
                                          attribute.func_name,
1192
 
                                          attribute.func_defaults,
1193
 
                                          attribute.func_closure)))
 
1141
                    attr[attrname] = (dbus_service_property
 
1142
                                      (alt_interface,
 
1143
                                       attribute._dbus_signature,
 
1144
                                       attribute._dbus_access,
 
1145
                                       attribute
 
1146
                                       ._dbus_get_args_options
 
1147
                                       ["byte_arrays"])
 
1148
                                      (types.FunctionType
 
1149
                                       (attribute.func_code,
 
1150
                                        attribute.func_globals,
 
1151
                                        attribute.func_name,
 
1152
                                        attribute.func_defaults,
 
1153
                                        attribute.func_closure)))
1194
1154
                    # Copy annotations, if any
1195
1155
                    try:
1196
 
                        attr[attrname]._dbus_annotations = dict(
1197
 
                            attribute._dbus_annotations)
 
1156
                        attr[attrname]._dbus_annotations = (
 
1157
                            dict(attribute._dbus_annotations))
1198
1158
                    except AttributeError:
1199
1159
                        pass
1200
1160
                # Is this a D-Bus interface?
1203
1163
                    # object.  Decorate it to be a new D-Bus interface
1204
1164
                    # with the alternate D-Bus interface name.  Add it
1205
1165
                    # to the class.
1206
 
                    attr[attrname] = (
1207
 
                        dbus_interface_annotations(alt_interface)
1208
 
                        (types.FunctionType(attribute.func_code,
1209
 
                                            attribute.func_globals,
1210
 
                                            attribute.func_name,
1211
 
                                            attribute.func_defaults,
1212
 
                                            attribute.func_closure)))
 
1166
                    attr[attrname] = (dbus_interface_annotations
 
1167
                                      (alt_interface)
 
1168
                                      (types.FunctionType
 
1169
                                       (attribute.func_code,
 
1170
                                        attribute.func_globals,
 
1171
                                        attribute.func_name,
 
1172
                                        attribute.func_defaults,
 
1173
                                        attribute.func_closure)))
1213
1174
            if deprecate:
1214
1175
                # Deprecate all alternate interfaces
1215
 
                iname="_AlternateDBusNames_interface_annotation{}"
 
1176
                iname="_AlternateDBusNames_interface_annotation{0}"
1216
1177
                for interface_name in interface_names:
1217
 
                    
1218
1178
                    @dbus_interface_annotations(interface_name)
1219
1179
                    def func(self):
1220
1180
                        return { "org.freedesktop.DBus.Deprecated":
1221
 
                                 "true" }
 
1181
                                     "true" }
1222
1182
                    # Find an unused name
1223
1183
                    for aname in (iname.format(i)
1224
1184
                                  for i in itertools.count()):
1228
1188
            if interface_names:
1229
1189
                # Replace the class with a new subclass of it with
1230
1190
                # methods, signals, etc. as created above.
1231
 
                cls = type(b"{}Alternate".format(cls.__name__),
1232
 
                           (cls, ), attr)
 
1191
                cls = type(b"{0}Alternate".format(cls.__name__),
 
1192
                           (cls,), attr)
1233
1193
        return cls
1234
 
    
1235
1194
    return wrapper
1236
1195
 
1237
1196
 
1238
1197
@alternate_dbus_interfaces({"se.recompile.Mandos":
1239
 
                            "se.bsnet.fukt.Mandos"})
 
1198
                                "se.bsnet.fukt.Mandos"})
1240
1199
class ClientDBus(Client, DBusObjectWithProperties):
1241
1200
    """A Client class using D-Bus
1242
1201
    
1246
1205
    """
1247
1206
    
1248
1207
    runtime_expansions = (Client.runtime_expansions
1249
 
                          + ("dbus_object_path", ))
1250
 
    
1251
 
    _interface = "se.recompile.Mandos.Client"
 
1208
                          + ("dbus_object_path",))
1252
1209
    
1253
1210
    # dbus.service.Object doesn't use super(), so we can't either.
1254
1211
    
1257
1214
        Client.__init__(self, *args, **kwargs)
1258
1215
        # Only now, when this client is initialized, can it show up on
1259
1216
        # the D-Bus
1260
 
        client_object_name = str(self.name).translate(
 
1217
        client_object_name = unicode(self.name).translate(
1261
1218
            {ord("."): ord("_"),
1262
1219
             ord("-"): ord("_")})
1263
 
        self.dbus_object_path = dbus.ObjectPath(
1264
 
            "/clients/" + client_object_name)
 
1220
        self.dbus_object_path = (dbus.ObjectPath
 
1221
                                 ("/clients/" + client_object_name))
1265
1222
        DBusObjectWithProperties.__init__(self, self.bus,
1266
1223
                                          self.dbus_object_path)
1267
1224
    
1268
 
    def notifychangeproperty(transform_func, dbus_name,
1269
 
                             type_func=lambda x: x,
1270
 
                             variant_level=1,
1271
 
                             invalidate_only=False,
1272
 
                             _interface=_interface):
 
1225
    def notifychangeproperty(transform_func,
 
1226
                             dbus_name, type_func=lambda x: x,
 
1227
                             variant_level=1):
1273
1228
        """ Modify a variable so that it's a property which announces
1274
1229
        its changes to DBus.
1275
1230
        
1280
1235
                   to the D-Bus.  Default: no transform
1281
1236
        variant_level: D-Bus variant level.  Default: 1
1282
1237
        """
1283
 
        attrname = "_{}".format(dbus_name)
1284
 
        
 
1238
        attrname = "_{0}".format(dbus_name)
1285
1239
        def setter(self, value):
1286
1240
            if hasattr(self, "dbus_object_path"):
1287
1241
                if (not hasattr(self, attrname) or
1288
1242
                    type_func(getattr(self, attrname, None))
1289
1243
                    != type_func(value)):
1290
 
                    if invalidate_only:
1291
 
                        self.PropertiesChanged(
1292
 
                            _interface, dbus.Dictionary(),
1293
 
                            dbus.Array((dbus_name, )))
1294
 
                    else:
1295
 
                        dbus_value = transform_func(
1296
 
                            type_func(value),
1297
 
                            variant_level = variant_level)
1298
 
                        self.PropertyChanged(dbus.String(dbus_name),
1299
 
                                             dbus_value)
1300
 
                        self.PropertiesChanged(
1301
 
                            _interface,
1302
 
                            dbus.Dictionary({ dbus.String(dbus_name):
1303
 
                                              dbus_value }),
1304
 
                            dbus.Array())
 
1244
                    dbus_value = transform_func(type_func(value),
 
1245
                                                variant_level
 
1246
                                                =variant_level)
 
1247
                    self.PropertyChanged(dbus.String(dbus_name),
 
1248
                                         dbus_value)
1305
1249
            setattr(self, attrname, value)
1306
1250
        
1307
1251
        return property(lambda self: getattr(self, attrname), setter)
1313
1257
    enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1314
1258
    last_enabled = notifychangeproperty(datetime_to_dbus,
1315
1259
                                        "LastEnabled")
1316
 
    checker = notifychangeproperty(
1317
 
        dbus.Boolean, "CheckerRunning",
1318
 
        type_func = lambda checker: checker is not None)
 
1260
    checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
 
1261
                                   type_func = lambda checker:
 
1262
                                       checker is not None)
1319
1263
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
1320
1264
                                           "LastCheckedOK")
1321
1265
    last_checker_status = notifychangeproperty(dbus.Int16,
1324
1268
        datetime_to_dbus, "LastApprovalRequest")
1325
1269
    approved_by_default = notifychangeproperty(dbus.Boolean,
1326
1270
                                               "ApprovedByDefault")
1327
 
    approval_delay = notifychangeproperty(
1328
 
        dbus.UInt64, "ApprovalDelay",
1329
 
        type_func = lambda td: td.total_seconds() * 1000)
 
1271
    approval_delay = notifychangeproperty(dbus.UInt64,
 
1272
                                          "ApprovalDelay",
 
1273
                                          type_func =
 
1274
                                          timedelta_to_milliseconds)
1330
1275
    approval_duration = notifychangeproperty(
1331
1276
        dbus.UInt64, "ApprovalDuration",
1332
 
        type_func = lambda td: td.total_seconds() * 1000)
 
1277
        type_func = timedelta_to_milliseconds)
1333
1278
    host = notifychangeproperty(dbus.String, "Host")
1334
 
    timeout = notifychangeproperty(
1335
 
        dbus.UInt64, "Timeout",
1336
 
        type_func = lambda td: td.total_seconds() * 1000)
 
1279
    timeout = notifychangeproperty(dbus.UInt64, "Timeout",
 
1280
                                   type_func =
 
1281
                                   timedelta_to_milliseconds)
1337
1282
    extended_timeout = notifychangeproperty(
1338
1283
        dbus.UInt64, "ExtendedTimeout",
1339
 
        type_func = lambda td: td.total_seconds() * 1000)
1340
 
    interval = notifychangeproperty(
1341
 
        dbus.UInt64, "Interval",
1342
 
        type_func = lambda td: td.total_seconds() * 1000)
 
1284
        type_func = timedelta_to_milliseconds)
 
1285
    interval = notifychangeproperty(dbus.UInt64,
 
1286
                                    "Interval",
 
1287
                                    type_func =
 
1288
                                    timedelta_to_milliseconds)
1343
1289
    checker_command = notifychangeproperty(dbus.String, "Checker")
1344
 
    secret = notifychangeproperty(dbus.ByteArray, "Secret",
1345
 
                                  invalidate_only=True)
1346
1290
    
1347
1291
    del notifychangeproperty
1348
1292
    
1375
1319
                                       *args, **kwargs)
1376
1320
    
1377
1321
    def start_checker(self, *args, **kwargs):
1378
 
        old_checker_pid = getattr(self.checker, "pid", None)
 
1322
        old_checker = self.checker
 
1323
        if self.checker is not None:
 
1324
            old_checker_pid = self.checker.pid
 
1325
        else:
 
1326
            old_checker_pid = None
1379
1327
        r = Client.start_checker(self, *args, **kwargs)
1380
1328
        # Only if new checker process was started
1381
1329
        if (self.checker is not None
1390
1338
    
1391
1339
    def approve(self, value=True):
1392
1340
        self.approved = value
1393
 
        gobject.timeout_add(int(self.approval_duration.total_seconds()
1394
 
                                * 1000), self._reset_approved)
 
1341
        gobject.timeout_add(timedelta_to_milliseconds
 
1342
                            (self.approval_duration),
 
1343
                            self._reset_approved)
1395
1344
        self.send_changedstate()
1396
1345
    
1397
1346
    ## D-Bus methods, signals & properties
 
1347
    _interface = "se.recompile.Mandos.Client"
1398
1348
    
1399
1349
    ## Interfaces
1400
1350
    
 
1351
    @dbus_interface_annotations(_interface)
 
1352
    def _foo(self):
 
1353
        return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
 
1354
                     "false"}
 
1355
    
1401
1356
    ## Signals
1402
1357
    
1403
1358
    # CheckerCompleted - signal
1413
1368
        pass
1414
1369
    
1415
1370
    # PropertyChanged - signal
1416
 
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1417
1371
    @dbus.service.signal(_interface, signature="sv")
1418
1372
    def PropertyChanged(self, property, value):
1419
1373
        "D-Bus signal"
1483
1437
        return dbus.Boolean(bool(self.approvals_pending))
1484
1438
    
1485
1439
    # ApprovedByDefault - property
1486
 
    @dbus_service_property(_interface,
1487
 
                           signature="b",
 
1440
    @dbus_service_property(_interface, signature="b",
1488
1441
                           access="readwrite")
1489
1442
    def ApprovedByDefault_dbus_property(self, value=None):
1490
1443
        if value is None:       # get
1492
1445
        self.approved_by_default = bool(value)
1493
1446
    
1494
1447
    # ApprovalDelay - property
1495
 
    @dbus_service_property(_interface,
1496
 
                           signature="t",
 
1448
    @dbus_service_property(_interface, signature="t",
1497
1449
                           access="readwrite")
1498
1450
    def ApprovalDelay_dbus_property(self, value=None):
1499
1451
        if value is None:       # get
1500
 
            return dbus.UInt64(self.approval_delay.total_seconds()
1501
 
                               * 1000)
 
1452
            return dbus.UInt64(self.approval_delay_milliseconds())
1502
1453
        self.approval_delay = datetime.timedelta(0, 0, 0, value)
1503
1454
    
1504
1455
    # ApprovalDuration - property
1505
 
    @dbus_service_property(_interface,
1506
 
                           signature="t",
 
1456
    @dbus_service_property(_interface, signature="t",
1507
1457
                           access="readwrite")
1508
1458
    def ApprovalDuration_dbus_property(self, value=None):
1509
1459
        if value is None:       # get
1510
 
            return dbus.UInt64(self.approval_duration.total_seconds()
1511
 
                               * 1000)
 
1460
            return dbus.UInt64(timedelta_to_milliseconds(
 
1461
                    self.approval_duration))
1512
1462
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
1513
1463
    
1514
1464
    # Name - property
1522
1472
        return dbus.String(self.fingerprint)
1523
1473
    
1524
1474
    # Host - property
1525
 
    @dbus_service_property(_interface,
1526
 
                           signature="s",
 
1475
    @dbus_service_property(_interface, signature="s",
1527
1476
                           access="readwrite")
1528
1477
    def Host_dbus_property(self, value=None):
1529
1478
        if value is None:       # get
1530
1479
            return dbus.String(self.host)
1531
 
        self.host = str(value)
 
1480
        self.host = unicode(value)
1532
1481
    
1533
1482
    # Created - property
1534
1483
    @dbus_service_property(_interface, signature="s", access="read")
1541
1490
        return datetime_to_dbus(self.last_enabled)
1542
1491
    
1543
1492
    # Enabled - property
1544
 
    @dbus_service_property(_interface,
1545
 
                           signature="b",
 
1493
    @dbus_service_property(_interface, signature="b",
1546
1494
                           access="readwrite")
1547
1495
    def Enabled_dbus_property(self, value=None):
1548
1496
        if value is None:       # get
1553
1501
            self.disable()
1554
1502
    
1555
1503
    # LastCheckedOK - property
1556
 
    @dbus_service_property(_interface,
1557
 
                           signature="s",
 
1504
    @dbus_service_property(_interface, signature="s",
1558
1505
                           access="readwrite")
1559
1506
    def LastCheckedOK_dbus_property(self, value=None):
1560
1507
        if value is not None:
1563
1510
        return datetime_to_dbus(self.last_checked_ok)
1564
1511
    
1565
1512
    # LastCheckerStatus - property
1566
 
    @dbus_service_property(_interface, signature="n", access="read")
 
1513
    @dbus_service_property(_interface, signature="n",
 
1514
                           access="read")
1567
1515
    def LastCheckerStatus_dbus_property(self):
1568
1516
        return dbus.Int16(self.last_checker_status)
1569
1517
    
1578
1526
        return datetime_to_dbus(self.last_approval_request)
1579
1527
    
1580
1528
    # Timeout - property
1581
 
    @dbus_service_property(_interface,
1582
 
                           signature="t",
 
1529
    @dbus_service_property(_interface, signature="t",
1583
1530
                           access="readwrite")
1584
1531
    def Timeout_dbus_property(self, value=None):
1585
1532
        if value is None:       # get
1586
 
            return dbus.UInt64(self.timeout.total_seconds() * 1000)
 
1533
            return dbus.UInt64(self.timeout_milliseconds())
1587
1534
        old_timeout = self.timeout
1588
1535
        self.timeout = datetime.timedelta(0, 0, 0, value)
1589
1536
        # Reschedule disabling
1598
1545
                    is None):
1599
1546
                    return
1600
1547
                gobject.source_remove(self.disable_initiator_tag)
1601
 
                self.disable_initiator_tag = gobject.timeout_add(
1602
 
                    int((self.expires - now).total_seconds() * 1000),
1603
 
                    self.disable)
 
1548
                self.disable_initiator_tag = (
 
1549
                    gobject.timeout_add(
 
1550
                        timedelta_to_milliseconds(self.expires - now),
 
1551
                        self.disable))
1604
1552
    
1605
1553
    # ExtendedTimeout - property
1606
 
    @dbus_service_property(_interface,
1607
 
                           signature="t",
 
1554
    @dbus_service_property(_interface, signature="t",
1608
1555
                           access="readwrite")
1609
1556
    def ExtendedTimeout_dbus_property(self, value=None):
1610
1557
        if value is None:       # get
1611
 
            return dbus.UInt64(self.extended_timeout.total_seconds()
1612
 
                               * 1000)
 
1558
            return dbus.UInt64(self.extended_timeout_milliseconds())
1613
1559
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1614
1560
    
1615
1561
    # Interval - property
1616
 
    @dbus_service_property(_interface,
1617
 
                           signature="t",
 
1562
    @dbus_service_property(_interface, signature="t",
1618
1563
                           access="readwrite")
1619
1564
    def Interval_dbus_property(self, value=None):
1620
1565
        if value is None:       # get
1621
 
            return dbus.UInt64(self.interval.total_seconds() * 1000)
 
1566
            return dbus.UInt64(self.interval_milliseconds())
1622
1567
        self.interval = datetime.timedelta(0, 0, 0, value)
1623
1568
        if getattr(self, "checker_initiator_tag", None) is None:
1624
1569
            return
1625
1570
        if self.enabled:
1626
1571
            # Reschedule checker run
1627
1572
            gobject.source_remove(self.checker_initiator_tag)
1628
 
            self.checker_initiator_tag = gobject.timeout_add(
1629
 
                value, self.start_checker)
1630
 
            self.start_checker() # Start one now, too
 
1573
            self.checker_initiator_tag = (gobject.timeout_add
 
1574
                                          (value, self.start_checker))
 
1575
            self.start_checker()    # Start one now, too
1631
1576
    
1632
1577
    # Checker - property
1633
 
    @dbus_service_property(_interface,
1634
 
                           signature="s",
 
1578
    @dbus_service_property(_interface, signature="s",
1635
1579
                           access="readwrite")
1636
1580
    def Checker_dbus_property(self, value=None):
1637
1581
        if value is None:       # get
1638
1582
            return dbus.String(self.checker_command)
1639
 
        self.checker_command = str(value)
 
1583
        self.checker_command = unicode(value)
1640
1584
    
1641
1585
    # CheckerRunning - property
1642
 
    @dbus_service_property(_interface,
1643
 
                           signature="b",
 
1586
    @dbus_service_property(_interface, signature="b",
1644
1587
                           access="readwrite")
1645
1588
    def CheckerRunning_dbus_property(self, value=None):
1646
1589
        if value is None:       # get
1656
1599
        return self.dbus_object_path # is already a dbus.ObjectPath
1657
1600
    
1658
1601
    # Secret = property
1659
 
    @dbus_service_property(_interface,
1660
 
                           signature="ay",
1661
 
                           access="write",
1662
 
                           byte_arrays=True)
 
1602
    @dbus_service_property(_interface, signature="ay",
 
1603
                           access="write", byte_arrays=True)
1663
1604
    def Secret_dbus_property(self, value):
1664
 
        self.secret = bytes(value)
 
1605
        self.secret = str(value)
1665
1606
    
1666
1607
    del _interface
1667
1608
 
1681
1622
        if data[0] == 'data':
1682
1623
            return data[1]
1683
1624
        if data[0] == 'function':
1684
 
            
1685
1625
            def func(*args, **kwargs):
1686
1626
                self._pipe.send(('funcall', name, args, kwargs))
1687
1627
                return self._pipe.recv()[1]
1688
 
            
1689
1628
            return func
1690
1629
    
1691
1630
    def __setattr__(self, name, value):
1703
1642
    def handle(self):
1704
1643
        with contextlib.closing(self.server.child_pipe) as child_pipe:
1705
1644
            logger.info("TCP connection from: %s",
1706
 
                        str(self.client_address))
 
1645
                        unicode(self.client_address))
1707
1646
            logger.debug("Pipe FD: %d",
1708
1647
                         self.server.child_pipe.fileno())
1709
1648
            
1710
 
            session = gnutls.connection.ClientSession(
1711
 
                self.request, gnutls.connection .X509Credentials())
 
1649
            session = (gnutls.connection
 
1650
                       .ClientSession(self.request,
 
1651
                                      gnutls.connection
 
1652
                                      .X509Credentials()))
1712
1653
            
1713
1654
            # Note: gnutls.connection.X509Credentials is really a
1714
1655
            # generic GnuTLS certificate credentials object so long as
1723
1664
            priority = self.server.gnutls_priority
1724
1665
            if priority is None:
1725
1666
                priority = "NORMAL"
1726
 
            gnutls.library.functions.gnutls_priority_set_direct(
1727
 
                session._c_object, priority, None)
 
1667
            (gnutls.library.functions
 
1668
             .gnutls_priority_set_direct(session._c_object,
 
1669
                                         priority, None))
1728
1670
            
1729
1671
            # Start communication using the Mandos protocol
1730
1672
            # Get protocol number
1732
1674
            logger.debug("Protocol version: %r", line)
1733
1675
            try:
1734
1676
                if int(line.strip().split()[0]) > 1:
1735
 
                    raise RuntimeError(line)
 
1677
                    raise RuntimeError
1736
1678
            except (ValueError, IndexError, RuntimeError) as error:
1737
1679
                logger.error("Unknown protocol version: %s", error)
1738
1680
                return
1750
1692
            approval_required = False
1751
1693
            try:
1752
1694
                try:
1753
 
                    fpr = self.fingerprint(
1754
 
                        self.peer_certificate(session))
 
1695
                    fpr = self.fingerprint(self.peer_certificate
 
1696
                                           (session))
1755
1697
                except (TypeError,
1756
1698
                        gnutls.errors.GNUTLSError) as error:
1757
1699
                    logger.warning("Bad certificate: %s", error)
1772
1714
                while True:
1773
1715
                    if not client.enabled:
1774
1716
                        logger.info("Client %s is disabled",
1775
 
                                    client.name)
 
1717
                                       client.name)
1776
1718
                        if self.server.use_dbus:
1777
1719
                            # Emit D-Bus signal
1778
1720
                            client.Rejected("Disabled")
1787
1729
                        if self.server.use_dbus:
1788
1730
                            # Emit D-Bus signal
1789
1731
                            client.NeedApproval(
1790
 
                                client.approval_delay.total_seconds()
1791
 
                                * 1000, client.approved_by_default)
 
1732
                                client.approval_delay_milliseconds(),
 
1733
                                client.approved_by_default)
1792
1734
                    else:
1793
1735
                        logger.warning("Client %s was not approved",
1794
1736
                                       client.name)
1800
1742
                    #wait until timeout or approved
1801
1743
                    time = datetime.datetime.now()
1802
1744
                    client.changedstate.acquire()
1803
 
                    client.changedstate.wait(delay.total_seconds())
 
1745
                    client.changedstate.wait(
 
1746
                        float(timedelta_to_milliseconds(delay)
 
1747
                              / 1000))
1804
1748
                    client.changedstate.release()
1805
1749
                    time2 = datetime.datetime.now()
1806
1750
                    if (time2 - time) >= delay:
1825
1769
                        logger.warning("gnutls send failed",
1826
1770
                                       exc_info=error)
1827
1771
                        return
1828
 
                    logger.debug("Sent: %d, remaining: %d", sent,
1829
 
                                 len(client.secret) - (sent_size
1830
 
                                                       + sent))
 
1772
                    logger.debug("Sent: %d, remaining: %d",
 
1773
                                 sent, len(client.secret)
 
1774
                                 - (sent_size + sent))
1831
1775
                    sent_size += sent
1832
1776
                
1833
1777
                logger.info("Sending secret to %s", client.name)
1850
1794
    def peer_certificate(session):
1851
1795
        "Return the peer's OpenPGP certificate as a bytestring"
1852
1796
        # If not an OpenPGP certificate...
1853
 
        if (gnutls.library.functions.gnutls_certificate_type_get(
1854
 
                session._c_object)
 
1797
        if (gnutls.library.functions
 
1798
            .gnutls_certificate_type_get(session._c_object)
1855
1799
            != gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1856
1800
            # ...do the normal thing
1857
1801
            return session.peer_certificate
1871
1815
    def fingerprint(openpgp):
1872
1816
        "Convert an OpenPGP bytestring to a hexdigit fingerprint"
1873
1817
        # New GnuTLS "datum" with the OpenPGP public key
1874
 
        datum = gnutls.library.types.gnutls_datum_t(
1875
 
            ctypes.cast(ctypes.c_char_p(openpgp),
1876
 
                        ctypes.POINTER(ctypes.c_ubyte)),
1877
 
            ctypes.c_uint(len(openpgp)))
 
1818
        datum = (gnutls.library.types
 
1819
                 .gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
 
1820
                                             ctypes.POINTER
 
1821
                                             (ctypes.c_ubyte)),
 
1822
                                 ctypes.c_uint(len(openpgp))))
1878
1823
        # New empty GnuTLS certificate
1879
1824
        crt = gnutls.library.types.gnutls_openpgp_crt_t()
1880
 
        gnutls.library.functions.gnutls_openpgp_crt_init(
1881
 
            ctypes.byref(crt))
 
1825
        (gnutls.library.functions
 
1826
         .gnutls_openpgp_crt_init(ctypes.byref(crt)))
1882
1827
        # Import the OpenPGP public key into the certificate
1883
 
        gnutls.library.functions.gnutls_openpgp_crt_import(
1884
 
            crt, ctypes.byref(datum),
1885
 
            gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
 
1828
        (gnutls.library.functions
 
1829
         .gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
 
1830
                                    gnutls.library.constants
 
1831
                                    .GNUTLS_OPENPGP_FMT_RAW))
1886
1832
        # Verify the self signature in the key
1887
1833
        crtverify = ctypes.c_uint()
1888
 
        gnutls.library.functions.gnutls_openpgp_crt_verify_self(
1889
 
            crt, 0, ctypes.byref(crtverify))
 
1834
        (gnutls.library.functions
 
1835
         .gnutls_openpgp_crt_verify_self(crt, 0,
 
1836
                                         ctypes.byref(crtverify)))
1890
1837
        if crtverify.value != 0:
1891
1838
            gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1892
 
            raise gnutls.errors.CertificateSecurityError(
1893
 
                "Verify failed")
 
1839
            raise (gnutls.errors.CertificateSecurityError
 
1840
                   ("Verify failed"))
1894
1841
        # New buffer for the fingerprint
1895
1842
        buf = ctypes.create_string_buffer(20)
1896
1843
        buf_len = ctypes.c_size_t()
1897
1844
        # Get the fingerprint from the certificate into the buffer
1898
 
        gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint(
1899
 
            crt, ctypes.byref(buf), ctypes.byref(buf_len))
 
1845
        (gnutls.library.functions
 
1846
         .gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
 
1847
                                             ctypes.byref(buf_len)))
1900
1848
        # Deinit the certificate
1901
1849
        gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1902
1850
        # Convert the buffer to a Python bytestring
1908
1856
 
1909
1857
class MultiprocessingMixIn(object):
1910
1858
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
1911
 
    
1912
1859
    def sub_process_main(self, request, address):
1913
1860
        try:
1914
1861
            self.finish_request(request, address)
1926
1873
 
1927
1874
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1928
1875
    """ adds a pipe to the MixIn """
1929
 
    
1930
1876
    def process_request(self, request, client_address):
1931
1877
        """Overrides and wraps the original process_request().
1932
1878
        
1941
1887
    
1942
1888
    def add_pipe(self, parent_pipe, proc):
1943
1889
        """Dummy function; override as necessary"""
1944
 
        raise NotImplementedError()
 
1890
        raise NotImplementedError
1945
1891
 
1946
1892
 
1947
1893
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1953
1899
        interface:      None or a network interface name (string)
1954
1900
        use_ipv6:       Boolean; to use IPv6 or not
1955
1901
    """
1956
 
    
1957
1902
    def __init__(self, server_address, RequestHandlerClass,
1958
 
                 interface=None,
1959
 
                 use_ipv6=True,
1960
 
                 socketfd=None):
 
1903
                 interface=None, use_ipv6=True, socketfd=None):
1961
1904
        """If socketfd is set, use that file descriptor instead of
1962
1905
        creating a new one with socket.socket().
1963
1906
        """
2004
1947
                             self.interface)
2005
1948
            else:
2006
1949
                try:
2007
 
                    self.socket.setsockopt(
2008
 
                        socket.SOL_SOCKET, SO_BINDTODEVICE,
2009
 
                        (self.interface + "\0").encode("utf-8"))
 
1950
                    self.socket.setsockopt(socket.SOL_SOCKET,
 
1951
                                           SO_BINDTODEVICE,
 
1952
                                           str(self.interface + '\0'))
2010
1953
                except socket.error as error:
2011
1954
                    if error.errno == errno.EPERM:
2012
1955
                        logger.error("No permission to bind to"
2026
1969
                if self.address_family == socket.AF_INET6:
2027
1970
                    any_address = "::" # in6addr_any
2028
1971
                else:
2029
 
                    any_address = "0.0.0.0" # INADDR_ANY
 
1972
                    any_address = socket.INADDR_ANY
2030
1973
                self.server_address = (any_address,
2031
1974
                                       self.server_address[1])
2032
1975
            elif not self.server_address[1]:
2033
 
                self.server_address = (self.server_address[0], 0)
 
1976
                self.server_address = (self.server_address[0],
 
1977
                                       0)
2034
1978
#                 if self.interface:
2035
1979
#                     self.server_address = (self.server_address[0],
2036
1980
#                                            0, # port
2050
1994
    
2051
1995
    Assumes a gobject.MainLoop event loop.
2052
1996
    """
2053
 
    
2054
1997
    def __init__(self, server_address, RequestHandlerClass,
2055
 
                 interface=None,
2056
 
                 use_ipv6=True,
2057
 
                 clients=None,
2058
 
                 gnutls_priority=None,
2059
 
                 use_dbus=True,
2060
 
                 socketfd=None):
 
1998
                 interface=None, use_ipv6=True, clients=None,
 
1999
                 gnutls_priority=None, use_dbus=True, socketfd=None):
2061
2000
        self.enabled = False
2062
2001
        self.clients = clients
2063
2002
        if self.clients is None:
2069
2008
                                interface = interface,
2070
2009
                                use_ipv6 = use_ipv6,
2071
2010
                                socketfd = socketfd)
2072
 
    
2073
2011
    def server_activate(self):
2074
2012
        if self.enabled:
2075
2013
            return socketserver.TCPServer.server_activate(self)
2079
2017
    
2080
2018
    def add_pipe(self, parent_pipe, proc):
2081
2019
        # Call "handle_ipc" for both data and EOF events
2082
 
        gobject.io_add_watch(
2083
 
            parent_pipe.fileno(),
2084
 
            gobject.IO_IN | gobject.IO_HUP,
2085
 
            functools.partial(self.handle_ipc,
2086
 
                              parent_pipe = parent_pipe,
2087
 
                              proc = proc))
 
2020
        gobject.io_add_watch(parent_pipe.fileno(),
 
2021
                             gobject.IO_IN | gobject.IO_HUP,
 
2022
                             functools.partial(self.handle_ipc,
 
2023
                                               parent_pipe =
 
2024
                                               parent_pipe,
 
2025
                                               proc = proc))
2088
2026
    
2089
 
    def handle_ipc(self, source, condition,
2090
 
                   parent_pipe=None,
2091
 
                   proc = None,
2092
 
                   client_object=None):
 
2027
    def handle_ipc(self, source, condition, parent_pipe=None,
 
2028
                   proc = None, client_object=None):
2093
2029
        # error, or the other end of multiprocessing.Pipe has closed
2094
2030
        if condition & (gobject.IO_ERR | gobject.IO_HUP):
2095
2031
            # Wait for other process to exit
2118
2054
                parent_pipe.send(False)
2119
2055
                return False
2120
2056
            
2121
 
            gobject.io_add_watch(
2122
 
                parent_pipe.fileno(),
2123
 
                gobject.IO_IN | gobject.IO_HUP,
2124
 
                functools.partial(self.handle_ipc,
2125
 
                                  parent_pipe = parent_pipe,
2126
 
                                  proc = proc,
2127
 
                                  client_object = client))
 
2057
            gobject.io_add_watch(parent_pipe.fileno(),
 
2058
                                 gobject.IO_IN | gobject.IO_HUP,
 
2059
                                 functools.partial(self.handle_ipc,
 
2060
                                                   parent_pipe =
 
2061
                                                   parent_pipe,
 
2062
                                                   proc = proc,
 
2063
                                                   client_object =
 
2064
                                                   client))
2128
2065
            parent_pipe.send(True)
2129
2066
            # remove the old hook in favor of the new above hook on
2130
2067
            # same fileno
2136
2073
            
2137
2074
            parent_pipe.send(('data', getattr(client_object,
2138
2075
                                              funcname)(*args,
2139
 
                                                        **kwargs)))
 
2076
                                                         **kwargs)))
2140
2077
        
2141
2078
        if command == 'getattr':
2142
2079
            attrname = request[1]
2143
2080
            if callable(client_object.__getattribute__(attrname)):
2144
 
                parent_pipe.send(('function', ))
 
2081
                parent_pipe.send(('function',))
2145
2082
            else:
2146
 
                parent_pipe.send((
2147
 
                    'data', client_object.__getattribute__(attrname)))
 
2083
                parent_pipe.send(('data', client_object
 
2084
                                  .__getattribute__(attrname)))
2148
2085
        
2149
2086
        if command == 'setattr':
2150
2087
            attrname = request[1]
2190
2127
                                              # None
2191
2128
                                    "followers")) # Tokens valid after
2192
2129
                                                  # this token
2193
 
    Token = collections.namedtuple("Token", (
2194
 
        "regexp",  # To match token; if "value" is not None, must have
2195
 
                   # a "group" containing digits
2196
 
        "value",   # datetime.timedelta or None
2197
 
        "followers"))           # Tokens valid after this token
2198
2130
    # RFC 3339 "duration" tokens, syntax, and semantics; taken from
2199
2131
    # the "duration" ABNF definition in RFC 3339, Appendix A.
2200
2132
    token_end = Token(re.compile(r"$"), None, frozenset())
2201
2133
    token_second = Token(re.compile(r"(\d+)S"),
2202
2134
                         datetime.timedelta(seconds=1),
2203
 
                         frozenset((token_end, )))
 
2135
                         frozenset((token_end,)))
2204
2136
    token_minute = Token(re.compile(r"(\d+)M"),
2205
2137
                         datetime.timedelta(minutes=1),
2206
2138
                         frozenset((token_second, token_end)))
2222
2154
                       frozenset((token_month, token_end)))
2223
2155
    token_week = Token(re.compile(r"(\d+)W"),
2224
2156
                       datetime.timedelta(weeks=1),
2225
 
                       frozenset((token_end, )))
 
2157
                       frozenset((token_end,)))
2226
2158
    token_duration = Token(re.compile(r"P"), None,
2227
2159
                           frozenset((token_year, token_month,
2228
2160
                                      token_day, token_time,
2229
 
                                      token_week)))
 
2161
                                      token_week))),
2230
2162
    # Define starting values
2231
2163
    value = datetime.timedelta() # Value so far
2232
2164
    found_token = None
2233
 
    followers = frozenset((token_duration,)) # Following valid tokens
 
2165
    followers = frozenset(token_duration,) # Following valid tokens
2234
2166
    s = duration                # String left to parse
2235
2167
    # Loop until end token is found
2236
2168
    while found_token is not token_end:
2283
2215
    timevalue = datetime.timedelta(0)
2284
2216
    for s in interval.split():
2285
2217
        try:
2286
 
            suffix = s[-1]
 
2218
            suffix = unicode(s[-1])
2287
2219
            value = int(s[:-1])
2288
2220
            if suffix == "d":
2289
2221
                delta = datetime.timedelta(value)
2296
2228
            elif suffix == "w":
2297
2229
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
2298
2230
            else:
2299
 
                raise ValueError("Unknown suffix {!r}".format(suffix))
2300
 
        except IndexError as e:
 
2231
                raise ValueError("Unknown suffix {0!r}"
 
2232
                                 .format(suffix))
 
2233
        except (ValueError, IndexError) as e:
2301
2234
            raise ValueError(*(e.args))
2302
2235
        timevalue += delta
2303
2236
    return timevalue
2319
2252
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2320
2253
        if not stat.S_ISCHR(os.fstat(null).st_mode):
2321
2254
            raise OSError(errno.ENODEV,
2322
 
                          "{} not a character device"
 
2255
                          "{0} not a character device"
2323
2256
                          .format(os.devnull))
2324
2257
        os.dup2(null, sys.stdin.fileno())
2325
2258
        os.dup2(null, sys.stdout.fileno())
2335
2268
    
2336
2269
    parser = argparse.ArgumentParser()
2337
2270
    parser.add_argument("-v", "--version", action="version",
2338
 
                        version = "%(prog)s {}".format(version),
 
2271
                        version = "%(prog)s {0}".format(version),
2339
2272
                        help="show version number and exit")
2340
2273
    parser.add_argument("-i", "--interface", metavar="IF",
2341
2274
                        help="Bind to interface IF")
2347
2280
                        help="Run self-test")
2348
2281
    parser.add_argument("--debug", action="store_true",
2349
2282
                        help="Debug mode; run in foreground and log"
2350
 
                        " to terminal", default=None)
 
2283
                        " to terminal")
2351
2284
    parser.add_argument("--debuglevel", metavar="LEVEL",
2352
2285
                        help="Debug level for stdout output")
2353
2286
    parser.add_argument("--priority", help="GnuTLS"
2360
2293
                        " files")
2361
2294
    parser.add_argument("--no-dbus", action="store_false",
2362
2295
                        dest="use_dbus", help="Do not provide D-Bus"
2363
 
                        " system bus interface", default=None)
 
2296
                        " system bus interface")
2364
2297
    parser.add_argument("--no-ipv6", action="store_false",
2365
 
                        dest="use_ipv6", help="Do not use IPv6",
2366
 
                        default=None)
 
2298
                        dest="use_ipv6", help="Do not use IPv6")
2367
2299
    parser.add_argument("--no-restore", action="store_false",
2368
2300
                        dest="restore", help="Do not restore stored"
2369
 
                        " state", default=None)
 
2301
                        " state")
2370
2302
    parser.add_argument("--socket", type=int,
2371
2303
                        help="Specify a file descriptor to a network"
2372
2304
                        " socket to use instead of creating one")
2373
2305
    parser.add_argument("--statedir", metavar="DIR",
2374
2306
                        help="Directory to save/restore state in")
2375
2307
    parser.add_argument("--foreground", action="store_true",
2376
 
                        help="Run in foreground", default=None)
2377
 
    parser.add_argument("--no-zeroconf", action="store_false",
2378
 
                        dest="zeroconf", help="Do not use Zeroconf",
2379
 
                        default=None)
 
2308
                        help="Run in foreground")
2380
2309
    
2381
2310
    options = parser.parse_args()
2382
2311
    
2383
2312
    if options.check:
2384
2313
        import doctest
2385
 
        fail_count, test_count = doctest.testmod()
2386
 
        sys.exit(os.EX_OK if fail_count == 0 else 1)
 
2314
        doctest.testmod()
 
2315
        sys.exit()
2387
2316
    
2388
2317
    # Default values for config file for server-global settings
2389
2318
    server_defaults = { "interface": "",
2391
2320
                        "port": "",
2392
2321
                        "debug": "False",
2393
2322
                        "priority":
2394
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2395
 
                        ":+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
 
2323
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
2396
2324
                        "servicename": "Mandos",
2397
2325
                        "use_dbus": "True",
2398
2326
                        "use_ipv6": "True",
2401
2329
                        "socket": "",
2402
2330
                        "statedir": "/var/lib/mandos",
2403
2331
                        "foreground": "False",
2404
 
                        "zeroconf": "True",
2405
 
                    }
 
2332
                        }
2406
2333
    
2407
2334
    # Parse config file for server-global settings
2408
2335
    server_config = configparser.SafeConfigParser(server_defaults)
2409
2336
    del server_defaults
2410
 
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
 
2337
    server_config.read(os.path.join(options.configdir,
 
2338
                                    "mandos.conf"))
2411
2339
    # Convert the SafeConfigParser object to a dict
2412
2340
    server_settings = server_config.defaults()
2413
2341
    # Use the appropriate methods on the non-string config options
2431
2359
    # Override the settings from the config file with command line
2432
2360
    # options, if set.
2433
2361
    for option in ("interface", "address", "port", "debug",
2434
 
                   "priority", "servicename", "configdir", "use_dbus",
2435
 
                   "use_ipv6", "debuglevel", "restore", "statedir",
2436
 
                   "socket", "foreground", "zeroconf"):
 
2362
                   "priority", "servicename", "configdir",
 
2363
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
 
2364
                   "statedir", "socket", "foreground"):
2437
2365
        value = getattr(options, option)
2438
2366
        if value is not None:
2439
2367
            server_settings[option] = value
2440
2368
    del options
2441
2369
    # Force all strings to be unicode
2442
2370
    for option in server_settings.keys():
2443
 
        if isinstance(server_settings[option], bytes):
2444
 
            server_settings[option] = (server_settings[option]
2445
 
                                       .decode("utf-8"))
2446
 
    # Force all boolean options to be boolean
2447
 
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
2448
 
                   "foreground", "zeroconf"):
2449
 
        server_settings[option] = bool(server_settings[option])
 
2371
        if type(server_settings[option]) is str:
 
2372
            server_settings[option] = unicode(server_settings[option])
2450
2373
    # Debug implies foreground
2451
2374
    if server_settings["debug"]:
2452
2375
        server_settings["foreground"] = True
2454
2377
    
2455
2378
    ##################################################################
2456
2379
    
2457
 
    if (not server_settings["zeroconf"]
2458
 
        and not (server_settings["port"]
2459
 
                 or server_settings["socket"] != "")):
2460
 
        parser.error("Needs port or socket to work without Zeroconf")
2461
 
    
2462
2380
    # For convenience
2463
2381
    debug = server_settings["debug"]
2464
2382
    debuglevel = server_settings["debuglevel"]
2467
2385
    stored_state_path = os.path.join(server_settings["statedir"],
2468
2386
                                     stored_state_file)
2469
2387
    foreground = server_settings["foreground"]
2470
 
    zeroconf = server_settings["zeroconf"]
2471
2388
    
2472
2389
    if debug:
2473
2390
        initlogger(debug, logging.DEBUG)
2479
2396
            initlogger(debug, level)
2480
2397
    
2481
2398
    if server_settings["servicename"] != "Mandos":
2482
 
        syslogger.setFormatter(
2483
 
            logging.Formatter('Mandos ({}) [%(process)d]:'
2484
 
                              ' %(levelname)s: %(message)s'.format(
2485
 
                                  server_settings["servicename"])))
 
2399
        syslogger.setFormatter(logging.Formatter
 
2400
                               ('Mandos ({0}) [%(process)d]:'
 
2401
                                ' %(levelname)s: %(message)s'
 
2402
                                .format(server_settings
 
2403
                                        ["servicename"])))
2486
2404
    
2487
2405
    # Parse config file with clients
2488
2406
    client_config = configparser.SafeConfigParser(Client
2493
2411
    global mandos_dbus_service
2494
2412
    mandos_dbus_service = None
2495
2413
    
2496
 
    socketfd = None
2497
 
    if server_settings["socket"] != "":
2498
 
        socketfd = server_settings["socket"]
2499
 
    tcp_server = MandosServer(
2500
 
        (server_settings["address"], server_settings["port"]),
2501
 
        ClientHandler,
2502
 
        interface=(server_settings["interface"] or None),
2503
 
        use_ipv6=use_ipv6,
2504
 
        gnutls_priority=server_settings["priority"],
2505
 
        use_dbus=use_dbus,
2506
 
        socketfd=socketfd)
 
2414
    tcp_server = MandosServer((server_settings["address"],
 
2415
                               server_settings["port"]),
 
2416
                              ClientHandler,
 
2417
                              interface=(server_settings["interface"]
 
2418
                                         or None),
 
2419
                              use_ipv6=use_ipv6,
 
2420
                              gnutls_priority=
 
2421
                              server_settings["priority"],
 
2422
                              use_dbus=use_dbus,
 
2423
                              socketfd=(server_settings["socket"]
 
2424
                                        or None))
2507
2425
    if not foreground:
2508
 
        pidfilename = "/run/mandos.pid"
2509
 
        if not os.path.isdir("/run/."):
2510
 
            pidfilename = "/var/run/mandos.pid"
 
2426
        pidfilename = "/var/run/mandos.pid"
2511
2427
        pidfile = None
2512
2428
        try:
2513
2429
            pidfile = open(pidfilename, "w")
2530
2446
        os.setuid(uid)
2531
2447
    except OSError as error:
2532
2448
        if error.errno != errno.EPERM:
2533
 
            raise
 
2449
            raise error
2534
2450
    
2535
2451
    if debug:
2536
2452
        # Enable all possible GnuTLS debugging
2543
2459
        def debug_gnutls(level, string):
2544
2460
            logger.debug("GnuTLS: %s", string[:-1])
2545
2461
        
2546
 
        gnutls.library.functions.gnutls_global_set_log_function(
2547
 
            debug_gnutls)
 
2462
        (gnutls.library.functions
 
2463
         .gnutls_global_set_log_function(debug_gnutls))
2548
2464
        
2549
2465
        # Redirect stdin so all checkers get /dev/null
2550
2466
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2570
2486
    if use_dbus:
2571
2487
        try:
2572
2488
            bus_name = dbus.service.BusName("se.recompile.Mandos",
2573
 
                                            bus,
2574
 
                                            do_not_queue=True)
2575
 
            old_bus_name = dbus.service.BusName(
2576
 
                "se.bsnet.fukt.Mandos", bus,
2577
 
                do_not_queue=True)
2578
 
        except dbus.exceptions.DBusException as e:
 
2489
                                            bus, do_not_queue=True)
 
2490
            old_bus_name = (dbus.service.BusName
 
2491
                            ("se.bsnet.fukt.Mandos", bus,
 
2492
                             do_not_queue=True))
 
2493
        except dbus.exceptions.NameExistsException as e:
2579
2494
            logger.error("Disabling D-Bus:", exc_info=e)
2580
2495
            use_dbus = False
2581
2496
            server_settings["use_dbus"] = False
2582
2497
            tcp_server.use_dbus = False
2583
 
    if zeroconf:
2584
 
        protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2585
 
        service = AvahiServiceToSyslog(
2586
 
            name = server_settings["servicename"],
2587
 
            servicetype = "_mandos._tcp",
2588
 
            protocol = protocol,
2589
 
            bus = bus)
2590
 
        if server_settings["interface"]:
2591
 
            service.interface = if_nametoindex(
2592
 
                server_settings["interface"].encode("utf-8"))
 
2498
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
 
2499
    service = AvahiServiceToSyslog(name =
 
2500
                                   server_settings["servicename"],
 
2501
                                   servicetype = "_mandos._tcp",
 
2502
                                   protocol = protocol, bus = bus)
 
2503
    if server_settings["interface"]:
 
2504
        service.interface = (if_nametoindex
 
2505
                             (str(server_settings["interface"])))
2593
2506
    
2594
2507
    global multiprocessing_manager
2595
2508
    multiprocessing_manager = multiprocessing.Manager()
2602
2515
    old_client_settings = {}
2603
2516
    clients_data = {}
2604
2517
    
2605
 
    # This is used to redirect stdout and stderr for checker processes
2606
 
    global wnull
2607
 
    wnull = open(os.devnull, "w") # A writable /dev/null
2608
 
    # Only used if server is running in foreground but not in debug
2609
 
    # mode
2610
 
    if debug or not foreground:
2611
 
        wnull.close()
2612
 
    
2613
2518
    # Get client data and settings from last running state.
2614
2519
    if server_settings["restore"]:
2615
2520
        try:
2616
2521
            with open(stored_state_path, "rb") as stored_state:
2617
 
                clients_data, old_client_settings = pickle.load(
2618
 
                    stored_state)
 
2522
                clients_data, old_client_settings = (pickle.load
 
2523
                                                     (stored_state))
2619
2524
            os.remove(stored_state_path)
2620
2525
        except IOError as e:
2621
2526
            if e.errno == errno.ENOENT:
2622
 
                logger.warning("Could not load persistent state:"
2623
 
                               " {}".format(os.strerror(e.errno)))
 
2527
                logger.warning("Could not load persistent state: {0}"
 
2528
                                .format(os.strerror(e.errno)))
2624
2529
            else:
2625
2530
                logger.critical("Could not load persistent state:",
2626
2531
                                exc_info=e)
2627
2532
                raise
2628
2533
        except EOFError as e:
2629
2534
            logger.warning("Could not load persistent state: "
2630
 
                           "EOFError:",
2631
 
                           exc_info=e)
 
2535
                           "EOFError:", exc_info=e)
2632
2536
    
2633
2537
    with PGPEngine() as pgp:
2634
 
        for client_name, client in clients_data.items():
2635
 
            # Skip removed clients
2636
 
            if client_name not in client_settings:
2637
 
                continue
2638
 
            
 
2538
        for client_name, client in clients_data.iteritems():
2639
2539
            # Decide which value to use after restoring saved state.
2640
2540
            # We have three different values: Old config file,
2641
2541
            # new config file, and saved state.
2646
2546
                    # For each value in new config, check if it
2647
2547
                    # differs from the old config value (Except for
2648
2548
                    # the "secret" attribute)
2649
 
                    if (name != "secret"
2650
 
                        and (value !=
2651
 
                             old_client_settings[client_name][name])):
 
2549
                    if (name != "secret" and
 
2550
                        value != old_client_settings[client_name]
 
2551
                        [name]):
2652
2552
                        client[name] = value
2653
2553
                except KeyError:
2654
2554
                    pass
2662
2562
                if datetime.datetime.utcnow() >= client["expires"]:
2663
2563
                    if not client["last_checked_ok"]:
2664
2564
                        logger.warning(
2665
 
                            "disabling client {} - Client never "
2666
 
                            "performed a successful checker".format(
2667
 
                                client_name))
 
2565
                            "disabling client {0} - Client never "
 
2566
                            "performed a successful checker"
 
2567
                            .format(client_name))
2668
2568
                        client["enabled"] = False
2669
2569
                    elif client["last_checker_status"] != 0:
2670
2570
                        logger.warning(
2671
 
                            "disabling client {} - Client last"
2672
 
                            " checker failed with error code"
2673
 
                            " {}".format(
2674
 
                                client_name,
2675
 
                                client["last_checker_status"]))
 
2571
                            "disabling client {0} - Client "
 
2572
                            "last checker failed with error code {1}"
 
2573
                            .format(client_name,
 
2574
                                    client["last_checker_status"]))
2676
2575
                        client["enabled"] = False
2677
2576
                    else:
2678
 
                        client["expires"] = (
2679
 
                            datetime.datetime.utcnow()
2680
 
                            + client["timeout"])
 
2577
                        client["expires"] = (datetime.datetime
 
2578
                                             .utcnow()
 
2579
                                             + client["timeout"])
2681
2580
                        logger.debug("Last checker succeeded,"
2682
 
                                     " keeping {} enabled".format(
2683
 
                                         client_name))
 
2581
                                     " keeping {0} enabled"
 
2582
                                     .format(client_name))
2684
2583
            try:
2685
 
                client["secret"] = pgp.decrypt(
2686
 
                    client["encrypted_secret"],
2687
 
                    client_settings[client_name]["secret"])
 
2584
                client["secret"] = (
 
2585
                    pgp.decrypt(client["encrypted_secret"],
 
2586
                                client_settings[client_name]
 
2587
                                ["secret"]))
2688
2588
            except PGPError:
2689
2589
                # If decryption fails, we use secret from new settings
2690
 
                logger.debug("Failed to decrypt {} old secret".format(
2691
 
                    client_name))
2692
 
                client["secret"] = (client_settings[client_name]
2693
 
                                    ["secret"])
 
2590
                logger.debug("Failed to decrypt {0} old secret"
 
2591
                             .format(client_name))
 
2592
                client["secret"] = (
 
2593
                    client_settings[client_name]["secret"])
2694
2594
    
2695
2595
    # Add/remove clients based on new changes made to config
2696
2596
    for client_name in (set(old_client_settings)
2701
2601
        clients_data[client_name] = client_settings[client_name]
2702
2602
    
2703
2603
    # Create all client objects
2704
 
    for client_name, client in clients_data.items():
 
2604
    for client_name, client in clients_data.iteritems():
2705
2605
        tcp_server.clients[client_name] = client_class(
2706
 
            name = client_name,
2707
 
            settings = client,
2708
 
            server_settings = server_settings)
 
2606
            name = client_name, settings = client)
2709
2607
    
2710
2608
    if not tcp_server.clients:
2711
2609
        logger.warning("No clients defined")
2715
2613
            try:
2716
2614
                with pidfile:
2717
2615
                    pid = os.getpid()
2718
 
                    pidfile.write("{}\n".format(pid).encode("utf-8"))
 
2616
                    pidfile.write(str(pid) + "\n".encode("utf-8"))
2719
2617
            except IOError:
2720
2618
                logger.error("Could not write to file %r with PID %d",
2721
2619
                             pidfilename, pid)
2726
2624
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2727
2625
    
2728
2626
    if use_dbus:
2729
 
        
2730
 
        @alternate_dbus_interfaces(
2731
 
            { "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
 
2627
        @alternate_dbus_interfaces({"se.recompile.Mandos":
 
2628
                                        "se.bsnet.fukt.Mandos"})
2732
2629
        class MandosDBusService(DBusObjectWithProperties):
2733
2630
            """A D-Bus proxy object"""
2734
 
            
2735
2631
            def __init__(self):
2736
2632
                dbus.service.Object.__init__(self, bus, "/")
2737
 
            
2738
2633
            _interface = "se.recompile.Mandos"
2739
2634
            
2740
2635
            @dbus_interface_annotations(_interface)
2741
2636
            def _foo(self):
2742
 
                return {
2743
 
                    "org.freedesktop.DBus.Property.EmitsChangedSignal":
2744
 
                    "false" }
 
2637
                return { "org.freedesktop.DBus.Property"
 
2638
                         ".EmitsChangedSignal":
 
2639
                             "false"}
2745
2640
            
2746
2641
            @dbus.service.signal(_interface, signature="o")
2747
2642
            def ClientAdded(self, objpath):
2761
2656
            @dbus.service.method(_interface, out_signature="ao")
2762
2657
            def GetAllClients(self):
2763
2658
                "D-Bus method"
2764
 
                return dbus.Array(c.dbus_object_path for c in
 
2659
                return dbus.Array(c.dbus_object_path
 
2660
                                  for c in
2765
2661
                                  tcp_server.clients.itervalues())
2766
2662
            
2767
2663
            @dbus.service.method(_interface,
2769
2665
            def GetAllClientsWithProperties(self):
2770
2666
                "D-Bus method"
2771
2667
                return dbus.Dictionary(
2772
 
                    { c.dbus_object_path: c.GetAll("")
2773
 
                      for c in tcp_server.clients.itervalues() },
 
2668
                    ((c.dbus_object_path, c.GetAll(""))
 
2669
                     for c in tcp_server.clients.itervalues()),
2774
2670
                    signature="oa{sv}")
2775
2671
            
2776
2672
            @dbus.service.method(_interface, in_signature="o")
2793
2689
    
2794
2690
    def cleanup():
2795
2691
        "Cleanup function; run on exit"
2796
 
        if zeroconf:
2797
 
            service.cleanup()
 
2692
        service.cleanup()
2798
2693
        
2799
2694
        multiprocessing.active_children()
2800
 
        wnull.close()
2801
2695
        if not (tcp_server.clients or client_settings):
2802
2696
            return
2803
2697
        
2814
2708
                
2815
2709
                # A list of attributes that can not be pickled
2816
2710
                # + secret.
2817
 
                exclude = { "bus", "changedstate", "secret",
2818
 
                            "checker", "server_settings" }
2819
 
                for name, typ in inspect.getmembers(dbus.service
2820
 
                                                    .Object):
 
2711
                exclude = set(("bus", "changedstate", "secret",
 
2712
                               "checker"))
 
2713
                for name, typ in (inspect.getmembers
 
2714
                                  (dbus.service.Object)):
2821
2715
                    exclude.add(name)
2822
2716
                
2823
2717
                client_dict["encrypted_secret"] = (client
2830
2724
                del client_settings[client.name]["secret"]
2831
2725
        
2832
2726
        try:
2833
 
            with tempfile.NamedTemporaryFile(
2834
 
                    mode='wb',
2835
 
                    suffix=".pickle",
2836
 
                    prefix='clients-',
2837
 
                    dir=os.path.dirname(stored_state_path),
2838
 
                    delete=False) as stored_state:
 
2727
            with (tempfile.NamedTemporaryFile
 
2728
                  (mode='wb', suffix=".pickle", prefix='clients-',
 
2729
                   dir=os.path.dirname(stored_state_path),
 
2730
                   delete=False)) as stored_state:
2839
2731
                pickle.dump((clients, client_settings), stored_state)
2840
 
                tempname = stored_state.name
 
2732
                tempname=stored_state.name
2841
2733
            os.rename(tempname, stored_state_path)
2842
2734
        except (IOError, OSError) as e:
2843
2735
            if not debug:
2846
2738
                except NameError:
2847
2739
                    pass
2848
2740
            if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
2849
 
                logger.warning("Could not save persistent state: {}"
 
2741
                logger.warning("Could not save persistent state: {0}"
2850
2742
                               .format(os.strerror(e.errno)))
2851
2743
            else:
2852
2744
                logger.warning("Could not save persistent state:",
2853
2745
                               exc_info=e)
2854
 
                raise
 
2746
                raise e
2855
2747
        
2856
2748
        # Delete all clients, and settings from config
2857
2749
        while tcp_server.clients:
2862
2754
            client.disable(quiet=True)
2863
2755
            if use_dbus:
2864
2756
                # Emit D-Bus signal
2865
 
                mandos_dbus_service.ClientRemoved(
2866
 
                    client.dbus_object_path, client.name)
 
2757
                mandos_dbus_service.ClientRemoved(client
 
2758
                                                  .dbus_object_path,
 
2759
                                                  client.name)
2867
2760
        client_settings.clear()
2868
2761
    
2869
2762
    atexit.register(cleanup)
2880
2773
    tcp_server.server_activate()
2881
2774
    
2882
2775
    # Find out what port we got
2883
 
    if zeroconf:
2884
 
        service.port = tcp_server.socket.getsockname()[1]
 
2776
    service.port = tcp_server.socket.getsockname()[1]
2885
2777
    if use_ipv6:
2886
2778
        logger.info("Now listening on address %r, port %d,"
2887
2779
                    " flowinfo %d, scope_id %d",
2893
2785
    #service.interface = tcp_server.socket.getsockname()[3]
2894
2786
    
2895
2787
    try:
2896
 
        if zeroconf:
2897
 
            # From the Avahi example code
2898
 
            try:
2899
 
                service.activate()
2900
 
            except dbus.exceptions.DBusException as error:
2901
 
                logger.critical("D-Bus Exception", exc_info=error)
2902
 
                cleanup()
2903
 
                sys.exit(1)
2904
 
            # End of Avahi example code
 
2788
        # From the Avahi example code
 
2789
        try:
 
2790
            service.activate()
 
2791
        except dbus.exceptions.DBusException as error:
 
2792
            logger.critical("D-Bus Exception", exc_info=error)
 
2793
            cleanup()
 
2794
            sys.exit(1)
 
2795
        # End of Avahi example code
2905
2796
        
2906
2797
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2907
2798
                             lambda *args, **kwargs:
2922
2813
    # Must run before the D-Bus bus name gets deregistered
2923
2814
    cleanup()
2924
2815
 
2925
 
 
2926
2816
if __name__ == '__main__':
2927
2817
    main()