/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2015-05-22 20:23:46 UTC
  • Revision ID: teddy@recompile.se-20150522202346-taccq232srbszyd9
mandos-keygen: Bug fix: Only use one SSH key from ssh-keyscan

If ssh-keyscan found keys of more than one type, the generated output
would be incorrect.  Restrict the output to one type of key.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python
 
1
#!/usr/bin/python2.7
2
2
# -*- mode: python; coding: utf-8 -*-
3
3
4
4
# Mandos server - give out binary blobs to connecting clients.
11
11
# "AvahiService" class, and some lines in "main".
12
12
13
13
# Everything else is
14
 
# Copyright © 2008-2012 Teddy Hogeborn
15
 
# Copyright © 2008-2012 Björn Påhlsson
 
14
# Copyright © 2008-2015 Teddy Hogeborn
 
15
# Copyright © 2008-2015 Björn Påhlsson
16
16
17
17
# This program is free software: you can redistribute it and/or modify
18
18
# it under the terms of the GNU General Public License as published by
34
34
from __future__ import (division, absolute_import, print_function,
35
35
                        unicode_literals)
36
36
 
 
37
from future_builtins import *
 
38
 
37
39
import SocketServer as socketserver
38
40
import socket
39
41
import argparse
66
68
import binascii
67
69
import tempfile
68
70
import itertools
 
71
import collections
69
72
 
70
73
import dbus
71
74
import dbus.service
76
79
import ctypes.util
77
80
import xml.dom.minidom
78
81
import inspect
79
 
import GnuPGInterface
80
82
 
81
83
try:
82
84
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
86
88
    except ImportError:
87
89
        SO_BINDTODEVICE = None
88
90
 
89
 
version = "1.5.3"
 
91
if sys.version_info.major == 2:
 
92
    str = unicode
 
93
 
 
94
version = "1.6.9"
90
95
stored_state_file = "clients.pickle"
91
96
 
92
97
logger = logging.getLogger()
93
 
syslogger = (logging.handlers.SysLogHandler
94
 
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
95
 
              address = str("/dev/log")))
 
98
syslogger = None
96
99
 
97
100
try:
98
 
    if_nametoindex = (ctypes.cdll.LoadLibrary
99
 
                      (ctypes.util.find_library("c"))
100
 
                      .if_nametoindex)
 
101
    if_nametoindex = ctypes.cdll.LoadLibrary(
 
102
        ctypes.util.find_library("c")).if_nametoindex
101
103
except (OSError, AttributeError):
 
104
    
102
105
    def if_nametoindex(interface):
103
106
        "Get an interface index the hard way, i.e. using fcntl()"
104
107
        SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
105
108
        with contextlib.closing(socket.socket()) as s:
106
109
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
107
 
                                struct.pack(str("16s16x"),
108
 
                                            interface))
109
 
        interface_index = struct.unpack(str("I"),
110
 
                                        ifreq[16:20])[0]
 
110
                                struct.pack(b"16s16x", interface))
 
111
        interface_index = struct.unpack("I", ifreq[16:20])[0]
111
112
        return interface_index
112
113
 
113
114
 
114
115
def initlogger(debug, level=logging.WARNING):
115
116
    """init logger and add loglevel"""
116
117
    
 
118
    global syslogger
 
119
    syslogger = (logging.handlers.SysLogHandler(
 
120
        facility = logging.handlers.SysLogHandler.LOG_DAEMON,
 
121
        address = "/dev/log"))
117
122
    syslogger.setFormatter(logging.Formatter
118
123
                           ('Mandos [%(process)d]: %(levelname)s:'
119
124
                            ' %(message)s'))
136
141
 
137
142
class PGPEngine(object):
138
143
    """A simple class for OpenPGP symmetric encryption & decryption"""
 
144
    
139
145
    def __init__(self):
140
 
        self.gnupg = GnuPGInterface.GnuPG()
141
146
        self.tempdir = tempfile.mkdtemp(prefix="mandos-")
142
 
        self.gnupg = GnuPGInterface.GnuPG()
143
 
        self.gnupg.options.meta_interactive = False
144
 
        self.gnupg.options.homedir = self.tempdir
145
 
        self.gnupg.options.extra_args.extend(['--force-mdc',
146
 
                                              '--quiet',
147
 
                                              '--no-use-agent'])
 
147
        self.gnupgargs = ['--batch',
 
148
                          '--home', self.tempdir,
 
149
                          '--force-mdc',
 
150
                          '--quiet',
 
151
                          '--no-use-agent']
148
152
    
149
153
    def __enter__(self):
150
154
        return self
151
155
    
152
 
    def __exit__ (self, exc_type, exc_value, traceback):
 
156
    def __exit__(self, exc_type, exc_value, traceback):
153
157
        self._cleanup()
154
158
        return False
155
159
    
172
176
    def password_encode(self, password):
173
177
        # Passphrase can not be empty and can not contain newlines or
174
178
        # NUL bytes.  So we prefix it and hex encode it.
175
 
        return b"mandos" + binascii.hexlify(password)
 
179
        encoded = b"mandos" + binascii.hexlify(password)
 
180
        if len(encoded) > 2048:
 
181
            # GnuPG can't handle long passwords, so encode differently
 
182
            encoded = (b"mandos" + password.replace(b"\\", b"\\\\")
 
183
                       .replace(b"\n", b"\\n")
 
184
                       .replace(b"\0", b"\\x00"))
 
185
        return encoded
176
186
    
177
187
    def encrypt(self, data, password):
178
 
        self.gnupg.passphrase = self.password_encode(password)
179
 
        with open(os.devnull, "w") as devnull:
180
 
            try:
181
 
                proc = self.gnupg.run(['--symmetric'],
182
 
                                      create_fhs=['stdin', 'stdout'],
183
 
                                      attach_fhs={'stderr': devnull})
184
 
                with contextlib.closing(proc.handles['stdin']) as f:
185
 
                    f.write(data)
186
 
                with contextlib.closing(proc.handles['stdout']) as f:
187
 
                    ciphertext = f.read()
188
 
                proc.wait()
189
 
            except IOError as e:
190
 
                raise PGPError(e)
191
 
        self.gnupg.passphrase = None
 
188
        passphrase = self.password_encode(password)
 
189
        with tempfile.NamedTemporaryFile(
 
190
                dir=self.tempdir) as passfile:
 
191
            passfile.write(passphrase)
 
192
            passfile.flush()
 
193
            proc = subprocess.Popen(['gpg', '--symmetric',
 
194
                                     '--passphrase-file',
 
195
                                     passfile.name]
 
196
                                    + self.gnupgargs,
 
197
                                    stdin = subprocess.PIPE,
 
198
                                    stdout = subprocess.PIPE,
 
199
                                    stderr = subprocess.PIPE)
 
200
            ciphertext, err = proc.communicate(input = data)
 
201
        if proc.returncode != 0:
 
202
            raise PGPError(err)
192
203
        return ciphertext
193
204
    
194
205
    def decrypt(self, data, password):
195
 
        self.gnupg.passphrase = self.password_encode(password)
196
 
        with open(os.devnull, "w") as devnull:
197
 
            try:
198
 
                proc = self.gnupg.run(['--decrypt'],
199
 
                                      create_fhs=['stdin', 'stdout'],
200
 
                                      attach_fhs={'stderr': devnull})
201
 
                with contextlib.closing(proc.handles['stdin']) as f:
202
 
                    f.write(data)
203
 
                with contextlib.closing(proc.handles['stdout']) as f:
204
 
                    decrypted_plaintext = f.read()
205
 
                proc.wait()
206
 
            except IOError as e:
207
 
                raise PGPError(e)
208
 
        self.gnupg.passphrase = None
 
206
        passphrase = self.password_encode(password)
 
207
        with tempfile.NamedTemporaryFile(
 
208
                dir = self.tempdir) as passfile:
 
209
            passfile.write(passphrase)
 
210
            passfile.flush()
 
211
            proc = subprocess.Popen(['gpg', '--decrypt',
 
212
                                     '--passphrase-file',
 
213
                                     passfile.name]
 
214
                                    + self.gnupgargs,
 
215
                                    stdin = subprocess.PIPE,
 
216
                                    stdout = subprocess.PIPE,
 
217
                                    stderr = subprocess.PIPE)
 
218
            decrypted_plaintext, err = proc.communicate(input = data)
 
219
        if proc.returncode != 0:
 
220
            raise PGPError(err)
209
221
        return decrypted_plaintext
210
222
 
211
223
 
212
224
class AvahiError(Exception):
213
225
    def __init__(self, value, *args, **kwargs):
214
226
        self.value = value
215
 
        super(AvahiError, self).__init__(value, *args, **kwargs)
216
 
    def __unicode__(self):
217
 
        return unicode(repr(self.value))
 
227
        return super(AvahiError, self).__init__(value, *args,
 
228
                                                **kwargs)
 
229
 
218
230
 
219
231
class AvahiServiceError(AvahiError):
220
232
    pass
221
233
 
 
234
 
222
235
class AvahiGroupError(AvahiError):
223
236
    pass
224
237
 
231
244
               Used to optionally bind to the specified interface.
232
245
    name: string; Example: 'Mandos'
233
246
    type: string; Example: '_mandos._tcp'.
234
 
                  See <http://www.dns-sd.org/ServiceTypes.html>
 
247
     See <https://www.iana.org/assignments/service-names-port-numbers>
235
248
    port: integer; what port to announce
236
249
    TXT: list of strings; TXT record for the service
237
250
    domain: string; Domain to publish on, default to .local if empty.
244
257
    bus: dbus.SystemBus()
245
258
    """
246
259
    
247
 
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
248
 
                 servicetype = None, port = None, TXT = None,
249
 
                 domain = "", host = "", max_renames = 32768,
250
 
                 protocol = avahi.PROTO_UNSPEC, bus = None):
 
260
    def __init__(self,
 
261
                 interface = avahi.IF_UNSPEC,
 
262
                 name = None,
 
263
                 servicetype = None,
 
264
                 port = None,
 
265
                 TXT = None,
 
266
                 domain = "",
 
267
                 host = "",
 
268
                 max_renames = 32768,
 
269
                 protocol = avahi.PROTO_UNSPEC,
 
270
                 bus = None):
251
271
        self.interface = interface
252
272
        self.name = name
253
273
        self.type = servicetype
263
283
        self.bus = bus
264
284
        self.entry_group_state_changed_match = None
265
285
    
266
 
    def rename(self):
 
286
    def rename(self, remove=True):
267
287
        """Derived from the Avahi example code"""
268
288
        if self.rename_count >= self.max_renames:
269
289
            logger.critical("No suitable Zeroconf service name found"
270
290
                            " after %i retries, exiting.",
271
291
                            self.rename_count)
272
292
            raise AvahiServiceError("Too many renames")
273
 
        self.name = unicode(self.server
274
 
                            .GetAlternativeServiceName(self.name))
 
293
        self.name = str(
 
294
            self.server.GetAlternativeServiceName(self.name))
 
295
        self.rename_count += 1
275
296
        logger.info("Changing Zeroconf service name to %r ...",
276
297
                    self.name)
277
 
        self.remove()
 
298
        if remove:
 
299
            self.remove()
278
300
        try:
279
301
            self.add()
280
302
        except dbus.exceptions.DBusException as error:
281
 
            logger.critical("D-Bus Exception", exc_info=error)
282
 
            self.cleanup()
283
 
            os._exit(1)
284
 
        self.rename_count += 1
 
303
            if (error.get_dbus_name()
 
304
                == "org.freedesktop.Avahi.CollisionError"):
 
305
                logger.info("Local Zeroconf service name collision.")
 
306
                return self.rename(remove=False)
 
307
            else:
 
308
                logger.critical("D-Bus Exception", exc_info=error)
 
309
                self.cleanup()
 
310
                os._exit(1)
285
311
    
286
312
    def remove(self):
287
313
        """Derived from the Avahi example code"""
325
351
            self.rename()
326
352
        elif state == avahi.ENTRY_GROUP_FAILURE:
327
353
            logger.critical("Avahi: Error in group state changed %s",
328
 
                            unicode(error))
329
 
            raise AvahiGroupError("State changed: {0!s}"
330
 
                                  .format(error))
 
354
                            str(error))
 
355
            raise AvahiGroupError("State changed: {!s}".format(error))
331
356
    
332
357
    def cleanup(self):
333
358
        """Derived from the Avahi example code"""
343
368
    def server_state_changed(self, state, error=None):
344
369
        """Derived from the Avahi example code"""
345
370
        logger.debug("Avahi server state change: %i", state)
346
 
        bad_states = { avahi.SERVER_INVALID:
347
 
                           "Zeroconf server invalid",
348
 
                       avahi.SERVER_REGISTERING: None,
349
 
                       avahi.SERVER_COLLISION:
350
 
                           "Zeroconf server name collision",
351
 
                       avahi.SERVER_FAILURE:
352
 
                           "Zeroconf server failure" }
 
371
        bad_states = {
 
372
            avahi.SERVER_INVALID: "Zeroconf server invalid",
 
373
            avahi.SERVER_REGISTERING: None,
 
374
            avahi.SERVER_COLLISION: "Zeroconf server name collision",
 
375
            avahi.SERVER_FAILURE: "Zeroconf server failure",
 
376
        }
353
377
        if state in bad_states:
354
378
            if bad_states[state] is not None:
355
379
                if error is None:
374
398
                                    follow_name_owner_changes=True),
375
399
                avahi.DBUS_INTERFACE_SERVER)
376
400
        self.server.connect_to_signal("StateChanged",
377
 
                                 self.server_state_changed)
 
401
                                      self.server_state_changed)
378
402
        self.server_state_changed(self.server.GetState())
379
403
 
 
404
 
380
405
class AvahiServiceToSyslog(AvahiService):
381
 
    def rename(self):
 
406
    def rename(self, *args, **kwargs):
382
407
        """Add the new name to the syslog messages"""
383
 
        ret = AvahiService.rename(self)
384
 
        syslogger.setFormatter(logging.Formatter
385
 
                               ('Mandos ({0}) [%(process)d]:'
386
 
                                ' %(levelname)s: %(message)s'
387
 
                                .format(self.name)))
 
408
        ret = AvahiService.rename(self, *args, **kwargs)
 
409
        syslogger.setFormatter(logging.Formatter(
 
410
            'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
 
411
            .format(self.name)))
388
412
        return ret
389
413
 
390
 
def timedelta_to_milliseconds(td):
391
 
    "Convert a datetime.timedelta() to milliseconds"
392
 
    return ((td.days * 24 * 60 * 60 * 1000)
393
 
            + (td.seconds * 1000)
394
 
            + (td.microseconds // 1000))
395
414
 
396
415
class Client(object):
397
416
    """A representation of a client host served by this server.
434
453
    runtime_expansions: Allowed attributes for runtime expansion.
435
454
    expires:    datetime.datetime(); time (UTC) when a client will be
436
455
                disabled, or None
 
456
    server_settings: The server_settings dict from main()
437
457
    """
438
458
    
439
459
    runtime_expansions = ("approval_delay", "approval_duration",
440
 
                          "created", "enabled", "fingerprint",
441
 
                          "host", "interval", "last_checked_ok",
 
460
                          "created", "enabled", "expires",
 
461
                          "fingerprint", "host", "interval",
 
462
                          "last_approval_request", "last_checked_ok",
442
463
                          "last_enabled", "name", "timeout")
443
 
    client_defaults = { "timeout": "5m",
444
 
                        "extended_timeout": "15m",
445
 
                        "interval": "2m",
446
 
                        "checker": "fping -q -- %%(host)s",
447
 
                        "host": "",
448
 
                        "approval_delay": "0s",
449
 
                        "approval_duration": "1s",
450
 
                        "approved_by_default": "True",
451
 
                        "enabled": "True",
452
 
                        }
453
 
    
454
 
    def timeout_milliseconds(self):
455
 
        "Return the 'timeout' attribute in milliseconds"
456
 
        return timedelta_to_milliseconds(self.timeout)
457
 
    
458
 
    def extended_timeout_milliseconds(self):
459
 
        "Return the 'extended_timeout' attribute in milliseconds"
460
 
        return timedelta_to_milliseconds(self.extended_timeout)
461
 
    
462
 
    def interval_milliseconds(self):
463
 
        "Return the 'interval' attribute in milliseconds"
464
 
        return timedelta_to_milliseconds(self.interval)
465
 
    
466
 
    def approval_delay_milliseconds(self):
467
 
        return timedelta_to_milliseconds(self.approval_delay)
 
464
    client_defaults = {
 
465
        "timeout": "PT5M",
 
466
        "extended_timeout": "PT15M",
 
467
        "interval": "PT2M",
 
468
        "checker": "fping -q -- %%(host)s",
 
469
        "host": "",
 
470
        "approval_delay": "PT0S",
 
471
        "approval_duration": "PT1S",
 
472
        "approved_by_default": "True",
 
473
        "enabled": "True",
 
474
    }
468
475
    
469
476
    @staticmethod
470
477
    def config_parser(config):
486
493
            client["enabled"] = config.getboolean(client_name,
487
494
                                                  "enabled")
488
495
            
 
496
            # Uppercase and remove spaces from fingerprint for later
 
497
            # comparison purposes with return value from the
 
498
            # fingerprint() function
489
499
            client["fingerprint"] = (section["fingerprint"].upper()
490
500
                                     .replace(" ", ""))
491
501
            if "secret" in section:
496
506
                          "rb") as secfile:
497
507
                    client["secret"] = secfile.read()
498
508
            else:
499
 
                raise TypeError("No secret or secfile for section {0}"
 
509
                raise TypeError("No secret or secfile for section {}"
500
510
                                .format(section))
501
511
            client["timeout"] = string_to_delta(section["timeout"])
502
512
            client["extended_timeout"] = string_to_delta(
513
523
        
514
524
        return settings
515
525
    
516
 
    def __init__(self, settings, name = None):
 
526
    def __init__(self, settings, name = None, server_settings=None):
517
527
        self.name = name
 
528
        if server_settings is None:
 
529
            server_settings = {}
 
530
        self.server_settings = server_settings
518
531
        # adding all client settings
519
 
        for setting, value in settings.iteritems():
 
532
        for setting, value in settings.items():
520
533
            setattr(self, setting, value)
521
534
        
522
535
        if self.enabled:
530
543
            self.expires = None
531
544
        
532
545
        logger.debug("Creating client %r", self.name)
533
 
        # Uppercase and remove spaces from fingerprint for later
534
 
        # comparison purposes with return value from the fingerprint()
535
 
        # function
536
546
        logger.debug("  Fingerprint: %s", self.fingerprint)
537
547
        self.created = settings.get("created",
538
548
                                    datetime.datetime.utcnow())
545
555
        self.current_checker_command = None
546
556
        self.approved = None
547
557
        self.approvals_pending = 0
548
 
        self.changedstate = (multiprocessing_manager
549
 
                             .Condition(multiprocessing_manager
550
 
                                        .Lock()))
551
 
        self.client_structure = [attr for attr in
552
 
                                 self.__dict__.iterkeys()
 
558
        self.changedstate = multiprocessing_manager.Condition(
 
559
            multiprocessing_manager.Lock())
 
560
        self.client_structure = [attr
 
561
                                 for attr in self.__dict__.iterkeys()
553
562
                                 if not attr.startswith("_")]
554
563
        self.client_structure.append("client_structure")
555
564
        
556
 
        for name, t in inspect.getmembers(type(self),
557
 
                                          lambda obj:
558
 
                                              isinstance(obj,
559
 
                                                         property)):
 
565
        for name, t in inspect.getmembers(
 
566
                type(self), lambda obj: isinstance(obj, property)):
560
567
            if not name.startswith("_"):
561
568
                self.client_structure.append(name)
562
569
    
570
577
        if getattr(self, "enabled", False):
571
578
            # Already enabled
572
579
            return
573
 
        self.send_changedstate()
574
580
        self.expires = datetime.datetime.utcnow() + self.timeout
575
581
        self.enabled = True
576
582
        self.last_enabled = datetime.datetime.utcnow()
577
583
        self.init_checker()
 
584
        self.send_changedstate()
578
585
    
579
586
    def disable(self, quiet=True):
580
587
        """Disable this client."""
581
588
        if not getattr(self, "enabled", False):
582
589
            return False
583
590
        if not quiet:
584
 
            self.send_changedstate()
585
 
        if not quiet:
586
591
            logger.info("Disabling client %s", self.name)
587
 
        if getattr(self, "disable_initiator_tag", False):
 
592
        if getattr(self, "disable_initiator_tag", None) is not None:
588
593
            gobject.source_remove(self.disable_initiator_tag)
589
594
            self.disable_initiator_tag = None
590
595
        self.expires = None
591
 
        if getattr(self, "checker_initiator_tag", False):
 
596
        if getattr(self, "checker_initiator_tag", None) is not None:
592
597
            gobject.source_remove(self.checker_initiator_tag)
593
598
            self.checker_initiator_tag = None
594
599
        self.stop_checker()
595
600
        self.enabled = False
 
601
        if not quiet:
 
602
            self.send_changedstate()
596
603
        # Do not run this again if called by a gobject.timeout_add
597
604
        return False
598
605
    
602
609
    def init_checker(self):
603
610
        # Schedule a new checker to be started an 'interval' from now,
604
611
        # and every interval from then on.
605
 
        self.checker_initiator_tag = (gobject.timeout_add
606
 
                                      (self.interval_milliseconds(),
607
 
                                       self.start_checker))
 
612
        if self.checker_initiator_tag is not None:
 
613
            gobject.source_remove(self.checker_initiator_tag)
 
614
        self.checker_initiator_tag = gobject.timeout_add(
 
615
            int(self.interval.total_seconds() * 1000),
 
616
            self.start_checker)
608
617
        # Schedule a disable() when 'timeout' has passed
609
 
        self.disable_initiator_tag = (gobject.timeout_add
610
 
                                   (self.timeout_milliseconds(),
611
 
                                    self.disable))
 
618
        if self.disable_initiator_tag is not None:
 
619
            gobject.source_remove(self.disable_initiator_tag)
 
620
        self.disable_initiator_tag = gobject.timeout_add(
 
621
            int(self.timeout.total_seconds() * 1000), self.disable)
612
622
        # Also start a new checker *right now*.
613
623
        self.start_checker()
614
624
    
623
633
                            vars(self))
624
634
                self.checked_ok()
625
635
            else:
626
 
                logger.info("Checker for %(name)s failed",
627
 
                            vars(self))
 
636
                logger.info("Checker for %(name)s failed", vars(self))
628
637
        else:
629
638
            self.last_checker_status = -1
630
639
            logger.warning("Checker for %(name)s crashed?",
642
651
            timeout = self.timeout
643
652
        if self.disable_initiator_tag is not None:
644
653
            gobject.source_remove(self.disable_initiator_tag)
 
654
            self.disable_initiator_tag = None
645
655
        if getattr(self, "enabled", False):
646
 
            self.disable_initiator_tag = (gobject.timeout_add
647
 
                                          (timedelta_to_milliseconds
648
 
                                           (timeout), self.disable))
 
656
            self.disable_initiator_tag = gobject.timeout_add(
 
657
                int(timeout.total_seconds() * 1000), self.disable)
649
658
            self.expires = datetime.datetime.utcnow() + timeout
650
659
    
651
660
    def need_approval(self):
668
677
        # If a checker exists, make sure it is not a zombie
669
678
        try:
670
679
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
671
 
        except (AttributeError, OSError) as error:
672
 
            if (isinstance(error, OSError)
673
 
                and error.errno != errno.ECHILD):
674
 
                raise error
 
680
        except AttributeError:
 
681
            pass
 
682
        except OSError as error:
 
683
            if error.errno != errno.ECHILD:
 
684
                raise
675
685
        else:
676
686
            if pid:
677
687
                logger.warning("Checker was a zombie")
680
690
                                      self.current_checker_command)
681
691
        # Start a new checker if needed
682
692
        if self.checker is None:
 
693
            # Escape attributes for the shell
 
694
            escaped_attrs = {
 
695
                attr: re.escape(str(getattr(self, attr)))
 
696
                for attr in self.runtime_expansions }
683
697
            try:
684
 
                # In case checker_command has exactly one % operator
685
 
                command = self.checker_command % self.host
686
 
            except TypeError:
687
 
                # Escape attributes for the shell
688
 
                escaped_attrs = dict(
689
 
                    (attr,
690
 
                     re.escape(unicode(str(getattr(self, attr, "")),
691
 
                                       errors=
692
 
                                       'replace')))
693
 
                    for attr in
694
 
                    self.runtime_expansions)
695
 
                
696
 
                try:
697
 
                    command = self.checker_command % escaped_attrs
698
 
                except TypeError as error:
699
 
                    logger.error('Could not format string "%s"',
700
 
                                 self.checker_command, exc_info=error)
701
 
                    return True # Try again later
 
698
                command = self.checker_command % escaped_attrs
 
699
            except TypeError as error:
 
700
                logger.error('Could not format string "%s"',
 
701
                             self.checker_command,
 
702
                             exc_info=error)
 
703
                return True     # Try again later
702
704
            self.current_checker_command = command
703
705
            try:
704
 
                logger.info("Starting checker %r for %s",
705
 
                            command, self.name)
 
706
                logger.info("Starting checker %r for %s", command,
 
707
                            self.name)
706
708
                # We don't need to redirect stdout and stderr, since
707
709
                # in normal mode, that is already done by daemon(),
708
710
                # and in debug mode we don't want to.  (Stdin is
709
711
                # always replaced by /dev/null.)
 
712
                # The exception is when not debugging but nevertheless
 
713
                # running in the foreground; use the previously
 
714
                # created wnull.
 
715
                popen_args = {}
 
716
                if (not self.server_settings["debug"]
 
717
                    and self.server_settings["foreground"]):
 
718
                    popen_args.update({"stdout": wnull,
 
719
                                       "stderr": wnull })
710
720
                self.checker = subprocess.Popen(command,
711
721
                                                close_fds=True,
712
 
                                                shell=True, cwd="/")
713
 
                self.checker_callback_tag = (gobject.child_watch_add
714
 
                                             (self.checker.pid,
715
 
                                              self.checker_callback,
716
 
                                              data=command))
717
 
                # The checker may have completed before the gobject
718
 
                # watch was added.  Check for this.
 
722
                                                shell=True,
 
723
                                                cwd="/",
 
724
                                                **popen_args)
 
725
            except OSError as error:
 
726
                logger.error("Failed to start subprocess",
 
727
                             exc_info=error)
 
728
                return True
 
729
            self.checker_callback_tag = gobject.child_watch_add(
 
730
                self.checker.pid, self.checker_callback, data=command)
 
731
            # The checker may have completed before the gobject
 
732
            # watch was added.  Check for this.
 
733
            try:
719
734
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
720
 
                if pid:
721
 
                    gobject.source_remove(self.checker_callback_tag)
722
 
                    self.checker_callback(pid, status, command)
723
735
            except OSError as error:
724
 
                logger.error("Failed to start subprocess",
725
 
                             exc_info=error)
 
736
                if error.errno == errno.ECHILD:
 
737
                    # This should never happen
 
738
                    logger.error("Child process vanished",
 
739
                                 exc_info=error)
 
740
                    return True
 
741
                raise
 
742
            if pid:
 
743
                gobject.source_remove(self.checker_callback_tag)
 
744
                self.checker_callback(pid, status, command)
726
745
        # Re-run this periodically if run by gobject.timeout_add
727
746
        return True
728
747
    
745
764
        self.checker = None
746
765
 
747
766
 
748
 
def dbus_service_property(dbus_interface, signature="v",
749
 
                          access="readwrite", byte_arrays=False):
 
767
def dbus_service_property(dbus_interface,
 
768
                          signature="v",
 
769
                          access="readwrite",
 
770
                          byte_arrays=False):
750
771
    """Decorators for marking methods of a DBusObjectWithProperties to
751
772
    become properties on the D-Bus.
752
773
    
761
782
    # "Set" method, so we fail early here:
762
783
    if byte_arrays and signature != "ay":
763
784
        raise ValueError("Byte arrays not supported for non-'ay'"
764
 
                         " signature {0!r}".format(signature))
 
785
                         " signature {!r}".format(signature))
 
786
    
765
787
    def decorator(func):
766
788
        func._dbus_is_property = True
767
789
        func._dbus_interface = dbus_interface
772
794
            func._dbus_name = func._dbus_name[:-14]
773
795
        func._dbus_get_args_options = {'byte_arrays': byte_arrays }
774
796
        return func
 
797
    
775
798
    return decorator
776
799
 
777
800
 
786
809
                "org.freedesktop.DBus.Property.EmitsChangedSignal":
787
810
                    "false"}
788
811
    """
 
812
    
789
813
    def decorator(func):
790
814
        func._dbus_is_interface = True
791
815
        func._dbus_interface = dbus_interface
792
816
        func._dbus_name = dbus_interface
793
817
        return func
 
818
    
794
819
    return decorator
795
820
 
796
821
 
798
823
    """Decorator to annotate D-Bus methods, signals or properties
799
824
    Usage:
800
825
    
 
826
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
 
827
                       "org.freedesktop.DBus.Property."
 
828
                       "EmitsChangedSignal": "false"})
801
829
    @dbus_service_property("org.example.Interface", signature="b",
802
830
                           access="r")
803
 
    @dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
804
 
                        "org.freedesktop.DBus.Property."
805
 
                        "EmitsChangedSignal": "false"})
806
831
    def Property_dbus_property(self):
807
832
        return dbus.Boolean(False)
808
833
    """
 
834
    
809
835
    def decorator(func):
810
836
        func._dbus_annotations = annotations
811
837
        return func
 
838
    
812
839
    return decorator
813
840
 
814
841
 
815
842
class DBusPropertyException(dbus.exceptions.DBusException):
816
843
    """A base class for D-Bus property-related exceptions
817
844
    """
818
 
    def __unicode__(self):
819
 
        return unicode(str(self))
 
845
    pass
820
846
 
821
847
 
822
848
class DBusPropertyAccessException(DBusPropertyException):
846
872
        If called like _is_dbus_thing("method") it returns a function
847
873
        suitable for use as predicate to inspect.getmembers().
848
874
        """
849
 
        return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
 
875
        return lambda obj: getattr(obj, "_dbus_is_{}".format(thing),
850
876
                                   False)
851
877
    
852
878
    def _get_all_dbus_things(self, thing):
853
879
        """Returns a generator of (name, attribute) pairs
854
880
        """
855
 
        return ((getattr(athing.__get__(self), "_dbus_name",
856
 
                         name),
 
881
        return ((getattr(athing.__get__(self), "_dbus_name", name),
857
882
                 athing.__get__(self))
858
883
                for cls in self.__class__.__mro__
859
884
                for name, athing in
860
 
                inspect.getmembers(cls,
861
 
                                   self._is_dbus_thing(thing)))
 
885
                inspect.getmembers(cls, self._is_dbus_thing(thing)))
862
886
    
863
887
    def _get_dbus_property(self, interface_name, property_name):
864
888
        """Returns a bound method if one exists which is a D-Bus
865
889
        property with the specified name and interface.
866
890
        """
867
 
        for cls in  self.__class__.__mro__:
868
 
            for name, value in (inspect.getmembers
869
 
                                (cls,
870
 
                                 self._is_dbus_thing("property"))):
 
891
        for cls in self.__class__.__mro__:
 
892
            for name, value in inspect.getmembers(
 
893
                    cls, self._is_dbus_thing("property")):
871
894
                if (value._dbus_name == property_name
872
895
                    and value._dbus_interface == interface_name):
873
896
                    return value.__get__(self)
874
897
        
875
898
        # No such property
876
 
        raise DBusPropertyNotFound(self.dbus_object_path + ":"
877
 
                                   + interface_name + "."
878
 
                                   + property_name)
 
899
        raise DBusPropertyNotFound("{}:{}.{}".format(
 
900
            self.dbus_object_path, interface_name, property_name))
879
901
    
880
 
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
 
902
    @dbus.service.method(dbus.PROPERTIES_IFACE,
 
903
                         in_signature="ss",
881
904
                         out_signature="v")
882
905
    def Get(self, interface_name, property_name):
883
906
        """Standard D-Bus property Get() method, see D-Bus standard.
901
924
            # The byte_arrays option is not supported yet on
902
925
            # signatures other than "ay".
903
926
            if prop._dbus_signature != "ay":
904
 
                raise ValueError
 
927
                raise ValueError("Byte arrays not supported for non-"
 
928
                                 "'ay' signature {!r}"
 
929
                                 .format(prop._dbus_signature))
905
930
            value = dbus.ByteArray(b''.join(chr(byte)
906
931
                                            for byte in value))
907
932
        prop(value)
908
933
    
909
 
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
 
934
    @dbus.service.method(dbus.PROPERTIES_IFACE,
 
935
                         in_signature="s",
910
936
                         out_signature="a{sv}")
911
937
    def GetAll(self, interface_name):
912
938
        """Standard D-Bus property GetAll() method, see D-Bus
927
953
            if not hasattr(value, "variant_level"):
928
954
                properties[name] = value
929
955
                continue
930
 
            properties[name] = type(value)(value, variant_level=
931
 
                                           value.variant_level+1)
 
956
            properties[name] = type(value)(
 
957
                value, variant_level = value.variant_level + 1)
932
958
        return dbus.Dictionary(properties, signature="sv")
933
959
    
 
960
    @dbus.service.signal(dbus.PROPERTIES_IFACE, signature="sa{sv}as")
 
961
    def PropertiesChanged(self, interface_name, changed_properties,
 
962
                          invalidated_properties):
 
963
        """Standard D-Bus PropertiesChanged() signal, see D-Bus
 
964
        standard.
 
965
        """
 
966
        pass
 
967
    
934
968
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
935
969
                         out_signature="s",
936
970
                         path_keyword='object_path',
944
978
                                                   connection)
945
979
        try:
946
980
            document = xml.dom.minidom.parseString(xmlstring)
 
981
            
947
982
            def make_tag(document, name, prop):
948
983
                e = document.createElement("property")
949
984
                e.setAttribute("name", name)
950
985
                e.setAttribute("type", prop._dbus_signature)
951
986
                e.setAttribute("access", prop._dbus_access)
952
987
                return e
 
988
            
953
989
            for if_tag in document.getElementsByTagName("interface"):
954
990
                # Add property tags
955
991
                for tag in (make_tag(document, name, prop)
967
1003
                            if (name == tag.getAttribute("name")
968
1004
                                and prop._dbus_interface
969
1005
                                == if_tag.getAttribute("name")):
970
 
                                annots.update(getattr
971
 
                                              (prop,
972
 
                                               "_dbus_annotations",
973
 
                                               {}))
974
 
                        for name, value in annots.iteritems():
 
1006
                                annots.update(getattr(
 
1007
                                    prop, "_dbus_annotations", {}))
 
1008
                        for name, value in annots.items():
975
1009
                            ann_tag = document.createElement(
976
1010
                                "annotation")
977
1011
                            ann_tag.setAttribute("name", name)
980
1014
                # Add interface annotation tags
981
1015
                for annotation, value in dict(
982
1016
                    itertools.chain.from_iterable(
983
 
                        annotations().iteritems()
984
 
                        for name, annotations in
985
 
                        self._get_all_dbus_things("interface")
 
1017
                        annotations().items()
 
1018
                        for name, annotations
 
1019
                        in self._get_all_dbus_things("interface")
986
1020
                        if name == if_tag.getAttribute("name")
987
 
                        )).iteritems():
 
1021
                        )).items():
988
1022
                    ann_tag = document.createElement("annotation")
989
1023
                    ann_tag.setAttribute("name", annotation)
990
1024
                    ann_tag.setAttribute("value", value)
1013
1047
        return xmlstring
1014
1048
 
1015
1049
 
1016
 
def datetime_to_dbus (dt, variant_level=0):
 
1050
def datetime_to_dbus(dt, variant_level=0):
1017
1051
    """Convert a UTC datetime.datetime() to a D-Bus type."""
1018
1052
    if dt is None:
1019
1053
        return dbus.String("", variant_level = variant_level)
1020
 
    return dbus.String(dt.isoformat(),
1021
 
                       variant_level=variant_level)
 
1054
    return dbus.String(dt.isoformat(), variant_level=variant_level)
1022
1055
 
1023
1056
 
1024
1057
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1027
1060
    interface names according to the "alt_interface_names" mapping.
1028
1061
    Usage:
1029
1062
    
1030
 
    @alternate_dbus_names({"org.example.Interface":
1031
 
                               "net.example.AlternateInterface"})
 
1063
    @alternate_dbus_interfaces({"org.example.Interface":
 
1064
                                    "net.example.AlternateInterface"})
1032
1065
    class SampleDBusObject(dbus.service.Object):
1033
1066
        @dbus.service.method("org.example.Interface")
1034
1067
        def SampleDBusMethod():
1044
1077
    (from DBusObjectWithProperties) and interfaces (from the
1045
1078
    dbus_interface_annotations decorator).
1046
1079
    """
 
1080
    
1047
1081
    def wrapper(cls):
1048
1082
        for orig_interface_name, alt_interface_name in (
1049
 
            alt_interface_names.iteritems()):
 
1083
                alt_interface_names.items()):
1050
1084
            attr = {}
1051
1085
            interface_names = set()
1052
1086
            # Go though all attributes of the class
1054
1088
                # Ignore non-D-Bus attributes, and D-Bus attributes
1055
1089
                # with the wrong interface name
1056
1090
                if (not hasattr(attribute, "_dbus_interface")
1057
 
                    or not attribute._dbus_interface
1058
 
                    .startswith(orig_interface_name)):
 
1091
                    or not attribute._dbus_interface.startswith(
 
1092
                        orig_interface_name)):
1059
1093
                    continue
1060
1094
                # Create an alternate D-Bus interface name based on
1061
1095
                # the current name
1062
 
                alt_interface = (attribute._dbus_interface
1063
 
                                 .replace(orig_interface_name,
1064
 
                                          alt_interface_name))
 
1096
                alt_interface = attribute._dbus_interface.replace(
 
1097
                    orig_interface_name, alt_interface_name)
1065
1098
                interface_names.add(alt_interface)
1066
1099
                # Is this a D-Bus signal?
1067
1100
                if getattr(attribute, "_dbus_is_signal", False):
1068
 
                    # Extract the original non-method function by
1069
 
                    # black magic
 
1101
                    # Extract the original non-method undecorated
 
1102
                    # function by black magic
1070
1103
                    nonmethod_func = (dict(
1071
 
                            zip(attribute.func_code.co_freevars,
1072
 
                                attribute.__closure__))["func"]
1073
 
                                      .cell_contents)
 
1104
                        zip(attribute.func_code.co_freevars,
 
1105
                            attribute.__closure__))
 
1106
                                      ["func"].cell_contents)
1074
1107
                    # Create a new, but exactly alike, function
1075
1108
                    # object, and decorate it to be a new D-Bus signal
1076
1109
                    # with the alternate D-Bus interface name
1077
 
                    new_function = (dbus.service.signal
1078
 
                                    (alt_interface,
1079
 
                                     attribute._dbus_signature)
 
1110
                    new_function = (dbus.service.signal(
 
1111
                        alt_interface, attribute._dbus_signature)
1080
1112
                                    (types.FunctionType(
1081
 
                                nonmethod_func.func_code,
1082
 
                                nonmethod_func.func_globals,
1083
 
                                nonmethod_func.func_name,
1084
 
                                nonmethod_func.func_defaults,
1085
 
                                nonmethod_func.func_closure)))
 
1113
                                        nonmethod_func.func_code,
 
1114
                                        nonmethod_func.func_globals,
 
1115
                                        nonmethod_func.func_name,
 
1116
                                        nonmethod_func.func_defaults,
 
1117
                                        nonmethod_func.func_closure)))
1086
1118
                    # Copy annotations, if any
1087
1119
                    try:
1088
 
                        new_function._dbus_annotations = (
1089
 
                            dict(attribute._dbus_annotations))
 
1120
                        new_function._dbus_annotations = dict(
 
1121
                            attribute._dbus_annotations)
1090
1122
                    except AttributeError:
1091
1123
                        pass
1092
1124
                    # Define a creator of a function to call both the
1097
1129
                        """This function is a scope container to pass
1098
1130
                        func1 and func2 to the "call_both" function
1099
1131
                        outside of its arguments"""
 
1132
                        
1100
1133
                        def call_both(*args, **kwargs):
1101
1134
                            """This function will emit two D-Bus
1102
1135
                            signals by calling func1 and func2"""
1103
1136
                            func1(*args, **kwargs)
1104
1137
                            func2(*args, **kwargs)
 
1138
                        
1105
1139
                        return call_both
1106
1140
                    # Create the "call_both" function and add it to
1107
1141
                    # the class
1112
1146
                    # object.  Decorate it to be a new D-Bus method
1113
1147
                    # with the alternate D-Bus interface name.  Add it
1114
1148
                    # to the class.
1115
 
                    attr[attrname] = (dbus.service.method
1116
 
                                      (alt_interface,
1117
 
                                       attribute._dbus_in_signature,
1118
 
                                       attribute._dbus_out_signature)
1119
 
                                      (types.FunctionType
1120
 
                                       (attribute.func_code,
1121
 
                                        attribute.func_globals,
1122
 
                                        attribute.func_name,
1123
 
                                        attribute.func_defaults,
1124
 
                                        attribute.func_closure)))
 
1149
                    attr[attrname] = (
 
1150
                        dbus.service.method(
 
1151
                            alt_interface,
 
1152
                            attribute._dbus_in_signature,
 
1153
                            attribute._dbus_out_signature)
 
1154
                        (types.FunctionType(attribute.func_code,
 
1155
                                            attribute.func_globals,
 
1156
                                            attribute.func_name,
 
1157
                                            attribute.func_defaults,
 
1158
                                            attribute.func_closure)))
1125
1159
                    # Copy annotations, if any
1126
1160
                    try:
1127
 
                        attr[attrname]._dbus_annotations = (
1128
 
                            dict(attribute._dbus_annotations))
 
1161
                        attr[attrname]._dbus_annotations = dict(
 
1162
                            attribute._dbus_annotations)
1129
1163
                    except AttributeError:
1130
1164
                        pass
1131
1165
                # Is this a D-Bus property?
1134
1168
                    # object, and decorate it to be a new D-Bus
1135
1169
                    # property with the alternate D-Bus interface
1136
1170
                    # name.  Add it to the class.
1137
 
                    attr[attrname] = (dbus_service_property
1138
 
                                      (alt_interface,
1139
 
                                       attribute._dbus_signature,
1140
 
                                       attribute._dbus_access,
1141
 
                                       attribute
1142
 
                                       ._dbus_get_args_options
1143
 
                                       ["byte_arrays"])
1144
 
                                      (types.FunctionType
1145
 
                                       (attribute.func_code,
1146
 
                                        attribute.func_globals,
1147
 
                                        attribute.func_name,
1148
 
                                        attribute.func_defaults,
1149
 
                                        attribute.func_closure)))
 
1171
                    attr[attrname] = (dbus_service_property(
 
1172
                        alt_interface, attribute._dbus_signature,
 
1173
                        attribute._dbus_access,
 
1174
                        attribute._dbus_get_args_options
 
1175
                        ["byte_arrays"])
 
1176
                                      (types.FunctionType(
 
1177
                                          attribute.func_code,
 
1178
                                          attribute.func_globals,
 
1179
                                          attribute.func_name,
 
1180
                                          attribute.func_defaults,
 
1181
                                          attribute.func_closure)))
1150
1182
                    # Copy annotations, if any
1151
1183
                    try:
1152
 
                        attr[attrname]._dbus_annotations = (
1153
 
                            dict(attribute._dbus_annotations))
 
1184
                        attr[attrname]._dbus_annotations = dict(
 
1185
                            attribute._dbus_annotations)
1154
1186
                    except AttributeError:
1155
1187
                        pass
1156
1188
                # Is this a D-Bus interface?
1159
1191
                    # object.  Decorate it to be a new D-Bus interface
1160
1192
                    # with the alternate D-Bus interface name.  Add it
1161
1193
                    # to the class.
1162
 
                    attr[attrname] = (dbus_interface_annotations
1163
 
                                      (alt_interface)
1164
 
                                      (types.FunctionType
1165
 
                                       (attribute.func_code,
1166
 
                                        attribute.func_globals,
1167
 
                                        attribute.func_name,
1168
 
                                        attribute.func_defaults,
1169
 
                                        attribute.func_closure)))
 
1194
                    attr[attrname] = (
 
1195
                        dbus_interface_annotations(alt_interface)
 
1196
                        (types.FunctionType(attribute.func_code,
 
1197
                                            attribute.func_globals,
 
1198
                                            attribute.func_name,
 
1199
                                            attribute.func_defaults,
 
1200
                                            attribute.func_closure)))
1170
1201
            if deprecate:
1171
1202
                # Deprecate all alternate interfaces
1172
 
                iname="_AlternateDBusNames_interface_annotation{0}"
 
1203
                iname="_AlternateDBusNames_interface_annotation{}"
1173
1204
                for interface_name in interface_names:
 
1205
                    
1174
1206
                    @dbus_interface_annotations(interface_name)
1175
1207
                    def func(self):
1176
1208
                        return { "org.freedesktop.DBus.Deprecated":
1177
 
                                     "true" }
 
1209
                                 "true" }
1178
1210
                    # Find an unused name
1179
1211
                    for aname in (iname.format(i)
1180
1212
                                  for i in itertools.count()):
1184
1216
            if interface_names:
1185
1217
                # Replace the class with a new subclass of it with
1186
1218
                # methods, signals, etc. as created above.
1187
 
                cls = type(b"{0}Alternate".format(cls.__name__),
1188
 
                           (cls,), attr)
 
1219
                cls = type(b"{}Alternate".format(cls.__name__),
 
1220
                           (cls, ), attr)
1189
1221
        return cls
 
1222
    
1190
1223
    return wrapper
1191
1224
 
1192
1225
 
1193
1226
@alternate_dbus_interfaces({"se.recompile.Mandos":
1194
 
                                "se.bsnet.fukt.Mandos"})
 
1227
                            "se.bsnet.fukt.Mandos"})
1195
1228
class ClientDBus(Client, DBusObjectWithProperties):
1196
1229
    """A Client class using D-Bus
1197
1230
    
1201
1234
    """
1202
1235
    
1203
1236
    runtime_expansions = (Client.runtime_expansions
1204
 
                          + ("dbus_object_path",))
 
1237
                          + ("dbus_object_path", ))
 
1238
    
 
1239
    _interface = "se.recompile.Mandos.Client"
1205
1240
    
1206
1241
    # dbus.service.Object doesn't use super(), so we can't either.
1207
1242
    
1210
1245
        Client.__init__(self, *args, **kwargs)
1211
1246
        # Only now, when this client is initialized, can it show up on
1212
1247
        # the D-Bus
1213
 
        client_object_name = unicode(self.name).translate(
 
1248
        client_object_name = str(self.name).translate(
1214
1249
            {ord("."): ord("_"),
1215
1250
             ord("-"): ord("_")})
1216
 
        self.dbus_object_path = (dbus.ObjectPath
1217
 
                                 ("/clients/" + client_object_name))
 
1251
        self.dbus_object_path = dbus.ObjectPath(
 
1252
            "/clients/" + client_object_name)
1218
1253
        DBusObjectWithProperties.__init__(self, self.bus,
1219
1254
                                          self.dbus_object_path)
1220
1255
    
1221
 
    def notifychangeproperty(transform_func,
1222
 
                             dbus_name, type_func=lambda x: x,
1223
 
                             variant_level=1):
 
1256
    def notifychangeproperty(transform_func, dbus_name,
 
1257
                             type_func=lambda x: x,
 
1258
                             variant_level=1,
 
1259
                             invalidate_only=False,
 
1260
                             _interface=_interface):
1224
1261
        """ Modify a variable so that it's a property which announces
1225
1262
        its changes to DBus.
1226
1263
        
1231
1268
                   to the D-Bus.  Default: no transform
1232
1269
        variant_level: D-Bus variant level.  Default: 1
1233
1270
        """
1234
 
        attrname = "_{0}".format(dbus_name)
 
1271
        attrname = "_{}".format(dbus_name)
 
1272
        
1235
1273
        def setter(self, value):
1236
1274
            if hasattr(self, "dbus_object_path"):
1237
1275
                if (not hasattr(self, attrname) or
1238
1276
                    type_func(getattr(self, attrname, None))
1239
1277
                    != type_func(value)):
1240
 
                    dbus_value = transform_func(type_func(value),
1241
 
                                                variant_level
1242
 
                                                =variant_level)
1243
 
                    self.PropertyChanged(dbus.String(dbus_name),
1244
 
                                         dbus_value)
 
1278
                    if invalidate_only:
 
1279
                        self.PropertiesChanged(
 
1280
                            _interface, dbus.Dictionary(),
 
1281
                            dbus.Array((dbus_name, )))
 
1282
                    else:
 
1283
                        dbus_value = transform_func(
 
1284
                            type_func(value),
 
1285
                            variant_level = variant_level)
 
1286
                        self.PropertyChanged(dbus.String(dbus_name),
 
1287
                                             dbus_value)
 
1288
                        self.PropertiesChanged(
 
1289
                            _interface,
 
1290
                            dbus.Dictionary({ dbus.String(dbus_name):
 
1291
                                              dbus_value }),
 
1292
                            dbus.Array())
1245
1293
            setattr(self, attrname, value)
1246
1294
        
1247
1295
        return property(lambda self: getattr(self, attrname), setter)
1253
1301
    enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1254
1302
    last_enabled = notifychangeproperty(datetime_to_dbus,
1255
1303
                                        "LastEnabled")
1256
 
    checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1257
 
                                   type_func = lambda checker:
1258
 
                                       checker is not None)
 
1304
    checker = notifychangeproperty(
 
1305
        dbus.Boolean, "CheckerRunning",
 
1306
        type_func = lambda checker: checker is not None)
1259
1307
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
1260
1308
                                           "LastCheckedOK")
1261
1309
    last_checker_status = notifychangeproperty(dbus.Int16,
1264
1312
        datetime_to_dbus, "LastApprovalRequest")
1265
1313
    approved_by_default = notifychangeproperty(dbus.Boolean,
1266
1314
                                               "ApprovedByDefault")
1267
 
    approval_delay = notifychangeproperty(dbus.UInt64,
1268
 
                                          "ApprovalDelay",
1269
 
                                          type_func =
1270
 
                                          timedelta_to_milliseconds)
 
1315
    approval_delay = notifychangeproperty(
 
1316
        dbus.UInt64, "ApprovalDelay",
 
1317
        type_func = lambda td: td.total_seconds() * 1000)
1271
1318
    approval_duration = notifychangeproperty(
1272
1319
        dbus.UInt64, "ApprovalDuration",
1273
 
        type_func = timedelta_to_milliseconds)
 
1320
        type_func = lambda td: td.total_seconds() * 1000)
1274
1321
    host = notifychangeproperty(dbus.String, "Host")
1275
 
    timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1276
 
                                   type_func =
1277
 
                                   timedelta_to_milliseconds)
 
1322
    timeout = notifychangeproperty(
 
1323
        dbus.UInt64, "Timeout",
 
1324
        type_func = lambda td: td.total_seconds() * 1000)
1278
1325
    extended_timeout = notifychangeproperty(
1279
1326
        dbus.UInt64, "ExtendedTimeout",
1280
 
        type_func = timedelta_to_milliseconds)
1281
 
    interval = notifychangeproperty(dbus.UInt64,
1282
 
                                    "Interval",
1283
 
                                    type_func =
1284
 
                                    timedelta_to_milliseconds)
 
1327
        type_func = lambda td: td.total_seconds() * 1000)
 
1328
    interval = notifychangeproperty(
 
1329
        dbus.UInt64, "Interval",
 
1330
        type_func = lambda td: td.total_seconds() * 1000)
1285
1331
    checker_command = notifychangeproperty(dbus.String, "Checker")
 
1332
    secret = notifychangeproperty(dbus.ByteArray, "Secret",
 
1333
                                  invalidate_only=True)
1286
1334
    
1287
1335
    del notifychangeproperty
1288
1336
    
1315
1363
                                       *args, **kwargs)
1316
1364
    
1317
1365
    def start_checker(self, *args, **kwargs):
1318
 
        old_checker = self.checker
1319
 
        if self.checker is not None:
1320
 
            old_checker_pid = self.checker.pid
1321
 
        else:
1322
 
            old_checker_pid = None
 
1366
        old_checker_pid = getattr(self.checker, "pid", None)
1323
1367
        r = Client.start_checker(self, *args, **kwargs)
1324
1368
        # Only if new checker process was started
1325
1369
        if (self.checker is not None
1333
1377
        return False
1334
1378
    
1335
1379
    def approve(self, value=True):
1336
 
        self.send_changedstate()
1337
1380
        self.approved = value
1338
 
        gobject.timeout_add(timedelta_to_milliseconds
1339
 
                            (self.approval_duration),
1340
 
                            self._reset_approved)
 
1381
        gobject.timeout_add(int(self.approval_duration.total_seconds()
 
1382
                                * 1000), self._reset_approved)
 
1383
        self.send_changedstate()
1341
1384
    
1342
1385
    ## D-Bus methods, signals & properties
1343
 
    _interface = "se.recompile.Mandos.Client"
1344
1386
    
1345
1387
    ## Interfaces
1346
1388
    
1347
 
    @dbus_interface_annotations(_interface)
1348
 
    def _foo(self):
1349
 
        return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
1350
 
                     "false"}
1351
 
    
1352
1389
    ## Signals
1353
1390
    
1354
1391
    # CheckerCompleted - signal
1364
1401
        pass
1365
1402
    
1366
1403
    # PropertyChanged - signal
 
1404
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1367
1405
    @dbus.service.signal(_interface, signature="sv")
1368
1406
    def PropertyChanged(self, property, value):
1369
1407
        "D-Bus signal"
1433
1471
        return dbus.Boolean(bool(self.approvals_pending))
1434
1472
    
1435
1473
    # ApprovedByDefault - property
1436
 
    @dbus_service_property(_interface, signature="b",
 
1474
    @dbus_service_property(_interface,
 
1475
                           signature="b",
1437
1476
                           access="readwrite")
1438
1477
    def ApprovedByDefault_dbus_property(self, value=None):
1439
1478
        if value is None:       # get
1441
1480
        self.approved_by_default = bool(value)
1442
1481
    
1443
1482
    # ApprovalDelay - property
1444
 
    @dbus_service_property(_interface, signature="t",
 
1483
    @dbus_service_property(_interface,
 
1484
                           signature="t",
1445
1485
                           access="readwrite")
1446
1486
    def ApprovalDelay_dbus_property(self, value=None):
1447
1487
        if value is None:       # get
1448
 
            return dbus.UInt64(self.approval_delay_milliseconds())
 
1488
            return dbus.UInt64(self.approval_delay.total_seconds()
 
1489
                               * 1000)
1449
1490
        self.approval_delay = datetime.timedelta(0, 0, 0, value)
1450
1491
    
1451
1492
    # ApprovalDuration - property
1452
 
    @dbus_service_property(_interface, signature="t",
 
1493
    @dbus_service_property(_interface,
 
1494
                           signature="t",
1453
1495
                           access="readwrite")
1454
1496
    def ApprovalDuration_dbus_property(self, value=None):
1455
1497
        if value is None:       # get
1456
 
            return dbus.UInt64(timedelta_to_milliseconds(
1457
 
                    self.approval_duration))
 
1498
            return dbus.UInt64(self.approval_duration.total_seconds()
 
1499
                               * 1000)
1458
1500
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
1459
1501
    
1460
1502
    # Name - property
1468
1510
        return dbus.String(self.fingerprint)
1469
1511
    
1470
1512
    # Host - property
1471
 
    @dbus_service_property(_interface, signature="s",
 
1513
    @dbus_service_property(_interface,
 
1514
                           signature="s",
1472
1515
                           access="readwrite")
1473
1516
    def Host_dbus_property(self, value=None):
1474
1517
        if value is None:       # get
1475
1518
            return dbus.String(self.host)
1476
 
        self.host = unicode(value)
 
1519
        self.host = str(value)
1477
1520
    
1478
1521
    # Created - property
1479
1522
    @dbus_service_property(_interface, signature="s", access="read")
1486
1529
        return datetime_to_dbus(self.last_enabled)
1487
1530
    
1488
1531
    # Enabled - property
1489
 
    @dbus_service_property(_interface, signature="b",
 
1532
    @dbus_service_property(_interface,
 
1533
                           signature="b",
1490
1534
                           access="readwrite")
1491
1535
    def Enabled_dbus_property(self, value=None):
1492
1536
        if value is None:       # get
1497
1541
            self.disable()
1498
1542
    
1499
1543
    # LastCheckedOK - property
1500
 
    @dbus_service_property(_interface, signature="s",
 
1544
    @dbus_service_property(_interface,
 
1545
                           signature="s",
1501
1546
                           access="readwrite")
1502
1547
    def LastCheckedOK_dbus_property(self, value=None):
1503
1548
        if value is not None:
1506
1551
        return datetime_to_dbus(self.last_checked_ok)
1507
1552
    
1508
1553
    # LastCheckerStatus - property
1509
 
    @dbus_service_property(_interface, signature="n",
1510
 
                           access="read")
 
1554
    @dbus_service_property(_interface, signature="n", access="read")
1511
1555
    def LastCheckerStatus_dbus_property(self):
1512
1556
        return dbus.Int16(self.last_checker_status)
1513
1557
    
1522
1566
        return datetime_to_dbus(self.last_approval_request)
1523
1567
    
1524
1568
    # Timeout - property
1525
 
    @dbus_service_property(_interface, signature="t",
 
1569
    @dbus_service_property(_interface,
 
1570
                           signature="t",
1526
1571
                           access="readwrite")
1527
1572
    def Timeout_dbus_property(self, value=None):
1528
1573
        if value is None:       # get
1529
 
            return dbus.UInt64(self.timeout_milliseconds())
 
1574
            return dbus.UInt64(self.timeout.total_seconds() * 1000)
 
1575
        old_timeout = self.timeout
1530
1576
        self.timeout = datetime.timedelta(0, 0, 0, value)
1531
 
        # Reschedule timeout
 
1577
        # Reschedule disabling
1532
1578
        if self.enabled:
1533
1579
            now = datetime.datetime.utcnow()
1534
 
            time_to_die = timedelta_to_milliseconds(
1535
 
                (self.last_checked_ok + self.timeout) - now)
1536
 
            if time_to_die <= 0:
 
1580
            self.expires += self.timeout - old_timeout
 
1581
            if self.expires <= now:
1537
1582
                # The timeout has passed
1538
1583
                self.disable()
1539
1584
            else:
1540
 
                self.expires = (now +
1541
 
                                datetime.timedelta(milliseconds =
1542
 
                                                   time_to_die))
1543
1585
                if (getattr(self, "disable_initiator_tag", None)
1544
1586
                    is None):
1545
1587
                    return
1546
1588
                gobject.source_remove(self.disable_initiator_tag)
1547
 
                self.disable_initiator_tag = (gobject.timeout_add
1548
 
                                              (time_to_die,
1549
 
                                               self.disable))
 
1589
                self.disable_initiator_tag = gobject.timeout_add(
 
1590
                    int((self.expires - now).total_seconds() * 1000),
 
1591
                    self.disable)
1550
1592
    
1551
1593
    # ExtendedTimeout - property
1552
 
    @dbus_service_property(_interface, signature="t",
 
1594
    @dbus_service_property(_interface,
 
1595
                           signature="t",
1553
1596
                           access="readwrite")
1554
1597
    def ExtendedTimeout_dbus_property(self, value=None):
1555
1598
        if value is None:       # get
1556
 
            return dbus.UInt64(self.extended_timeout_milliseconds())
 
1599
            return dbus.UInt64(self.extended_timeout.total_seconds()
 
1600
                               * 1000)
1557
1601
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1558
1602
    
1559
1603
    # Interval - property
1560
 
    @dbus_service_property(_interface, signature="t",
 
1604
    @dbus_service_property(_interface,
 
1605
                           signature="t",
1561
1606
                           access="readwrite")
1562
1607
    def Interval_dbus_property(self, value=None):
1563
1608
        if value is None:       # get
1564
 
            return dbus.UInt64(self.interval_milliseconds())
 
1609
            return dbus.UInt64(self.interval.total_seconds() * 1000)
1565
1610
        self.interval = datetime.timedelta(0, 0, 0, value)
1566
1611
        if getattr(self, "checker_initiator_tag", None) is None:
1567
1612
            return
1568
1613
        if self.enabled:
1569
1614
            # Reschedule checker run
1570
1615
            gobject.source_remove(self.checker_initiator_tag)
1571
 
            self.checker_initiator_tag = (gobject.timeout_add
1572
 
                                          (value, self.start_checker))
1573
 
            self.start_checker()    # Start one now, too
 
1616
            self.checker_initiator_tag = gobject.timeout_add(
 
1617
                value, self.start_checker)
 
1618
            self.start_checker() # Start one now, too
1574
1619
    
1575
1620
    # Checker - property
1576
 
    @dbus_service_property(_interface, signature="s",
 
1621
    @dbus_service_property(_interface,
 
1622
                           signature="s",
1577
1623
                           access="readwrite")
1578
1624
    def Checker_dbus_property(self, value=None):
1579
1625
        if value is None:       # get
1580
1626
            return dbus.String(self.checker_command)
1581
 
        self.checker_command = unicode(value)
 
1627
        self.checker_command = str(value)
1582
1628
    
1583
1629
    # CheckerRunning - property
1584
 
    @dbus_service_property(_interface, signature="b",
 
1630
    @dbus_service_property(_interface,
 
1631
                           signature="b",
1585
1632
                           access="readwrite")
1586
1633
    def CheckerRunning_dbus_property(self, value=None):
1587
1634
        if value is None:       # get
1597
1644
        return self.dbus_object_path # is already a dbus.ObjectPath
1598
1645
    
1599
1646
    # Secret = property
1600
 
    @dbus_service_property(_interface, signature="ay",
1601
 
                           access="write", byte_arrays=True)
 
1647
    @dbus_service_property(_interface,
 
1648
                           signature="ay",
 
1649
                           access="write",
 
1650
                           byte_arrays=True)
1602
1651
    def Secret_dbus_property(self, value):
1603
 
        self.secret = str(value)
 
1652
        self.secret = bytes(value)
1604
1653
    
1605
1654
    del _interface
1606
1655
 
1620
1669
        if data[0] == 'data':
1621
1670
            return data[1]
1622
1671
        if data[0] == 'function':
 
1672
            
1623
1673
            def func(*args, **kwargs):
1624
1674
                self._pipe.send(('funcall', name, args, kwargs))
1625
1675
                return self._pipe.recv()[1]
 
1676
            
1626
1677
            return func
1627
1678
    
1628
1679
    def __setattr__(self, name, value):
1640
1691
    def handle(self):
1641
1692
        with contextlib.closing(self.server.child_pipe) as child_pipe:
1642
1693
            logger.info("TCP connection from: %s",
1643
 
                        unicode(self.client_address))
 
1694
                        str(self.client_address))
1644
1695
            logger.debug("Pipe FD: %d",
1645
1696
                         self.server.child_pipe.fileno())
1646
1697
            
1647
 
            session = (gnutls.connection
1648
 
                       .ClientSession(self.request,
1649
 
                                      gnutls.connection
1650
 
                                      .X509Credentials()))
 
1698
            session = gnutls.connection.ClientSession(
 
1699
                self.request, gnutls.connection .X509Credentials())
1651
1700
            
1652
1701
            # Note: gnutls.connection.X509Credentials is really a
1653
1702
            # generic GnuTLS certificate credentials object so long as
1662
1711
            priority = self.server.gnutls_priority
1663
1712
            if priority is None:
1664
1713
                priority = "NORMAL"
1665
 
            (gnutls.library.functions
1666
 
             .gnutls_priority_set_direct(session._c_object,
1667
 
                                         priority, None))
 
1714
            gnutls.library.functions.gnutls_priority_set_direct(
 
1715
                session._c_object, priority, None)
1668
1716
            
1669
1717
            # Start communication using the Mandos protocol
1670
1718
            # Get protocol number
1672
1720
            logger.debug("Protocol version: %r", line)
1673
1721
            try:
1674
1722
                if int(line.strip().split()[0]) > 1:
1675
 
                    raise RuntimeError
 
1723
                    raise RuntimeError(line)
1676
1724
            except (ValueError, IndexError, RuntimeError) as error:
1677
1725
                logger.error("Unknown protocol version: %s", error)
1678
1726
                return
1690
1738
            approval_required = False
1691
1739
            try:
1692
1740
                try:
1693
 
                    fpr = self.fingerprint(self.peer_certificate
1694
 
                                           (session))
 
1741
                    fpr = self.fingerprint(
 
1742
                        self.peer_certificate(session))
1695
1743
                except (TypeError,
1696
1744
                        gnutls.errors.GNUTLSError) as error:
1697
1745
                    logger.warning("Bad certificate: %s", error)
1712
1760
                while True:
1713
1761
                    if not client.enabled:
1714
1762
                        logger.info("Client %s is disabled",
1715
 
                                       client.name)
 
1763
                                    client.name)
1716
1764
                        if self.server.use_dbus:
1717
1765
                            # Emit D-Bus signal
1718
1766
                            client.Rejected("Disabled")
1727
1775
                        if self.server.use_dbus:
1728
1776
                            # Emit D-Bus signal
1729
1777
                            client.NeedApproval(
1730
 
                                client.approval_delay_milliseconds(),
1731
 
                                client.approved_by_default)
 
1778
                                client.approval_delay.total_seconds()
 
1779
                                * 1000, client.approved_by_default)
1732
1780
                    else:
1733
1781
                        logger.warning("Client %s was not approved",
1734
1782
                                       client.name)
1740
1788
                    #wait until timeout or approved
1741
1789
                    time = datetime.datetime.now()
1742
1790
                    client.changedstate.acquire()
1743
 
                    (client.changedstate.wait
1744
 
                     (float(client.timedelta_to_milliseconds(delay)
1745
 
                            / 1000)))
 
1791
                    client.changedstate.wait(delay.total_seconds())
1746
1792
                    client.changedstate.release()
1747
1793
                    time2 = datetime.datetime.now()
1748
1794
                    if (time2 - time) >= delay:
1767
1813
                        logger.warning("gnutls send failed",
1768
1814
                                       exc_info=error)
1769
1815
                        return
1770
 
                    logger.debug("Sent: %d, remaining: %d",
1771
 
                                 sent, len(client.secret)
1772
 
                                 - (sent_size + sent))
 
1816
                    logger.debug("Sent: %d, remaining: %d", sent,
 
1817
                                 len(client.secret) - (sent_size
 
1818
                                                       + sent))
1773
1819
                    sent_size += sent
1774
1820
                
1775
1821
                logger.info("Sending secret to %s", client.name)
1792
1838
    def peer_certificate(session):
1793
1839
        "Return the peer's OpenPGP certificate as a bytestring"
1794
1840
        # If not an OpenPGP certificate...
1795
 
        if (gnutls.library.functions
1796
 
            .gnutls_certificate_type_get(session._c_object)
 
1841
        if (gnutls.library.functions.gnutls_certificate_type_get(
 
1842
                session._c_object)
1797
1843
            != gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1798
1844
            # ...do the normal thing
1799
1845
            return session.peer_certificate
1813
1859
    def fingerprint(openpgp):
1814
1860
        "Convert an OpenPGP bytestring to a hexdigit fingerprint"
1815
1861
        # New GnuTLS "datum" with the OpenPGP public key
1816
 
        datum = (gnutls.library.types
1817
 
                 .gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1818
 
                                             ctypes.POINTER
1819
 
                                             (ctypes.c_ubyte)),
1820
 
                                 ctypes.c_uint(len(openpgp))))
 
1862
        datum = gnutls.library.types.gnutls_datum_t(
 
1863
            ctypes.cast(ctypes.c_char_p(openpgp),
 
1864
                        ctypes.POINTER(ctypes.c_ubyte)),
 
1865
            ctypes.c_uint(len(openpgp)))
1821
1866
        # New empty GnuTLS certificate
1822
1867
        crt = gnutls.library.types.gnutls_openpgp_crt_t()
1823
 
        (gnutls.library.functions
1824
 
         .gnutls_openpgp_crt_init(ctypes.byref(crt)))
 
1868
        gnutls.library.functions.gnutls_openpgp_crt_init(
 
1869
            ctypes.byref(crt))
1825
1870
        # Import the OpenPGP public key into the certificate
1826
 
        (gnutls.library.functions
1827
 
         .gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1828
 
                                    gnutls.library.constants
1829
 
                                    .GNUTLS_OPENPGP_FMT_RAW))
 
1871
        gnutls.library.functions.gnutls_openpgp_crt_import(
 
1872
            crt, ctypes.byref(datum),
 
1873
            gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
1830
1874
        # Verify the self signature in the key
1831
1875
        crtverify = ctypes.c_uint()
1832
 
        (gnutls.library.functions
1833
 
         .gnutls_openpgp_crt_verify_self(crt, 0,
1834
 
                                         ctypes.byref(crtverify)))
 
1876
        gnutls.library.functions.gnutls_openpgp_crt_verify_self(
 
1877
            crt, 0, ctypes.byref(crtverify))
1835
1878
        if crtverify.value != 0:
1836
1879
            gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1837
 
            raise (gnutls.errors.CertificateSecurityError
1838
 
                   ("Verify failed"))
 
1880
            raise gnutls.errors.CertificateSecurityError(
 
1881
                "Verify failed")
1839
1882
        # New buffer for the fingerprint
1840
1883
        buf = ctypes.create_string_buffer(20)
1841
1884
        buf_len = ctypes.c_size_t()
1842
1885
        # Get the fingerprint from the certificate into the buffer
1843
 
        (gnutls.library.functions
1844
 
         .gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1845
 
                                             ctypes.byref(buf_len)))
 
1886
        gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint(
 
1887
            crt, ctypes.byref(buf), ctypes.byref(buf_len))
1846
1888
        # Deinit the certificate
1847
1889
        gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1848
1890
        # Convert the buffer to a Python bytestring
1854
1896
 
1855
1897
class MultiprocessingMixIn(object):
1856
1898
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
 
1899
    
1857
1900
    def sub_process_main(self, request, address):
1858
1901
        try:
1859
1902
            self.finish_request(request, address)
1864
1907
    def process_request(self, request, address):
1865
1908
        """Start a new process to process the request."""
1866
1909
        proc = multiprocessing.Process(target = self.sub_process_main,
1867
 
                                       args = (request,
1868
 
                                               address))
 
1910
                                       args = (request, address))
1869
1911
        proc.start()
1870
1912
        return proc
1871
1913
 
1872
1914
 
1873
1915
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1874
1916
    """ adds a pipe to the MixIn """
 
1917
    
1875
1918
    def process_request(self, request, client_address):
1876
1919
        """Overrides and wraps the original process_request().
1877
1920
        
1886
1929
    
1887
1930
    def add_pipe(self, parent_pipe, proc):
1888
1931
        """Dummy function; override as necessary"""
1889
 
        raise NotImplementedError
 
1932
        raise NotImplementedError()
1890
1933
 
1891
1934
 
1892
1935
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1898
1941
        interface:      None or a network interface name (string)
1899
1942
        use_ipv6:       Boolean; to use IPv6 or not
1900
1943
    """
 
1944
    
1901
1945
    def __init__(self, server_address, RequestHandlerClass,
1902
 
                 interface=None, use_ipv6=True):
 
1946
                 interface=None,
 
1947
                 use_ipv6=True,
 
1948
                 socketfd=None):
 
1949
        """If socketfd is set, use that file descriptor instead of
 
1950
        creating a new one with socket.socket().
 
1951
        """
1903
1952
        self.interface = interface
1904
1953
        if use_ipv6:
1905
1954
            self.address_family = socket.AF_INET6
 
1955
        if socketfd is not None:
 
1956
            # Save the file descriptor
 
1957
            self.socketfd = socketfd
 
1958
            # Save the original socket.socket() function
 
1959
            self.socket_socket = socket.socket
 
1960
            # To implement --socket, we monkey patch socket.socket.
 
1961
            # 
 
1962
            # (When socketserver.TCPServer is a new-style class, we
 
1963
            # could make self.socket into a property instead of monkey
 
1964
            # patching socket.socket.)
 
1965
            # 
 
1966
            # Create a one-time-only replacement for socket.socket()
 
1967
            @functools.wraps(socket.socket)
 
1968
            def socket_wrapper(*args, **kwargs):
 
1969
                # Restore original function so subsequent calls are
 
1970
                # not affected.
 
1971
                socket.socket = self.socket_socket
 
1972
                del self.socket_socket
 
1973
                # This time only, return a new socket object from the
 
1974
                # saved file descriptor.
 
1975
                return socket.fromfd(self.socketfd, *args, **kwargs)
 
1976
            # Replace socket.socket() function with wrapper
 
1977
            socket.socket = socket_wrapper
 
1978
        # The socketserver.TCPServer.__init__ will call
 
1979
        # socket.socket(), which might be our replacement,
 
1980
        # socket_wrapper(), if socketfd was set.
1906
1981
        socketserver.TCPServer.__init__(self, server_address,
1907
1982
                                        RequestHandlerClass)
 
1983
    
1908
1984
    def server_bind(self):
1909
1985
        """This overrides the normal server_bind() function
1910
1986
        to bind to an interface if one was specified, and also NOT to
1916
1992
                             self.interface)
1917
1993
            else:
1918
1994
                try:
1919
 
                    self.socket.setsockopt(socket.SOL_SOCKET,
1920
 
                                           SO_BINDTODEVICE,
1921
 
                                           str(self.interface
1922
 
                                               + '\0'))
 
1995
                    self.socket.setsockopt(
 
1996
                        socket.SOL_SOCKET, SO_BINDTODEVICE,
 
1997
                        (self.interface + "\0").encode("utf-8"))
1923
1998
                except socket.error as error:
1924
 
                    if error[0] == errno.EPERM:
1925
 
                        logger.error("No permission to"
1926
 
                                     " bind to interface %s",
1927
 
                                     self.interface)
1928
 
                    elif error[0] == errno.ENOPROTOOPT:
 
1999
                    if error.errno == errno.EPERM:
 
2000
                        logger.error("No permission to bind to"
 
2001
                                     " interface %s", self.interface)
 
2002
                    elif error.errno == errno.ENOPROTOOPT:
1929
2003
                        logger.error("SO_BINDTODEVICE not available;"
1930
2004
                                     " cannot bind to interface %s",
1931
2005
                                     self.interface)
 
2006
                    elif error.errno == errno.ENODEV:
 
2007
                        logger.error("Interface %s does not exist,"
 
2008
                                     " cannot bind", self.interface)
1932
2009
                    else:
1933
2010
                        raise
1934
2011
        # Only bind(2) the socket if we really need to.
1937
2014
                if self.address_family == socket.AF_INET6:
1938
2015
                    any_address = "::" # in6addr_any
1939
2016
                else:
1940
 
                    any_address = socket.INADDR_ANY
 
2017
                    any_address = "0.0.0.0" # INADDR_ANY
1941
2018
                self.server_address = (any_address,
1942
2019
                                       self.server_address[1])
1943
2020
            elif not self.server_address[1]:
1944
 
                self.server_address = (self.server_address[0],
1945
 
                                       0)
 
2021
                self.server_address = (self.server_address[0], 0)
1946
2022
#                 if self.interface:
1947
2023
#                     self.server_address = (self.server_address[0],
1948
2024
#                                            0, # port
1962
2038
    
1963
2039
    Assumes a gobject.MainLoop event loop.
1964
2040
    """
 
2041
    
1965
2042
    def __init__(self, server_address, RequestHandlerClass,
1966
 
                 interface=None, use_ipv6=True, clients=None,
1967
 
                 gnutls_priority=None, use_dbus=True):
 
2043
                 interface=None,
 
2044
                 use_ipv6=True,
 
2045
                 clients=None,
 
2046
                 gnutls_priority=None,
 
2047
                 use_dbus=True,
 
2048
                 socketfd=None):
1968
2049
        self.enabled = False
1969
2050
        self.clients = clients
1970
2051
        if self.clients is None:
1974
2055
        IPv6_TCPServer.__init__(self, server_address,
1975
2056
                                RequestHandlerClass,
1976
2057
                                interface = interface,
1977
 
                                use_ipv6 = use_ipv6)
 
2058
                                use_ipv6 = use_ipv6,
 
2059
                                socketfd = socketfd)
 
2060
    
1978
2061
    def server_activate(self):
1979
2062
        if self.enabled:
1980
2063
            return socketserver.TCPServer.server_activate(self)
1984
2067
    
1985
2068
    def add_pipe(self, parent_pipe, proc):
1986
2069
        # Call "handle_ipc" for both data and EOF events
1987
 
        gobject.io_add_watch(parent_pipe.fileno(),
1988
 
                             gobject.IO_IN | gobject.IO_HUP,
1989
 
                             functools.partial(self.handle_ipc,
1990
 
                                               parent_pipe =
1991
 
                                               parent_pipe,
1992
 
                                               proc = proc))
 
2070
        gobject.io_add_watch(
 
2071
            parent_pipe.fileno(),
 
2072
            gobject.IO_IN | gobject.IO_HUP,
 
2073
            functools.partial(self.handle_ipc,
 
2074
                              parent_pipe = parent_pipe,
 
2075
                              proc = proc))
1993
2076
    
1994
 
    def handle_ipc(self, source, condition, parent_pipe=None,
1995
 
                   proc = None, client_object=None):
 
2077
    def handle_ipc(self, source, condition,
 
2078
                   parent_pipe=None,
 
2079
                   proc = None,
 
2080
                   client_object=None):
1996
2081
        # error, or the other end of multiprocessing.Pipe has closed
1997
2082
        if condition & (gobject.IO_ERR | gobject.IO_HUP):
1998
2083
            # Wait for other process to exit
2021
2106
                parent_pipe.send(False)
2022
2107
                return False
2023
2108
            
2024
 
            gobject.io_add_watch(parent_pipe.fileno(),
2025
 
                                 gobject.IO_IN | gobject.IO_HUP,
2026
 
                                 functools.partial(self.handle_ipc,
2027
 
                                                   parent_pipe =
2028
 
                                                   parent_pipe,
2029
 
                                                   proc = proc,
2030
 
                                                   client_object =
2031
 
                                                   client))
 
2109
            gobject.io_add_watch(
 
2110
                parent_pipe.fileno(),
 
2111
                gobject.IO_IN | gobject.IO_HUP,
 
2112
                functools.partial(self.handle_ipc,
 
2113
                                  parent_pipe = parent_pipe,
 
2114
                                  proc = proc,
 
2115
                                  client_object = client))
2032
2116
            parent_pipe.send(True)
2033
2117
            # remove the old hook in favor of the new above hook on
2034
2118
            # same fileno
2040
2124
            
2041
2125
            parent_pipe.send(('data', getattr(client_object,
2042
2126
                                              funcname)(*args,
2043
 
                                                         **kwargs)))
 
2127
                                                        **kwargs)))
2044
2128
        
2045
2129
        if command == 'getattr':
2046
2130
            attrname = request[1]
2047
2131
            if callable(client_object.__getattribute__(attrname)):
2048
 
                parent_pipe.send(('function',))
 
2132
                parent_pipe.send(('function', ))
2049
2133
            else:
2050
 
                parent_pipe.send(('data', client_object
2051
 
                                  .__getattribute__(attrname)))
 
2134
                parent_pipe.send((
 
2135
                    'data', client_object.__getattribute__(attrname)))
2052
2136
        
2053
2137
        if command == 'setattr':
2054
2138
            attrname = request[1]
2058
2142
        return True
2059
2143
 
2060
2144
 
 
2145
def rfc3339_duration_to_delta(duration):
 
2146
    """Parse an RFC 3339 "duration" and return a datetime.timedelta
 
2147
    
 
2148
    >>> rfc3339_duration_to_delta("P7D")
 
2149
    datetime.timedelta(7)
 
2150
    >>> rfc3339_duration_to_delta("PT60S")
 
2151
    datetime.timedelta(0, 60)
 
2152
    >>> rfc3339_duration_to_delta("PT60M")
 
2153
    datetime.timedelta(0, 3600)
 
2154
    >>> rfc3339_duration_to_delta("PT24H")
 
2155
    datetime.timedelta(1)
 
2156
    >>> rfc3339_duration_to_delta("P1W")
 
2157
    datetime.timedelta(7)
 
2158
    >>> rfc3339_duration_to_delta("PT5M30S")
 
2159
    datetime.timedelta(0, 330)
 
2160
    >>> rfc3339_duration_to_delta("P1DT3M20S")
 
2161
    datetime.timedelta(1, 200)
 
2162
    """
 
2163
    
 
2164
    # Parsing an RFC 3339 duration with regular expressions is not
 
2165
    # possible - there would have to be multiple places for the same
 
2166
    # values, like seconds.  The current code, while more esoteric, is
 
2167
    # cleaner without depending on a parsing library.  If Python had a
 
2168
    # built-in library for parsing we would use it, but we'd like to
 
2169
    # avoid excessive use of external libraries.
 
2170
    
 
2171
    # New type for defining tokens, syntax, and semantics all-in-one
 
2172
    Token = collections.namedtuple("Token",
 
2173
                                   ("regexp", # To match token; if
 
2174
                                              # "value" is not None,
 
2175
                                              # must have a "group"
 
2176
                                              # containing digits
 
2177
                                    "value",  # datetime.timedelta or
 
2178
                                              # None
 
2179
                                    "followers")) # Tokens valid after
 
2180
                                                  # this token
 
2181
    Token = collections.namedtuple("Token", (
 
2182
        "regexp",  # To match token; if "value" is not None, must have
 
2183
                   # a "group" containing digits
 
2184
        "value",   # datetime.timedelta or None
 
2185
        "followers"))           # Tokens valid after this token
 
2186
    # RFC 3339 "duration" tokens, syntax, and semantics; taken from
 
2187
    # the "duration" ABNF definition in RFC 3339, Appendix A.
 
2188
    token_end = Token(re.compile(r"$"), None, frozenset())
 
2189
    token_second = Token(re.compile(r"(\d+)S"),
 
2190
                         datetime.timedelta(seconds=1),
 
2191
                         frozenset((token_end, )))
 
2192
    token_minute = Token(re.compile(r"(\d+)M"),
 
2193
                         datetime.timedelta(minutes=1),
 
2194
                         frozenset((token_second, token_end)))
 
2195
    token_hour = Token(re.compile(r"(\d+)H"),
 
2196
                       datetime.timedelta(hours=1),
 
2197
                       frozenset((token_minute, token_end)))
 
2198
    token_time = Token(re.compile(r"T"),
 
2199
                       None,
 
2200
                       frozenset((token_hour, token_minute,
 
2201
                                  token_second)))
 
2202
    token_day = Token(re.compile(r"(\d+)D"),
 
2203
                      datetime.timedelta(days=1),
 
2204
                      frozenset((token_time, token_end)))
 
2205
    token_month = Token(re.compile(r"(\d+)M"),
 
2206
                        datetime.timedelta(weeks=4),
 
2207
                        frozenset((token_day, token_end)))
 
2208
    token_year = Token(re.compile(r"(\d+)Y"),
 
2209
                       datetime.timedelta(weeks=52),
 
2210
                       frozenset((token_month, token_end)))
 
2211
    token_week = Token(re.compile(r"(\d+)W"),
 
2212
                       datetime.timedelta(weeks=1),
 
2213
                       frozenset((token_end, )))
 
2214
    token_duration = Token(re.compile(r"P"), None,
 
2215
                           frozenset((token_year, token_month,
 
2216
                                      token_day, token_time,
 
2217
                                      token_week)))
 
2218
    # Define starting values
 
2219
    value = datetime.timedelta() # Value so far
 
2220
    found_token = None
 
2221
    followers = frozenset((token_duration,)) # Following valid tokens
 
2222
    s = duration                # String left to parse
 
2223
    # Loop until end token is found
 
2224
    while found_token is not token_end:
 
2225
        # Search for any currently valid tokens
 
2226
        for token in followers:
 
2227
            match = token.regexp.match(s)
 
2228
            if match is not None:
 
2229
                # Token found
 
2230
                if token.value is not None:
 
2231
                    # Value found, parse digits
 
2232
                    factor = int(match.group(1), 10)
 
2233
                    # Add to value so far
 
2234
                    value += factor * token.value
 
2235
                # Strip token from string
 
2236
                s = token.regexp.sub("", s, 1)
 
2237
                # Go to found token
 
2238
                found_token = token
 
2239
                # Set valid next tokens
 
2240
                followers = found_token.followers
 
2241
                break
 
2242
        else:
 
2243
            # No currently valid tokens were found
 
2244
            raise ValueError("Invalid RFC 3339 duration")
 
2245
    # End token found
 
2246
    return value
 
2247
 
 
2248
 
2061
2249
def string_to_delta(interval):
2062
2250
    """Parse a string and return a datetime.timedelta
2063
2251
    
2074
2262
    >>> string_to_delta('5m 30s')
2075
2263
    datetime.timedelta(0, 330)
2076
2264
    """
 
2265
    
 
2266
    try:
 
2267
        return rfc3339_duration_to_delta(interval)
 
2268
    except ValueError:
 
2269
        pass
 
2270
    
2077
2271
    timevalue = datetime.timedelta(0)
2078
2272
    for s in interval.split():
2079
2273
        try:
2080
 
            suffix = unicode(s[-1])
 
2274
            suffix = s[-1]
2081
2275
            value = int(s[:-1])
2082
2276
            if suffix == "d":
2083
2277
                delta = datetime.timedelta(value)
2090
2284
            elif suffix == "w":
2091
2285
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
2092
2286
            else:
2093
 
                raise ValueError("Unknown suffix {0!r}"
2094
 
                                 .format(suffix))
2095
 
        except (ValueError, IndexError) as e:
 
2287
                raise ValueError("Unknown suffix {!r}".format(suffix))
 
2288
        except IndexError as e:
2096
2289
            raise ValueError(*(e.args))
2097
2290
        timevalue += delta
2098
2291
    return timevalue
2114
2307
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2115
2308
        if not stat.S_ISCHR(os.fstat(null).st_mode):
2116
2309
            raise OSError(errno.ENODEV,
2117
 
                          "{0} not a character device"
 
2310
                          "{} not a character device"
2118
2311
                          .format(os.devnull))
2119
2312
        os.dup2(null, sys.stdin.fileno())
2120
2313
        os.dup2(null, sys.stdout.fileno())
2130
2323
    
2131
2324
    parser = argparse.ArgumentParser()
2132
2325
    parser.add_argument("-v", "--version", action="version",
2133
 
                        version = "%(prog)s {0}".format(version),
 
2326
                        version = "%(prog)s {}".format(version),
2134
2327
                        help="show version number and exit")
2135
2328
    parser.add_argument("-i", "--interface", metavar="IF",
2136
2329
                        help="Bind to interface IF")
2142
2335
                        help="Run self-test")
2143
2336
    parser.add_argument("--debug", action="store_true",
2144
2337
                        help="Debug mode; run in foreground and log"
2145
 
                        " to terminal")
 
2338
                        " to terminal", default=None)
2146
2339
    parser.add_argument("--debuglevel", metavar="LEVEL",
2147
2340
                        help="Debug level for stdout output")
2148
2341
    parser.add_argument("--priority", help="GnuTLS"
2155
2348
                        " files")
2156
2349
    parser.add_argument("--no-dbus", action="store_false",
2157
2350
                        dest="use_dbus", help="Do not provide D-Bus"
2158
 
                        " system bus interface")
 
2351
                        " system bus interface", default=None)
2159
2352
    parser.add_argument("--no-ipv6", action="store_false",
2160
 
                        dest="use_ipv6", help="Do not use IPv6")
 
2353
                        dest="use_ipv6", help="Do not use IPv6",
 
2354
                        default=None)
2161
2355
    parser.add_argument("--no-restore", action="store_false",
2162
2356
                        dest="restore", help="Do not restore stored"
2163
 
                        " state")
 
2357
                        " state", default=None)
 
2358
    parser.add_argument("--socket", type=int,
 
2359
                        help="Specify a file descriptor to a network"
 
2360
                        " socket to use instead of creating one")
2164
2361
    parser.add_argument("--statedir", metavar="DIR",
2165
2362
                        help="Directory to save/restore state in")
 
2363
    parser.add_argument("--foreground", action="store_true",
 
2364
                        help="Run in foreground", default=None)
 
2365
    parser.add_argument("--no-zeroconf", action="store_false",
 
2366
                        dest="zeroconf", help="Do not use Zeroconf",
 
2367
                        default=None)
2166
2368
    
2167
2369
    options = parser.parse_args()
2168
2370
    
2169
2371
    if options.check:
2170
2372
        import doctest
2171
 
        doctest.testmod()
2172
 
        sys.exit()
 
2373
        fail_count, test_count = doctest.testmod()
 
2374
        sys.exit(os.EX_OK if fail_count == 0 else 1)
2173
2375
    
2174
2376
    # Default values for config file for server-global settings
2175
2377
    server_defaults = { "interface": "",
2177
2379
                        "port": "",
2178
2380
                        "debug": "False",
2179
2381
                        "priority":
2180
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
 
2382
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
 
2383
                        ":+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
2181
2384
                        "servicename": "Mandos",
2182
2385
                        "use_dbus": "True",
2183
2386
                        "use_ipv6": "True",
2184
2387
                        "debuglevel": "",
2185
2388
                        "restore": "True",
2186
 
                        "statedir": "/var/lib/mandos"
2187
 
                        }
 
2389
                        "socket": "",
 
2390
                        "statedir": "/var/lib/mandos",
 
2391
                        "foreground": "False",
 
2392
                        "zeroconf": "True",
 
2393
                    }
2188
2394
    
2189
2395
    # Parse config file for server-global settings
2190
2396
    server_config = configparser.SafeConfigParser(server_defaults)
2191
2397
    del server_defaults
2192
 
    server_config.read(os.path.join(options.configdir,
2193
 
                                    "mandos.conf"))
 
2398
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
2194
2399
    # Convert the SafeConfigParser object to a dict
2195
2400
    server_settings = server_config.defaults()
2196
2401
    # Use the appropriate methods on the non-string config options
2197
 
    for option in ("debug", "use_dbus", "use_ipv6"):
 
2402
    for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
2198
2403
        server_settings[option] = server_config.getboolean("DEFAULT",
2199
2404
                                                           option)
2200
2405
    if server_settings["port"]:
2201
2406
        server_settings["port"] = server_config.getint("DEFAULT",
2202
2407
                                                       "port")
 
2408
    if server_settings["socket"]:
 
2409
        server_settings["socket"] = server_config.getint("DEFAULT",
 
2410
                                                         "socket")
 
2411
        # Later, stdin will, and stdout and stderr might, be dup'ed
 
2412
        # over with an opened os.devnull.  But we don't want this to
 
2413
        # happen with a supplied network socket.
 
2414
        if 0 <= server_settings["socket"] <= 2:
 
2415
            server_settings["socket"] = os.dup(server_settings
 
2416
                                               ["socket"])
2203
2417
    del server_config
2204
2418
    
2205
2419
    # Override the settings from the config file with command line
2206
2420
    # options, if set.
2207
2421
    for option in ("interface", "address", "port", "debug",
2208
 
                   "priority", "servicename", "configdir",
2209
 
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
2210
 
                   "statedir"):
 
2422
                   "priority", "servicename", "configdir", "use_dbus",
 
2423
                   "use_ipv6", "debuglevel", "restore", "statedir",
 
2424
                   "socket", "foreground", "zeroconf"):
2211
2425
        value = getattr(options, option)
2212
2426
        if value is not None:
2213
2427
            server_settings[option] = value
2214
2428
    del options
2215
2429
    # Force all strings to be unicode
2216
2430
    for option in server_settings.keys():
2217
 
        if type(server_settings[option]) is str:
2218
 
            server_settings[option] = unicode(server_settings[option])
 
2431
        if isinstance(server_settings[option], bytes):
 
2432
            server_settings[option] = (server_settings[option]
 
2433
                                       .decode("utf-8"))
 
2434
    # Force all boolean options to be boolean
 
2435
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
 
2436
                   "foreground", "zeroconf"):
 
2437
        server_settings[option] = bool(server_settings[option])
 
2438
    # Debug implies foreground
 
2439
    if server_settings["debug"]:
 
2440
        server_settings["foreground"] = True
2219
2441
    # Now we have our good server settings in "server_settings"
2220
2442
    
2221
2443
    ##################################################################
2222
2444
    
 
2445
    if (not server_settings["zeroconf"]
 
2446
        and not (server_settings["port"]
 
2447
                 or server_settings["socket"] != "")):
 
2448
        parser.error("Needs port or socket to work without Zeroconf")
 
2449
    
2223
2450
    # For convenience
2224
2451
    debug = server_settings["debug"]
2225
2452
    debuglevel = server_settings["debuglevel"]
2227
2454
    use_ipv6 = server_settings["use_ipv6"]
2228
2455
    stored_state_path = os.path.join(server_settings["statedir"],
2229
2456
                                     stored_state_file)
 
2457
    foreground = server_settings["foreground"]
 
2458
    zeroconf = server_settings["zeroconf"]
2230
2459
    
2231
2460
    if debug:
2232
2461
        initlogger(debug, logging.DEBUG)
2238
2467
            initlogger(debug, level)
2239
2468
    
2240
2469
    if server_settings["servicename"] != "Mandos":
2241
 
        syslogger.setFormatter(logging.Formatter
2242
 
                               ('Mandos ({0}) [%(process)d]:'
2243
 
                                ' %(levelname)s: %(message)s'
2244
 
                                .format(server_settings
2245
 
                                        ["servicename"])))
 
2470
        syslogger.setFormatter(
 
2471
            logging.Formatter('Mandos ({}) [%(process)d]:'
 
2472
                              ' %(levelname)s: %(message)s'.format(
 
2473
                                  server_settings["servicename"])))
2246
2474
    
2247
2475
    # Parse config file with clients
2248
2476
    client_config = configparser.SafeConfigParser(Client
2253
2481
    global mandos_dbus_service
2254
2482
    mandos_dbus_service = None
2255
2483
    
2256
 
    tcp_server = MandosServer((server_settings["address"],
2257
 
                               server_settings["port"]),
2258
 
                              ClientHandler,
2259
 
                              interface=(server_settings["interface"]
2260
 
                                         or None),
2261
 
                              use_ipv6=use_ipv6,
2262
 
                              gnutls_priority=
2263
 
                              server_settings["priority"],
2264
 
                              use_dbus=use_dbus)
2265
 
    if not debug:
2266
 
        pidfilename = "/var/run/mandos.pid"
 
2484
    socketfd = None
 
2485
    if server_settings["socket"] != "":
 
2486
        socketfd = server_settings["socket"]
 
2487
    tcp_server = MandosServer(
 
2488
        (server_settings["address"], server_settings["port"]),
 
2489
        ClientHandler,
 
2490
        interface=(server_settings["interface"] or None),
 
2491
        use_ipv6=use_ipv6,
 
2492
        gnutls_priority=server_settings["priority"],
 
2493
        use_dbus=use_dbus,
 
2494
        socketfd=socketfd)
 
2495
    if not foreground:
 
2496
        pidfilename = "/run/mandos.pid"
 
2497
        if not os.path.isdir("/run/."):
 
2498
            pidfilename = "/var/run/mandos.pid"
 
2499
        pidfile = None
2267
2500
        try:
2268
2501
            pidfile = open(pidfilename, "w")
2269
2502
        except IOError as e:
2284
2517
        os.setgid(gid)
2285
2518
        os.setuid(uid)
2286
2519
    except OSError as error:
2287
 
        if error[0] != errno.EPERM:
2288
 
            raise error
 
2520
        if error.errno != errno.EPERM:
 
2521
            raise
2289
2522
    
2290
2523
    if debug:
2291
2524
        # Enable all possible GnuTLS debugging
2298
2531
        def debug_gnutls(level, string):
2299
2532
            logger.debug("GnuTLS: %s", string[:-1])
2300
2533
        
2301
 
        (gnutls.library.functions
2302
 
         .gnutls_global_set_log_function(debug_gnutls))
 
2534
        gnutls.library.functions.gnutls_global_set_log_function(
 
2535
            debug_gnutls)
2303
2536
        
2304
2537
        # Redirect stdin so all checkers get /dev/null
2305
2538
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2308
2541
            os.close(null)
2309
2542
    
2310
2543
    # Need to fork before connecting to D-Bus
2311
 
    if not debug:
 
2544
    if not foreground:
2312
2545
        # Close all input and output, do double fork, etc.
2313
2546
        daemon()
2314
2547
    
 
2548
    # multiprocessing will use threads, so before we use gobject we
 
2549
    # need to inform gobject that threads will be used.
2315
2550
    gobject.threads_init()
2316
2551
    
2317
2552
    global main_loop
2323
2558
    if use_dbus:
2324
2559
        try:
2325
2560
            bus_name = dbus.service.BusName("se.recompile.Mandos",
2326
 
                                            bus, do_not_queue=True)
2327
 
            old_bus_name = (dbus.service.BusName
2328
 
                            ("se.bsnet.fukt.Mandos", bus,
2329
 
                             do_not_queue=True))
 
2561
                                            bus,
 
2562
                                            do_not_queue=True)
 
2563
            old_bus_name = dbus.service.BusName(
 
2564
                "se.bsnet.fukt.Mandos", bus,
 
2565
                do_not_queue=True)
2330
2566
        except dbus.exceptions.NameExistsException as e:
2331
2567
            logger.error("Disabling D-Bus:", exc_info=e)
2332
2568
            use_dbus = False
2333
2569
            server_settings["use_dbus"] = False
2334
2570
            tcp_server.use_dbus = False
2335
 
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2336
 
    service = AvahiServiceToSyslog(name =
2337
 
                                   server_settings["servicename"],
2338
 
                                   servicetype = "_mandos._tcp",
2339
 
                                   protocol = protocol, bus = bus)
2340
 
    if server_settings["interface"]:
2341
 
        service.interface = (if_nametoindex
2342
 
                             (str(server_settings["interface"])))
 
2571
    if zeroconf:
 
2572
        protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
 
2573
        service = AvahiServiceToSyslog(
 
2574
            name = server_settings["servicename"],
 
2575
            servicetype = "_mandos._tcp",
 
2576
            protocol = protocol,
 
2577
            bus = bus)
 
2578
        if server_settings["interface"]:
 
2579
            service.interface = if_nametoindex(
 
2580
                server_settings["interface"].encode("utf-8"))
2343
2581
    
2344
2582
    global multiprocessing_manager
2345
2583
    multiprocessing_manager = multiprocessing.Manager()
2352
2590
    old_client_settings = {}
2353
2591
    clients_data = {}
2354
2592
    
 
2593
    # This is used to redirect stdout and stderr for checker processes
 
2594
    global wnull
 
2595
    wnull = open(os.devnull, "w") # A writable /dev/null
 
2596
    # Only used if server is running in foreground but not in debug
 
2597
    # mode
 
2598
    if debug or not foreground:
 
2599
        wnull.close()
 
2600
    
2355
2601
    # Get client data and settings from last running state.
2356
2602
    if server_settings["restore"]:
2357
2603
        try:
2358
2604
            with open(stored_state_path, "rb") as stored_state:
2359
 
                clients_data, old_client_settings = (pickle.load
2360
 
                                                     (stored_state))
 
2605
                clients_data, old_client_settings = pickle.load(
 
2606
                    stored_state)
2361
2607
            os.remove(stored_state_path)
2362
2608
        except IOError as e:
2363
2609
            if e.errno == errno.ENOENT:
2364
 
                logger.warning("Could not load persistent state: {0}"
2365
 
                                .format(os.strerror(e.errno)))
 
2610
                logger.warning("Could not load persistent state:"
 
2611
                               " {}".format(os.strerror(e.errno)))
2366
2612
            else:
2367
2613
                logger.critical("Could not load persistent state:",
2368
2614
                                exc_info=e)
2369
2615
                raise
2370
2616
        except EOFError as e:
2371
2617
            logger.warning("Could not load persistent state: "
2372
 
                           "EOFError:", exc_info=e)
 
2618
                           "EOFError:",
 
2619
                           exc_info=e)
2373
2620
    
2374
2621
    with PGPEngine() as pgp:
2375
 
        for client_name, client in clients_data.iteritems():
 
2622
        for client_name, client in clients_data.items():
 
2623
            # Skip removed clients
 
2624
            if client_name not in client_settings:
 
2625
                continue
 
2626
            
2376
2627
            # Decide which value to use after restoring saved state.
2377
2628
            # We have three different values: Old config file,
2378
2629
            # new config file, and saved state.
2383
2634
                    # For each value in new config, check if it
2384
2635
                    # differs from the old config value (Except for
2385
2636
                    # the "secret" attribute)
2386
 
                    if (name != "secret" and
2387
 
                        value != old_client_settings[client_name]
2388
 
                        [name]):
 
2637
                    if (name != "secret"
 
2638
                        and (value !=
 
2639
                             old_client_settings[client_name][name])):
2389
2640
                        client[name] = value
2390
2641
                except KeyError:
2391
2642
                    pass
2399
2650
                if datetime.datetime.utcnow() >= client["expires"]:
2400
2651
                    if not client["last_checked_ok"]:
2401
2652
                        logger.warning(
2402
 
                            "disabling client {0} - Client never "
2403
 
                            "performed a successful checker"
2404
 
                            .format(client_name))
 
2653
                            "disabling client {} - Client never "
 
2654
                            "performed a successful checker".format(
 
2655
                                client_name))
2405
2656
                        client["enabled"] = False
2406
2657
                    elif client["last_checker_status"] != 0:
2407
2658
                        logger.warning(
2408
 
                            "disabling client {0} - Client "
2409
 
                            "last checker failed with error code {1}"
2410
 
                            .format(client_name,
2411
 
                                    client["last_checker_status"]))
 
2659
                            "disabling client {} - Client last"
 
2660
                            " checker failed with error code"
 
2661
                            " {}".format(
 
2662
                                client_name,
 
2663
                                client["last_checker_status"]))
2412
2664
                        client["enabled"] = False
2413
2665
                    else:
2414
 
                        client["expires"] = (datetime.datetime
2415
 
                                             .utcnow()
2416
 
                                             + client["timeout"])
 
2666
                        client["expires"] = (
 
2667
                            datetime.datetime.utcnow()
 
2668
                            + client["timeout"])
2417
2669
                        logger.debug("Last checker succeeded,"
2418
 
                                     " keeping {0} enabled"
2419
 
                                     .format(client_name))
 
2670
                                     " keeping {} enabled".format(
 
2671
                                         client_name))
2420
2672
            try:
2421
 
                client["secret"] = (
2422
 
                    pgp.decrypt(client["encrypted_secret"],
2423
 
                                client_settings[client_name]
2424
 
                                ["secret"]))
 
2673
                client["secret"] = pgp.decrypt(
 
2674
                    client["encrypted_secret"],
 
2675
                    client_settings[client_name]["secret"])
2425
2676
            except PGPError:
2426
2677
                # If decryption fails, we use secret from new settings
2427
 
                logger.debug("Failed to decrypt {0} old secret"
2428
 
                             .format(client_name))
2429
 
                client["secret"] = (
2430
 
                    client_settings[client_name]["secret"])
 
2678
                logger.debug("Failed to decrypt {} old secret".format(
 
2679
                    client_name))
 
2680
                client["secret"] = (client_settings[client_name]
 
2681
                                    ["secret"])
2431
2682
    
2432
2683
    # Add/remove clients based on new changes made to config
2433
2684
    for client_name in (set(old_client_settings)
2438
2689
        clients_data[client_name] = client_settings[client_name]
2439
2690
    
2440
2691
    # Create all client objects
2441
 
    for client_name, client in clients_data.iteritems():
 
2692
    for client_name, client in clients_data.items():
2442
2693
        tcp_server.clients[client_name] = client_class(
2443
 
            name = client_name, settings = client)
 
2694
            name = client_name,
 
2695
            settings = client,
 
2696
            server_settings = server_settings)
2444
2697
    
2445
2698
    if not tcp_server.clients:
2446
2699
        logger.warning("No clients defined")
2447
2700
    
2448
 
    if not debug:
2449
 
        try:
2450
 
            with pidfile:
2451
 
                pid = os.getpid()
2452
 
                pidfile.write(str(pid) + "\n".encode("utf-8"))
2453
 
            del pidfile
2454
 
        except IOError:
2455
 
            logger.error("Could not write to file %r with PID %d",
2456
 
                         pidfilename, pid)
2457
 
        except NameError:
2458
 
            # "pidfile" was never created
2459
 
            pass
 
2701
    if not foreground:
 
2702
        if pidfile is not None:
 
2703
            try:
 
2704
                with pidfile:
 
2705
                    pid = os.getpid()
 
2706
                    pidfile.write("{}\n".format(pid).encode("utf-8"))
 
2707
            except IOError:
 
2708
                logger.error("Could not write to file %r with PID %d",
 
2709
                             pidfilename, pid)
 
2710
        del pidfile
2460
2711
        del pidfilename
2461
 
        signal.signal(signal.SIGINT, signal.SIG_IGN)
2462
2712
    
2463
2713
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2464
2714
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2465
2715
    
2466
2716
    if use_dbus:
2467
 
        @alternate_dbus_interfaces({"se.recompile.Mandos":
2468
 
                                        "se.bsnet.fukt.Mandos"})
 
2717
        
 
2718
        @alternate_dbus_interfaces(
 
2719
            { "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
2469
2720
        class MandosDBusService(DBusObjectWithProperties):
2470
2721
            """A D-Bus proxy object"""
 
2722
            
2471
2723
            def __init__(self):
2472
2724
                dbus.service.Object.__init__(self, bus, "/")
 
2725
            
2473
2726
            _interface = "se.recompile.Mandos"
2474
2727
            
2475
2728
            @dbus_interface_annotations(_interface)
2476
2729
            def _foo(self):
2477
 
                return { "org.freedesktop.DBus.Property"
2478
 
                         ".EmitsChangedSignal":
2479
 
                             "false"}
 
2730
                return {
 
2731
                    "org.freedesktop.DBus.Property.EmitsChangedSignal":
 
2732
                    "false" }
2480
2733
            
2481
2734
            @dbus.service.signal(_interface, signature="o")
2482
2735
            def ClientAdded(self, objpath):
2496
2749
            @dbus.service.method(_interface, out_signature="ao")
2497
2750
            def GetAllClients(self):
2498
2751
                "D-Bus method"
2499
 
                return dbus.Array(c.dbus_object_path
2500
 
                                  for c in
 
2752
                return dbus.Array(c.dbus_object_path for c in
2501
2753
                                  tcp_server.clients.itervalues())
2502
2754
            
2503
2755
            @dbus.service.method(_interface,
2505
2757
            def GetAllClientsWithProperties(self):
2506
2758
                "D-Bus method"
2507
2759
                return dbus.Dictionary(
2508
 
                    ((c.dbus_object_path, c.GetAll(""))
2509
 
                     for c in tcp_server.clients.itervalues()),
 
2760
                    { c.dbus_object_path: c.GetAll("")
 
2761
                      for c in tcp_server.clients.itervalues() },
2510
2762
                    signature="oa{sv}")
2511
2763
            
2512
2764
            @dbus.service.method(_interface, in_signature="o")
2529
2781
    
2530
2782
    def cleanup():
2531
2783
        "Cleanup function; run on exit"
2532
 
        service.cleanup()
 
2784
        if zeroconf:
 
2785
            service.cleanup()
2533
2786
        
2534
2787
        multiprocessing.active_children()
 
2788
        wnull.close()
2535
2789
        if not (tcp_server.clients or client_settings):
2536
2790
            return
2537
2791
        
2548
2802
                
2549
2803
                # A list of attributes that can not be pickled
2550
2804
                # + secret.
2551
 
                exclude = set(("bus", "changedstate", "secret",
2552
 
                               "checker"))
2553
 
                for name, typ in (inspect.getmembers
2554
 
                                  (dbus.service.Object)):
 
2805
                exclude = { "bus", "changedstate", "secret",
 
2806
                            "checker", "server_settings" }
 
2807
                for name, typ in inspect.getmembers(dbus.service
 
2808
                                                    .Object):
2555
2809
                    exclude.add(name)
2556
2810
                
2557
2811
                client_dict["encrypted_secret"] = (client
2564
2818
                del client_settings[client.name]["secret"]
2565
2819
        
2566
2820
        try:
2567
 
            tempfd, tempname = tempfile.mkstemp(suffix=".pickle",
2568
 
                                                prefix="clients-",
2569
 
                                                dir=os.path.dirname
2570
 
                                                (stored_state_path))
2571
 
            with os.fdopen(tempfd, "wb") as stored_state:
 
2821
            with tempfile.NamedTemporaryFile(
 
2822
                    mode='wb',
 
2823
                    suffix=".pickle",
 
2824
                    prefix='clients-',
 
2825
                    dir=os.path.dirname(stored_state_path),
 
2826
                    delete=False) as stored_state:
2572
2827
                pickle.dump((clients, client_settings), stored_state)
 
2828
                tempname = stored_state.name
2573
2829
            os.rename(tempname, stored_state_path)
2574
2830
        except (IOError, OSError) as e:
2575
2831
            if not debug:
2578
2834
                except NameError:
2579
2835
                    pass
2580
2836
            if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
2581
 
                logger.warning("Could not save persistent state: {0}"
 
2837
                logger.warning("Could not save persistent state: {}"
2582
2838
                               .format(os.strerror(e.errno)))
2583
2839
            else:
2584
2840
                logger.warning("Could not save persistent state:",
2585
2841
                               exc_info=e)
2586
 
                raise e
 
2842
                raise
2587
2843
        
2588
2844
        # Delete all clients, and settings from config
2589
2845
        while tcp_server.clients:
2594
2850
            client.disable(quiet=True)
2595
2851
            if use_dbus:
2596
2852
                # Emit D-Bus signal
2597
 
                mandos_dbus_service.ClientRemoved(client
2598
 
                                                  .dbus_object_path,
2599
 
                                                  client.name)
 
2853
                mandos_dbus_service.ClientRemoved(
 
2854
                    client.dbus_object_path, client.name)
2600
2855
        client_settings.clear()
2601
2856
    
2602
2857
    atexit.register(cleanup)
2613
2868
    tcp_server.server_activate()
2614
2869
    
2615
2870
    # Find out what port we got
2616
 
    service.port = tcp_server.socket.getsockname()[1]
 
2871
    if zeroconf:
 
2872
        service.port = tcp_server.socket.getsockname()[1]
2617
2873
    if use_ipv6:
2618
2874
        logger.info("Now listening on address %r, port %d,"
2619
2875
                    " flowinfo %d, scope_id %d",
2625
2881
    #service.interface = tcp_server.socket.getsockname()[3]
2626
2882
    
2627
2883
    try:
2628
 
        # From the Avahi example code
2629
 
        try:
2630
 
            service.activate()
2631
 
        except dbus.exceptions.DBusException as error:
2632
 
            logger.critical("D-Bus Exception", exc_info=error)
2633
 
            cleanup()
2634
 
            sys.exit(1)
2635
 
        # End of Avahi example code
 
2884
        if zeroconf:
 
2885
            # From the Avahi example code
 
2886
            try:
 
2887
                service.activate()
 
2888
            except dbus.exceptions.DBusException as error:
 
2889
                logger.critical("D-Bus Exception", exc_info=error)
 
2890
                cleanup()
 
2891
                sys.exit(1)
 
2892
            # End of Avahi example code
2636
2893
        
2637
2894
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2638
2895
                             lambda *args, **kwargs:
2653
2910
    # Must run before the D-Bus bus name gets deregistered
2654
2911
    cleanup()
2655
2912
 
 
2913
 
2656
2914
if __name__ == '__main__':
2657
2915
    main()