/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2016-10-29 13:44:49 UTC
  • Revision ID: teddy@recompile.se-20161029134449-imf8eidhzwfnax9w
mandos: Use "self" instead of class name "GnuTLS" in __init__.

* mandos (GnuTLS.__init__): Use "self" instead of class name "GnuTLS".

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#!/usr/bin/python
2
2
# -*- mode: python; coding: utf-8 -*-
3
 
 
3
#
4
4
# Mandos server - give out binary blobs to connecting clients.
5
 
 
5
#
6
6
# This program is partly derived from an example program for an Avahi
7
7
# service publisher, downloaded from
8
8
# <http://avahi.org/wiki/PythonPublishExample>.  This includes the
9
9
# methods "add", "remove", "server_state_changed",
10
10
# "entry_group_state_changed", "cleanup", and "activate" in the
11
11
# "AvahiService" class, and some lines in "main".
12
 
 
12
#
13
13
# Everything else is
14
14
# Copyright © 2008-2016 Teddy Hogeborn
15
15
# Copyright © 2008-2016 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
19
19
# the Free Software Foundation, either version 3 of the License, or
23
23
#     but WITHOUT ANY WARRANTY; without even the implied warranty of
24
24
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25
25
#     GNU General Public License for more details.
26
 
 
26
#
27
27
# You should have received a copy of the GNU General Public License
28
28
# along with this program.  If not, see
29
29
# <http://www.gnu.org/licenses/>.
30
 
 
30
#
31
31
# Contact the authors at <mandos@recompile.se>.
32
 
 
32
#
33
33
 
34
34
from __future__ import (division, absolute_import, print_function,
35
35
                        unicode_literals)
114
114
if sys.version_info.major == 2:
115
115
    str = unicode
116
116
 
117
 
version = "1.7.7"
 
117
version = "1.7.13"
118
118
stored_state_file = "clients.pickle"
119
119
 
120
120
logger = logging.getLogger()
124
124
    if_nametoindex = ctypes.cdll.LoadLibrary(
125
125
        ctypes.util.find_library("c")).if_nametoindex
126
126
except (OSError, AttributeError):
127
 
    
 
127
 
128
128
    def if_nametoindex(interface):
129
129
        "Get an interface index the hard way, i.e. using fcntl()"
130
130
        SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
153
153
 
154
154
def initlogger(debug, level=logging.WARNING):
155
155
    """init logger and add loglevel"""
156
 
    
 
156
 
157
157
    global syslogger
158
158
    syslogger = (logging.handlers.SysLogHandler(
159
 
        facility = logging.handlers.SysLogHandler.LOG_DAEMON,
160
 
        address = "/dev/log"))
 
159
        facility=logging.handlers.SysLogHandler.LOG_DAEMON,
 
160
        address="/dev/log"))
161
161
    syslogger.setFormatter(logging.Formatter
162
162
                           ('Mandos [%(process)d]: %(levelname)s:'
163
163
                            ' %(message)s'))
164
164
    logger.addHandler(syslogger)
165
 
    
 
165
 
166
166
    if debug:
167
167
        console = logging.StreamHandler()
168
168
        console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
180
180
 
181
181
class PGPEngine(object):
182
182
    """A simple class for OpenPGP symmetric encryption & decryption"""
183
 
    
 
183
 
184
184
    def __init__(self):
185
185
        self.tempdir = tempfile.mkdtemp(prefix="mandos-")
186
186
        self.gpg = "gpg"
201
201
        # Only GPG version 1 has the --no-use-agent option.
202
202
        if self.gpg == "gpg" or self.gpg.endswith("/gpg"):
203
203
            self.gnupgargs.append("--no-use-agent")
204
 
    
 
204
 
205
205
    def __enter__(self):
206
206
        return self
207
 
    
 
207
 
208
208
    def __exit__(self, exc_type, exc_value, traceback):
209
209
        self._cleanup()
210
210
        return False
211
 
    
 
211
 
212
212
    def __del__(self):
213
213
        self._cleanup()
214
 
    
 
214
 
215
215
    def _cleanup(self):
216
216
        if self.tempdir is not None:
217
217
            # Delete contents of tempdir
218
218
            for root, dirs, files in os.walk(self.tempdir,
219
 
                                             topdown = False):
 
219
                                             topdown=False):
220
220
                for filename in files:
221
221
                    os.remove(os.path.join(root, filename))
222
222
                for dirname in dirs:
224
224
            # Remove tempdir
225
225
            os.rmdir(self.tempdir)
226
226
            self.tempdir = None
227
 
    
 
227
 
228
228
    def password_encode(self, password):
229
229
        # Passphrase can not be empty and can not contain newlines or
230
230
        # NUL bytes.  So we prefix it and hex encode it.
235
235
                       .replace(b"\n", b"\\n")
236
236
                       .replace(b"\0", b"\\x00"))
237
237
        return encoded
238
 
    
 
238
 
239
239
    def encrypt(self, data, password):
240
240
        passphrase = self.password_encode(password)
241
241
        with tempfile.NamedTemporaryFile(
246
246
                                     '--passphrase-file',
247
247
                                     passfile.name]
248
248
                                    + self.gnupgargs,
249
 
                                    stdin = subprocess.PIPE,
250
 
                                    stdout = subprocess.PIPE,
251
 
                                    stderr = subprocess.PIPE)
252
 
            ciphertext, err = proc.communicate(input = data)
 
249
                                    stdin=subprocess.PIPE,
 
250
                                    stdout=subprocess.PIPE,
 
251
                                    stderr=subprocess.PIPE)
 
252
            ciphertext, err = proc.communicate(input=data)
253
253
        if proc.returncode != 0:
254
254
            raise PGPError(err)
255
255
        return ciphertext
256
 
    
 
256
 
257
257
    def decrypt(self, data, password):
258
258
        passphrase = self.password_encode(password)
259
259
        with tempfile.NamedTemporaryFile(
260
 
                dir = self.tempdir) as passfile:
 
260
                dir=self.tempdir) as passfile:
261
261
            passfile.write(passphrase)
262
262
            passfile.flush()
263
263
            proc = subprocess.Popen([self.gpg, '--decrypt',
264
264
                                     '--passphrase-file',
265
265
                                     passfile.name]
266
266
                                    + self.gnupgargs,
267
 
                                    stdin = subprocess.PIPE,
268
 
                                    stdout = subprocess.PIPE,
269
 
                                    stderr = subprocess.PIPE)
270
 
            decrypted_plaintext, err = proc.communicate(input = data)
 
267
                                    stdin=subprocess.PIPE,
 
268
                                    stdout=subprocess.PIPE,
 
269
                                    stderr=subprocess.PIPE)
 
270
            decrypted_plaintext, err = proc.communicate(input=data)
271
271
        if proc.returncode != 0:
272
272
            raise PGPError(err)
273
273
        return decrypted_plaintext
274
274
 
 
275
 
275
276
# Pretend that we have an Avahi module
276
277
class Avahi(object):
277
278
    """This isn't so much a class as it is a module-like namespace.
278
279
    It is instantiated once, and simulates having an Avahi module."""
279
 
    IF_UNSPEC = -1              # avahi-common/address.h
280
 
    PROTO_UNSPEC = -1           # avahi-common/address.h
281
 
    PROTO_INET = 0              # avahi-common/address.h
282
 
    PROTO_INET6 = 1             # avahi-common/address.h
 
280
    IF_UNSPEC = -1               # avahi-common/address.h
 
281
    PROTO_UNSPEC = -1            # avahi-common/address.h
 
282
    PROTO_INET = 0               # avahi-common/address.h
 
283
    PROTO_INET6 = 1              # avahi-common/address.h
283
284
    DBUS_NAME = "org.freedesktop.Avahi"
284
285
    DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"
285
286
    DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
286
287
    DBUS_PATH_SERVER = "/"
 
288
 
287
289
    def string_array_to_txt_array(self, t):
288
290
        return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
289
291
                           for s in t), signature="ay")
290
 
    ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h
291
 
    ENTRY_GROUP_COLLISION = 3   # avahi-common/defs.h
292
 
    ENTRY_GROUP_FAILURE = 4     # avahi-common/defs.h
293
 
    SERVER_INVALID = 0          # avahi-common/defs.h
294
 
    SERVER_REGISTERING = 1      # avahi-common/defs.h
295
 
    SERVER_RUNNING = 2          # avahi-common/defs.h
296
 
    SERVER_COLLISION = 3        # avahi-common/defs.h
297
 
    SERVER_FAILURE = 4          # avahi-common/defs.h
 
292
    ENTRY_GROUP_ESTABLISHED = 2  # avahi-common/defs.h
 
293
    ENTRY_GROUP_COLLISION = 3    # avahi-common/defs.h
 
294
    ENTRY_GROUP_FAILURE = 4      # avahi-common/defs.h
 
295
    SERVER_INVALID = 0           # avahi-common/defs.h
 
296
    SERVER_REGISTERING = 1       # avahi-common/defs.h
 
297
    SERVER_RUNNING = 2           # avahi-common/defs.h
 
298
    SERVER_COLLISION = 3         # avahi-common/defs.h
 
299
    SERVER_FAILURE = 4           # avahi-common/defs.h
298
300
avahi = Avahi()
299
301
 
 
302
 
300
303
class AvahiError(Exception):
301
304
    def __init__(self, value, *args, **kwargs):
302
305
        self.value = value
314
317
 
315
318
class AvahiService(object):
316
319
    """An Avahi (Zeroconf) service.
317
 
    
 
320
 
318
321
    Attributes:
319
322
    interface: integer; avahi.IF_UNSPEC or an interface index.
320
323
               Used to optionally bind to the specified interface.
332
335
    server: D-Bus Server
333
336
    bus: dbus.SystemBus()
334
337
    """
335
 
    
 
338
 
336
339
    def __init__(self,
337
 
                 interface = avahi.IF_UNSPEC,
338
 
                 name = None,
339
 
                 servicetype = None,
340
 
                 port = None,
341
 
                 TXT = None,
342
 
                 domain = "",
343
 
                 host = "",
344
 
                 max_renames = 32768,
345
 
                 protocol = avahi.PROTO_UNSPEC,
346
 
                 bus = None):
 
340
                 interface=avahi.IF_UNSPEC,
 
341
                 name=None,
 
342
                 servicetype=None,
 
343
                 port=None,
 
344
                 TXT=None,
 
345
                 domain="",
 
346
                 host="",
 
347
                 max_renames=32768,
 
348
                 protocol=avahi.PROTO_UNSPEC,
 
349
                 bus=None):
347
350
        self.interface = interface
348
351
        self.name = name
349
352
        self.type = servicetype
358
361
        self.server = None
359
362
        self.bus = bus
360
363
        self.entry_group_state_changed_match = None
361
 
    
 
364
 
362
365
    def rename(self, remove=True):
363
366
        """Derived from the Avahi example code"""
364
367
        if self.rename_count >= self.max_renames:
384
387
                logger.critical("D-Bus Exception", exc_info=error)
385
388
                self.cleanup()
386
389
                os._exit(1)
387
 
    
 
390
 
388
391
    def remove(self):
389
392
        """Derived from the Avahi example code"""
390
393
        if self.entry_group_state_changed_match is not None:
392
395
            self.entry_group_state_changed_match = None
393
396
        if self.group is not None:
394
397
            self.group.Reset()
395
 
    
 
398
 
396
399
    def add(self):
397
400
        """Derived from the Avahi example code"""
398
401
        self.remove()
415
418
            dbus.UInt16(self.port),
416
419
            avahi.string_array_to_txt_array(self.TXT))
417
420
        self.group.Commit()
418
 
    
 
421
 
419
422
    def entry_group_state_changed(self, state, error):
420
423
        """Derived from the Avahi example code"""
421
424
        logger.debug("Avahi entry group state change: %i", state)
422
 
        
 
425
 
423
426
        if state == avahi.ENTRY_GROUP_ESTABLISHED:
424
427
            logger.debug("Zeroconf service established.")
425
428
        elif state == avahi.ENTRY_GROUP_COLLISION:
429
432
            logger.critical("Avahi: Error in group state changed %s",
430
433
                            str(error))
431
434
            raise AvahiGroupError("State changed: {!s}".format(error))
432
 
    
 
435
 
433
436
    def cleanup(self):
434
437
        """Derived from the Avahi example code"""
435
438
        if self.group is not None:
440
443
                pass
441
444
            self.group = None
442
445
        self.remove()
443
 
    
 
446
 
444
447
    def server_state_changed(self, state, error=None):
445
448
        """Derived from the Avahi example code"""
446
449
        logger.debug("Avahi server state change: %i", state)
475
478
                logger.debug("Unknown state: %r", state)
476
479
            else:
477
480
                logger.debug("Unknown state: %r: %r", state, error)
478
 
    
 
481
 
479
482
    def activate(self):
480
483
        """Derived from the Avahi example code"""
481
484
        if self.server is None:
498
501
            .format(self.name)))
499
502
        return ret
500
503
 
 
504
 
501
505
# Pretend that we have a GnuTLS module
502
506
class GnuTLS(object):
503
507
    """This isn't so much a class as it is a module-like namespace.
504
508
    It is instantiated once, and simulates having a GnuTLS module."""
505
 
    
506
 
    _library = ctypes.cdll.LoadLibrary(
507
 
        ctypes.util.find_library("gnutls"))
 
509
 
 
510
    library = ctypes.util.find_library("gnutls")
 
511
    if library is None:
 
512
        library = ctypes.util.find_library("gnutls-deb0")
 
513
    _library = ctypes.cdll.LoadLibrary(library)
 
514
    del library
508
515
    _need_version = b"3.3.0"
 
516
 
509
517
    def __init__(self):
510
 
        # Need to use class name "GnuTLS" here, since this method is
511
 
        # called before the assignment to the "gnutls" global variable
512
 
        # happens.
513
 
        if GnuTLS.check_version(self._need_version) is None:
514
 
            raise GnuTLS.Error("Needs GnuTLS {} or later"
515
 
                               .format(self._need_version))
516
 
    
 
518
        # Need to use "self" here, since this method is called before
 
519
        # the assignment to the "gnutls" global variable happens.
 
520
        if self.check_version(self._need_version) is None:
 
521
            raise self.Error("Needs GnuTLS {} or later"
 
522
                             .format(self._need_version))
 
523
 
517
524
    # Unless otherwise indicated, the constants and types below are
518
525
    # all from the gnutls/gnutls.h C header file.
519
 
    
 
526
 
520
527
    # Constants
521
528
    E_SUCCESS = 0
522
529
    E_INTERRUPTED = -52
527
534
    CRD_CERTIFICATE = 1
528
535
    E_NO_CERTIFICATE_FOUND = -49
529
536
    OPENPGP_FMT_RAW = 0         # gnutls/openpgp.h
530
 
    
 
537
 
531
538
    # Types
532
539
    class session_int(ctypes.Structure):
533
540
        _fields_ = []
534
541
    session_t = ctypes.POINTER(session_int)
 
542
 
535
543
    class certificate_credentials_st(ctypes.Structure):
536
544
        _fields_ = []
537
545
    certificate_credentials_t = ctypes.POINTER(
538
546
        certificate_credentials_st)
539
547
    certificate_type_t = ctypes.c_int
 
548
 
540
549
    class datum_t(ctypes.Structure):
541
550
        _fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
542
551
                    ('size', ctypes.c_uint)]
 
552
 
543
553
    class openpgp_crt_int(ctypes.Structure):
544
554
        _fields_ = []
545
555
    openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
546
 
    openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
 
556
    openpgp_crt_fmt_t = ctypes.c_int  # gnutls/openpgp.h
547
557
    log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
548
558
    credentials_type_t = ctypes.c_int
549
559
    transport_ptr_t = ctypes.c_void_p
550
560
    close_request_t = ctypes.c_int
551
 
    
 
561
 
552
562
    # Exceptions
553
563
    class Error(Exception):
554
564
        # We need to use the class name "GnuTLS" here, since this
555
565
        # exception might be raised from within GnuTLS.__init__,
556
566
        # which is called before the assignment to the "gnutls"
557
567
        # global variable has happened.
558
 
        def __init__(self, message = None, code = None, args=()):
 
568
        def __init__(self, message=None, code=None, args=()):
559
569
            # Default usage is by a message string, but if a return
560
570
            # code is passed, convert it to a string with
561
571
            # gnutls.strerror()
564
574
                message = GnuTLS.strerror(code)
565
575
            return super(GnuTLS.Error, self).__init__(
566
576
                message, *args)
567
 
    
 
577
 
568
578
    class CertificateSecurityError(Error):
569
579
        pass
570
 
    
 
580
 
571
581
    # Classes
572
582
    class Credentials(object):
573
583
        def __init__(self):
575
585
            gnutls.certificate_allocate_credentials(
576
586
                ctypes.byref(self._c_object))
577
587
            self.type = gnutls.CRD_CERTIFICATE
578
 
        
 
588
 
579
589
        def __del__(self):
580
590
            gnutls.certificate_free_credentials(self._c_object)
581
 
    
 
591
 
582
592
    class ClientSession(object):
583
 
        def __init__(self, socket, credentials = None):
 
593
        def __init__(self, socket, credentials=None):
584
594
            self._c_object = gnutls.session_t()
585
595
            gnutls.init(ctypes.byref(self._c_object), gnutls.CLIENT)
586
596
            gnutls.set_default_priority(self._c_object)
594
604
                                   ctypes.cast(credentials._c_object,
595
605
                                               ctypes.c_void_p))
596
606
            self.credentials = credentials
597
 
        
 
607
 
598
608
        def __del__(self):
599
609
            gnutls.deinit(self._c_object)
600
 
        
 
610
 
601
611
        def handshake(self):
602
612
            return gnutls.handshake(self._c_object)
603
 
        
 
613
 
604
614
        def send(self, data):
605
615
            data = bytes(data)
606
616
            data_len = len(data)
608
618
                data_len -= gnutls.record_send(self._c_object,
609
619
                                               data[-data_len:],
610
620
                                               data_len)
611
 
        
 
621
 
612
622
        def bye(self):
613
623
            return gnutls.bye(self._c_object, gnutls.SHUT_RDWR)
614
 
    
 
624
 
615
625
    # Error handling functions
616
626
    def _error_code(result):
617
627
        """A function to raise exceptions on errors, suitable
619
629
        if result >= 0:
620
630
            return result
621
631
        if result == gnutls.E_NO_CERTIFICATE_FOUND:
622
 
            raise gnutls.CertificateSecurityError(code = result)
623
 
        raise gnutls.Error(code = result)
624
 
    
 
632
            raise gnutls.CertificateSecurityError(code=result)
 
633
        raise gnutls.Error(code=result)
 
634
 
625
635
    def _retry_on_error(result, func, arguments):
626
636
        """A function to retry on some errors, suitable
627
637
        for the 'errcheck' attribute on ctypes functions"""
630
640
                return _error_code(result)
631
641
            result = func(*arguments)
632
642
        return result
633
 
    
 
643
 
634
644
    # Unless otherwise indicated, the function declarations below are
635
645
    # all from the gnutls/gnutls.h C header file.
636
 
    
 
646
 
637
647
    # Functions
638
648
    priority_set_direct = _library.gnutls_priority_set_direct
639
649
    priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
640
650
                                    ctypes.POINTER(ctypes.c_char_p)]
641
651
    priority_set_direct.restype = _error_code
642
 
    
 
652
 
643
653
    init = _library.gnutls_init
644
654
    init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
645
655
    init.restype = _error_code
646
 
    
 
656
 
647
657
    set_default_priority = _library.gnutls_set_default_priority
648
658
    set_default_priority.argtypes = [session_t]
649
659
    set_default_priority.restype = _error_code
650
 
    
 
660
 
651
661
    record_send = _library.gnutls_record_send
652
662
    record_send.argtypes = [session_t, ctypes.c_void_p,
653
663
                            ctypes.c_size_t]
654
664
    record_send.restype = ctypes.c_ssize_t
655
665
    record_send.errcheck = _retry_on_error
656
 
    
 
666
 
657
667
    certificate_allocate_credentials = (
658
668
        _library.gnutls_certificate_allocate_credentials)
659
669
    certificate_allocate_credentials.argtypes = [
660
670
        ctypes.POINTER(certificate_credentials_t)]
661
671
    certificate_allocate_credentials.restype = _error_code
662
 
    
 
672
 
663
673
    certificate_free_credentials = (
664
674
        _library.gnutls_certificate_free_credentials)
665
 
    certificate_free_credentials.argtypes = [certificate_credentials_t]
 
675
    certificate_free_credentials.argtypes = [
 
676
        certificate_credentials_t]
666
677
    certificate_free_credentials.restype = None
667
 
    
 
678
 
668
679
    handshake_set_private_extensions = (
669
680
        _library.gnutls_handshake_set_private_extensions)
670
681
    handshake_set_private_extensions.argtypes = [session_t,
671
682
                                                 ctypes.c_int]
672
683
    handshake_set_private_extensions.restype = None
673
 
    
 
684
 
674
685
    credentials_set = _library.gnutls_credentials_set
675
686
    credentials_set.argtypes = [session_t, credentials_type_t,
676
687
                                ctypes.c_void_p]
677
688
    credentials_set.restype = _error_code
678
 
    
 
689
 
679
690
    strerror = _library.gnutls_strerror
680
691
    strerror.argtypes = [ctypes.c_int]
681
692
    strerror.restype = ctypes.c_char_p
682
 
    
 
693
 
683
694
    certificate_type_get = _library.gnutls_certificate_type_get
684
695
    certificate_type_get.argtypes = [session_t]
685
696
    certificate_type_get.restype = _error_code
686
 
    
 
697
 
687
698
    certificate_get_peers = _library.gnutls_certificate_get_peers
688
699
    certificate_get_peers.argtypes = [session_t,
689
700
                                      ctypes.POINTER(ctypes.c_uint)]
690
701
    certificate_get_peers.restype = ctypes.POINTER(datum_t)
691
 
    
 
702
 
692
703
    global_set_log_level = _library.gnutls_global_set_log_level
693
704
    global_set_log_level.argtypes = [ctypes.c_int]
694
705
    global_set_log_level.restype = None
695
 
    
 
706
 
696
707
    global_set_log_function = _library.gnutls_global_set_log_function
697
708
    global_set_log_function.argtypes = [log_func]
698
709
    global_set_log_function.restype = None
699
 
    
 
710
 
700
711
    deinit = _library.gnutls_deinit
701
712
    deinit.argtypes = [session_t]
702
713
    deinit.restype = None
703
 
    
 
714
 
704
715
    handshake = _library.gnutls_handshake
705
716
    handshake.argtypes = [session_t]
706
717
    handshake.restype = _error_code
707
718
    handshake.errcheck = _retry_on_error
708
 
    
 
719
 
709
720
    transport_set_ptr = _library.gnutls_transport_set_ptr
710
721
    transport_set_ptr.argtypes = [session_t, transport_ptr_t]
711
722
    transport_set_ptr.restype = None
712
 
    
 
723
 
713
724
    bye = _library.gnutls_bye
714
725
    bye.argtypes = [session_t, close_request_t]
715
726
    bye.restype = _error_code
716
727
    bye.errcheck = _retry_on_error
717
 
    
 
728
 
718
729
    check_version = _library.gnutls_check_version
719
730
    check_version.argtypes = [ctypes.c_char_p]
720
731
    check_version.restype = ctypes.c_char_p
721
 
    
 
732
 
722
733
    # All the function declarations below are from gnutls/openpgp.h
723
 
    
 
734
 
724
735
    openpgp_crt_init = _library.gnutls_openpgp_crt_init
725
736
    openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
726
737
    openpgp_crt_init.restype = _error_code
727
 
    
 
738
 
728
739
    openpgp_crt_import = _library.gnutls_openpgp_crt_import
729
740
    openpgp_crt_import.argtypes = [openpgp_crt_t,
730
741
                                   ctypes.POINTER(datum_t),
731
742
                                   openpgp_crt_fmt_t]
732
743
    openpgp_crt_import.restype = _error_code
733
 
    
 
744
 
734
745
    openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
735
746
    openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
736
747
                                        ctypes.POINTER(ctypes.c_uint)]
737
748
    openpgp_crt_verify_self.restype = _error_code
738
 
    
 
749
 
739
750
    openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
740
751
    openpgp_crt_deinit.argtypes = [openpgp_crt_t]
741
752
    openpgp_crt_deinit.restype = None
742
 
    
 
753
 
743
754
    openpgp_crt_get_fingerprint = (
744
755
        _library.gnutls_openpgp_crt_get_fingerprint)
745
756
    openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
747
758
                                            ctypes.POINTER(
748
759
                                                ctypes.c_size_t)]
749
760
    openpgp_crt_get_fingerprint.restype = _error_code
750
 
    
 
761
 
751
762
    # Remove non-public functions
752
763
    del _error_code, _retry_on_error
753
764
# Create the global "gnutls" object, simulating a module
754
765
gnutls = GnuTLS()
755
766
 
 
767
 
756
768
def call_pipe(connection,       # : multiprocessing.Connection
757
769
              func, *args, **kwargs):
758
770
    """This function is meant to be called by multiprocessing.Process
759
 
    
 
771
 
760
772
    This function runs func(*args, **kwargs), and writes the resulting
761
773
    return value on the provided multiprocessing.Connection.
762
774
    """
763
775
    connection.send(func(*args, **kwargs))
764
776
    connection.close()
765
777
 
 
778
 
766
779
class Client(object):
767
780
    """A representation of a client host served by this server.
768
 
    
 
781
 
769
782
    Attributes:
770
783
    approved:   bool(); 'None' if not yet approved/disapproved
771
784
    approval_delay: datetime.timedelta(); Time to wait for approval
808
821
                disabled, or None
809
822
    server_settings: The server_settings dict from main()
810
823
    """
811
 
    
 
824
 
812
825
    runtime_expansions = ("approval_delay", "approval_duration",
813
826
                          "created", "enabled", "expires",
814
827
                          "fingerprint", "host", "interval",
825
838
        "approved_by_default": "True",
826
839
        "enabled": "True",
827
840
    }
828
 
    
 
841
 
829
842
    @staticmethod
830
843
    def config_parser(config):
831
844
        """Construct a new dict of client settings of this form:
838
851
        for client_name in config.sections():
839
852
            section = dict(config.items(client_name))
840
853
            client = settings[client_name] = {}
841
 
            
 
854
 
842
855
            client["host"] = section["host"]
843
856
            # Reformat values from string types to Python types
844
857
            client["approved_by_default"] = config.getboolean(
845
858
                client_name, "approved_by_default")
846
859
            client["enabled"] = config.getboolean(client_name,
847
860
                                                  "enabled")
848
 
            
 
861
 
849
862
            # Uppercase and remove spaces from fingerprint for later
850
863
            # comparison purposes with return value from the
851
864
            # fingerprint() function
875
888
            client["last_approval_request"] = None
876
889
            client["last_checked_ok"] = None
877
890
            client["last_checker_status"] = -2
878
 
        
 
891
 
879
892
        return settings
880
 
    
881
 
    def __init__(self, settings, name = None, server_settings=None):
 
893
 
 
894
    def __init__(self, settings, name=None, server_settings=None):
882
895
        self.name = name
883
896
        if server_settings is None:
884
897
            server_settings = {}
886
899
        # adding all client settings
887
900
        for setting, value in settings.items():
888
901
            setattr(self, setting, value)
889
 
        
 
902
 
890
903
        if self.enabled:
891
904
            if not hasattr(self, "last_enabled"):
892
905
                self.last_enabled = datetime.datetime.utcnow()
896
909
        else:
897
910
            self.last_enabled = None
898
911
            self.expires = None
899
 
        
 
912
 
900
913
        logger.debug("Creating client %r", self.name)
901
914
        logger.debug("  Fingerprint: %s", self.fingerprint)
902
915
        self.created = settings.get("created",
903
916
                                    datetime.datetime.utcnow())
904
 
        
 
917
 
905
918
        # attributes specific for this server instance
906
919
        self.checker = None
907
920
        self.checker_initiator_tag = None
916
929
                                 for attr in self.__dict__.keys()
917
930
                                 if not attr.startswith("_")]
918
931
        self.client_structure.append("client_structure")
919
 
        
 
932
 
920
933
        for name, t in inspect.getmembers(
921
934
                type(self), lambda obj: isinstance(obj, property)):
922
935
            if not name.startswith("_"):
923
936
                self.client_structure.append(name)
924
 
    
 
937
 
925
938
    # Send notice to process children that client state has changed
926
939
    def send_changedstate(self):
927
940
        with self.changedstate:
928
941
            self.changedstate.notify_all()
929
 
    
 
942
 
930
943
    def enable(self):
931
944
        """Start this client's checker and timeout hooks"""
932
945
        if getattr(self, "enabled", False):
937
950
        self.last_enabled = datetime.datetime.utcnow()
938
951
        self.init_checker()
939
952
        self.send_changedstate()
940
 
    
 
953
 
941
954
    def disable(self, quiet=True):
942
955
        """Disable this client."""
943
956
        if not getattr(self, "enabled", False):
957
970
            self.send_changedstate()
958
971
        # Do not run this again if called by a GLib.timeout_add
959
972
        return False
960
 
    
 
973
 
961
974
    def __del__(self):
962
975
        self.disable()
963
 
    
 
976
 
964
977
    def init_checker(self):
965
978
        # Schedule a new checker to be started an 'interval' from now,
966
979
        # and every interval from then on.
976
989
            int(self.timeout.total_seconds() * 1000), self.disable)
977
990
        # Also start a new checker *right now*.
978
991
        self.start_checker()
979
 
    
 
992
 
980
993
    def checker_callback(self, source, condition, connection,
981
994
                         command):
982
995
        """The checker has completed, so take appropriate actions."""
985
998
        # Read return code from connection (see call_pipe)
986
999
        returncode = connection.recv()
987
1000
        connection.close()
988
 
        
 
1001
 
989
1002
        if returncode >= 0:
990
1003
            self.last_checker_status = returncode
991
1004
            self.last_checker_signal = None
1001
1014
            logger.warning("Checker for %(name)s crashed?",
1002
1015
                           vars(self))
1003
1016
        return False
1004
 
    
 
1017
 
1005
1018
    def checked_ok(self):
1006
1019
        """Assert that the client has been seen, alive and well."""
1007
1020
        self.last_checked_ok = datetime.datetime.utcnow()
1008
1021
        self.last_checker_status = 0
1009
1022
        self.last_checker_signal = None
1010
1023
        self.bump_timeout()
1011
 
    
 
1024
 
1012
1025
    def bump_timeout(self, timeout=None):
1013
1026
        """Bump up the timeout for this client."""
1014
1027
        if timeout is None:
1020
1033
            self.disable_initiator_tag = GLib.timeout_add(
1021
1034
                int(timeout.total_seconds() * 1000), self.disable)
1022
1035
            self.expires = datetime.datetime.utcnow() + timeout
1023
 
    
 
1036
 
1024
1037
    def need_approval(self):
1025
1038
        self.last_approval_request = datetime.datetime.utcnow()
1026
 
    
 
1039
 
1027
1040
    def start_checker(self):
1028
1041
        """Start a new checker subprocess if one is not running.
1029
 
        
 
1042
 
1030
1043
        If a checker already exists, leave it running and do
1031
1044
        nothing."""
1032
1045
        # The reason for not killing a running checker is that if we
1037
1050
        # checkers alone, the checker would have to take more time
1038
1051
        # than 'timeout' for the client to be disabled, which is as it
1039
1052
        # should be.
1040
 
        
 
1053
 
1041
1054
        if self.checker is not None and not self.checker.is_alive():
1042
1055
            logger.warning("Checker was not alive; joining")
1043
1056
            self.checker.join()
1047
1060
            # Escape attributes for the shell
1048
1061
            escaped_attrs = {
1049
1062
                attr: re.escape(str(getattr(self, attr)))
1050
 
                for attr in self.runtime_expansions }
 
1063
                for attr in self.runtime_expansions}
1051
1064
            try:
1052
1065
                command = self.checker_command % escaped_attrs
1053
1066
            except TypeError as error:
1065
1078
            # The exception is when not debugging but nevertheless
1066
1079
            # running in the foreground; use the previously
1067
1080
            # created wnull.
1068
 
            popen_args = { "close_fds": True,
1069
 
                           "shell": True,
1070
 
                           "cwd": "/" }
 
1081
            popen_args = {"close_fds": True,
 
1082
                          "shell": True,
 
1083
                          "cwd": "/"}
1071
1084
            if (not self.server_settings["debug"]
1072
1085
                and self.server_settings["foreground"]):
1073
1086
                popen_args.update({"stdout": wnull,
1074
 
                                   "stderr": wnull })
1075
 
            pipe = multiprocessing.Pipe(duplex = False)
 
1087
                                   "stderr": wnull})
 
1088
            pipe = multiprocessing.Pipe(duplex=False)
1076
1089
            self.checker = multiprocessing.Process(
1077
 
                target = call_pipe,
1078
 
                args = (pipe[1], subprocess.call, command),
1079
 
                kwargs = popen_args)
 
1090
                target=call_pipe,
 
1091
                args=(pipe[1], subprocess.call, command),
 
1092
                kwargs=popen_args)
1080
1093
            self.checker.start()
1081
1094
            self.checker_callback_tag = GLib.io_add_watch(
1082
1095
                pipe[0].fileno(), GLib.IO_IN,
1083
1096
                self.checker_callback, pipe[0], command)
1084
1097
        # Re-run this periodically if run by GLib.timeout_add
1085
1098
        return True
1086
 
    
 
1099
 
1087
1100
    def stop_checker(self):
1088
1101
        """Force the checker process, if any, to stop."""
1089
1102
        if self.checker_callback_tag:
1102
1115
                          byte_arrays=False):
1103
1116
    """Decorators for marking methods of a DBusObjectWithProperties to
1104
1117
    become properties on the D-Bus.
1105
 
    
 
1118
 
1106
1119
    The decorated method will be called with no arguments by "Get"
1107
1120
    and with one argument by "Set".
1108
 
    
 
1121
 
1109
1122
    The parameters, where they are supported, are the same as
1110
1123
    dbus.service.method, except there is only "signature", since the
1111
1124
    type from Get() and the type sent to Set() is the same.
1115
1128
    if byte_arrays and signature != "ay":
1116
1129
        raise ValueError("Byte arrays not supported for non-'ay'"
1117
1130
                         " signature {!r}".format(signature))
1118
 
    
 
1131
 
1119
1132
    def decorator(func):
1120
1133
        func._dbus_is_property = True
1121
1134
        func._dbus_interface = dbus_interface
1124
1137
        func._dbus_name = func.__name__
1125
1138
        if func._dbus_name.endswith("_dbus_property"):
1126
1139
            func._dbus_name = func._dbus_name[:-14]
1127
 
        func._dbus_get_args_options = {'byte_arrays': byte_arrays }
 
1140
        func._dbus_get_args_options = {'byte_arrays': byte_arrays}
1128
1141
        return func
1129
 
    
 
1142
 
1130
1143
    return decorator
1131
1144
 
1132
1145
 
1133
1146
def dbus_interface_annotations(dbus_interface):
1134
1147
    """Decorator for marking functions returning interface annotations
1135
 
    
 
1148
 
1136
1149
    Usage:
1137
 
    
 
1150
 
1138
1151
    @dbus_interface_annotations("org.example.Interface")
1139
1152
    def _foo(self):  # Function name does not matter
1140
1153
        return {"org.freedesktop.DBus.Deprecated": "true",
1141
1154
                "org.freedesktop.DBus.Property.EmitsChangedSignal":
1142
1155
                    "false"}
1143
1156
    """
1144
 
    
 
1157
 
1145
1158
    def decorator(func):
1146
1159
        func._dbus_is_interface = True
1147
1160
        func._dbus_interface = dbus_interface
1148
1161
        func._dbus_name = dbus_interface
1149
1162
        return func
1150
 
    
 
1163
 
1151
1164
    return decorator
1152
1165
 
1153
1166
 
1154
1167
def dbus_annotations(annotations):
1155
1168
    """Decorator to annotate D-Bus methods, signals or properties
1156
1169
    Usage:
1157
 
    
 
1170
 
1158
1171
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
1159
1172
                       "org.freedesktop.DBus.Property."
1160
1173
                       "EmitsChangedSignal": "false"})
1162
1175
                           access="r")
1163
1176
    def Property_dbus_property(self):
1164
1177
        return dbus.Boolean(False)
1165
 
    
 
1178
 
1166
1179
    See also the DBusObjectWithAnnotations class.
1167
1180
    """
1168
 
    
 
1181
 
1169
1182
    def decorator(func):
1170
1183
        func._dbus_annotations = annotations
1171
1184
        return func
1172
 
    
 
1185
 
1173
1186
    return decorator
1174
1187
 
1175
1188
 
1193
1206
 
1194
1207
class DBusObjectWithAnnotations(dbus.service.Object):
1195
1208
    """A D-Bus object with annotations.
1196
 
    
 
1209
 
1197
1210
    Classes inheriting from this can use the dbus_annotations
1198
1211
    decorator to add annotations to methods or signals.
1199
1212
    """
1200
 
    
 
1213
 
1201
1214
    @staticmethod
1202
1215
    def _is_dbus_thing(thing):
1203
1216
        """Returns a function testing if an attribute is a D-Bus thing
1204
 
        
 
1217
 
1205
1218
        If called like _is_dbus_thing("method") it returns a function
1206
1219
        suitable for use as predicate to inspect.getmembers().
1207
1220
        """
1208
1221
        return lambda obj: getattr(obj, "_dbus_is_{}".format(thing),
1209
1222
                                   False)
1210
 
    
 
1223
 
1211
1224
    def _get_all_dbus_things(self, thing):
1212
1225
        """Returns a generator of (name, attribute) pairs
1213
1226
        """
1216
1229
                for cls in self.__class__.__mro__
1217
1230
                for name, athing in
1218
1231
                inspect.getmembers(cls, self._is_dbus_thing(thing)))
1219
 
    
 
1232
 
1220
1233
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1221
 
                         out_signature = "s",
1222
 
                         path_keyword = 'object_path',
1223
 
                         connection_keyword = 'connection')
 
1234
                         out_signature="s",
 
1235
                         path_keyword='object_path',
 
1236
                         connection_keyword='connection')
1224
1237
    def Introspect(self, object_path, connection):
1225
1238
        """Overloading of standard D-Bus method.
1226
 
        
 
1239
 
1227
1240
        Inserts annotation tags on methods and signals.
1228
1241
        """
1229
1242
        xmlstring = dbus.service.Object.Introspect(self, object_path,
1230
1243
                                                   connection)
1231
1244
        try:
1232
1245
            document = xml.dom.minidom.parseString(xmlstring)
1233
 
            
 
1246
 
1234
1247
            for if_tag in document.getElementsByTagName("interface"):
1235
1248
                # Add annotation tags
1236
1249
                for typ in ("method", "signal"):
1263
1276
                    if_tag.appendChild(ann_tag)
1264
1277
                # Fix argument name for the Introspect method itself
1265
1278
                if (if_tag.getAttribute("name")
1266
 
                                == dbus.INTROSPECTABLE_IFACE):
 
1279
                    == dbus.INTROSPECTABLE_IFACE):
1267
1280
                    for cn in if_tag.getElementsByTagName("method"):
1268
1281
                        if cn.getAttribute("name") == "Introspect":
1269
1282
                            for arg in cn.getElementsByTagName("arg"):
1282
1295
 
1283
1296
class DBusObjectWithProperties(DBusObjectWithAnnotations):
1284
1297
    """A D-Bus object with properties.
1285
 
    
 
1298
 
1286
1299
    Classes inheriting from this can use the dbus_service_property
1287
1300
    decorator to expose methods as D-Bus properties.  It exposes the
1288
1301
    standard Get(), Set(), and GetAll() methods on the D-Bus.
1289
1302
    """
1290
 
    
 
1303
 
1291
1304
    def _get_dbus_property(self, interface_name, property_name):
1292
1305
        """Returns a bound method if one exists which is a D-Bus
1293
1306
        property with the specified name and interface.
1298
1311
                if (value._dbus_name == property_name
1299
1312
                    and value._dbus_interface == interface_name):
1300
1313
                    return value.__get__(self)
1301
 
        
 
1314
 
1302
1315
        # No such property
1303
1316
        raise DBusPropertyNotFound("{}:{}.{}".format(
1304
1317
            self.dbus_object_path, interface_name, property_name))
1305
 
    
 
1318
 
1306
1319
    @classmethod
1307
1320
    def _get_all_interface_names(cls):
1308
1321
        """Get a sequence of all interfaces supported by an object"""
1311
1324
                                     for x in (inspect.getmro(cls))
1312
1325
                                     for attr in dir(x))
1313
1326
                if name is not None)
1314
 
    
 
1327
 
1315
1328
    @dbus.service.method(dbus.PROPERTIES_IFACE,
1316
1329
                         in_signature="ss",
1317
1330
                         out_signature="v")
1325
1338
        if not hasattr(value, "variant_level"):
1326
1339
            return value
1327
1340
        return type(value)(value, variant_level=value.variant_level+1)
1328
 
    
 
1341
 
1329
1342
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
1330
1343
    def Set(self, interface_name, property_name, value):
1331
1344
        """Standard D-Bus property Set() method, see D-Bus standard.
1343
1356
            value = dbus.ByteArray(b''.join(chr(byte)
1344
1357
                                            for byte in value))
1345
1358
        prop(value)
1346
 
    
 
1359
 
1347
1360
    @dbus.service.method(dbus.PROPERTIES_IFACE,
1348
1361
                         in_signature="s",
1349
1362
                         out_signature="a{sv}")
1350
1363
    def GetAll(self, interface_name):
1351
1364
        """Standard D-Bus property GetAll() method, see D-Bus
1352
1365
        standard.
1353
 
        
 
1366
 
1354
1367
        Note: Will not include properties with access="write".
1355
1368
        """
1356
1369
        properties = {}
1367
1380
                properties[name] = value
1368
1381
                continue
1369
1382
            properties[name] = type(value)(
1370
 
                value, variant_level = value.variant_level + 1)
 
1383
                value, variant_level=value.variant_level + 1)
1371
1384
        return dbus.Dictionary(properties, signature="sv")
1372
 
    
 
1385
 
1373
1386
    @dbus.service.signal(dbus.PROPERTIES_IFACE, signature="sa{sv}as")
1374
1387
    def PropertiesChanged(self, interface_name, changed_properties,
1375
1388
                          invalidated_properties):
1377
1390
        standard.
1378
1391
        """
1379
1392
        pass
1380
 
    
 
1393
 
1381
1394
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1382
1395
                         out_signature="s",
1383
1396
                         path_keyword='object_path',
1384
1397
                         connection_keyword='connection')
1385
1398
    def Introspect(self, object_path, connection):
1386
1399
        """Overloading of standard D-Bus method.
1387
 
        
 
1400
 
1388
1401
        Inserts property tags and interface annotation tags.
1389
1402
        """
1390
1403
        xmlstring = DBusObjectWithAnnotations.Introspect(self,
1392
1405
                                                         connection)
1393
1406
        try:
1394
1407
            document = xml.dom.minidom.parseString(xmlstring)
1395
 
            
 
1408
 
1396
1409
            def make_tag(document, name, prop):
1397
1410
                e = document.createElement("property")
1398
1411
                e.setAttribute("name", name)
1399
1412
                e.setAttribute("type", prop._dbus_signature)
1400
1413
                e.setAttribute("access", prop._dbus_access)
1401
1414
                return e
1402
 
            
 
1415
 
1403
1416
            for if_tag in document.getElementsByTagName("interface"):
1404
1417
                # Add property tags
1405
1418
                for tag in (make_tag(document, name, prop)
1452
1465
except AttributeError:
1453
1466
    dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1454
1467
 
 
1468
 
1455
1469
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1456
1470
    """A D-Bus object with an ObjectManager.
1457
 
    
 
1471
 
1458
1472
    Classes inheriting from this exposes the standard
1459
1473
    GetManagedObjects call and the InterfacesAdded and
1460
1474
    InterfacesRemoved signals on the standard
1461
1475
    "org.freedesktop.DBus.ObjectManager" interface.
1462
 
    
 
1476
 
1463
1477
    Note: No signals are sent automatically; they must be sent
1464
1478
    manually.
1465
1479
    """
1466
1480
    @dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1467
 
                         out_signature = "a{oa{sa{sv}}}")
 
1481
                         out_signature="a{oa{sa{sv}}}")
1468
1482
    def GetManagedObjects(self):
1469
1483
        """This function must be overridden"""
1470
1484
        raise NotImplementedError()
1471
 
    
 
1485
 
1472
1486
    @dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1473
 
                         signature = "oa{sa{sv}}")
 
1487
                         signature="oa{sa{sv}}")
1474
1488
    def InterfacesAdded(self, object_path, interfaces_and_properties):
1475
1489
        pass
1476
 
    
1477
 
    @dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
 
1490
 
 
1491
    @dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature="oas")
1478
1492
    def InterfacesRemoved(self, object_path, interfaces):
1479
1493
        pass
1480
 
    
 
1494
 
1481
1495
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1482
 
                         out_signature = "s",
1483
 
                         path_keyword = 'object_path',
1484
 
                         connection_keyword = 'connection')
 
1496
                         out_signature="s",
 
1497
                         path_keyword='object_path',
 
1498
                         connection_keyword='connection')
1485
1499
    def Introspect(self, object_path, connection):
1486
1500
        """Overloading of standard D-Bus method.
1487
 
        
 
1501
 
1488
1502
        Override return argument name of GetManagedObjects to be
1489
1503
        "objpath_interfaces_and_properties"
1490
1504
        """
1493
1507
                                                         connection)
1494
1508
        try:
1495
1509
            document = xml.dom.minidom.parseString(xmlstring)
1496
 
            
 
1510
 
1497
1511
            for if_tag in document.getElementsByTagName("interface"):
1498
1512
                # Fix argument name for the GetManagedObjects method
1499
1513
                if (if_tag.getAttribute("name")
1500
 
                                == dbus.OBJECT_MANAGER_IFACE):
 
1514
                    == dbus.OBJECT_MANAGER_IFACE):
1501
1515
                    for cn in if_tag.getElementsByTagName("method"):
1502
1516
                        if (cn.getAttribute("name")
1503
1517
                            == "GetManagedObjects"):
1513
1527
        except (AttributeError, xml.dom.DOMException,
1514
1528
                xml.parsers.expat.ExpatError) as error:
1515
1529
            logger.error("Failed to override Introspection method",
1516
 
                         exc_info = error)
 
1530
                         exc_info=error)
1517
1531
        return xmlstring
1518
1532
 
 
1533
 
1519
1534
def datetime_to_dbus(dt, variant_level=0):
1520
1535
    """Convert a UTC datetime.datetime() to a D-Bus type."""
1521
1536
    if dt is None:
1522
 
        return dbus.String("", variant_level = variant_level)
 
1537
        return dbus.String("", variant_level=variant_level)
1523
1538
    return dbus.String(dt.isoformat(), variant_level=variant_level)
1524
1539
 
1525
1540
 
1528
1543
    dbus.service.Object, it will add alternate D-Bus attributes with
1529
1544
    interface names according to the "alt_interface_names" mapping.
1530
1545
    Usage:
1531
 
    
 
1546
 
1532
1547
    @alternate_dbus_interfaces({"org.example.Interface":
1533
1548
                                    "net.example.AlternateInterface"})
1534
1549
    class SampleDBusObject(dbus.service.Object):
1535
1550
        @dbus.service.method("org.example.Interface")
1536
1551
        def SampleDBusMethod():
1537
1552
            pass
1538
 
    
 
1553
 
1539
1554
    The above "SampleDBusMethod" on "SampleDBusObject" will be
1540
1555
    reachable via two interfaces: "org.example.Interface" and
1541
1556
    "net.example.AlternateInterface", the latter of which will have
1542
1557
    its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1543
1558
    "true", unless "deprecate" is passed with a False value.
1544
 
    
 
1559
 
1545
1560
    This works for methods and signals, and also for D-Bus properties
1546
1561
    (from DBusObjectWithProperties) and interfaces (from the
1547
1562
    dbus_interface_annotations decorator).
1548
1563
    """
1549
 
    
 
1564
 
1550
1565
    def wrapper(cls):
1551
1566
        for orig_interface_name, alt_interface_name in (
1552
1567
                alt_interface_names.items()):
1592
1607
                            attribute._dbus_annotations)
1593
1608
                    except AttributeError:
1594
1609
                        pass
 
1610
 
1595
1611
                    # Define a creator of a function to call both the
1596
1612
                    # original and alternate functions, so both the
1597
1613
                    # original and alternate signals gets sent when
1600
1616
                        """This function is a scope container to pass
1601
1617
                        func1 and func2 to the "call_both" function
1602
1618
                        outside of its arguments"""
1603
 
                        
 
1619
 
1604
1620
                        @functools.wraps(func2)
1605
1621
                        def call_both(*args, **kwargs):
1606
1622
                            """This function will emit two D-Bus
1607
1623
                            signals by calling func1 and func2"""
1608
1624
                            func1(*args, **kwargs)
1609
1625
                            func2(*args, **kwargs)
1610
 
                        # Make wrapper function look like a D-Bus signal
 
1626
                        # Make wrapper function look like a D-Bus
 
1627
                        # signal
1611
1628
                        for name, attr in inspect.getmembers(func2):
1612
1629
                            if name.startswith("_dbus_"):
1613
1630
                                setattr(call_both, name, attr)
1614
 
                        
 
1631
 
1615
1632
                        return call_both
1616
1633
                    # Create the "call_both" function and add it to
1617
1634
                    # the class
1663
1680
                        (copy_function(attribute)))
1664
1681
            if deprecate:
1665
1682
                # Deprecate all alternate interfaces
1666
 
                iname="_AlternateDBusNames_interface_annotation{}"
 
1683
                iname = "_AlternateDBusNames_interface_annotation{}"
1667
1684
                for interface_name in interface_names:
1668
 
                    
 
1685
 
1669
1686
                    @dbus_interface_annotations(interface_name)
1670
1687
                    def func(self):
1671
 
                        return { "org.freedesktop.DBus.Deprecated":
1672
 
                                 "true" }
 
1688
                        return {"org.freedesktop.DBus.Deprecated":
 
1689
                                "true"}
1673
1690
                    # Find an unused name
1674
1691
                    for aname in (iname.format(i)
1675
1692
                                  for i in itertools.count()):
1686
1703
                    cls = type("{}Alternate".format(cls.__name__),
1687
1704
                               (cls, ), attr)
1688
1705
        return cls
1689
 
    
 
1706
 
1690
1707
    return wrapper
1691
1708
 
1692
1709
 
1694
1711
                            "se.bsnet.fukt.Mandos"})
1695
1712
class ClientDBus(Client, DBusObjectWithProperties):
1696
1713
    """A Client class using D-Bus
1697
 
    
 
1714
 
1698
1715
    Attributes:
1699
1716
    dbus_object_path: dbus.ObjectPath
1700
1717
    bus: dbus.SystemBus()
1701
1718
    """
1702
 
    
 
1719
 
1703
1720
    runtime_expansions = (Client.runtime_expansions
1704
1721
                          + ("dbus_object_path", ))
1705
 
    
 
1722
 
1706
1723
    _interface = "se.recompile.Mandos.Client"
1707
 
    
 
1724
 
1708
1725
    # dbus.service.Object doesn't use super(), so we can't either.
1709
 
    
1710
 
    def __init__(self, bus = None, *args, **kwargs):
 
1726
 
 
1727
    def __init__(self, bus=None, *args, **kwargs):
1711
1728
        self.bus = bus
1712
1729
        Client.__init__(self, *args, **kwargs)
1713
1730
        # Only now, when this client is initialized, can it show up on
1719
1736
            "/clients/" + client_object_name)
1720
1737
        DBusObjectWithProperties.__init__(self, self.bus,
1721
1738
                                          self.dbus_object_path)
1722
 
    
 
1739
 
1723
1740
    def notifychangeproperty(transform_func, dbus_name,
1724
1741
                             type_func=lambda x: x,
1725
1742
                             variant_level=1,
1727
1744
                             _interface=_interface):
1728
1745
        """ Modify a variable so that it's a property which announces
1729
1746
        its changes to DBus.
1730
 
        
 
1747
 
1731
1748
        transform_fun: Function that takes a value and a variant_level
1732
1749
                       and transforms it to a D-Bus type.
1733
1750
        dbus_name: D-Bus name of the variable
1736
1753
        variant_level: D-Bus variant level.  Default: 1
1737
1754
        """
1738
1755
        attrname = "_{}".format(dbus_name)
1739
 
        
 
1756
 
1740
1757
        def setter(self, value):
1741
1758
            if hasattr(self, "dbus_object_path"):
1742
1759
                if (not hasattr(self, attrname) or
1749
1766
                    else:
1750
1767
                        dbus_value = transform_func(
1751
1768
                            type_func(value),
1752
 
                            variant_level = variant_level)
 
1769
                            variant_level=variant_level)
1753
1770
                        self.PropertyChanged(dbus.String(dbus_name),
1754
1771
                                             dbus_value)
1755
1772
                        self.PropertiesChanged(
1756
1773
                            _interface,
1757
 
                            dbus.Dictionary({ dbus.String(dbus_name):
1758
 
                                              dbus_value }),
 
1774
                            dbus.Dictionary({dbus.String(dbus_name):
 
1775
                                             dbus_value}),
1759
1776
                            dbus.Array())
1760
1777
            setattr(self, attrname, value)
1761
 
        
 
1778
 
1762
1779
        return property(lambda self: getattr(self, attrname), setter)
1763
 
    
 
1780
 
1764
1781
    expires = notifychangeproperty(datetime_to_dbus, "Expires")
1765
1782
    approvals_pending = notifychangeproperty(dbus.Boolean,
1766
1783
                                             "ApprovalPending",
1767
 
                                             type_func = bool)
 
1784
                                             type_func=bool)
1768
1785
    enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1769
1786
    last_enabled = notifychangeproperty(datetime_to_dbus,
1770
1787
                                        "LastEnabled")
1771
1788
    checker = notifychangeproperty(
1772
1789
        dbus.Boolean, "CheckerRunning",
1773
 
        type_func = lambda checker: checker is not None)
 
1790
        type_func=lambda checker: checker is not None)
1774
1791
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
1775
1792
                                           "LastCheckedOK")
1776
1793
    last_checker_status = notifychangeproperty(dbus.Int16,
1781
1798
                                               "ApprovedByDefault")
1782
1799
    approval_delay = notifychangeproperty(
1783
1800
        dbus.UInt64, "ApprovalDelay",
1784
 
        type_func = lambda td: td.total_seconds() * 1000)
 
1801
        type_func=lambda td: td.total_seconds() * 1000)
1785
1802
    approval_duration = notifychangeproperty(
1786
1803
        dbus.UInt64, "ApprovalDuration",
1787
 
        type_func = lambda td: td.total_seconds() * 1000)
 
1804
        type_func=lambda td: td.total_seconds() * 1000)
1788
1805
    host = notifychangeproperty(dbus.String, "Host")
1789
1806
    timeout = notifychangeproperty(
1790
1807
        dbus.UInt64, "Timeout",
1791
 
        type_func = lambda td: td.total_seconds() * 1000)
 
1808
        type_func=lambda td: td.total_seconds() * 1000)
1792
1809
    extended_timeout = notifychangeproperty(
1793
1810
        dbus.UInt64, "ExtendedTimeout",
1794
 
        type_func = lambda td: td.total_seconds() * 1000)
 
1811
        type_func=lambda td: td.total_seconds() * 1000)
1795
1812
    interval = notifychangeproperty(
1796
1813
        dbus.UInt64, "Interval",
1797
 
        type_func = lambda td: td.total_seconds() * 1000)
 
1814
        type_func=lambda td: td.total_seconds() * 1000)
1798
1815
    checker_command = notifychangeproperty(dbus.String, "Checker")
1799
1816
    secret = notifychangeproperty(dbus.ByteArray, "Secret",
1800
1817
                                  invalidate_only=True)
1801
 
    
 
1818
 
1802
1819
    del notifychangeproperty
1803
 
    
 
1820
 
1804
1821
    def __del__(self, *args, **kwargs):
1805
1822
        try:
1806
1823
            self.remove_from_connection()
1809
1826
        if hasattr(DBusObjectWithProperties, "__del__"):
1810
1827
            DBusObjectWithProperties.__del__(self, *args, **kwargs)
1811
1828
        Client.__del__(self, *args, **kwargs)
1812
 
    
 
1829
 
1813
1830
    def checker_callback(self, source, condition,
1814
1831
                         connection, command, *args, **kwargs):
1815
1832
        ret = Client.checker_callback(self, source, condition,
1831
1848
                                      | self.last_checker_signal),
1832
1849
                                  dbus.String(command))
1833
1850
        return ret
1834
 
    
 
1851
 
1835
1852
    def start_checker(self, *args, **kwargs):
1836
1853
        old_checker_pid = getattr(self.checker, "pid", None)
1837
1854
        r = Client.start_checker(self, *args, **kwargs)
1841
1858
            # Emit D-Bus signal
1842
1859
            self.CheckerStarted(self.current_checker_command)
1843
1860
        return r
1844
 
    
 
1861
 
1845
1862
    def _reset_approved(self):
1846
1863
        self.approved = None
1847
1864
        return False
1848
 
    
 
1865
 
1849
1866
    def approve(self, value=True):
1850
1867
        self.approved = value
1851
1868
        GLib.timeout_add(int(self.approval_duration.total_seconds()
1852
1869
                             * 1000), self._reset_approved)
1853
1870
        self.send_changedstate()
1854
 
    
1855
 
    ## D-Bus methods, signals & properties
1856
 
    
1857
 
    ## Interfaces
1858
 
    
1859
 
    ## Signals
1860
 
    
 
1871
 
 
1872
    #  D-Bus methods, signals & properties
 
1873
 
 
1874
    #  Interfaces
 
1875
 
 
1876
    #  Signals
 
1877
 
1861
1878
    # CheckerCompleted - signal
1862
1879
    @dbus.service.signal(_interface, signature="nxs")
1863
1880
    def CheckerCompleted(self, exitcode, waitstatus, command):
1864
1881
        "D-Bus signal"
1865
1882
        pass
1866
 
    
 
1883
 
1867
1884
    # CheckerStarted - signal
1868
1885
    @dbus.service.signal(_interface, signature="s")
1869
1886
    def CheckerStarted(self, command):
1870
1887
        "D-Bus signal"
1871
1888
        pass
1872
 
    
 
1889
 
1873
1890
    # PropertyChanged - signal
1874
1891
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1875
1892
    @dbus.service.signal(_interface, signature="sv")
1876
1893
    def PropertyChanged(self, property, value):
1877
1894
        "D-Bus signal"
1878
1895
        pass
1879
 
    
 
1896
 
1880
1897
    # GotSecret - signal
1881
1898
    @dbus.service.signal(_interface)
1882
1899
    def GotSecret(self):
1885
1902
        server to mandos-client
1886
1903
        """
1887
1904
        pass
1888
 
    
 
1905
 
1889
1906
    # Rejected - signal
1890
1907
    @dbus.service.signal(_interface, signature="s")
1891
1908
    def Rejected(self, reason):
1892
1909
        "D-Bus signal"
1893
1910
        pass
1894
 
    
 
1911
 
1895
1912
    # NeedApproval - signal
1896
1913
    @dbus.service.signal(_interface, signature="tb")
1897
1914
    def NeedApproval(self, timeout, default):
1898
1915
        "D-Bus signal"
1899
1916
        return self.need_approval()
1900
 
    
1901
 
    ## Methods
1902
 
    
 
1917
 
 
1918
    #  Methods
 
1919
 
1903
1920
    # Approve - method
1904
1921
    @dbus.service.method(_interface, in_signature="b")
1905
1922
    def Approve(self, value):
1906
1923
        self.approve(value)
1907
 
    
 
1924
 
1908
1925
    # CheckedOK - method
1909
1926
    @dbus.service.method(_interface)
1910
1927
    def CheckedOK(self):
1911
1928
        self.checked_ok()
1912
 
    
 
1929
 
1913
1930
    # Enable - method
1914
1931
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1915
1932
    @dbus.service.method(_interface)
1916
1933
    def Enable(self):
1917
1934
        "D-Bus method"
1918
1935
        self.enable()
1919
 
    
 
1936
 
1920
1937
    # StartChecker - method
1921
1938
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1922
1939
    @dbus.service.method(_interface)
1923
1940
    def StartChecker(self):
1924
1941
        "D-Bus method"
1925
1942
        self.start_checker()
1926
 
    
 
1943
 
1927
1944
    # Disable - method
1928
1945
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1929
1946
    @dbus.service.method(_interface)
1930
1947
    def Disable(self):
1931
1948
        "D-Bus method"
1932
1949
        self.disable()
1933
 
    
 
1950
 
1934
1951
    # StopChecker - method
1935
1952
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1936
1953
    @dbus.service.method(_interface)
1937
1954
    def StopChecker(self):
1938
1955
        self.stop_checker()
1939
 
    
1940
 
    ## Properties
1941
 
    
 
1956
 
 
1957
    #  Properties
 
1958
 
1942
1959
    # ApprovalPending - property
1943
1960
    @dbus_service_property(_interface, signature="b", access="read")
1944
1961
    def ApprovalPending_dbus_property(self):
1945
1962
        return dbus.Boolean(bool(self.approvals_pending))
1946
 
    
 
1963
 
1947
1964
    # ApprovedByDefault - property
1948
1965
    @dbus_service_property(_interface,
1949
1966
                           signature="b",
1952
1969
        if value is None:       # get
1953
1970
            return dbus.Boolean(self.approved_by_default)
1954
1971
        self.approved_by_default = bool(value)
1955
 
    
 
1972
 
1956
1973
    # ApprovalDelay - property
1957
1974
    @dbus_service_property(_interface,
1958
1975
                           signature="t",
1962
1979
            return dbus.UInt64(self.approval_delay.total_seconds()
1963
1980
                               * 1000)
1964
1981
        self.approval_delay = datetime.timedelta(0, 0, 0, value)
1965
 
    
 
1982
 
1966
1983
    # ApprovalDuration - property
1967
1984
    @dbus_service_property(_interface,
1968
1985
                           signature="t",
1972
1989
            return dbus.UInt64(self.approval_duration.total_seconds()
1973
1990
                               * 1000)
1974
1991
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
1975
 
    
 
1992
 
1976
1993
    # Name - property
1977
1994
    @dbus_annotations(
1978
1995
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1979
1996
    @dbus_service_property(_interface, signature="s", access="read")
1980
1997
    def Name_dbus_property(self):
1981
1998
        return dbus.String(self.name)
1982
 
    
 
1999
 
1983
2000
    # Fingerprint - property
1984
2001
    @dbus_annotations(
1985
2002
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1986
2003
    @dbus_service_property(_interface, signature="s", access="read")
1987
2004
    def Fingerprint_dbus_property(self):
1988
2005
        return dbus.String(self.fingerprint)
1989
 
    
 
2006
 
1990
2007
    # Host - property
1991
2008
    @dbus_service_property(_interface,
1992
2009
                           signature="s",
1995
2012
        if value is None:       # get
1996
2013
            return dbus.String(self.host)
1997
2014
        self.host = str(value)
1998
 
    
 
2015
 
1999
2016
    # Created - property
2000
2017
    @dbus_annotations(
2001
2018
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
2002
2019
    @dbus_service_property(_interface, signature="s", access="read")
2003
2020
    def Created_dbus_property(self):
2004
2021
        return datetime_to_dbus(self.created)
2005
 
    
 
2022
 
2006
2023
    # LastEnabled - property
2007
2024
    @dbus_service_property(_interface, signature="s", access="read")
2008
2025
    def LastEnabled_dbus_property(self):
2009
2026
        return datetime_to_dbus(self.last_enabled)
2010
 
    
 
2027
 
2011
2028
    # Enabled - property
2012
2029
    @dbus_service_property(_interface,
2013
2030
                           signature="b",
2019
2036
            self.enable()
2020
2037
        else:
2021
2038
            self.disable()
2022
 
    
 
2039
 
2023
2040
    # LastCheckedOK - property
2024
2041
    @dbus_service_property(_interface,
2025
2042
                           signature="s",
2029
2046
            self.checked_ok()
2030
2047
            return
2031
2048
        return datetime_to_dbus(self.last_checked_ok)
2032
 
    
 
2049
 
2033
2050
    # LastCheckerStatus - property
2034
2051
    @dbus_service_property(_interface, signature="n", access="read")
2035
2052
    def LastCheckerStatus_dbus_property(self):
2036
2053
        return dbus.Int16(self.last_checker_status)
2037
 
    
 
2054
 
2038
2055
    # Expires - property
2039
2056
    @dbus_service_property(_interface, signature="s", access="read")
2040
2057
    def Expires_dbus_property(self):
2041
2058
        return datetime_to_dbus(self.expires)
2042
 
    
 
2059
 
2043
2060
    # LastApprovalRequest - property
2044
2061
    @dbus_service_property(_interface, signature="s", access="read")
2045
2062
    def LastApprovalRequest_dbus_property(self):
2046
2063
        return datetime_to_dbus(self.last_approval_request)
2047
 
    
 
2064
 
2048
2065
    # Timeout - property
2049
2066
    @dbus_service_property(_interface,
2050
2067
                           signature="t",
2069
2086
                self.disable_initiator_tag = GLib.timeout_add(
2070
2087
                    int((self.expires - now).total_seconds() * 1000),
2071
2088
                    self.disable)
2072
 
    
 
2089
 
2073
2090
    # ExtendedTimeout - property
2074
2091
    @dbus_service_property(_interface,
2075
2092
                           signature="t",
2079
2096
            return dbus.UInt64(self.extended_timeout.total_seconds()
2080
2097
                               * 1000)
2081
2098
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
2082
 
    
 
2099
 
2083
2100
    # Interval - property
2084
2101
    @dbus_service_property(_interface,
2085
2102
                           signature="t",
2095
2112
            GLib.source_remove(self.checker_initiator_tag)
2096
2113
            self.checker_initiator_tag = GLib.timeout_add(
2097
2114
                value, self.start_checker)
2098
 
            self.start_checker() # Start one now, too
2099
 
    
 
2115
            self.start_checker()  # Start one now, too
 
2116
 
2100
2117
    # Checker - property
2101
2118
    @dbus_service_property(_interface,
2102
2119
                           signature="s",
2105
2122
        if value is None:       # get
2106
2123
            return dbus.String(self.checker_command)
2107
2124
        self.checker_command = str(value)
2108
 
    
 
2125
 
2109
2126
    # CheckerRunning - property
2110
2127
    @dbus_service_property(_interface,
2111
2128
                           signature="b",
2117
2134
            self.start_checker()
2118
2135
        else:
2119
2136
            self.stop_checker()
2120
 
    
 
2137
 
2121
2138
    # ObjectPath - property
2122
2139
    @dbus_annotations(
2123
2140
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const",
2124
2141
         "org.freedesktop.DBus.Deprecated": "true"})
2125
2142
    @dbus_service_property(_interface, signature="o", access="read")
2126
2143
    def ObjectPath_dbus_property(self):
2127
 
        return self.dbus_object_path # is already a dbus.ObjectPath
2128
 
    
 
2144
        return self.dbus_object_path  # is already a dbus.ObjectPath
 
2145
 
2129
2146
    # Secret = property
2130
2147
    @dbus_annotations(
2131
2148
        {"org.freedesktop.DBus.Property.EmitsChangedSignal":
2136
2153
                           byte_arrays=True)
2137
2154
    def Secret_dbus_property(self, value):
2138
2155
        self.secret = bytes(value)
2139
 
    
 
2156
 
2140
2157
    del _interface
2141
2158
 
2142
2159
 
2146
2163
        self._pipe.send(('init', fpr, address))
2147
2164
        if not self._pipe.recv():
2148
2165
            raise KeyError(fpr)
2149
 
    
 
2166
 
2150
2167
    def __getattribute__(self, name):
2151
2168
        if name == '_pipe':
2152
2169
            return super(ProxyClient, self).__getattribute__(name)
2155
2172
        if data[0] == 'data':
2156
2173
            return data[1]
2157
2174
        if data[0] == 'function':
2158
 
            
 
2175
 
2159
2176
            def func(*args, **kwargs):
2160
2177
                self._pipe.send(('funcall', name, args, kwargs))
2161
2178
                return self._pipe.recv()[1]
2162
 
            
 
2179
 
2163
2180
            return func
2164
 
    
 
2181
 
2165
2182
    def __setattr__(self, name, value):
2166
2183
        if name == '_pipe':
2167
2184
            return super(ProxyClient, self).__setattr__(name, value)
2170
2187
 
2171
2188
class ClientHandler(socketserver.BaseRequestHandler, object):
2172
2189
    """A class to handle client connections.
2173
 
    
 
2190
 
2174
2191
    Instantiated once for each connection to handle it.
2175
2192
    Note: This will run in its own forked process."""
2176
 
    
 
2193
 
2177
2194
    def handle(self):
2178
2195
        with contextlib.closing(self.server.child_pipe) as child_pipe:
2179
2196
            logger.info("TCP connection from: %s",
2180
2197
                        str(self.client_address))
2181
2198
            logger.debug("Pipe FD: %d",
2182
2199
                         self.server.child_pipe.fileno())
2183
 
            
 
2200
 
2184
2201
            session = gnutls.ClientSession(self.request)
2185
 
            
2186
 
            #priority = ':'.join(("NONE", "+VERS-TLS1.1",
2187
 
            #                      "+AES-256-CBC", "+SHA1",
2188
 
            #                      "+COMP-NULL", "+CTYPE-OPENPGP",
2189
 
            #                      "+DHE-DSS"))
 
2202
 
 
2203
            # priority = ':'.join(("NONE", "+VERS-TLS1.1",
 
2204
            #                       "+AES-256-CBC", "+SHA1",
 
2205
            #                       "+COMP-NULL", "+CTYPE-OPENPGP",
 
2206
            #                       "+DHE-DSS"))
2190
2207
            # Use a fallback default, since this MUST be set.
2191
2208
            priority = self.server.gnutls_priority
2192
2209
            if priority is None:
2194
2211
            gnutls.priority_set_direct(session._c_object,
2195
2212
                                       priority.encode("utf-8"),
2196
2213
                                       None)
2197
 
            
 
2214
 
2198
2215
            # Start communication using the Mandos protocol
2199
2216
            # Get protocol number
2200
2217
            line = self.request.makefile().readline()
2205
2222
            except (ValueError, IndexError, RuntimeError) as error:
2206
2223
                logger.error("Unknown protocol version: %s", error)
2207
2224
                return
2208
 
            
 
2225
 
2209
2226
            # Start GnuTLS connection
2210
2227
            try:
2211
2228
                session.handshake()
2215
2232
                # established.  Just abandon the request.
2216
2233
                return
2217
2234
            logger.debug("Handshake succeeded")
2218
 
            
 
2235
 
2219
2236
            approval_required = False
2220
2237
            try:
2221
2238
                try:
2225
2242
                    logger.warning("Bad certificate: %s", error)
2226
2243
                    return
2227
2244
                logger.debug("Fingerprint: %s", fpr)
2228
 
                
 
2245
 
2229
2246
                try:
2230
2247
                    client = ProxyClient(child_pipe, fpr,
2231
2248
                                         self.client_address)
2232
2249
                except KeyError:
2233
2250
                    return
2234
 
                
 
2251
 
2235
2252
                if client.approval_delay:
2236
2253
                    delay = client.approval_delay
2237
2254
                    client.approvals_pending += 1
2238
2255
                    approval_required = True
2239
 
                
 
2256
 
2240
2257
                while True:
2241
2258
                    if not client.enabled:
2242
2259
                        logger.info("Client %s is disabled",
2245
2262
                            # Emit D-Bus signal
2246
2263
                            client.Rejected("Disabled")
2247
2264
                        return
2248
 
                    
 
2265
 
2249
2266
                    if client.approved or not client.approval_delay:
2250
 
                        #We are approved or approval is disabled
 
2267
                        # We are approved or approval is disabled
2251
2268
                        break
2252
2269
                    elif client.approved is None:
2253
2270
                        logger.info("Client %s needs approval",
2264
2281
                            # Emit D-Bus signal
2265
2282
                            client.Rejected("Denied")
2266
2283
                        return
2267
 
                    
2268
 
                    #wait until timeout or approved
 
2284
 
 
2285
                    # wait until timeout or approved
2269
2286
                    time = datetime.datetime.now()
2270
2287
                    client.changedstate.acquire()
2271
2288
                    client.changedstate.wait(delay.total_seconds())
2284
2301
                            break
2285
2302
                    else:
2286
2303
                        delay -= time2 - time
2287
 
                
 
2304
 
2288
2305
                try:
2289
2306
                    session.send(client.secret)
2290
2307
                except gnutls.Error as error:
2291
2308
                    logger.warning("gnutls send failed",
2292
 
                                   exc_info = error)
 
2309
                                   exc_info=error)
2293
2310
                    return
2294
 
                
 
2311
 
2295
2312
                logger.info("Sending secret to %s", client.name)
2296
2313
                # bump the timeout using extended_timeout
2297
2314
                client.bump_timeout(client.extended_timeout)
2298
2315
                if self.server.use_dbus:
2299
2316
                    # Emit D-Bus signal
2300
2317
                    client.GotSecret()
2301
 
            
 
2318
 
2302
2319
            finally:
2303
2320
                if approval_required:
2304
2321
                    client.approvals_pending -= 1
2307
2324
                except gnutls.Error as error:
2308
2325
                    logger.warning("GnuTLS bye failed",
2309
2326
                                   exc_info=error)
2310
 
    
 
2327
 
2311
2328
    @staticmethod
2312
2329
    def peer_certificate(session):
2313
2330
        "Return the peer's OpenPGP certificate as a bytestring"
2325
2342
            return None
2326
2343
        cert = cert_list[0]
2327
2344
        return ctypes.string_at(cert.data, cert.size)
2328
 
    
 
2345
 
2329
2346
    @staticmethod
2330
2347
    def fingerprint(openpgp):
2331
2348
        "Convert an OpenPGP bytestring to a hexdigit fingerprint"
2364
2381
 
2365
2382
class MultiprocessingMixIn(object):
2366
2383
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
2367
 
    
 
2384
 
2368
2385
    def sub_process_main(self, request, address):
2369
2386
        try:
2370
2387
            self.finish_request(request, address)
2371
2388
        except Exception:
2372
2389
            self.handle_error(request, address)
2373
2390
        self.close_request(request)
2374
 
    
 
2391
 
2375
2392
    def process_request(self, request, address):
2376
2393
        """Start a new process to process the request."""
2377
 
        proc = multiprocessing.Process(target = self.sub_process_main,
2378
 
                                       args = (request, address))
 
2394
        proc = multiprocessing.Process(target=self.sub_process_main,
 
2395
                                       args=(request, address))
2379
2396
        proc.start()
2380
2397
        return proc
2381
2398
 
2382
2399
 
2383
2400
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
2384
2401
    """ adds a pipe to the MixIn """
2385
 
    
 
2402
 
2386
2403
    def process_request(self, request, client_address):
2387
2404
        """Overrides and wraps the original process_request().
2388
 
        
 
2405
 
2389
2406
        This function creates a new pipe in self.pipe
2390
2407
        """
2391
2408
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
2392
 
        
 
2409
 
2393
2410
        proc = MultiprocessingMixIn.process_request(self, request,
2394
2411
                                                    client_address)
2395
2412
        self.child_pipe.close()
2396
2413
        self.add_pipe(parent_pipe, proc)
2397
 
    
 
2414
 
2398
2415
    def add_pipe(self, parent_pipe, proc):
2399
2416
        """Dummy function; override as necessary"""
2400
2417
        raise NotImplementedError()
2403
2420
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
2404
2421
                     socketserver.TCPServer, object):
2405
2422
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
2406
 
    
 
2423
 
2407
2424
    Attributes:
2408
2425
        enabled:        Boolean; whether this server is activated yet
2409
2426
        interface:      None or a network interface name (string)
2410
2427
        use_ipv6:       Boolean; to use IPv6 or not
2411
2428
    """
2412
 
    
 
2429
 
2413
2430
    def __init__(self, server_address, RequestHandlerClass,
2414
2431
                 interface=None,
2415
2432
                 use_ipv6=True,
2425
2442
            self.socketfd = socketfd
2426
2443
            # Save the original socket.socket() function
2427
2444
            self.socket_socket = socket.socket
 
2445
 
2428
2446
            # To implement --socket, we monkey patch socket.socket.
2429
 
            # 
 
2447
            #
2430
2448
            # (When socketserver.TCPServer is a new-style class, we
2431
2449
            # could make self.socket into a property instead of monkey
2432
2450
            # patching socket.socket.)
2433
 
            # 
 
2451
            #
2434
2452
            # Create a one-time-only replacement for socket.socket()
2435
2453
            @functools.wraps(socket.socket)
2436
2454
            def socket_wrapper(*args, **kwargs):
2448
2466
        # socket_wrapper(), if socketfd was set.
2449
2467
        socketserver.TCPServer.__init__(self, server_address,
2450
2468
                                        RequestHandlerClass)
2451
 
    
 
2469
 
2452
2470
    def server_bind(self):
2453
2471
        """This overrides the normal server_bind() function
2454
2472
        to bind to an interface if one was specified, and also NOT to
2455
2473
        bind to an address or port if they were not specified."""
 
2474
        global SO_BINDTODEVICE
2456
2475
        if self.interface is not None:
2457
2476
            if SO_BINDTODEVICE is None:
2458
2477
                # Fall back to a hard-coded value which seems to be
2480
2499
        if self.server_address[0] or self.server_address[1]:
2481
2500
            if not self.server_address[0]:
2482
2501
                if self.address_family == socket.AF_INET6:
2483
 
                    any_address = "::" # in6addr_any
 
2502
                    any_address = "::"  # in6addr_any
2484
2503
                else:
2485
 
                    any_address = "0.0.0.0" # INADDR_ANY
 
2504
                    any_address = "0.0.0.0"  # INADDR_ANY
2486
2505
                self.server_address = (any_address,
2487
2506
                                       self.server_address[1])
2488
2507
            elif not self.server_address[1]:
2498
2517
 
2499
2518
class MandosServer(IPv6_TCPServer):
2500
2519
    """Mandos server.
2501
 
    
 
2520
 
2502
2521
    Attributes:
2503
2522
        clients:        set of Client objects
2504
2523
        gnutls_priority GnuTLS priority string
2505
2524
        use_dbus:       Boolean; to emit D-Bus signals or not
2506
 
    
 
2525
 
2507
2526
    Assumes a GLib.MainLoop event loop.
2508
2527
    """
2509
 
    
 
2528
 
2510
2529
    def __init__(self, server_address, RequestHandlerClass,
2511
2530
                 interface=None,
2512
2531
                 use_ipv6=True,
2522
2541
        self.gnutls_priority = gnutls_priority
2523
2542
        IPv6_TCPServer.__init__(self, server_address,
2524
2543
                                RequestHandlerClass,
2525
 
                                interface = interface,
2526
 
                                use_ipv6 = use_ipv6,
2527
 
                                socketfd = socketfd)
2528
 
    
 
2544
                                interface=interface,
 
2545
                                use_ipv6=use_ipv6,
 
2546
                                socketfd=socketfd)
 
2547
 
2529
2548
    def server_activate(self):
2530
2549
        if self.enabled:
2531
2550
            return socketserver.TCPServer.server_activate(self)
2532
 
    
 
2551
 
2533
2552
    def enable(self):
2534
2553
        self.enabled = True
2535
 
    
 
2554
 
2536
2555
    def add_pipe(self, parent_pipe, proc):
2537
2556
        # Call "handle_ipc" for both data and EOF events
2538
2557
        GLib.io_add_watch(
2539
2558
            parent_pipe.fileno(),
2540
2559
            GLib.IO_IN | GLib.IO_HUP,
2541
2560
            functools.partial(self.handle_ipc,
2542
 
                              parent_pipe = parent_pipe,
2543
 
                              proc = proc))
2544
 
    
 
2561
                              parent_pipe=parent_pipe,
 
2562
                              proc=proc))
 
2563
 
2545
2564
    def handle_ipc(self, source, condition,
2546
2565
                   parent_pipe=None,
2547
 
                   proc = None,
 
2566
                   proc=None,
2548
2567
                   client_object=None):
2549
2568
        # error, or the other end of multiprocessing.Pipe has closed
2550
2569
        if condition & (GLib.IO_ERR | GLib.IO_HUP):
2551
2570
            # Wait for other process to exit
2552
2571
            proc.join()
2553
2572
            return False
2554
 
        
 
2573
 
2555
2574
        # Read a request from the child
2556
2575
        request = parent_pipe.recv()
2557
2576
        command = request[0]
2558
 
        
 
2577
 
2559
2578
        if command == 'init':
2560
2579
            fpr = request[1]
2561
2580
            address = request[2]
2562
 
            
 
2581
 
2563
2582
            for c in self.clients.values():
2564
2583
                if c.fingerprint == fpr:
2565
2584
                    client = c
2573
2592
                                                       address[0])
2574
2593
                parent_pipe.send(False)
2575
2594
                return False
2576
 
            
 
2595
 
2577
2596
            GLib.io_add_watch(
2578
2597
                parent_pipe.fileno(),
2579
2598
                GLib.IO_IN | GLib.IO_HUP,
2580
2599
                functools.partial(self.handle_ipc,
2581
 
                                  parent_pipe = parent_pipe,
2582
 
                                  proc = proc,
2583
 
                                  client_object = client))
 
2600
                                  parent_pipe=parent_pipe,
 
2601
                                  proc=proc,
 
2602
                                  client_object=client))
2584
2603
            parent_pipe.send(True)
2585
2604
            # remove the old hook in favor of the new above hook on
2586
2605
            # same fileno
2589
2608
            funcname = request[1]
2590
2609
            args = request[2]
2591
2610
            kwargs = request[3]
2592
 
            
 
2611
 
2593
2612
            parent_pipe.send(('data', getattr(client_object,
2594
2613
                                              funcname)(*args,
2595
2614
                                                        **kwargs)))
2596
 
        
 
2615
 
2597
2616
        if command == 'getattr':
2598
2617
            attrname = request[1]
2599
2618
            if isinstance(client_object.__getattribute__(attrname),
2602
2621
            else:
2603
2622
                parent_pipe.send((
2604
2623
                    'data', client_object.__getattribute__(attrname)))
2605
 
        
 
2624
 
2606
2625
        if command == 'setattr':
2607
2626
            attrname = request[1]
2608
2627
            value = request[2]
2609
2628
            setattr(client_object, attrname, value)
2610
 
        
 
2629
 
2611
2630
        return True
2612
2631
 
2613
2632
 
2614
2633
def rfc3339_duration_to_delta(duration):
2615
2634
    """Parse an RFC 3339 "duration" and return a datetime.timedelta
2616
 
    
 
2635
 
2617
2636
    >>> rfc3339_duration_to_delta("P7D")
2618
2637
    datetime.timedelta(7)
2619
2638
    >>> rfc3339_duration_to_delta("PT60S")
2629
2648
    >>> rfc3339_duration_to_delta("P1DT3M20S")
2630
2649
    datetime.timedelta(1, 200)
2631
2650
    """
2632
 
    
 
2651
 
2633
2652
    # Parsing an RFC 3339 duration with regular expressions is not
2634
2653
    # possible - there would have to be multiple places for the same
2635
2654
    # values, like seconds.  The current code, while more esoteric, is
2636
2655
    # cleaner without depending on a parsing library.  If Python had a
2637
2656
    # built-in library for parsing we would use it, but we'd like to
2638
2657
    # avoid excessive use of external libraries.
2639
 
    
 
2658
 
2640
2659
    # New type for defining tokens, syntax, and semantics all-in-one
2641
2660
    Token = collections.namedtuple("Token", (
2642
2661
        "regexp",  # To match token; if "value" is not None, must have
2675
2694
                           frozenset((token_year, token_month,
2676
2695
                                      token_day, token_time,
2677
2696
                                      token_week)))
2678
 
    # Define starting values
2679
 
    value = datetime.timedelta() # Value so far
 
2697
    # Define starting values:
 
2698
    # Value so far
 
2699
    value = datetime.timedelta()
2680
2700
    found_token = None
2681
 
    followers = frozenset((token_duration, )) # Following valid tokens
2682
 
    s = duration                # String left to parse
 
2701
    # Following valid tokens
 
2702
    followers = frozenset((token_duration, ))
 
2703
    # String left to parse
 
2704
    s = duration
2683
2705
    # Loop until end token is found
2684
2706
    while found_token is not token_end:
2685
2707
        # Search for any currently valid tokens
2709
2731
 
2710
2732
def string_to_delta(interval):
2711
2733
    """Parse a string and return a datetime.timedelta
2712
 
    
 
2734
 
2713
2735
    >>> string_to_delta('7d')
2714
2736
    datetime.timedelta(7)
2715
2737
    >>> string_to_delta('60s')
2723
2745
    >>> string_to_delta('5m 30s')
2724
2746
    datetime.timedelta(0, 330)
2725
2747
    """
2726
 
    
 
2748
 
2727
2749
    try:
2728
2750
        return rfc3339_duration_to_delta(interval)
2729
2751
    except ValueError:
2730
2752
        pass
2731
 
    
 
2753
 
2732
2754
    timevalue = datetime.timedelta(0)
2733
2755
    for s in interval.split():
2734
2756
        try:
2752
2774
    return timevalue
2753
2775
 
2754
2776
 
2755
 
def daemon(nochdir = False, noclose = False):
 
2777
def daemon(nochdir=False, noclose=False):
2756
2778
    """See daemon(3).  Standard BSD Unix function.
2757
 
    
 
2779
 
2758
2780
    This should really exist as os.daemon, but it doesn't (yet)."""
2759
2781
    if os.fork():
2760
2782
        sys.exit()
2778
2800
 
2779
2801
 
2780
2802
def main():
2781
 
    
 
2803
 
2782
2804
    ##################################################################
2783
2805
    # Parsing of options, both command line and config file
2784
 
    
 
2806
 
2785
2807
    parser = argparse.ArgumentParser()
2786
2808
    parser.add_argument("-v", "--version", action="version",
2787
 
                        version = "%(prog)s {}".format(version),
 
2809
                        version="%(prog)s {}".format(version),
2788
2810
                        help="show version number and exit")
2789
2811
    parser.add_argument("-i", "--interface", metavar="IF",
2790
2812
                        help="Bind to interface IF")
2826
2848
    parser.add_argument("--no-zeroconf", action="store_false",
2827
2849
                        dest="zeroconf", help="Do not use Zeroconf",
2828
2850
                        default=None)
2829
 
    
 
2851
 
2830
2852
    options = parser.parse_args()
2831
 
    
 
2853
 
2832
2854
    if options.check:
2833
2855
        import doctest
2834
2856
        fail_count, test_count = doctest.testmod()
2835
2857
        sys.exit(os.EX_OK if fail_count == 0 else 1)
2836
 
    
 
2858
 
2837
2859
    # Default values for config file for server-global settings
2838
 
    server_defaults = { "interface": "",
2839
 
                        "address": "",
2840
 
                        "port": "",
2841
 
                        "debug": "False",
2842
 
                        "priority":
2843
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2844
 
                        ":+SIGN-DSA-SHA256",
2845
 
                        "servicename": "Mandos",
2846
 
                        "use_dbus": "True",
2847
 
                        "use_ipv6": "True",
2848
 
                        "debuglevel": "",
2849
 
                        "restore": "True",
2850
 
                        "socket": "",
2851
 
                        "statedir": "/var/lib/mandos",
2852
 
                        "foreground": "False",
2853
 
                        "zeroconf": "True",
2854
 
                    }
2855
 
    
 
2860
    server_defaults = {"interface": "",
 
2861
                       "address": "",
 
2862
                       "port": "",
 
2863
                       "debug": "False",
 
2864
                       "priority":
 
2865
                       "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
 
2866
                       ":+SIGN-DSA-SHA256",
 
2867
                       "servicename": "Mandos",
 
2868
                       "use_dbus": "True",
 
2869
                       "use_ipv6": "True",
 
2870
                       "debuglevel": "",
 
2871
                       "restore": "True",
 
2872
                       "socket": "",
 
2873
                       "statedir": "/var/lib/mandos",
 
2874
                       "foreground": "False",
 
2875
                       "zeroconf": "True",
 
2876
                       }
 
2877
 
2856
2878
    # Parse config file for server-global settings
2857
2879
    server_config = configparser.SafeConfigParser(server_defaults)
2858
2880
    del server_defaults
2876
2898
            server_settings["socket"] = os.dup(server_settings
2877
2899
                                               ["socket"])
2878
2900
    del server_config
2879
 
    
 
2901
 
2880
2902
    # Override the settings from the config file with command line
2881
2903
    # options, if set.
2882
2904
    for option in ("interface", "address", "port", "debug",
2900
2922
    if server_settings["debug"]:
2901
2923
        server_settings["foreground"] = True
2902
2924
    # Now we have our good server settings in "server_settings"
2903
 
    
 
2925
 
2904
2926
    ##################################################################
2905
 
    
 
2927
 
2906
2928
    if (not server_settings["zeroconf"]
2907
2929
        and not (server_settings["port"]
2908
2930
                 or server_settings["socket"] != "")):
2909
2931
        parser.error("Needs port or socket to work without Zeroconf")
2910
 
    
 
2932
 
2911
2933
    # For convenience
2912
2934
    debug = server_settings["debug"]
2913
2935
    debuglevel = server_settings["debuglevel"]
2917
2939
                                     stored_state_file)
2918
2940
    foreground = server_settings["foreground"]
2919
2941
    zeroconf = server_settings["zeroconf"]
2920
 
    
 
2942
 
2921
2943
    if debug:
2922
2944
        initlogger(debug, logging.DEBUG)
2923
2945
    else:
2926
2948
        else:
2927
2949
            level = getattr(logging, debuglevel.upper())
2928
2950
            initlogger(debug, level)
2929
 
    
 
2951
 
2930
2952
    if server_settings["servicename"] != "Mandos":
2931
2953
        syslogger.setFormatter(
2932
2954
            logging.Formatter('Mandos ({}) [%(process)d]:'
2933
2955
                              ' %(levelname)s: %(message)s'.format(
2934
2956
                                  server_settings["servicename"])))
2935
 
    
 
2957
 
2936
2958
    # Parse config file with clients
2937
2959
    client_config = configparser.SafeConfigParser(Client
2938
2960
                                                  .client_defaults)
2939
2961
    client_config.read(os.path.join(server_settings["configdir"],
2940
2962
                                    "clients.conf"))
2941
 
    
 
2963
 
2942
2964
    global mandos_dbus_service
2943
2965
    mandos_dbus_service = None
2944
 
    
 
2966
 
2945
2967
    socketfd = None
2946
2968
    if server_settings["socket"] != "":
2947
2969
        socketfd = server_settings["socket"]
2963
2985
        except IOError as e:
2964
2986
            logger.error("Could not open file %r", pidfilename,
2965
2987
                         exc_info=e)
2966
 
    
 
2988
 
2967
2989
    for name, group in (("_mandos", "_mandos"),
2968
2990
                        ("mandos", "mandos"),
2969
2991
                        ("nobody", "nogroup")):
2987
3009
                       .format(uid, gid, os.strerror(error.errno)))
2988
3010
        if error.errno != errno.EPERM:
2989
3011
            raise
2990
 
    
 
3012
 
2991
3013
    if debug:
2992
3014
        # Enable all possible GnuTLS debugging
2993
 
        
 
3015
 
2994
3016
        # "Use a log level over 10 to enable all debugging options."
2995
3017
        # - GnuTLS manual
2996
3018
        gnutls.global_set_log_level(11)
2997
 
        
 
3019
 
2998
3020
        @gnutls.log_func
2999
3021
        def debug_gnutls(level, string):
3000
3022
            logger.debug("GnuTLS: %s", string[:-1])
3001
 
        
 
3023
 
3002
3024
        gnutls.global_set_log_function(debug_gnutls)
3003
 
        
 
3025
 
3004
3026
        # Redirect stdin so all checkers get /dev/null
3005
3027
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
3006
3028
        os.dup2(null, sys.stdin.fileno())
3007
3029
        if null > 2:
3008
3030
            os.close(null)
3009
 
    
 
3031
 
3010
3032
    # Need to fork before connecting to D-Bus
3011
3033
    if not foreground:
3012
3034
        # Close all input and output, do double fork, etc.
3013
3035
        daemon()
3014
 
    
 
3036
 
3015
3037
    # multiprocessing will use threads, so before we use GLib we need
3016
3038
    # to inform GLib that threads will be used.
3017
3039
    GLib.threads_init()
3018
 
    
 
3040
 
3019
3041
    global main_loop
3020
3042
    # From the Avahi example code
3021
3043
    DBusGMainLoop(set_as_default=True)
3038
3060
    if zeroconf:
3039
3061
        protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
3040
3062
        service = AvahiServiceToSyslog(
3041
 
            name = server_settings["servicename"],
3042
 
            servicetype = "_mandos._tcp",
3043
 
            protocol = protocol,
3044
 
            bus = bus)
 
3063
            name=server_settings["servicename"],
 
3064
            servicetype="_mandos._tcp",
 
3065
            protocol=protocol,
 
3066
            bus=bus)
3045
3067
        if server_settings["interface"]:
3046
3068
            service.interface = if_nametoindex(
3047
3069
                server_settings["interface"].encode("utf-8"))
3048
 
    
 
3070
 
3049
3071
    global multiprocessing_manager
3050
3072
    multiprocessing_manager = multiprocessing.Manager()
3051
 
    
 
3073
 
3052
3074
    client_class = Client
3053
3075
    if use_dbus:
3054
 
        client_class = functools.partial(ClientDBus, bus = bus)
3055
 
    
 
3076
        client_class = functools.partial(ClientDBus, bus=bus)
 
3077
 
3056
3078
    client_settings = Client.config_parser(client_config)
3057
3079
    old_client_settings = {}
3058
3080
    clients_data = {}
3059
 
    
 
3081
 
3060
3082
    # This is used to redirect stdout and stderr for checker processes
3061
3083
    global wnull
3062
 
    wnull = open(os.devnull, "w") # A writable /dev/null
 
3084
    wnull = open(os.devnull, "w")  # A writable /dev/null
3063
3085
    # Only used if server is running in foreground but not in debug
3064
3086
    # mode
3065
3087
    if debug or not foreground:
3066
3088
        wnull.close()
3067
 
    
 
3089
 
3068
3090
    # Get client data and settings from last running state.
3069
3091
    if server_settings["restore"]:
3070
3092
        try:
3071
3093
            with open(stored_state_path, "rb") as stored_state:
3072
 
                if sys.version_info.major == 2:                
 
3094
                if sys.version_info.major == 2:
3073
3095
                    clients_data, old_client_settings = pickle.load(
3074
3096
                        stored_state)
3075
3097
                else:
3076
3098
                    bytes_clients_data, bytes_old_client_settings = (
3077
 
                        pickle.load(stored_state, encoding = "bytes"))
3078
 
                    ### Fix bytes to strings
3079
 
                    ## clients_data
 
3099
                        pickle.load(stored_state, encoding="bytes"))
 
3100
                    #   Fix bytes to strings
 
3101
                    #  clients_data
3080
3102
                    # .keys()
3081
 
                    clients_data = { (key.decode("utf-8")
3082
 
                                      if isinstance(key, bytes)
3083
 
                                      else key): value
3084
 
                                     for key, value in
3085
 
                                     bytes_clients_data.items() }
 
3103
                    clients_data = {(key.decode("utf-8")
 
3104
                                     if isinstance(key, bytes)
 
3105
                                     else key): value
 
3106
                                    for key, value in
 
3107
                                    bytes_clients_data.items()}
3086
3108
                    del bytes_clients_data
3087
3109
                    for key in clients_data:
3088
 
                        value = { (k.decode("utf-8")
3089
 
                                   if isinstance(k, bytes) else k): v
3090
 
                                  for k, v in
3091
 
                                  clients_data[key].items() }
 
3110
                        value = {(k.decode("utf-8")
 
3111
                                  if isinstance(k, bytes) else k): v
 
3112
                                 for k, v in
 
3113
                                 clients_data[key].items()}
3092
3114
                        clients_data[key] = value
3093
3115
                        # .client_structure
3094
3116
                        value["client_structure"] = [
3095
3117
                            (s.decode("utf-8")
3096
3118
                             if isinstance(s, bytes)
3097
3119
                             else s) for s in
3098
 
                            value["client_structure"] ]
 
3120
                            value["client_structure"]]
3099
3121
                        # .name & .host
3100
3122
                        for k in ("name", "host"):
3101
3123
                            if isinstance(value[k], bytes):
3102
3124
                                value[k] = value[k].decode("utf-8")
3103
 
                    ## old_client_settings
 
3125
                    #  old_client_settings
3104
3126
                    # .keys()
3105
3127
                    old_client_settings = {
3106
3128
                        (key.decode("utf-8")
3107
3129
                         if isinstance(key, bytes)
3108
3130
                         else key): value
3109
3131
                        for key, value in
3110
 
                        bytes_old_client_settings.items() }
 
3132
                        bytes_old_client_settings.items()}
3111
3133
                    del bytes_old_client_settings
3112
3134
                    # .host
3113
3135
                    for value in old_client_settings.values():
3127
3149
            logger.warning("Could not load persistent state: "
3128
3150
                           "EOFError:",
3129
3151
                           exc_info=e)
3130
 
    
 
3152
 
3131
3153
    with PGPEngine() as pgp:
3132
3154
        for client_name, client in clients_data.items():
3133
3155
            # Skip removed clients
3134
3156
            if client_name not in client_settings:
3135
3157
                continue
3136
 
            
 
3158
 
3137
3159
            # Decide which value to use after restoring saved state.
3138
3160
            # We have three different values: Old config file,
3139
3161
            # new config file, and saved state.
3150
3172
                        client[name] = value
3151
3173
                except KeyError:
3152
3174
                    pass
3153
 
            
 
3175
 
3154
3176
            # Clients who has passed its expire date can still be
3155
3177
            # enabled if its last checker was successful.  A Client
3156
3178
            # whose checker succeeded before we stored its state is
3189
3211
                    client_name))
3190
3212
                client["secret"] = (client_settings[client_name]
3191
3213
                                    ["secret"])
3192
 
    
 
3214
 
3193
3215
    # Add/remove clients based on new changes made to config
3194
3216
    for client_name in (set(old_client_settings)
3195
3217
                        - set(client_settings)):
3197
3219
    for client_name in (set(client_settings)
3198
3220
                        - set(old_client_settings)):
3199
3221
        clients_data[client_name] = client_settings[client_name]
3200
 
    
 
3222
 
3201
3223
    # Create all client objects
3202
3224
    for client_name, client in clients_data.items():
3203
3225
        tcp_server.clients[client_name] = client_class(
3204
 
            name = client_name,
3205
 
            settings = client,
3206
 
            server_settings = server_settings)
3207
 
    
 
3226
            name=client_name,
 
3227
            settings=client,
 
3228
            server_settings=server_settings)
 
3229
 
3208
3230
    if not tcp_server.clients:
3209
3231
        logger.warning("No clients defined")
3210
 
    
 
3232
 
3211
3233
    if not foreground:
3212
3234
        if pidfile is not None:
3213
3235
            pid = os.getpid()
3219
3241
                             pidfilename, pid)
3220
3242
        del pidfile
3221
3243
        del pidfilename
3222
 
    
 
3244
 
3223
3245
    for termsig in (signal.SIGHUP, signal.SIGTERM):
3224
3246
        GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig,
3225
3247
                             lambda: main_loop.quit() and False)
3226
 
    
 
3248
 
3227
3249
    if use_dbus:
3228
 
        
 
3250
 
3229
3251
        @alternate_dbus_interfaces(
3230
 
            { "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
 
3252
            {"se.recompile.Mandos": "se.bsnet.fukt.Mandos"})
3231
3253
        class MandosDBusService(DBusObjectWithObjectManager):
3232
3254
            """A D-Bus proxy object"""
3233
 
            
 
3255
 
3234
3256
            def __init__(self):
3235
3257
                dbus.service.Object.__init__(self, bus, "/")
3236
 
            
 
3258
 
3237
3259
            _interface = "se.recompile.Mandos"
3238
 
            
 
3260
 
3239
3261
            @dbus.service.signal(_interface, signature="o")
3240
3262
            def ClientAdded(self, objpath):
3241
3263
                "D-Bus signal"
3242
3264
                pass
3243
 
            
 
3265
 
3244
3266
            @dbus.service.signal(_interface, signature="ss")
3245
3267
            def ClientNotFound(self, fingerprint, address):
3246
3268
                "D-Bus signal"
3247
3269
                pass
3248
 
            
 
3270
 
3249
3271
            @dbus_annotations({"org.freedesktop.DBus.Deprecated":
3250
3272
                               "true"})
3251
3273
            @dbus.service.signal(_interface, signature="os")
3252
3274
            def ClientRemoved(self, objpath, name):
3253
3275
                "D-Bus signal"
3254
3276
                pass
3255
 
            
 
3277
 
3256
3278
            @dbus_annotations({"org.freedesktop.DBus.Deprecated":
3257
3279
                               "true"})
3258
3280
            @dbus.service.method(_interface, out_signature="ao")
3260
3282
                "D-Bus method"
3261
3283
                return dbus.Array(c.dbus_object_path for c in
3262
3284
                                  tcp_server.clients.values())
3263
 
            
 
3285
 
3264
3286
            @dbus_annotations({"org.freedesktop.DBus.Deprecated":
3265
3287
                               "true"})
3266
3288
            @dbus.service.method(_interface,
3268
3290
            def GetAllClientsWithProperties(self):
3269
3291
                "D-Bus method"
3270
3292
                return dbus.Dictionary(
3271
 
                    { c.dbus_object_path: c.GetAll(
 
3293
                    {c.dbus_object_path: c.GetAll(
3272
3294
                        "se.recompile.Mandos.Client")
3273
 
                      for c in tcp_server.clients.values() },
 
3295
                     for c in tcp_server.clients.values()},
3274
3296
                    signature="oa{sv}")
3275
 
            
 
3297
 
3276
3298
            @dbus.service.method(_interface, in_signature="o")
3277
3299
            def RemoveClient(self, object_path):
3278
3300
                "D-Bus method"
3286
3308
                        self.client_removed_signal(c)
3287
3309
                        return
3288
3310
                raise KeyError(object_path)
3289
 
            
 
3311
 
3290
3312
            del _interface
3291
 
            
 
3313
 
3292
3314
            @dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
3293
 
                                 out_signature = "a{oa{sa{sv}}}")
 
3315
                                 out_signature="a{oa{sa{sv}}}")
3294
3316
            def GetManagedObjects(self):
3295
3317
                """D-Bus method"""
3296
3318
                return dbus.Dictionary(
3297
 
                    { client.dbus_object_path:
3298
 
                      dbus.Dictionary(
3299
 
                          { interface: client.GetAll(interface)
3300
 
                            for interface in
3301
 
                                 client._get_all_interface_names()})
3302
 
                      for client in tcp_server.clients.values()})
3303
 
            
 
3319
                    {client.dbus_object_path:
 
3320
                     dbus.Dictionary(
 
3321
                         {interface: client.GetAll(interface)
 
3322
                          for interface in
 
3323
                          client._get_all_interface_names()})
 
3324
                     for client in tcp_server.clients.values()})
 
3325
 
3304
3326
            def client_added_signal(self, client):
3305
3327
                """Send the new standard signal and the old signal"""
3306
3328
                if use_dbus:
3308
3330
                    self.InterfacesAdded(
3309
3331
                        client.dbus_object_path,
3310
3332
                        dbus.Dictionary(
3311
 
                            { interface: client.GetAll(interface)
3312
 
                              for interface in
3313
 
                              client._get_all_interface_names()}))
 
3333
                            {interface: client.GetAll(interface)
 
3334
                             for interface in
 
3335
                             client._get_all_interface_names()}))
3314
3336
                    # Old signal
3315
3337
                    self.ClientAdded(client.dbus_object_path)
3316
 
            
 
3338
 
3317
3339
            def client_removed_signal(self, client):
3318
3340
                """Send the new standard signal and the old signal"""
3319
3341
                if use_dbus:
3324
3346
                    # Old signal
3325
3347
                    self.ClientRemoved(client.dbus_object_path,
3326
3348
                                       client.name)
3327
 
        
 
3349
 
3328
3350
        mandos_dbus_service = MandosDBusService()
3329
 
    
 
3351
 
3330
3352
    # Save modules to variables to exempt the modules from being
3331
3353
    # unloaded before the function registered with atexit() is run.
3332
3354
    mp = multiprocessing
3333
3355
    wn = wnull
 
3356
 
3334
3357
    def cleanup():
3335
3358
        "Cleanup function; run on exit"
3336
3359
        if zeroconf:
3337
3360
            service.cleanup()
3338
 
        
 
3361
 
3339
3362
        mp.active_children()
3340
3363
        wn.close()
3341
3364
        if not (tcp_server.clients or client_settings):
3342
3365
            return
3343
 
        
 
3366
 
3344
3367
        # Store client before exiting. Secrets are encrypted with key
3345
3368
        # based on what config file has. If config file is
3346
3369
        # removed/edited, old secret will thus be unrecovable.
3351
3374
                client.encrypted_secret = pgp.encrypt(client.secret,
3352
3375
                                                      key)
3353
3376
                client_dict = {}
3354
 
                
 
3377
 
3355
3378
                # A list of attributes that can not be pickled
3356
3379
                # + secret.
3357
 
                exclude = { "bus", "changedstate", "secret",
3358
 
                            "checker", "server_settings" }
 
3380
                exclude = {"bus", "changedstate", "secret",
 
3381
                           "checker", "server_settings"}
3359
3382
                for name, typ in inspect.getmembers(dbus.service
3360
3383
                                                    .Object):
3361
3384
                    exclude.add(name)
3362
 
                
 
3385
 
3363
3386
                client_dict["encrypted_secret"] = (client
3364
3387
                                                   .encrypted_secret)
3365
3388
                for attr in client.client_structure:
3366
3389
                    if attr not in exclude:
3367
3390
                        client_dict[attr] = getattr(client, attr)
3368
 
                
 
3391
 
3369
3392
                clients[client.name] = client_dict
3370
3393
                del client_settings[client.name]["secret"]
3371
 
        
 
3394
 
3372
3395
        try:
3373
3396
            with tempfile.NamedTemporaryFile(
3374
3397
                    mode='wb',
3377
3400
                    dir=os.path.dirname(stored_state_path),
3378
3401
                    delete=False) as stored_state:
3379
3402
                pickle.dump((clients, client_settings), stored_state,
3380
 
                            protocol = 2)
 
3403
                            protocol=2)
3381
3404
                tempname = stored_state.name
3382
3405
            os.rename(tempname, stored_state_path)
3383
3406
        except (IOError, OSError) as e:
3393
3416
                logger.warning("Could not save persistent state:",
3394
3417
                               exc_info=e)
3395
3418
                raise
3396
 
        
 
3419
 
3397
3420
        # Delete all clients, and settings from config
3398
3421
        while tcp_server.clients:
3399
3422
            name, client = tcp_server.clients.popitem()
3405
3428
            if use_dbus:
3406
3429
                mandos_dbus_service.client_removed_signal(client)
3407
3430
        client_settings.clear()
3408
 
    
 
3431
 
3409
3432
    atexit.register(cleanup)
3410
 
    
 
3433
 
3411
3434
    for client in tcp_server.clients.values():
3412
3435
        if use_dbus:
3413
3436
            # Emit D-Bus signal for adding
3415
3438
        # Need to initiate checking of clients
3416
3439
        if client.enabled:
3417
3440
            client.init_checker()
3418
 
    
 
3441
 
3419
3442
    tcp_server.enable()
3420
3443
    tcp_server.server_activate()
3421
 
    
 
3444
 
3422
3445
    # Find out what port we got
3423
3446
    if zeroconf:
3424
3447
        service.port = tcp_server.socket.getsockname()[1]
3429
3452
    else:                       # IPv4
3430
3453
        logger.info("Now listening on address %r, port %d",
3431
3454
                    *tcp_server.socket.getsockname())
3432
 
    
3433
 
    #service.interface = tcp_server.socket.getsockname()[3]
3434
 
    
 
3455
 
 
3456
    # service.interface = tcp_server.socket.getsockname()[3]
 
3457
 
3435
3458
    try:
3436
3459
        if zeroconf:
3437
3460
            # From the Avahi example code
3442
3465
                cleanup()
3443
3466
                sys.exit(1)
3444
3467
            # End of Avahi example code
3445
 
        
 
3468
 
3446
3469
        GLib.io_add_watch(tcp_server.fileno(), GLib.IO_IN,
3447
3470
                          lambda *args, **kwargs:
3448
3471
                          (tcp_server.handle_request
3449
3472
                           (*args[2:], **kwargs) or True))
3450
 
        
 
3473
 
3451
3474
        logger.debug("Starting main loop")
3452
3475
        main_loop.run()
3453
3476
    except AvahiError as error: