/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: 2022-04-25 18:28:52 UTC
  • Revision ID: teddy@recompile.se-20220425182852-5y9a2b9oy9rm1ipf
From: Camaleón <noelamac@gmail.com>

Add Spanish debconf translation

* debian/po/es.po: New

Acked-by: Teddy Hogeborn <teddy@recompile.se>

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#!/usr/bin/python3 -bI
2
 
# -*- mode: python; after-save-hook: (lambda () (let ((command (if (fboundp 'file-local-name) (file-local-name (buffer-file-name)) (or (file-remote-p (buffer-file-name) 'localname) (buffer-file-name))))) (if (= (progn (if (get-buffer "*Test*") (kill-buffer "*Test*")) (process-file-shell-command (format "%s --check" (shell-quote-argument command)) nil "*Test*")) 0) (let ((w (get-buffer-window "*Test*"))) (if w (delete-window w))) (progn (with-current-buffer "*Test*" (compilation-mode)) (display-buffer "*Test*" '(display-buffer-in-side-window)))))); coding: utf-8 -*-
 
2
# -*- coding: utf-8; lexical-binding: t -*-
3
3
#
4
4
# Mandos server - give out binary blobs to connecting clients.
5
5
#
11
11
# "AvahiService" class, and some lines in "main".
12
12
#
13
13
# Everything else is
14
 
# Copyright © 2008-2019 Teddy Hogeborn
15
 
# Copyright © 2008-2019 Björn Påhlsson
 
14
# Copyright © 2008-2022 Teddy Hogeborn
 
15
# Copyright © 2008-2022 Björn Påhlsson
16
16
#
17
17
# This file is part of Mandos.
18
18
#
31
31
#
32
32
# Contact the authors at <mandos@recompile.se>.
33
33
#
34
 
 
35
34
from __future__ import (division, absolute_import, print_function,
36
35
                        unicode_literals)
37
36
 
40
39
except ImportError:
41
40
    pass
42
41
 
 
42
import sys
 
43
import unittest
 
44
import argparse
 
45
import logging
 
46
import os
43
47
try:
44
48
    import SocketServer as socketserver
45
49
except ImportError:
46
50
    import socketserver
47
51
import socket
48
 
import argparse
49
52
import datetime
50
53
import errno
51
54
try:
52
55
    import ConfigParser as configparser
53
56
except ImportError:
54
57
    import configparser
55
 
import sys
56
58
import re
57
 
import os
58
59
import signal
59
60
import subprocess
60
61
import atexit
61
62
import stat
62
 
import logging
63
63
import logging.handlers
64
64
import pwd
65
65
import contextlib
77
77
import itertools
78
78
import collections
79
79
import codecs
80
 
import unittest
81
80
import random
 
81
import shlex
82
82
 
83
83
import dbus
84
84
import dbus.service
93
93
if sys.version_info.major == 2:
94
94
    __metaclass__ = type
95
95
    str = unicode
 
96
    input = raw_input
 
97
 
 
98
# Add collections.abc.Callable if it does not exist
 
99
try:
 
100
    collections.abc.Callable
 
101
except AttributeError:
 
102
    class abc:
 
103
        Callable = collections.Callable
 
104
    collections.abc = abc
 
105
    del abc
 
106
 
 
107
# Add shlex.quote if it does not exist
 
108
try:
 
109
    shlex.quote
 
110
except AttributeError:
 
111
    shlex.quote = re.escape
96
112
 
97
113
# Show warnings by default
98
114
if not sys.warnoptions:
127
143
if sys.version_info < (3, 2):
128
144
    configparser.Configparser = configparser.SafeConfigParser
129
145
 
130
 
version = "1.8.9"
 
146
version = "1.8.14"
131
147
stored_state_file = "clients.pickle"
132
148
 
133
 
logger = logging.getLogger()
 
149
log = logging.getLogger(os.path.basename(sys.argv[0]))
134
150
logging.captureWarnings(True)   # Show warnings via the logging system
135
151
syslogger = None
136
152
 
173
189
        facility=logging.handlers.SysLogHandler.LOG_DAEMON,
174
190
        address="/dev/log"))
175
191
    syslogger.setFormatter(logging.Formatter
176
 
                           ('Mandos [%(process)d]: %(levelname)s:'
177
 
                            ' %(message)s'))
178
 
    logger.addHandler(syslogger)
 
192
                           ("Mandos [%(process)d]: %(levelname)s:"
 
193
                            " %(message)s"))
 
194
    log.addHandler(syslogger)
179
195
 
180
196
    if debug:
181
197
        console = logging.StreamHandler()
182
 
        console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
183
 
                                               ' [%(process)d]:'
184
 
                                               ' %(levelname)s:'
185
 
                                               ' %(message)s'))
186
 
        logger.addHandler(console)
187
 
    logger.setLevel(level)
 
198
        console.setFormatter(logging.Formatter("%(asctime)s %(name)s"
 
199
                                               " [%(process)d]:"
 
200
                                               " %(levelname)s:"
 
201
                                               " %(message)s"))
 
202
        log.addHandler(console)
 
203
    log.setLevel(level)
188
204
 
189
205
 
190
206
class PGPError(Exception):
208
224
        except OSError as e:
209
225
            if e.errno != errno.ENOENT:
210
226
                raise
211
 
        self.gnupgargs = ['--batch',
212
 
                          '--homedir', self.tempdir,
213
 
                          '--force-mdc',
214
 
                          '--quiet']
 
227
        self.gnupgargs = ["--batch",
 
228
                          "--homedir", self.tempdir,
 
229
                          "--force-mdc",
 
230
                          "--quiet"]
215
231
        # Only GPG version 1 has the --no-use-agent option.
216
232
        if self.gpg == b"gpg" or self.gpg.endswith(b"/gpg"):
217
233
            self.gnupgargs.append("--no-use-agent")
256
272
                dir=self.tempdir) as passfile:
257
273
            passfile.write(passphrase)
258
274
            passfile.flush()
259
 
            proc = subprocess.Popen([self.gpg, '--symmetric',
260
 
                                     '--passphrase-file',
 
275
            proc = subprocess.Popen([self.gpg, "--symmetric",
 
276
                                     "--passphrase-file",
261
277
                                     passfile.name]
262
278
                                    + self.gnupgargs,
263
279
                                    stdin=subprocess.PIPE,
274
290
                dir=self.tempdir) as passfile:
275
291
            passfile.write(passphrase)
276
292
            passfile.flush()
277
 
            proc = subprocess.Popen([self.gpg, '--decrypt',
278
 
                                     '--passphrase-file',
 
293
            proc = subprocess.Popen([self.gpg, "--decrypt",
 
294
                                     "--passphrase-file",
279
295
                                     passfile.name]
280
296
                                    + self.gnupgargs,
281
297
                                    stdin=subprocess.PIPE,
334
350
    Attributes:
335
351
    interface: integer; avahi.IF_UNSPEC or an interface index.
336
352
               Used to optionally bind to the specified interface.
337
 
    name: string; Example: 'Mandos'
338
 
    type: string; Example: '_mandos._tcp'.
 
353
    name: string; Example: "Mandos"
 
354
    type: string; Example: "_mandos._tcp".
339
355
     See <https://www.iana.org/assignments/service-names-port-numbers>
340
356
    port: integer; what port to announce
341
357
    TXT: list of strings; TXT record for the service
378
394
    def rename(self, remove=True):
379
395
        """Derived from the Avahi example code"""
380
396
        if self.rename_count >= self.max_renames:
381
 
            logger.critical("No suitable Zeroconf service name found"
382
 
                            " after %i retries, exiting.",
383
 
                            self.rename_count)
 
397
            log.critical("No suitable Zeroconf service name found"
 
398
                         " after %i retries, exiting.",
 
399
                         self.rename_count)
384
400
            raise AvahiServiceError("Too many renames")
385
401
        self.name = str(
386
402
            self.server.GetAlternativeServiceName(self.name))
387
403
        self.rename_count += 1
388
 
        logger.info("Changing Zeroconf service name to %r ...",
389
 
                    self.name)
 
404
        log.info("Changing Zeroconf service name to %r ...",
 
405
                 self.name)
390
406
        if remove:
391
407
            self.remove()
392
408
        try:
394
410
        except dbus.exceptions.DBusException as error:
395
411
            if (error.get_dbus_name()
396
412
                == "org.freedesktop.Avahi.CollisionError"):
397
 
                logger.info("Local Zeroconf service name collision.")
 
413
                log.info("Local Zeroconf service name collision.")
398
414
                return self.rename(remove=False)
399
415
            else:
400
 
                logger.critical("D-Bus Exception", exc_info=error)
 
416
                log.critical("D-Bus Exception", exc_info=error)
401
417
                self.cleanup()
402
418
                os._exit(1)
403
419
 
419
435
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
420
436
        self.entry_group_state_changed_match = (
421
437
            self.group.connect_to_signal(
422
 
                'StateChanged', self.entry_group_state_changed))
423
 
        logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
424
 
                     self.name, self.type)
 
438
                "StateChanged", self.entry_group_state_changed))
 
439
        log.debug("Adding Zeroconf service '%s' of type '%s' ...",
 
440
                  self.name, self.type)
425
441
        self.group.AddService(
426
442
            self.interface,
427
443
            self.protocol,
434
450
 
435
451
    def entry_group_state_changed(self, state, error):
436
452
        """Derived from the Avahi example code"""
437
 
        logger.debug("Avahi entry group state change: %i", state)
 
453
        log.debug("Avahi entry group state change: %i", state)
438
454
 
439
455
        if state == avahi.ENTRY_GROUP_ESTABLISHED:
440
 
            logger.debug("Zeroconf service established.")
 
456
            log.debug("Zeroconf service established.")
441
457
        elif state == avahi.ENTRY_GROUP_COLLISION:
442
 
            logger.info("Zeroconf service name collision.")
 
458
            log.info("Zeroconf service name collision.")
443
459
            self.rename()
444
460
        elif state == avahi.ENTRY_GROUP_FAILURE:
445
 
            logger.critical("Avahi: Error in group state changed %s",
446
 
                            str(error))
 
461
            log.critical("Avahi: Error in group state changed %s",
 
462
                         str(error))
447
463
            raise AvahiGroupError("State changed: {!s}".format(error))
448
464
 
449
465
    def cleanup(self):
459
475
 
460
476
    def server_state_changed(self, state, error=None):
461
477
        """Derived from the Avahi example code"""
462
 
        logger.debug("Avahi server state change: %i", state)
 
478
        log.debug("Avahi server state change: %i", state)
463
479
        bad_states = {
464
480
            avahi.SERVER_INVALID: "Zeroconf server invalid",
465
481
            avahi.SERVER_REGISTERING: None,
469
485
        if state in bad_states:
470
486
            if bad_states[state] is not None:
471
487
                if error is None:
472
 
                    logger.error(bad_states[state])
 
488
                    log.error(bad_states[state])
473
489
                else:
474
 
                    logger.error(bad_states[state] + ": %r", error)
 
490
                    log.error(bad_states[state] + ": %r", error)
475
491
            self.cleanup()
476
492
        elif state == avahi.SERVER_RUNNING:
477
493
            try:
479
495
            except dbus.exceptions.DBusException as error:
480
496
                if (error.get_dbus_name()
481
497
                    == "org.freedesktop.Avahi.CollisionError"):
482
 
                    logger.info("Local Zeroconf service name"
483
 
                                " collision.")
 
498
                    log.info("Local Zeroconf service name collision.")
484
499
                    return self.rename(remove=False)
485
500
                else:
486
 
                    logger.critical("D-Bus Exception", exc_info=error)
 
501
                    log.critical("D-Bus Exception", exc_info=error)
487
502
                    self.cleanup()
488
503
                    os._exit(1)
489
504
        else:
490
505
            if error is None:
491
 
                logger.debug("Unknown state: %r", state)
 
506
                log.debug("Unknown state: %r", state)
492
507
            else:
493
 
                logger.debug("Unknown state: %r: %r", state, error)
 
508
                log.debug("Unknown state: %r: %r", state, error)
494
509
 
495
510
    def activate(self):
496
511
        """Derived from the Avahi example code"""
508
523
class AvahiServiceToSyslog(AvahiService):
509
524
    def rename(self, *args, **kwargs):
510
525
        """Add the new name to the syslog messages"""
511
 
        ret = super(AvahiServiceToSyslog, self).rename(*args, **kwargs)
 
526
        ret = super(AvahiServiceToSyslog, self).rename(*args,
 
527
                                                       **kwargs)
512
528
        syslogger.setFormatter(logging.Formatter(
513
 
            'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
 
529
            "Mandos ({}) [%(process)d]: %(levelname)s: %(message)s"
514
530
            .format(self.name)))
515
531
        return ret
516
532
 
546
562
    OPENPGP_FMT_RAW = 0         # gnutls/openpgp.h
547
563
 
548
564
    # Types
549
 
    class session_int(ctypes.Structure):
 
565
    class _session_int(ctypes.Structure):
550
566
        _fields_ = []
551
 
    session_t = ctypes.POINTER(session_int)
 
567
    session_t = ctypes.POINTER(_session_int)
552
568
 
553
569
    class certificate_credentials_st(ctypes.Structure):
554
570
        _fields_ = []
557
573
    certificate_type_t = ctypes.c_int
558
574
 
559
575
    class datum_t(ctypes.Structure):
560
 
        _fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
561
 
                    ('size', ctypes.c_uint)]
 
576
        _fields_ = [("data", ctypes.POINTER(ctypes.c_ubyte)),
 
577
                    ("size", ctypes.c_uint)]
562
578
 
563
 
    class openpgp_crt_int(ctypes.Structure):
 
579
    class _openpgp_crt_int(ctypes.Structure):
564
580
        _fields_ = []
565
 
    openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
 
581
    openpgp_crt_t = ctypes.POINTER(_openpgp_crt_int)
566
582
    openpgp_crt_fmt_t = ctypes.c_int  # gnutls/openpgp.h
567
583
    log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
568
584
    credentials_type_t = ctypes.c_int
577
593
            # gnutls.strerror()
578
594
            self.code = code
579
595
            if message is None and code is not None:
580
 
                message = gnutls.strerror(code)
 
596
                message = gnutls.strerror(code).decode(
 
597
                    "utf-8", errors="replace")
581
598
            return super(gnutls.Error, self).__init__(
582
599
                message, *args)
583
600
 
584
601
    class CertificateSecurityError(Error):
585
602
        pass
586
603
 
 
604
    class PointerTo:
 
605
        def __init__(self, cls):
 
606
            self.cls = cls
 
607
 
 
608
        def from_param(self, obj):
 
609
            if not isinstance(obj, self.cls):
 
610
                raise TypeError("Not of type {}: {!r}"
 
611
                                .format(self.cls.__name__, obj))
 
612
            return ctypes.byref(obj.from_param(obj))
 
613
 
 
614
    class CastToVoidPointer:
 
615
        def __init__(self, cls):
 
616
            self.cls = cls
 
617
 
 
618
        def from_param(self, obj):
 
619
            if not isinstance(obj, self.cls):
 
620
                raise TypeError("Not of type {}: {!r}"
 
621
                                .format(self.cls.__name__, obj))
 
622
            return ctypes.cast(obj.from_param(obj), ctypes.c_void_p)
 
623
 
 
624
    class With_from_param:
 
625
        @classmethod
 
626
        def from_param(cls, obj):
 
627
            return obj._as_parameter_
 
628
 
587
629
    # Classes
588
 
    class Credentials:
 
630
    class Credentials(With_from_param):
589
631
        def __init__(self):
590
 
            self._c_object = gnutls.certificate_credentials_t()
591
 
            gnutls.certificate_allocate_credentials(
592
 
                ctypes.byref(self._c_object))
 
632
            self._as_parameter_ = gnutls.certificate_credentials_t()
 
633
            gnutls.certificate_allocate_credentials(self)
593
634
            self.type = gnutls.CRD_CERTIFICATE
594
635
 
595
636
        def __del__(self):
596
 
            gnutls.certificate_free_credentials(self._c_object)
 
637
            gnutls.certificate_free_credentials(self)
597
638
 
598
 
    class ClientSession:
 
639
    class ClientSession(With_from_param):
599
640
        def __init__(self, socket, credentials=None):
600
 
            self._c_object = gnutls.session_t()
 
641
            self._as_parameter_ = gnutls.session_t()
601
642
            gnutls_flags = gnutls.CLIENT
602
643
            if gnutls.check_version(b"3.5.6"):
603
644
                gnutls_flags |= gnutls.NO_TICKETS
604
645
            if gnutls.has_rawpk:
605
646
                gnutls_flags |= gnutls.ENABLE_RAWPK
606
 
            gnutls.init(ctypes.byref(self._c_object), gnutls_flags)
 
647
            gnutls.init(self, gnutls_flags)
607
648
            del gnutls_flags
608
 
            gnutls.set_default_priority(self._c_object)
609
 
            gnutls.transport_set_ptr(self._c_object, socket.fileno())
610
 
            gnutls.handshake_set_private_extensions(self._c_object,
611
 
                                                    True)
 
649
            gnutls.set_default_priority(self)
 
650
            gnutls.transport_set_ptr(self, socket.fileno())
 
651
            gnutls.handshake_set_private_extensions(self, True)
612
652
            self.socket = socket
613
653
            if credentials is None:
614
654
                credentials = gnutls.Credentials()
615
 
            gnutls.credentials_set(self._c_object, credentials.type,
616
 
                                   ctypes.cast(credentials._c_object,
617
 
                                               ctypes.c_void_p))
 
655
            gnutls.credentials_set(self, credentials.type,
 
656
                                   credentials)
618
657
            self.credentials = credentials
619
658
 
620
659
        def __del__(self):
621
 
            gnutls.deinit(self._c_object)
 
660
            gnutls.deinit(self)
622
661
 
623
662
        def handshake(self):
624
 
            return gnutls.handshake(self._c_object)
 
663
            return gnutls.handshake(self)
625
664
 
626
665
        def send(self, data):
627
666
            data = bytes(data)
628
667
            data_len = len(data)
629
668
            while data_len > 0:
630
 
                data_len -= gnutls.record_send(self._c_object,
631
 
                                               data[-data_len:],
 
669
                data_len -= gnutls.record_send(self, data[-data_len:],
632
670
                                               data_len)
633
671
 
634
672
        def bye(self):
635
 
            return gnutls.bye(self._c_object, gnutls.SHUT_RDWR)
 
673
            return gnutls.bye(self, gnutls.SHUT_RDWR)
636
674
 
637
675
    # Error handling functions
638
676
    def _error_code(result):
639
677
        """A function to raise exceptions on errors, suitable
640
 
        for the 'restype' attribute on ctypes functions"""
641
 
        if result >= 0:
 
678
        for the "restype" attribute on ctypes functions"""
 
679
        if result >= gnutls.E_SUCCESS:
642
680
            return result
643
681
        if result == gnutls.E_NO_CERTIFICATE_FOUND:
644
682
            raise gnutls.CertificateSecurityError(code=result)
645
683
        raise gnutls.Error(code=result)
646
684
 
647
 
    def _retry_on_error(result, func, arguments):
 
685
    def _retry_on_error(result, func, arguments,
 
686
                        _error_code=_error_code):
648
687
        """A function to retry on some errors, suitable
649
 
        for the 'errcheck' attribute on ctypes functions"""
650
 
        while result < 0:
 
688
        for the "errcheck" attribute on ctypes functions"""
 
689
        while result < gnutls.E_SUCCESS:
651
690
            if result not in (gnutls.E_INTERRUPTED, gnutls.E_AGAIN):
652
691
                return _error_code(result)
653
692
            result = func(*arguments)
658
697
 
659
698
    # Functions
660
699
    priority_set_direct = _library.gnutls_priority_set_direct
661
 
    priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
 
700
    priority_set_direct.argtypes = [ClientSession, ctypes.c_char_p,
662
701
                                    ctypes.POINTER(ctypes.c_char_p)]
663
702
    priority_set_direct.restype = _error_code
664
703
 
665
704
    init = _library.gnutls_init
666
 
    init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
 
705
    init.argtypes = [PointerTo(ClientSession), ctypes.c_int]
667
706
    init.restype = _error_code
668
707
 
669
708
    set_default_priority = _library.gnutls_set_default_priority
670
 
    set_default_priority.argtypes = [session_t]
 
709
    set_default_priority.argtypes = [ClientSession]
671
710
    set_default_priority.restype = _error_code
672
711
 
673
712
    record_send = _library.gnutls_record_send
674
 
    record_send.argtypes = [session_t, ctypes.c_void_p,
 
713
    record_send.argtypes = [ClientSession, ctypes.c_void_p,
675
714
                            ctypes.c_size_t]
676
715
    record_send.restype = ctypes.c_ssize_t
677
716
    record_send.errcheck = _retry_on_error
679
718
    certificate_allocate_credentials = (
680
719
        _library.gnutls_certificate_allocate_credentials)
681
720
    certificate_allocate_credentials.argtypes = [
682
 
        ctypes.POINTER(certificate_credentials_t)]
 
721
        PointerTo(Credentials)]
683
722
    certificate_allocate_credentials.restype = _error_code
684
723
 
685
724
    certificate_free_credentials = (
686
725
        _library.gnutls_certificate_free_credentials)
687
 
    certificate_free_credentials.argtypes = [
688
 
        certificate_credentials_t]
 
726
    certificate_free_credentials.argtypes = [Credentials]
689
727
    certificate_free_credentials.restype = None
690
728
 
691
729
    handshake_set_private_extensions = (
692
730
        _library.gnutls_handshake_set_private_extensions)
693
 
    handshake_set_private_extensions.argtypes = [session_t,
 
731
    handshake_set_private_extensions.argtypes = [ClientSession,
694
732
                                                 ctypes.c_int]
695
733
    handshake_set_private_extensions.restype = None
696
734
 
697
735
    credentials_set = _library.gnutls_credentials_set
698
 
    credentials_set.argtypes = [session_t, credentials_type_t,
699
 
                                ctypes.c_void_p]
 
736
    credentials_set.argtypes = [ClientSession, credentials_type_t,
 
737
                                CastToVoidPointer(Credentials)]
700
738
    credentials_set.restype = _error_code
701
739
 
702
740
    strerror = _library.gnutls_strerror
704
742
    strerror.restype = ctypes.c_char_p
705
743
 
706
744
    certificate_type_get = _library.gnutls_certificate_type_get
707
 
    certificate_type_get.argtypes = [session_t]
 
745
    certificate_type_get.argtypes = [ClientSession]
708
746
    certificate_type_get.restype = _error_code
709
747
 
710
748
    certificate_get_peers = _library.gnutls_certificate_get_peers
711
 
    certificate_get_peers.argtypes = [session_t,
 
749
    certificate_get_peers.argtypes = [ClientSession,
712
750
                                      ctypes.POINTER(ctypes.c_uint)]
713
751
    certificate_get_peers.restype = ctypes.POINTER(datum_t)
714
752
 
721
759
    global_set_log_function.restype = None
722
760
 
723
761
    deinit = _library.gnutls_deinit
724
 
    deinit.argtypes = [session_t]
 
762
    deinit.argtypes = [ClientSession]
725
763
    deinit.restype = None
726
764
 
727
765
    handshake = _library.gnutls_handshake
728
 
    handshake.argtypes = [session_t]
729
 
    handshake.restype = _error_code
 
766
    handshake.argtypes = [ClientSession]
 
767
    handshake.restype = ctypes.c_int
730
768
    handshake.errcheck = _retry_on_error
731
769
 
732
770
    transport_set_ptr = _library.gnutls_transport_set_ptr
733
 
    transport_set_ptr.argtypes = [session_t, transport_ptr_t]
 
771
    transport_set_ptr.argtypes = [ClientSession, transport_ptr_t]
734
772
    transport_set_ptr.restype = None
735
773
 
736
774
    bye = _library.gnutls_bye
737
 
    bye.argtypes = [session_t, close_request_t]
738
 
    bye.restype = _error_code
 
775
    bye.argtypes = [ClientSession, close_request_t]
 
776
    bye.restype = ctypes.c_int
739
777
    bye.errcheck = _retry_on_error
740
778
 
741
779
    check_version = _library.gnutls_check_version
758
796
 
759
797
        x509_crt_fmt_t = ctypes.c_int
760
798
 
761
 
        # All the function declarations below are from gnutls/abstract.h
 
799
        # All the function declarations below are from
 
800
        # gnutls/abstract.h
762
801
        pubkey_init = _library.gnutls_pubkey_init
763
802
        pubkey_init.argtypes = [ctypes.POINTER(pubkey_t)]
764
803
        pubkey_init.restype = _error_code
778
817
        pubkey_deinit.argtypes = [pubkey_t]
779
818
        pubkey_deinit.restype = None
780
819
    else:
781
 
        # All the function declarations below are from gnutls/openpgp.h
 
820
        # All the function declarations below are from
 
821
        # gnutls/openpgp.h
782
822
 
783
823
        openpgp_crt_init = _library.gnutls_openpgp_crt_init
784
824
        openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
790
830
                                       openpgp_crt_fmt_t]
791
831
        openpgp_crt_import.restype = _error_code
792
832
 
793
 
        openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
794
 
        openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
795
 
                                            ctypes.POINTER(ctypes.c_uint)]
 
833
        openpgp_crt_verify_self = \
 
834
            _library.gnutls_openpgp_crt_verify_self
 
835
        openpgp_crt_verify_self.argtypes = [
 
836
            openpgp_crt_t,
 
837
            ctypes.c_uint,
 
838
            ctypes.POINTER(ctypes.c_uint),
 
839
        ]
796
840
        openpgp_crt_verify_self.restype = _error_code
797
841
 
798
842
        openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
809
853
 
810
854
    if check_version(b"3.6.4"):
811
855
        certificate_type_get2 = _library.gnutls_certificate_type_get2
812
 
        certificate_type_get2.argtypes = [session_t, ctypes.c_int]
 
856
        certificate_type_get2.argtypes = [ClientSession, ctypes.c_int]
813
857
        certificate_type_get2.restype = _error_code
814
858
 
815
859
    # Remove non-public functions
831
875
    """A representation of a client host served by this server.
832
876
 
833
877
    Attributes:
834
 
    approved:   bool(); 'None' if not yet approved/disapproved
 
878
    approved:   bool(); None if not yet approved/disapproved
835
879
    approval_delay: datetime.timedelta(); Time to wait for approval
836
880
    approval_duration: datetime.timedelta(); Duration of one approval
837
881
    checker: multiprocessing.Process(); a running checker process used
838
 
             to see if the client lives. 'None' if no process is
 
882
             to see if the client lives. None if no process is
839
883
             running.
840
884
    checker_callback_tag: a GLib event source tag, or None
841
885
    checker_command: string; External command which is run to check
965
1009
            self.last_enabled = None
966
1010
            self.expires = None
967
1011
 
968
 
        logger.debug("Creating client %r", self.name)
969
 
        logger.debug("  Key ID: %s", self.key_id)
970
 
        logger.debug("  Fingerprint: %s", self.fingerprint)
 
1012
        log.debug("Creating client %r", self.name)
 
1013
        log.debug("  Key ID: %s", self.key_id)
 
1014
        log.debug("  Fingerprint: %s", self.fingerprint)
971
1015
        self.created = settings.get("created",
972
1016
                                    datetime.datetime.utcnow())
973
1017
 
1012
1056
        if not getattr(self, "enabled", False):
1013
1057
            return False
1014
1058
        if not quiet:
1015
 
            logger.info("Disabling client %s", self.name)
 
1059
            log.info("Disabling client %s", self.name)
1016
1060
        if getattr(self, "disable_initiator_tag", None) is not None:
1017
1061
            GLib.source_remove(self.disable_initiator_tag)
1018
1062
            self.disable_initiator_tag = None
1062
1106
            self.last_checker_status = returncode
1063
1107
            self.last_checker_signal = None
1064
1108
            if self.last_checker_status == 0:
1065
 
                logger.info("Checker for %(name)s succeeded",
1066
 
                            vars(self))
 
1109
                log.info("Checker for %(name)s succeeded", vars(self))
1067
1110
                self.checked_ok()
1068
1111
            else:
1069
 
                logger.info("Checker for %(name)s failed", vars(self))
 
1112
                log.info("Checker for %(name)s failed", vars(self))
1070
1113
        else:
1071
1114
            self.last_checker_status = -1
1072
1115
            self.last_checker_signal = -returncode
1073
 
            logger.warning("Checker for %(name)s crashed?",
1074
 
                           vars(self))
 
1116
            log.warning("Checker for %(name)s crashed?", vars(self))
1075
1117
        return False
1076
1118
 
1077
1119
    def checked_ok(self):
1111
1153
        # should be.
1112
1154
 
1113
1155
        if self.checker is not None and not self.checker.is_alive():
1114
 
            logger.warning("Checker was not alive; joining")
 
1156
            log.warning("Checker was not alive; joining")
1115
1157
            self.checker.join()
1116
1158
            self.checker = None
1117
1159
        # Start a new checker if needed
1118
1160
        if self.checker is None:
1119
1161
            # Escape attributes for the shell
1120
1162
            escaped_attrs = {
1121
 
                attr: re.escape(str(getattr(self, attr)))
 
1163
                attr: shlex.quote(str(getattr(self, attr)))
1122
1164
                for attr in self.runtime_expansions}
1123
1165
            try:
1124
1166
                command = self.checker_command % escaped_attrs
1125
1167
            except TypeError as error:
1126
 
                logger.error('Could not format string "%s"',
1127
 
                             self.checker_command,
1128
 
                             exc_info=error)
 
1168
                log.error('Could not format string "%s"',
 
1169
                          self.checker_command, exc_info=error)
1129
1170
                return True     # Try again later
1130
1171
            self.current_checker_command = command
1131
 
            logger.info("Starting checker %r for %s", command,
1132
 
                        self.name)
 
1172
            log.info("Starting checker %r for %s", command, self.name)
1133
1173
            # We don't need to redirect stdout and stderr, since
1134
1174
            # in normal mode, that is already done by daemon(),
1135
1175
            # and in debug mode we don't want to.  (Stdin is
1164
1204
            self.checker_callback_tag = None
1165
1205
        if getattr(self, "checker", None) is None:
1166
1206
            return
1167
 
        logger.debug("Stopping checker for %(name)s", vars(self))
 
1207
        log.debug("Stopping checker for %(name)s", vars(self))
1168
1208
        self.checker.terminate()
1169
1209
        self.checker = None
1170
1210
 
1197
1237
        func._dbus_name = func.__name__
1198
1238
        if func._dbus_name.endswith("_dbus_property"):
1199
1239
            func._dbus_name = func._dbus_name[:-14]
1200
 
        func._dbus_get_args_options = {'byte_arrays': byte_arrays}
 
1240
        func._dbus_get_args_options = {"byte_arrays": byte_arrays}
1201
1241
        return func
1202
1242
 
1203
1243
    return decorator
1292
1332
 
1293
1333
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1294
1334
                         out_signature="s",
1295
 
                         path_keyword='object_path',
1296
 
                         connection_keyword='connection')
 
1335
                         path_keyword="object_path",
 
1336
                         connection_keyword="connection")
1297
1337
    def Introspect(self, object_path, connection):
1298
1338
        """Overloading of standard D-Bus method.
1299
1339
 
1348
1388
            document.unlink()
1349
1389
        except (AttributeError, xml.dom.DOMException,
1350
1390
                xml.parsers.expat.ExpatError) as error:
1351
 
            logger.error("Failed to override Introspection method",
1352
 
                         exc_info=error)
 
1391
            log.error("Failed to override Introspection method",
 
1392
                      exc_info=error)
1353
1393
        return xmlstring
1354
1394
 
1355
1395
 
1452
1492
 
1453
1493
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1454
1494
                         out_signature="s",
1455
 
                         path_keyword='object_path',
1456
 
                         connection_keyword='connection')
 
1495
                         path_keyword="object_path",
 
1496
                         connection_keyword="connection")
1457
1497
    def Introspect(self, object_path, connection):
1458
1498
        """Overloading of standard D-Bus method.
1459
1499
 
1515
1555
            document.unlink()
1516
1556
        except (AttributeError, xml.dom.DOMException,
1517
1557
                xml.parsers.expat.ExpatError) as error:
1518
 
            logger.error("Failed to override Introspection method",
1519
 
                         exc_info=error)
 
1558
            log.error("Failed to override Introspection method",
 
1559
                      exc_info=error)
1520
1560
        return xmlstring
1521
1561
 
1522
1562
 
1554
1594
 
1555
1595
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1556
1596
                         out_signature="s",
1557
 
                         path_keyword='object_path',
1558
 
                         connection_keyword='connection')
 
1597
                         path_keyword="object_path",
 
1598
                         connection_keyword="connection")
1559
1599
    def Introspect(self, object_path, connection):
1560
1600
        """Overloading of standard D-Bus method.
1561
1601
 
1586
1626
            document.unlink()
1587
1627
        except (AttributeError, xml.dom.DOMException,
1588
1628
                xml.parsers.expat.ExpatError) as error:
1589
 
            logger.error("Failed to override Introspection method",
1590
 
                         exc_info=error)
 
1629
            log.error("Failed to override Introspection method",
 
1630
                      exc_info=error)
1591
1631
        return xmlstring
1592
1632
 
1593
1633
 
2227
2267
class ProxyClient:
2228
2268
    def __init__(self, child_pipe, key_id, fpr, address):
2229
2269
        self._pipe = child_pipe
2230
 
        self._pipe.send(('init', key_id, fpr, address))
 
2270
        self._pipe.send(("init", key_id, fpr, address))
2231
2271
        if not self._pipe.recv():
2232
2272
            raise KeyError(key_id or fpr)
2233
2273
 
2234
2274
    def __getattribute__(self, name):
2235
 
        if name == '_pipe':
 
2275
        if name == "_pipe":
2236
2276
            return super(ProxyClient, self).__getattribute__(name)
2237
 
        self._pipe.send(('getattr', name))
 
2277
        self._pipe.send(("getattr", name))
2238
2278
        data = self._pipe.recv()
2239
 
        if data[0] == 'data':
 
2279
        if data[0] == "data":
2240
2280
            return data[1]
2241
 
        if data[0] == 'function':
 
2281
        if data[0] == "function":
2242
2282
 
2243
2283
            def func(*args, **kwargs):
2244
 
                self._pipe.send(('funcall', name, args, kwargs))
 
2284
                self._pipe.send(("funcall", name, args, kwargs))
2245
2285
                return self._pipe.recv()[1]
2246
2286
 
2247
2287
            return func
2248
2288
 
2249
2289
    def __setattr__(self, name, value):
2250
 
        if name == '_pipe':
 
2290
        if name == "_pipe":
2251
2291
            return super(ProxyClient, self).__setattr__(name, value)
2252
 
        self._pipe.send(('setattr', name, value))
 
2292
        self._pipe.send(("setattr", name, value))
2253
2293
 
2254
2294
 
2255
2295
class ClientHandler(socketserver.BaseRequestHandler, object):
2260
2300
 
2261
2301
    def handle(self):
2262
2302
        with contextlib.closing(self.server.child_pipe) as child_pipe:
2263
 
            logger.info("TCP connection from: %s",
2264
 
                        str(self.client_address))
2265
 
            logger.debug("Pipe FD: %d",
2266
 
                         self.server.child_pipe.fileno())
 
2303
            log.info("TCP connection from: %s",
 
2304
                     str(self.client_address))
 
2305
            log.debug("Pipe FD: %d", self.server.child_pipe.fileno())
2267
2306
 
2268
2307
            session = gnutls.ClientSession(self.request)
2269
2308
 
2270
 
            # priority = ':'.join(("NONE", "+VERS-TLS1.1",
 
2309
            # priority = ":".join(("NONE", "+VERS-TLS1.1",
2271
2310
            #                       "+AES-256-CBC", "+SHA1",
2272
2311
            #                       "+COMP-NULL", "+CTYPE-OPENPGP",
2273
2312
            #                       "+DHE-DSS"))
2275
2314
            priority = self.server.gnutls_priority
2276
2315
            if priority is None:
2277
2316
                priority = "NORMAL"
2278
 
            gnutls.priority_set_direct(session._c_object,
2279
 
                                       priority.encode("utf-8"),
2280
 
                                       None)
 
2317
            gnutls.priority_set_direct(session,
 
2318
                                       priority.encode("utf-8"), None)
2281
2319
 
2282
2320
            # Start communication using the Mandos protocol
2283
2321
            # Get protocol number
2284
2322
            line = self.request.makefile().readline()
2285
 
            logger.debug("Protocol version: %r", line)
 
2323
            log.debug("Protocol version: %r", line)
2286
2324
            try:
2287
2325
                if int(line.strip().split()[0]) > 1:
2288
2326
                    raise RuntimeError(line)
2289
2327
            except (ValueError, IndexError, RuntimeError) as error:
2290
 
                logger.error("Unknown protocol version: %s", error)
 
2328
                log.error("Unknown protocol version: %s", error)
2291
2329
                return
2292
2330
 
2293
2331
            # Start GnuTLS connection
2294
2332
            try:
2295
2333
                session.handshake()
2296
2334
            except gnutls.Error as error:
2297
 
                logger.warning("Handshake failed: %s", error)
 
2335
                log.warning("Handshake failed: %s", error)
2298
2336
                # Do not run session.bye() here: the session is not
2299
2337
                # established.  Just abandon the request.
2300
2338
                return
2301
 
            logger.debug("Handshake succeeded")
 
2339
            log.debug("Handshake succeeded")
2302
2340
 
2303
2341
            approval_required = False
2304
2342
            try:
2308
2346
                        key_id = self.key_id(
2309
2347
                            self.peer_certificate(session))
2310
2348
                    except (TypeError, gnutls.Error) as error:
2311
 
                        logger.warning("Bad certificate: %s", error)
 
2349
                        log.warning("Bad certificate: %s", error)
2312
2350
                        return
2313
 
                    logger.debug("Key ID: %s", key_id)
 
2351
                    log.debug("Key ID: %s",
 
2352
                              key_id.decode("utf-8",
 
2353
                                            errors="replace"))
2314
2354
 
2315
2355
                else:
2316
2356
                    key_id = b""
2318
2358
                        fpr = self.fingerprint(
2319
2359
                            self.peer_certificate(session))
2320
2360
                    except (TypeError, gnutls.Error) as error:
2321
 
                        logger.warning("Bad certificate: %s", error)
 
2361
                        log.warning("Bad certificate: %s", error)
2322
2362
                        return
2323
 
                    logger.debug("Fingerprint: %s", fpr)
 
2363
                    log.debug("Fingerprint: %s", fpr)
2324
2364
 
2325
2365
                try:
2326
2366
                    client = ProxyClient(child_pipe, key_id, fpr,
2335
2375
 
2336
2376
                while True:
2337
2377
                    if not client.enabled:
2338
 
                        logger.info("Client %s is disabled",
2339
 
                                    client.name)
 
2378
                        log.info("Client %s is disabled", client.name)
2340
2379
                        if self.server.use_dbus:
2341
2380
                            # Emit D-Bus signal
2342
2381
                            client.Rejected("Disabled")
2346
2385
                        # We are approved or approval is disabled
2347
2386
                        break
2348
2387
                    elif client.approved is None:
2349
 
                        logger.info("Client %s needs approval",
2350
 
                                    client.name)
 
2388
                        log.info("Client %s needs approval",
 
2389
                                 client.name)
2351
2390
                        if self.server.use_dbus:
2352
2391
                            # Emit D-Bus signal
2353
2392
                            client.NeedApproval(
2354
2393
                                client.approval_delay.total_seconds()
2355
2394
                                * 1000, client.approved_by_default)
2356
2395
                    else:
2357
 
                        logger.warning("Client %s was not approved",
2358
 
                                       client.name)
 
2396
                        log.warning("Client %s was not approved",
 
2397
                                    client.name)
2359
2398
                        if self.server.use_dbus:
2360
2399
                            # Emit D-Bus signal
2361
2400
                            client.Rejected("Denied")
2369
2408
                    time2 = datetime.datetime.now()
2370
2409
                    if (time2 - time) >= delay:
2371
2410
                        if not client.approved_by_default:
2372
 
                            logger.warning("Client %s timed out while"
2373
 
                                           " waiting for approval",
2374
 
                                           client.name)
 
2411
                            log.warning("Client %s timed out while"
 
2412
                                        " waiting for approval",
 
2413
                                        client.name)
2375
2414
                            if self.server.use_dbus:
2376
2415
                                # Emit D-Bus signal
2377
2416
                                client.Rejected("Approval timed out")
2384
2423
                try:
2385
2424
                    session.send(client.secret)
2386
2425
                except gnutls.Error as error:
2387
 
                    logger.warning("gnutls send failed",
2388
 
                                   exc_info=error)
 
2426
                    log.warning("gnutls send failed", exc_info=error)
2389
2427
                    return
2390
2428
 
2391
 
                logger.info("Sending secret to %s", client.name)
 
2429
                log.info("Sending secret to %s", client.name)
2392
2430
                # bump the timeout using extended_timeout
2393
2431
                client.bump_timeout(client.extended_timeout)
2394
2432
                if self.server.use_dbus:
2401
2439
                try:
2402
2440
                    session.bye()
2403
2441
                except gnutls.Error as error:
2404
 
                    logger.warning("GnuTLS bye failed",
2405
 
                                   exc_info=error)
 
2442
                    log.warning("GnuTLS bye failed", exc_info=error)
2406
2443
 
2407
2444
    @staticmethod
2408
2445
    def peer_certificate(session):
2409
2446
        "Return the peer's certificate as a bytestring"
2410
2447
        try:
2411
 
            cert_type = gnutls.certificate_type_get2(session._c_object,
2412
 
                                                     gnutls.CTYPE_PEERS)
 
2448
            cert_type = gnutls.certificate_type_get2(
 
2449
                session, gnutls.CTYPE_PEERS)
2413
2450
        except AttributeError:
2414
 
            cert_type = gnutls.certificate_type_get(session._c_object)
 
2451
            cert_type = gnutls.certificate_type_get(session)
2415
2452
        if gnutls.has_rawpk:
2416
2453
            valid_cert_types = frozenset((gnutls.CRT_RAWPK,))
2417
2454
        else:
2418
2455
            valid_cert_types = frozenset((gnutls.CRT_OPENPGP,))
2419
2456
        # If not a valid certificate type...
2420
2457
        if cert_type not in valid_cert_types:
2421
 
            logger.info("Cert type %r not in %r", cert_type,
2422
 
                        valid_cert_types)
 
2458
            log.info("Cert type %r not in %r", cert_type,
 
2459
                     valid_cert_types)
2423
2460
            # ...return invalid data
2424
2461
            return b""
2425
2462
        list_size = ctypes.c_uint(1)
2426
2463
        cert_list = (gnutls.certificate_get_peers
2427
 
                     (session._c_object, ctypes.byref(list_size)))
 
2464
                     (session, ctypes.byref(list_size)))
2428
2465
        if not bool(cert_list) and list_size.value != 0:
2429
2466
            raise gnutls.Error("error getting peer certificate")
2430
2467
        if list_size.value == 0:
2452
2489
        buf = ctypes.create_string_buffer(32)
2453
2490
        buf_len = ctypes.c_size_t(len(buf))
2454
2491
        # Get the key ID from the raw public key into the buffer
2455
 
        gnutls.pubkey_get_key_id(pubkey,
2456
 
                                 gnutls.KEYID_USE_SHA256,
2457
 
                                 ctypes.cast(ctypes.byref(buf),
2458
 
                                             ctypes.POINTER(ctypes.c_ubyte)),
2459
 
                                 ctypes.byref(buf_len))
 
2492
        gnutls.pubkey_get_key_id(
 
2493
            pubkey,
 
2494
            gnutls.KEYID_USE_SHA256,
 
2495
            ctypes.cast(ctypes.byref(buf),
 
2496
                        ctypes.POINTER(ctypes.c_ubyte)),
 
2497
            ctypes.byref(buf_len))
2460
2498
        # Deinit the certificate
2461
2499
        gnutls.pubkey_deinit(pubkey)
2462
2500
 
2543
2581
 
2544
2582
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
2545
2583
                     socketserver.TCPServer):
2546
 
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
 
2584
    """IPv6-capable TCP server.  Accepts None as address and/or port
2547
2585
 
2548
2586
    Attributes:
2549
2587
        enabled:        Boolean; whether this server is activated yet
2600
2638
            if SO_BINDTODEVICE is None:
2601
2639
                # Fall back to a hard-coded value which seems to be
2602
2640
                # common enough.
2603
 
                logger.warning("SO_BINDTODEVICE not found, trying 25")
 
2641
                log.warning("SO_BINDTODEVICE not found, trying 25")
2604
2642
                SO_BINDTODEVICE = 25
2605
2643
            try:
2606
2644
                self.socket.setsockopt(
2608
2646
                    (self.interface + "\0").encode("utf-8"))
2609
2647
            except socket.error as error:
2610
2648
                if error.errno == errno.EPERM:
2611
 
                    logger.error("No permission to bind to"
2612
 
                                 " interface %s", self.interface)
 
2649
                    log.error("No permission to bind to interface %s",
 
2650
                              self.interface)
2613
2651
                elif error.errno == errno.ENOPROTOOPT:
2614
 
                    logger.error("SO_BINDTODEVICE not available;"
2615
 
                                 " cannot bind to interface %s",
2616
 
                                 self.interface)
 
2652
                    log.error("SO_BINDTODEVICE not available; cannot"
 
2653
                              " bind to interface %s", self.interface)
2617
2654
                elif error.errno == errno.ENODEV:
2618
 
                    logger.error("Interface %s does not exist,"
2619
 
                                 " cannot bind", self.interface)
 
2655
                    log.error("Interface %s does not exist, cannot"
 
2656
                              " bind", self.interface)
2620
2657
                else:
2621
2658
                    raise
2622
2659
        # Only bind(2) the socket if we really need to.
2701
2738
        request = parent_pipe.recv()
2702
2739
        command = request[0]
2703
2740
 
2704
 
        if command == 'init':
 
2741
        if command == "init":
2705
2742
            key_id = request[1].decode("ascii")
2706
2743
            fpr = request[2].decode("ascii")
2707
2744
            address = request[3]
2708
2745
 
2709
2746
            for c in self.clients.values():
2710
 
                if key_id == "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855":
 
2747
                if key_id == ("E3B0C44298FC1C149AFBF4C8996FB924"
 
2748
                              "27AE41E4649B934CA495991B7852B855"):
2711
2749
                    continue
2712
2750
                if key_id and c.key_id == key_id:
2713
2751
                    client = c
2716
2754
                    client = c
2717
2755
                    break
2718
2756
            else:
2719
 
                logger.info("Client not found for key ID: %s, address"
2720
 
                            ": %s", key_id or fpr, address)
 
2757
                log.info("Client not found for key ID: %s, address:"
 
2758
                         " %s", key_id or fpr, address)
2721
2759
                if self.use_dbus:
2722
2760
                    # Emit D-Bus signal
2723
2761
                    mandos_dbus_service.ClientNotFound(key_id or fpr,
2736
2774
            # remove the old hook in favor of the new above hook on
2737
2775
            # same fileno
2738
2776
            return False
2739
 
        if command == 'funcall':
 
2777
        if command == "funcall":
2740
2778
            funcname = request[1]
2741
2779
            args = request[2]
2742
2780
            kwargs = request[3]
2743
2781
 
2744
 
            parent_pipe.send(('data', getattr(client_object,
 
2782
            parent_pipe.send(("data", getattr(client_object,
2745
2783
                                              funcname)(*args,
2746
2784
                                                        **kwargs)))
2747
2785
 
2748
 
        if command == 'getattr':
 
2786
        if command == "getattr":
2749
2787
            attrname = request[1]
2750
2788
            if isinstance(client_object.__getattribute__(attrname),
2751
 
                          collections.Callable):
2752
 
                parent_pipe.send(('function', ))
 
2789
                          collections.abc.Callable):
 
2790
                parent_pipe.send(("function", ))
2753
2791
            else:
2754
2792
                parent_pipe.send((
2755
 
                    'data', client_object.__getattribute__(attrname)))
 
2793
                    "data", client_object.__getattribute__(attrname)))
2756
2794
 
2757
 
        if command == 'setattr':
 
2795
        if command == "setattr":
2758
2796
            attrname = request[1]
2759
2797
            value = request[2]
2760
2798
            setattr(client_object, attrname, value)
2765
2803
def rfc3339_duration_to_delta(duration):
2766
2804
    """Parse an RFC 3339 "duration" and return a datetime.timedelta
2767
2805
 
2768
 
    >>> rfc3339_duration_to_delta("P7D") == datetime.timedelta(7)
2769
 
    True
2770
 
    >>> rfc3339_duration_to_delta("PT60S") == datetime.timedelta(0, 60)
2771
 
    True
2772
 
    >>> rfc3339_duration_to_delta("PT60M") == datetime.timedelta(0, 3600)
2773
 
    True
2774
 
    >>> rfc3339_duration_to_delta("PT24H") == datetime.timedelta(1)
2775
 
    True
2776
 
    >>> rfc3339_duration_to_delta("P1W") == datetime.timedelta(7)
2777
 
    True
2778
 
    >>> rfc3339_duration_to_delta("PT5M30S") == datetime.timedelta(0, 330)
2779
 
    True
2780
 
    >>> rfc3339_duration_to_delta("P1DT3M20S") == datetime.timedelta(1, 200)
2781
 
    True
 
2806
    >>> timedelta = datetime.timedelta
 
2807
    >>> rfc3339_duration_to_delta("P7D") == timedelta(7)
 
2808
    True
 
2809
    >>> rfc3339_duration_to_delta("PT60S") == timedelta(0, 60)
 
2810
    True
 
2811
    >>> rfc3339_duration_to_delta("PT60M") == timedelta(0, 3600)
 
2812
    True
 
2813
    >>> rfc3339_duration_to_delta("PT24H") == timedelta(1)
 
2814
    True
 
2815
    >>> rfc3339_duration_to_delta("P1W") == timedelta(7)
 
2816
    True
 
2817
    >>> rfc3339_duration_to_delta("PT5M30S") == timedelta(0, 330)
 
2818
    True
 
2819
    >>> rfc3339_duration_to_delta("P1DT3M20S") == timedelta(1, 200)
 
2820
    True
 
2821
    >>> del timedelta
2782
2822
    """
2783
2823
 
2784
2824
    # Parsing an RFC 3339 duration with regular expressions is not
2864
2904
def string_to_delta(interval):
2865
2905
    """Parse a string and return a datetime.timedelta
2866
2906
 
2867
 
    >>> string_to_delta('7d') == datetime.timedelta(7)
2868
 
    True
2869
 
    >>> string_to_delta('60s') == datetime.timedelta(0, 60)
2870
 
    True
2871
 
    >>> string_to_delta('60m') == datetime.timedelta(0, 3600)
2872
 
    True
2873
 
    >>> string_to_delta('24h') == datetime.timedelta(1)
2874
 
    True
2875
 
    >>> string_to_delta('1w') == datetime.timedelta(7)
2876
 
    True
2877
 
    >>> string_to_delta('5m 30s') == datetime.timedelta(0, 330)
 
2907
    >>> string_to_delta("7d") == datetime.timedelta(7)
 
2908
    True
 
2909
    >>> string_to_delta("60s") == datetime.timedelta(0, 60)
 
2910
    True
 
2911
    >>> string_to_delta("60m") == datetime.timedelta(0, 3600)
 
2912
    True
 
2913
    >>> string_to_delta("24h") == datetime.timedelta(1)
 
2914
    True
 
2915
    >>> string_to_delta("1w") == datetime.timedelta(7)
 
2916
    True
 
2917
    >>> string_to_delta("5m 30s") == datetime.timedelta(0, 330)
2878
2918
    True
2879
2919
    """
2880
2920
 
3084
3124
 
3085
3125
    if server_settings["servicename"] != "Mandos":
3086
3126
        syslogger.setFormatter(
3087
 
            logging.Formatter('Mandos ({}) [%(process)d]:'
3088
 
                              ' %(levelname)s: %(message)s'.format(
 
3127
            logging.Formatter("Mandos ({}) [%(process)d]:"
 
3128
                              " %(levelname)s: %(message)s".format(
3089
3129
                                  server_settings["servicename"])))
3090
3130
 
3091
3131
    # Parse config file with clients
3115
3155
        try:
3116
3156
            pidfile = codecs.open(pidfilename, "w", encoding="utf-8")
3117
3157
        except IOError as e:
3118
 
            logger.error("Could not open file %r", pidfilename,
3119
 
                         exc_info=e)
 
3158
            log.error("Could not open file %r", pidfilename,
 
3159
                      exc_info=e)
3120
3160
 
3121
3161
    for name, group in (("_mandos", "_mandos"),
3122
3162
                        ("mandos", "mandos"),
3133
3173
    try:
3134
3174
        os.setgid(gid)
3135
3175
        os.setuid(uid)
3136
 
        if debug:
3137
 
            logger.debug("Did setuid/setgid to {}:{}".format(uid,
3138
 
                                                             gid))
 
3176
        log.debug("Did setuid/setgid to %s:%s", uid, gid)
3139
3177
    except OSError as error:
3140
 
        logger.warning("Failed to setuid/setgid to {}:{}: {}"
3141
 
                       .format(uid, gid, os.strerror(error.errno)))
 
3178
        log.warning("Failed to setuid/setgid to %s:%s: %s", uid, gid,
 
3179
                    os.strerror(error.errno))
3142
3180
        if error.errno != errno.EPERM:
3143
3181
            raise
3144
3182
 
3151
3189
 
3152
3190
        @gnutls.log_func
3153
3191
        def debug_gnutls(level, string):
3154
 
            logger.debug("GnuTLS: %s", string[:-1])
 
3192
            log.debug("GnuTLS: %s",
 
3193
                      string[:-1].decode("utf-8", errors="replace"))
3155
3194
 
3156
3195
        gnutls.global_set_log_function(debug_gnutls)
3157
3196
 
3186
3225
                "se.bsnet.fukt.Mandos", bus,
3187
3226
                do_not_queue=True)
3188
3227
        except dbus.exceptions.DBusException as e:
3189
 
            logger.error("Disabling D-Bus:", exc_info=e)
 
3228
            log.error("Disabling D-Bus:", exc_info=e)
3190
3229
            use_dbus = False
3191
3230
            server_settings["use_dbus"] = False
3192
3231
            tcp_server.use_dbus = False
3277
3316
            os.remove(stored_state_path)
3278
3317
        except IOError as e:
3279
3318
            if e.errno == errno.ENOENT:
3280
 
                logger.warning("Could not load persistent state:"
3281
 
                               " {}".format(os.strerror(e.errno)))
 
3319
                log.warning("Could not load persistent state:"
 
3320
                            " %s", os.strerror(e.errno))
3282
3321
            else:
3283
 
                logger.critical("Could not load persistent state:",
3284
 
                                exc_info=e)
 
3322
                log.critical("Could not load persistent state:",
 
3323
                             exc_info=e)
3285
3324
                raise
3286
3325
        except EOFError as e:
3287
 
            logger.warning("Could not load persistent state: "
3288
 
                           "EOFError:",
3289
 
                           exc_info=e)
 
3326
            log.warning("Could not load persistent state: EOFError:",
 
3327
                        exc_info=e)
3290
3328
 
3291
3329
    with PGPEngine() as pgp:
3292
3330
        for client_name, client in clients_data.items():
3319
3357
            if client["enabled"]:
3320
3358
                if datetime.datetime.utcnow() >= client["expires"]:
3321
3359
                    if not client["last_checked_ok"]:
3322
 
                        logger.warning(
3323
 
                            "disabling client {} - Client never "
3324
 
                            "performed a successful checker".format(
3325
 
                                client_name))
 
3360
                        log.warning("disabling client %s - Client"
 
3361
                                    " never performed a successful"
 
3362
                                    " checker", client_name)
3326
3363
                        client["enabled"] = False
3327
3364
                    elif client["last_checker_status"] != 0:
3328
 
                        logger.warning(
3329
 
                            "disabling client {} - Client last"
3330
 
                            " checker failed with error code"
3331
 
                            " {}".format(
3332
 
                                client_name,
3333
 
                                client["last_checker_status"]))
 
3365
                        log.warning("disabling client %s - Client"
 
3366
                                    " last checker failed with error"
 
3367
                                    " code %s", client_name,
 
3368
                                    client["last_checker_status"])
3334
3369
                        client["enabled"] = False
3335
3370
                    else:
3336
3371
                        client["expires"] = (
3337
3372
                            datetime.datetime.utcnow()
3338
3373
                            + client["timeout"])
3339
 
                        logger.debug("Last checker succeeded,"
3340
 
                                     " keeping {} enabled".format(
3341
 
                                         client_name))
 
3374
                        log.debug("Last checker succeeded, keeping %s"
 
3375
                                  " enabled", client_name)
3342
3376
            try:
3343
3377
                client["secret"] = pgp.decrypt(
3344
3378
                    client["encrypted_secret"],
3345
3379
                    client_settings[client_name]["secret"])
3346
3380
            except PGPError:
3347
3381
                # If decryption fails, we use secret from new settings
3348
 
                logger.debug("Failed to decrypt {} old secret".format(
3349
 
                    client_name))
 
3382
                log.debug("Failed to decrypt %s old secret",
 
3383
                          client_name)
3350
3384
                client["secret"] = (client_settings[client_name]
3351
3385
                                    ["secret"])
3352
3386
 
3366
3400
            server_settings=server_settings)
3367
3401
 
3368
3402
    if not tcp_server.clients:
3369
 
        logger.warning("No clients defined")
 
3403
        log.warning("No clients defined")
3370
3404
 
3371
3405
    if not foreground:
3372
3406
        if pidfile is not None:
3375
3409
                with pidfile:
3376
3410
                    print(pid, file=pidfile)
3377
3411
            except IOError:
3378
 
                logger.error("Could not write to file %r with PID %d",
3379
 
                             pidfilename, pid)
 
3412
                log.error("Could not write to file %r with PID %d",
 
3413
                          pidfilename, pid)
3380
3414
        del pidfile
3381
3415
        del pidfilename
3382
3416
 
3532
3566
 
3533
3567
        try:
3534
3568
            with tempfile.NamedTemporaryFile(
3535
 
                    mode='wb',
 
3569
                    mode="wb",
3536
3570
                    suffix=".pickle",
3537
 
                    prefix='clients-',
 
3571
                    prefix="clients-",
3538
3572
                    dir=os.path.dirname(stored_state_path),
3539
3573
                    delete=False) as stored_state:
3540
3574
                pickle.dump((clients, client_settings), stored_state,
3548
3582
                except NameError:
3549
3583
                    pass
3550
3584
            if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
3551
 
                logger.warning("Could not save persistent state: {}"
3552
 
                               .format(os.strerror(e.errno)))
 
3585
                log.warning("Could not save persistent state: %s",
 
3586
                            os.strerror(e.errno))
3553
3587
            else:
3554
 
                logger.warning("Could not save persistent state:",
3555
 
                               exc_info=e)
 
3588
                log.warning("Could not save persistent state:",
 
3589
                            exc_info=e)
3556
3590
                raise
3557
3591
 
3558
3592
        # Delete all clients, and settings from config
3584
3618
    if zeroconf:
3585
3619
        service.port = tcp_server.socket.getsockname()[1]
3586
3620
    if use_ipv6:
3587
 
        logger.info("Now listening on address %r, port %d,"
3588
 
                    " flowinfo %d, scope_id %d",
3589
 
                    *tcp_server.socket.getsockname())
 
3621
        log.info("Now listening on address %r, port %d, flowinfo %d,"
 
3622
                 " scope_id %d", *tcp_server.socket.getsockname())
3590
3623
    else:                       # IPv4
3591
 
        logger.info("Now listening on address %r, port %d",
3592
 
                    *tcp_server.socket.getsockname())
 
3624
        log.info("Now listening on address %r, port %d",
 
3625
                 *tcp_server.socket.getsockname())
3593
3626
 
3594
3627
    # service.interface = tcp_server.socket.getsockname()[3]
3595
3628
 
3599
3632
            try:
3600
3633
                service.activate()
3601
3634
            except dbus.exceptions.DBusException as error:
3602
 
                logger.critical("D-Bus Exception", exc_info=error)
 
3635
                log.critical("D-Bus Exception", exc_info=error)
3603
3636
                cleanup()
3604
3637
                sys.exit(1)
3605
3638
            # End of Avahi example code
3610
3643
            lambda *args, **kwargs: (tcp_server.handle_request
3611
3644
                                     (*args[2:], **kwargs) or True))
3612
3645
 
3613
 
        logger.debug("Starting main loop")
 
3646
        log.debug("Starting main loop")
3614
3647
        main_loop.run()
3615
3648
    except AvahiError as error:
3616
 
        logger.critical("Avahi Error", exc_info=error)
 
3649
        log.critical("Avahi Error", exc_info=error)
3617
3650
        cleanup()
3618
3651
        sys.exit(1)
3619
3652
    except KeyboardInterrupt:
3620
3653
        if debug:
3621
3654
            print("", file=sys.stderr)
3622
 
        logger.debug("Server received KeyboardInterrupt")
3623
 
    logger.debug("Server exiting")
 
3655
        log.debug("Server received KeyboardInterrupt")
 
3656
    log.debug("Server exiting")
3624
3657
    # Must run before the D-Bus bus name gets deregistered
3625
3658
    cleanup()
3626
3659
 
3627
3660
 
3628
 
def should_only_run_tests():
 
3661
def parse_test_args():
 
3662
    # type: () -> argparse.Namespace
3629
3663
    parser = argparse.ArgumentParser(add_help=False)
3630
 
    parser.add_argument("--check", action='store_true')
 
3664
    parser.add_argument("--check", action="store_true")
 
3665
    parser.add_argument("--prefix", )
3631
3666
    args, unknown_args = parser.parse_known_args()
3632
 
    run_tests = args.check
3633
 
    if run_tests:
3634
 
        # Remove --check argument from sys.argv
 
3667
    if args.check:
 
3668
        # Remove test options from sys.argv
3635
3669
        sys.argv[1:] = unknown_args
3636
 
    return run_tests
 
3670
    return args
3637
3671
 
3638
3672
# Add all tests from doctest strings
3639
3673
def load_tests(loader, tests, none):
3641
3675
    tests.addTests(doctest.DocTestSuite())
3642
3676
    return tests
3643
3677
 
3644
 
if __name__ == '__main__':
 
3678
if __name__ == "__main__":
 
3679
    options = parse_test_args()
3645
3680
    try:
3646
 
        if should_only_run_tests():
3647
 
            # Call using ./mandos --check [--verbose]
3648
 
            unittest.main()
 
3681
        if options.check:
 
3682
            extra_test_prefix = options.prefix
 
3683
            if extra_test_prefix is not None:
 
3684
                if not (unittest.main(argv=[""], exit=False)
 
3685
                        .result.wasSuccessful()):
 
3686
                    sys.exit(1)
 
3687
                class ExtraTestLoader(unittest.TestLoader):
 
3688
                    testMethodPrefix = extra_test_prefix
 
3689
                # Call using ./scriptname --test [--verbose]
 
3690
                unittest.main(argv=[""], testLoader=ExtraTestLoader())
 
3691
            else:
 
3692
                unittest.main(argv=[""])
3649
3693
        else:
3650
3694
            main()
3651
3695
    finally:
3652
3696
        logging.shutdown()
 
3697
 
 
3698
# Local Variables:
 
3699
# run-tests:
 
3700
# (lambda (&optional extra)
 
3701
#   (if (not (funcall run-tests-in-test-buffer default-directory
 
3702
#             extra))
 
3703
#       (funcall show-test-buffer-in-test-window)
 
3704
#     (funcall remove-test-window)
 
3705
#     (if extra (message "Extra tests run successfully!"))))
 
3706
# run-tests-in-test-buffer:
 
3707
# (lambda (dir &optional extra)
 
3708
#   (with-current-buffer (get-buffer-create "*Test*")
 
3709
#     (setq buffer-read-only nil
 
3710
#           default-directory dir)
 
3711
#     (erase-buffer)
 
3712
#     (compilation-mode))
 
3713
#   (let ((process-result
 
3714
#          (let ((inhibit-read-only t))
 
3715
#            (process-file-shell-command
 
3716
#             (funcall get-command-line extra) nil "*Test*"))))
 
3717
#     (and (numberp process-result)
 
3718
#          (= process-result 0))))
 
3719
# get-command-line:
 
3720
# (lambda (&optional extra)
 
3721
#   (let ((quoted-script
 
3722
#          (shell-quote-argument (funcall get-script-name))))
 
3723
#     (format
 
3724
#      (concat "%s --check" (if extra " --prefix=atest" ""))
 
3725
#      quoted-script)))
 
3726
# get-script-name:
 
3727
# (lambda ()
 
3728
#   (if (fboundp 'file-local-name)
 
3729
#       (file-local-name (buffer-file-name))
 
3730
#     (or (file-remote-p (buffer-file-name) 'localname)
 
3731
#         (buffer-file-name))))
 
3732
# remove-test-window:
 
3733
# (lambda ()
 
3734
#   (let ((test-window (get-buffer-window "*Test*")))
 
3735
#     (if test-window (delete-window test-window))))
 
3736
# show-test-buffer-in-test-window:
 
3737
# (lambda ()
 
3738
#   (when (not (get-buffer-window-list "*Test*"))
 
3739
#     (setq next-error-last-buffer (get-buffer "*Test*"))
 
3740
#     (let* ((side (if (>= (window-width) 146) 'right 'bottom))
 
3741
#            (display-buffer-overriding-action
 
3742
#             `((display-buffer-in-side-window) (side . ,side)
 
3743
#               (window-height . fit-window-to-buffer)
 
3744
#               (window-width . fit-window-to-buffer))))
 
3745
#       (display-buffer "*Test*"))))
 
3746
# eval:
 
3747
# (progn
 
3748
#   (let* ((run-extra-tests (lambda () (interactive)
 
3749
#                             (funcall run-tests t)))
 
3750
#          (inner-keymap `(keymap (116 . ,run-extra-tests))) ; t
 
3751
#          (outer-keymap `(keymap (3 . ,inner-keymap))))     ; C-c
 
3752
#     (setq minor-mode-overriding-map-alist
 
3753
#           (cons `(run-tests . ,outer-keymap)
 
3754
#                 minor-mode-overriding-map-alist)))
 
3755
#   (add-hook 'after-save-hook run-tests 90 t))
 
3756
# End: