/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: 2019-07-29 16:35:53 UTC
  • Revision ID: teddy@recompile.se-20190729163553-1i442i2cbx64c537
Make tests and man page examples match

Make the tests test_manual_page_example[1-5] match exactly what is
written in the manual page, and add comments to manual page as
reminders to keep tests and manual page examples in sync.

* mandos-ctl (Test_commands_from_options.test_manual_page_example_1):
  Remove "--verbose" option, since the manual does not have it as the
  first example, and change assertion to match.
* mandos-ctl.xml (EXAMPLE): Add comments to all examples documenting
  which test function they correspond to.  Also remove unnecessary
  quotes from option arguments in fourth example, and clarify language
  slightly in fifth example.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python2.7
 
1
#!/usr/bin/python
2
2
# -*- mode: python; coding: utf-8 -*-
3
 
 
3
#
4
4
# Mandos server - give out binary blobs to connecting clients.
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
 
# Copyright © 2008-2015 Teddy Hogeborn
15
 
# Copyright © 2008-2015 Björn Påhlsson
16
 
17
 
# This program is free software: you can redistribute it and/or modify
18
 
# it under the terms of the GNU General Public License as published by
 
14
# Copyright © 2008-2019 Teddy Hogeborn
 
15
# Copyright © 2008-2019 Björn Påhlsson
 
16
#
 
17
# This file is part of Mandos.
 
18
#
 
19
# Mandos is free software: you can redistribute it and/or modify it
 
20
# under the terms of the GNU General Public License as published by
19
21
# the Free Software Foundation, either version 3 of the License, or
20
22
# (at your option) any later version.
21
23
#
22
 
#     This program is distributed in the hope that it will be useful,
23
 
#     but WITHOUT ANY WARRANTY; without even the implied warranty of
 
24
#     Mandos is distributed in the hope that it will be useful, but
 
25
#     WITHOUT ANY WARRANTY; without even the implied warranty of
24
26
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25
27
#     GNU General Public License for more details.
26
 
 
28
#
27
29
# You should have received a copy of the GNU General Public License
28
 
# along with this program.  If not, see
29
 
# <http://www.gnu.org/licenses/>.
30
 
 
30
# along with Mandos.  If not, see <http://www.gnu.org/licenses/>.
 
31
#
31
32
# Contact the authors at <mandos@recompile.se>.
32
 
 
33
#
33
34
 
34
35
from __future__ import (division, absolute_import, print_function,
35
36
                        unicode_literals)
36
37
 
37
 
from future_builtins import *
 
38
try:
 
39
    from future_builtins import *
 
40
except ImportError:
 
41
    pass
38
42
 
39
43
try:
40
44
    import SocketServer as socketserver
44
48
import argparse
45
49
import datetime
46
50
import errno
47
 
import gnutls.connection
48
 
import gnutls.errors
49
 
import gnutls.library.functions
50
 
import gnutls.library.constants
51
 
import gnutls.library.types
52
51
try:
53
52
    import ConfigParser as configparser
54
53
except ImportError:
81
80
 
82
81
import dbus
83
82
import dbus.service
84
 
try:
85
 
    import gobject
86
 
except ImportError:
87
 
    from gi.repository import GObject as gobject
88
 
import avahi
 
83
from gi.repository import GLib
89
84
from dbus.mainloop.glib import DBusGMainLoop
90
85
import ctypes
91
86
import ctypes.util
92
87
import xml.dom.minidom
93
88
import inspect
94
89
 
 
90
# Try to find the value of SO_BINDTODEVICE:
95
91
try:
 
92
    # This is where SO_BINDTODEVICE is in Python 3.3 (or 3.4?) and
 
93
    # newer, and it is also the most natural place for it:
96
94
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
97
95
except AttributeError:
98
96
    try:
 
97
        # This is where SO_BINDTODEVICE was up to and including Python
 
98
        # 2.6, and also 3.2:
99
99
        from IN import SO_BINDTODEVICE
100
100
    except ImportError:
101
 
        SO_BINDTODEVICE = None
 
101
        # In Python 2.7 it seems to have been removed entirely.
 
102
        # Try running the C preprocessor:
 
103
        try:
 
104
            cc = subprocess.Popen(["cc", "--language=c", "-E",
 
105
                                   "/dev/stdin"],
 
106
                                  stdin=subprocess.PIPE,
 
107
                                  stdout=subprocess.PIPE)
 
108
            stdout = cc.communicate(
 
109
                "#include <sys/socket.h>\nSO_BINDTODEVICE\n")[0]
 
110
            SO_BINDTODEVICE = int(stdout.splitlines()[-1])
 
111
        except (OSError, ValueError, IndexError):
 
112
            # No value found
 
113
            SO_BINDTODEVICE = None
102
114
 
103
115
if sys.version_info.major == 2:
104
116
    str = unicode
105
117
 
106
 
version = "1.7.1"
 
118
version = "1.8.4"
107
119
stored_state_file = "clients.pickle"
108
120
 
109
121
logger = logging.getLogger()
113
125
    if_nametoindex = ctypes.cdll.LoadLibrary(
114
126
        ctypes.util.find_library("c")).if_nametoindex
115
127
except (OSError, AttributeError):
116
 
    
 
128
 
117
129
    def if_nametoindex(interface):
118
130
        "Get an interface index the hard way, i.e. using fcntl()"
119
131
        SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
124
136
        return interface_index
125
137
 
126
138
 
 
139
def copy_function(func):
 
140
    """Make a copy of a function"""
 
141
    if sys.version_info.major == 2:
 
142
        return types.FunctionType(func.func_code,
 
143
                                  func.func_globals,
 
144
                                  func.func_name,
 
145
                                  func.func_defaults,
 
146
                                  func.func_closure)
 
147
    else:
 
148
        return types.FunctionType(func.__code__,
 
149
                                  func.__globals__,
 
150
                                  func.__name__,
 
151
                                  func.__defaults__,
 
152
                                  func.__closure__)
 
153
 
 
154
 
127
155
def initlogger(debug, level=logging.WARNING):
128
156
    """init logger and add loglevel"""
129
 
    
 
157
 
130
158
    global syslogger
131
159
    syslogger = (logging.handlers.SysLogHandler(
132
 
        facility = logging.handlers.SysLogHandler.LOG_DAEMON,
133
 
        address = "/dev/log"))
 
160
        facility=logging.handlers.SysLogHandler.LOG_DAEMON,
 
161
        address="/dev/log"))
134
162
    syslogger.setFormatter(logging.Formatter
135
163
                           ('Mandos [%(process)d]: %(levelname)s:'
136
164
                            ' %(message)s'))
137
165
    logger.addHandler(syslogger)
138
 
    
 
166
 
139
167
    if debug:
140
168
        console = logging.StreamHandler()
141
169
        console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
153
181
 
154
182
class PGPEngine(object):
155
183
    """A simple class for OpenPGP symmetric encryption & decryption"""
156
 
    
 
184
 
157
185
    def __init__(self):
158
186
        self.tempdir = tempfile.mkdtemp(prefix="mandos-")
 
187
        self.gpg = "gpg"
 
188
        try:
 
189
            output = subprocess.check_output(["gpgconf"])
 
190
            for line in output.splitlines():
 
191
                name, text, path = line.split(b":")
 
192
                if name == "gpg":
 
193
                    self.gpg = path
 
194
                    break
 
195
        except OSError as e:
 
196
            if e.errno != errno.ENOENT:
 
197
                raise
159
198
        self.gnupgargs = ['--batch',
160
 
                          '--home', self.tempdir,
 
199
                          '--homedir', self.tempdir,
161
200
                          '--force-mdc',
162
 
                          '--quiet',
163
 
                          '--no-use-agent']
164
 
    
 
201
                          '--quiet']
 
202
        # Only GPG version 1 has the --no-use-agent option.
 
203
        if self.gpg == "gpg" or self.gpg.endswith("/gpg"):
 
204
            self.gnupgargs.append("--no-use-agent")
 
205
 
165
206
    def __enter__(self):
166
207
        return self
167
 
    
 
208
 
168
209
    def __exit__(self, exc_type, exc_value, traceback):
169
210
        self._cleanup()
170
211
        return False
171
 
    
 
212
 
172
213
    def __del__(self):
173
214
        self._cleanup()
174
 
    
 
215
 
175
216
    def _cleanup(self):
176
217
        if self.tempdir is not None:
177
218
            # Delete contents of tempdir
178
219
            for root, dirs, files in os.walk(self.tempdir,
179
 
                                             topdown = False):
 
220
                                             topdown=False):
180
221
                for filename in files:
181
222
                    os.remove(os.path.join(root, filename))
182
223
                for dirname in dirs:
184
225
            # Remove tempdir
185
226
            os.rmdir(self.tempdir)
186
227
            self.tempdir = None
187
 
    
 
228
 
188
229
    def password_encode(self, password):
189
230
        # Passphrase can not be empty and can not contain newlines or
190
231
        # NUL bytes.  So we prefix it and hex encode it.
195
236
                       .replace(b"\n", b"\\n")
196
237
                       .replace(b"\0", b"\\x00"))
197
238
        return encoded
198
 
    
 
239
 
199
240
    def encrypt(self, data, password):
200
241
        passphrase = self.password_encode(password)
201
242
        with tempfile.NamedTemporaryFile(
202
243
                dir=self.tempdir) as passfile:
203
244
            passfile.write(passphrase)
204
245
            passfile.flush()
205
 
            proc = subprocess.Popen(['gpg', '--symmetric',
 
246
            proc = subprocess.Popen([self.gpg, '--symmetric',
206
247
                                     '--passphrase-file',
207
248
                                     passfile.name]
208
249
                                    + self.gnupgargs,
209
 
                                    stdin = subprocess.PIPE,
210
 
                                    stdout = subprocess.PIPE,
211
 
                                    stderr = subprocess.PIPE)
212
 
            ciphertext, err = proc.communicate(input = data)
 
250
                                    stdin=subprocess.PIPE,
 
251
                                    stdout=subprocess.PIPE,
 
252
                                    stderr=subprocess.PIPE)
 
253
            ciphertext, err = proc.communicate(input=data)
213
254
        if proc.returncode != 0:
214
255
            raise PGPError(err)
215
256
        return ciphertext
216
 
    
 
257
 
217
258
    def decrypt(self, data, password):
218
259
        passphrase = self.password_encode(password)
219
260
        with tempfile.NamedTemporaryFile(
220
 
                dir = self.tempdir) as passfile:
 
261
                dir=self.tempdir) as passfile:
221
262
            passfile.write(passphrase)
222
263
            passfile.flush()
223
 
            proc = subprocess.Popen(['gpg', '--decrypt',
 
264
            proc = subprocess.Popen([self.gpg, '--decrypt',
224
265
                                     '--passphrase-file',
225
266
                                     passfile.name]
226
267
                                    + self.gnupgargs,
227
 
                                    stdin = subprocess.PIPE,
228
 
                                    stdout = subprocess.PIPE,
229
 
                                    stderr = subprocess.PIPE)
230
 
            decrypted_plaintext, err = proc.communicate(input = data)
 
268
                                    stdin=subprocess.PIPE,
 
269
                                    stdout=subprocess.PIPE,
 
270
                                    stderr=subprocess.PIPE)
 
271
            decrypted_plaintext, err = proc.communicate(input=data)
231
272
        if proc.returncode != 0:
232
273
            raise PGPError(err)
233
274
        return decrypted_plaintext
234
275
 
235
276
 
 
277
# Pretend that we have an Avahi module
 
278
class avahi(object):
 
279
    """This isn't so much a class as it is a module-like namespace."""
 
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
 
284
    DBUS_NAME = "org.freedesktop.Avahi"
 
285
    DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"
 
286
    DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
 
287
    DBUS_PATH_SERVER = "/"
 
288
 
 
289
    @staticmethod
 
290
    def string_array_to_txt_array(t):
 
291
        return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
 
292
                           for s in t), signature="ay")
 
293
    ENTRY_GROUP_ESTABLISHED = 2  # avahi-common/defs.h
 
294
    ENTRY_GROUP_COLLISION = 3    # avahi-common/defs.h
 
295
    ENTRY_GROUP_FAILURE = 4      # avahi-common/defs.h
 
296
    SERVER_INVALID = 0           # avahi-common/defs.h
 
297
    SERVER_REGISTERING = 1       # avahi-common/defs.h
 
298
    SERVER_RUNNING = 2           # avahi-common/defs.h
 
299
    SERVER_COLLISION = 3         # avahi-common/defs.h
 
300
    SERVER_FAILURE = 4           # avahi-common/defs.h
 
301
 
 
302
 
236
303
class AvahiError(Exception):
237
304
    def __init__(self, value, *args, **kwargs):
238
305
        self.value = value
250
317
 
251
318
class AvahiService(object):
252
319
    """An Avahi (Zeroconf) service.
253
 
    
 
320
 
254
321
    Attributes:
255
322
    interface: integer; avahi.IF_UNSPEC or an interface index.
256
323
               Used to optionally bind to the specified interface.
268
335
    server: D-Bus Server
269
336
    bus: dbus.SystemBus()
270
337
    """
271
 
    
 
338
 
272
339
    def __init__(self,
273
 
                 interface = avahi.IF_UNSPEC,
274
 
                 name = None,
275
 
                 servicetype = None,
276
 
                 port = None,
277
 
                 TXT = None,
278
 
                 domain = "",
279
 
                 host = "",
280
 
                 max_renames = 32768,
281
 
                 protocol = avahi.PROTO_UNSPEC,
282
 
                 bus = None):
 
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):
283
350
        self.interface = interface
284
351
        self.name = name
285
352
        self.type = servicetype
294
361
        self.server = None
295
362
        self.bus = bus
296
363
        self.entry_group_state_changed_match = None
297
 
    
 
364
 
298
365
    def rename(self, remove=True):
299
366
        """Derived from the Avahi example code"""
300
367
        if self.rename_count >= self.max_renames:
320
387
                logger.critical("D-Bus Exception", exc_info=error)
321
388
                self.cleanup()
322
389
                os._exit(1)
323
 
    
 
390
 
324
391
    def remove(self):
325
392
        """Derived from the Avahi example code"""
326
393
        if self.entry_group_state_changed_match is not None:
328
395
            self.entry_group_state_changed_match = None
329
396
        if self.group is not None:
330
397
            self.group.Reset()
331
 
    
 
398
 
332
399
    def add(self):
333
400
        """Derived from the Avahi example code"""
334
401
        self.remove()
351
418
            dbus.UInt16(self.port),
352
419
            avahi.string_array_to_txt_array(self.TXT))
353
420
        self.group.Commit()
354
 
    
 
421
 
355
422
    def entry_group_state_changed(self, state, error):
356
423
        """Derived from the Avahi example code"""
357
424
        logger.debug("Avahi entry group state change: %i", state)
358
 
        
 
425
 
359
426
        if state == avahi.ENTRY_GROUP_ESTABLISHED:
360
427
            logger.debug("Zeroconf service established.")
361
428
        elif state == avahi.ENTRY_GROUP_COLLISION:
365
432
            logger.critical("Avahi: Error in group state changed %s",
366
433
                            str(error))
367
434
            raise AvahiGroupError("State changed: {!s}".format(error))
368
 
    
 
435
 
369
436
    def cleanup(self):
370
437
        """Derived from the Avahi example code"""
371
438
        if self.group is not None:
376
443
                pass
377
444
            self.group = None
378
445
        self.remove()
379
 
    
 
446
 
380
447
    def server_state_changed(self, state, error=None):
381
448
        """Derived from the Avahi example code"""
382
449
        logger.debug("Avahi server state change: %i", state)
411
478
                logger.debug("Unknown state: %r", state)
412
479
            else:
413
480
                logger.debug("Unknown state: %r: %r", state, error)
414
 
    
 
481
 
415
482
    def activate(self):
416
483
        """Derived from the Avahi example code"""
417
484
        if self.server is None:
428
495
class AvahiServiceToSyslog(AvahiService):
429
496
    def rename(self, *args, **kwargs):
430
497
        """Add the new name to the syslog messages"""
431
 
        ret = AvahiService.rename(self, *args, **kwargs)
 
498
        ret = super(AvahiServiceToSyslog, self).rename(*args, **kwargs)
432
499
        syslogger.setFormatter(logging.Formatter(
433
500
            'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
434
501
            .format(self.name)))
435
502
        return ret
436
503
 
 
504
 
 
505
# Pretend that we have a GnuTLS module
 
506
class gnutls(object):
 
507
    """This isn't so much a class as it is a module-like namespace."""
 
508
 
 
509
    library = ctypes.util.find_library("gnutls")
 
510
    if library is None:
 
511
        library = ctypes.util.find_library("gnutls-deb0")
 
512
    _library = ctypes.cdll.LoadLibrary(library)
 
513
    del library
 
514
 
 
515
    # Unless otherwise indicated, the constants and types below are
 
516
    # all from the gnutls/gnutls.h C header file.
 
517
 
 
518
    # Constants
 
519
    E_SUCCESS = 0
 
520
    E_INTERRUPTED = -52
 
521
    E_AGAIN = -28
 
522
    CRT_OPENPGP = 2
 
523
    CRT_RAWPK = 3
 
524
    CLIENT = 2
 
525
    SHUT_RDWR = 0
 
526
    CRD_CERTIFICATE = 1
 
527
    E_NO_CERTIFICATE_FOUND = -49
 
528
    X509_FMT_DER = 0
 
529
    NO_TICKETS = 1<<10
 
530
    ENABLE_RAWPK = 1<<18
 
531
    CTYPE_PEERS = 3
 
532
    KEYID_USE_SHA256 = 1        # gnutls/x509.h
 
533
    OPENPGP_FMT_RAW = 0         # gnutls/openpgp.h
 
534
 
 
535
    # Types
 
536
    class session_int(ctypes.Structure):
 
537
        _fields_ = []
 
538
    session_t = ctypes.POINTER(session_int)
 
539
 
 
540
    class certificate_credentials_st(ctypes.Structure):
 
541
        _fields_ = []
 
542
    certificate_credentials_t = ctypes.POINTER(
 
543
        certificate_credentials_st)
 
544
    certificate_type_t = ctypes.c_int
 
545
 
 
546
    class datum_t(ctypes.Structure):
 
547
        _fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
 
548
                    ('size', ctypes.c_uint)]
 
549
 
 
550
    class openpgp_crt_int(ctypes.Structure):
 
551
        _fields_ = []
 
552
    openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
 
553
    openpgp_crt_fmt_t = ctypes.c_int  # gnutls/openpgp.h
 
554
    log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
 
555
    credentials_type_t = ctypes.c_int
 
556
    transport_ptr_t = ctypes.c_void_p
 
557
    close_request_t = ctypes.c_int
 
558
 
 
559
    # Exceptions
 
560
    class Error(Exception):
 
561
        def __init__(self, message=None, code=None, args=()):
 
562
            # Default usage is by a message string, but if a return
 
563
            # code is passed, convert it to a string with
 
564
            # gnutls.strerror()
 
565
            self.code = code
 
566
            if message is None and code is not None:
 
567
                message = gnutls.strerror(code)
 
568
            return super(gnutls.Error, self).__init__(
 
569
                message, *args)
 
570
 
 
571
    class CertificateSecurityError(Error):
 
572
        pass
 
573
 
 
574
    # Classes
 
575
    class Credentials(object):
 
576
        def __init__(self):
 
577
            self._c_object = gnutls.certificate_credentials_t()
 
578
            gnutls.certificate_allocate_credentials(
 
579
                ctypes.byref(self._c_object))
 
580
            self.type = gnutls.CRD_CERTIFICATE
 
581
 
 
582
        def __del__(self):
 
583
            gnutls.certificate_free_credentials(self._c_object)
 
584
 
 
585
    class ClientSession(object):
 
586
        def __init__(self, socket, credentials=None):
 
587
            self._c_object = gnutls.session_t()
 
588
            gnutls_flags = gnutls.CLIENT
 
589
            if gnutls.check_version(b"3.5.6"):
 
590
                gnutls_flags |= gnutls.NO_TICKETS
 
591
            if gnutls.has_rawpk:
 
592
                gnutls_flags |= gnutls.ENABLE_RAWPK
 
593
            gnutls.init(ctypes.byref(self._c_object), gnutls_flags)
 
594
            del gnutls_flags
 
595
            gnutls.set_default_priority(self._c_object)
 
596
            gnutls.transport_set_ptr(self._c_object, socket.fileno())
 
597
            gnutls.handshake_set_private_extensions(self._c_object,
 
598
                                                    True)
 
599
            self.socket = socket
 
600
            if credentials is None:
 
601
                credentials = gnutls.Credentials()
 
602
            gnutls.credentials_set(self._c_object, credentials.type,
 
603
                                   ctypes.cast(credentials._c_object,
 
604
                                               ctypes.c_void_p))
 
605
            self.credentials = credentials
 
606
 
 
607
        def __del__(self):
 
608
            gnutls.deinit(self._c_object)
 
609
 
 
610
        def handshake(self):
 
611
            return gnutls.handshake(self._c_object)
 
612
 
 
613
        def send(self, data):
 
614
            data = bytes(data)
 
615
            data_len = len(data)
 
616
            while data_len > 0:
 
617
                data_len -= gnutls.record_send(self._c_object,
 
618
                                               data[-data_len:],
 
619
                                               data_len)
 
620
 
 
621
        def bye(self):
 
622
            return gnutls.bye(self._c_object, gnutls.SHUT_RDWR)
 
623
 
 
624
    # Error handling functions
 
625
    def _error_code(result):
 
626
        """A function to raise exceptions on errors, suitable
 
627
        for the 'restype' attribute on ctypes functions"""
 
628
        if result >= 0:
 
629
            return result
 
630
        if result == gnutls.E_NO_CERTIFICATE_FOUND:
 
631
            raise gnutls.CertificateSecurityError(code=result)
 
632
        raise gnutls.Error(code=result)
 
633
 
 
634
    def _retry_on_error(result, func, arguments):
 
635
        """A function to retry on some errors, suitable
 
636
        for the 'errcheck' attribute on ctypes functions"""
 
637
        while result < 0:
 
638
            if result not in (gnutls.E_INTERRUPTED, gnutls.E_AGAIN):
 
639
                return _error_code(result)
 
640
            result = func(*arguments)
 
641
        return result
 
642
 
 
643
    # Unless otherwise indicated, the function declarations below are
 
644
    # all from the gnutls/gnutls.h C header file.
 
645
 
 
646
    # Functions
 
647
    priority_set_direct = _library.gnutls_priority_set_direct
 
648
    priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
 
649
                                    ctypes.POINTER(ctypes.c_char_p)]
 
650
    priority_set_direct.restype = _error_code
 
651
 
 
652
    init = _library.gnutls_init
 
653
    init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
 
654
    init.restype = _error_code
 
655
 
 
656
    set_default_priority = _library.gnutls_set_default_priority
 
657
    set_default_priority.argtypes = [session_t]
 
658
    set_default_priority.restype = _error_code
 
659
 
 
660
    record_send = _library.gnutls_record_send
 
661
    record_send.argtypes = [session_t, ctypes.c_void_p,
 
662
                            ctypes.c_size_t]
 
663
    record_send.restype = ctypes.c_ssize_t
 
664
    record_send.errcheck = _retry_on_error
 
665
 
 
666
    certificate_allocate_credentials = (
 
667
        _library.gnutls_certificate_allocate_credentials)
 
668
    certificate_allocate_credentials.argtypes = [
 
669
        ctypes.POINTER(certificate_credentials_t)]
 
670
    certificate_allocate_credentials.restype = _error_code
 
671
 
 
672
    certificate_free_credentials = (
 
673
        _library.gnutls_certificate_free_credentials)
 
674
    certificate_free_credentials.argtypes = [
 
675
        certificate_credentials_t]
 
676
    certificate_free_credentials.restype = None
 
677
 
 
678
    handshake_set_private_extensions = (
 
679
        _library.gnutls_handshake_set_private_extensions)
 
680
    handshake_set_private_extensions.argtypes = [session_t,
 
681
                                                 ctypes.c_int]
 
682
    handshake_set_private_extensions.restype = None
 
683
 
 
684
    credentials_set = _library.gnutls_credentials_set
 
685
    credentials_set.argtypes = [session_t, credentials_type_t,
 
686
                                ctypes.c_void_p]
 
687
    credentials_set.restype = _error_code
 
688
 
 
689
    strerror = _library.gnutls_strerror
 
690
    strerror.argtypes = [ctypes.c_int]
 
691
    strerror.restype = ctypes.c_char_p
 
692
 
 
693
    certificate_type_get = _library.gnutls_certificate_type_get
 
694
    certificate_type_get.argtypes = [session_t]
 
695
    certificate_type_get.restype = _error_code
 
696
 
 
697
    certificate_get_peers = _library.gnutls_certificate_get_peers
 
698
    certificate_get_peers.argtypes = [session_t,
 
699
                                      ctypes.POINTER(ctypes.c_uint)]
 
700
    certificate_get_peers.restype = ctypes.POINTER(datum_t)
 
701
 
 
702
    global_set_log_level = _library.gnutls_global_set_log_level
 
703
    global_set_log_level.argtypes = [ctypes.c_int]
 
704
    global_set_log_level.restype = None
 
705
 
 
706
    global_set_log_function = _library.gnutls_global_set_log_function
 
707
    global_set_log_function.argtypes = [log_func]
 
708
    global_set_log_function.restype = None
 
709
 
 
710
    deinit = _library.gnutls_deinit
 
711
    deinit.argtypes = [session_t]
 
712
    deinit.restype = None
 
713
 
 
714
    handshake = _library.gnutls_handshake
 
715
    handshake.argtypes = [session_t]
 
716
    handshake.restype = _error_code
 
717
    handshake.errcheck = _retry_on_error
 
718
 
 
719
    transport_set_ptr = _library.gnutls_transport_set_ptr
 
720
    transport_set_ptr.argtypes = [session_t, transport_ptr_t]
 
721
    transport_set_ptr.restype = None
 
722
 
 
723
    bye = _library.gnutls_bye
 
724
    bye.argtypes = [session_t, close_request_t]
 
725
    bye.restype = _error_code
 
726
    bye.errcheck = _retry_on_error
 
727
 
 
728
    check_version = _library.gnutls_check_version
 
729
    check_version.argtypes = [ctypes.c_char_p]
 
730
    check_version.restype = ctypes.c_char_p
 
731
 
 
732
    _need_version = b"3.3.0"
 
733
    if check_version(_need_version) is None:
 
734
        raise self.Error("Needs GnuTLS {} or later"
 
735
                         .format(_need_version))
 
736
 
 
737
    _tls_rawpk_version = b"3.6.6"
 
738
    has_rawpk = bool(check_version(_tls_rawpk_version))
 
739
 
 
740
    if has_rawpk:
 
741
        # Types
 
742
        class pubkey_st(ctypes.Structure):
 
743
            _fields = []
 
744
        pubkey_t = ctypes.POINTER(pubkey_st)
 
745
 
 
746
        x509_crt_fmt_t = ctypes.c_int
 
747
 
 
748
        # All the function declarations below are from gnutls/abstract.h
 
749
        pubkey_init = _library.gnutls_pubkey_init
 
750
        pubkey_init.argtypes = [ctypes.POINTER(pubkey_t)]
 
751
        pubkey_init.restype = _error_code
 
752
 
 
753
        pubkey_import = _library.gnutls_pubkey_import
 
754
        pubkey_import.argtypes = [pubkey_t, ctypes.POINTER(datum_t),
 
755
                                  x509_crt_fmt_t]
 
756
        pubkey_import.restype = _error_code
 
757
 
 
758
        pubkey_get_key_id = _library.gnutls_pubkey_get_key_id
 
759
        pubkey_get_key_id.argtypes = [pubkey_t, ctypes.c_int,
 
760
                                      ctypes.POINTER(ctypes.c_ubyte),
 
761
                                      ctypes.POINTER(ctypes.c_size_t)]
 
762
        pubkey_get_key_id.restype = _error_code
 
763
 
 
764
        pubkey_deinit = _library.gnutls_pubkey_deinit
 
765
        pubkey_deinit.argtypes = [pubkey_t]
 
766
        pubkey_deinit.restype = None
 
767
    else:
 
768
        # All the function declarations below are from gnutls/openpgp.h
 
769
 
 
770
        openpgp_crt_init = _library.gnutls_openpgp_crt_init
 
771
        openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
 
772
        openpgp_crt_init.restype = _error_code
 
773
 
 
774
        openpgp_crt_import = _library.gnutls_openpgp_crt_import
 
775
        openpgp_crt_import.argtypes = [openpgp_crt_t,
 
776
                                       ctypes.POINTER(datum_t),
 
777
                                       openpgp_crt_fmt_t]
 
778
        openpgp_crt_import.restype = _error_code
 
779
 
 
780
        openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
 
781
        openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
 
782
                                            ctypes.POINTER(ctypes.c_uint)]
 
783
        openpgp_crt_verify_self.restype = _error_code
 
784
 
 
785
        openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
 
786
        openpgp_crt_deinit.argtypes = [openpgp_crt_t]
 
787
        openpgp_crt_deinit.restype = None
 
788
 
 
789
        openpgp_crt_get_fingerprint = (
 
790
            _library.gnutls_openpgp_crt_get_fingerprint)
 
791
        openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
 
792
                                                ctypes.c_void_p,
 
793
                                                ctypes.POINTER(
 
794
                                                    ctypes.c_size_t)]
 
795
        openpgp_crt_get_fingerprint.restype = _error_code
 
796
 
 
797
    if check_version(b"3.6.4"):
 
798
        certificate_type_get2 = _library.gnutls_certificate_type_get2
 
799
        certificate_type_get2.argtypes = [session_t, ctypes.c_int]
 
800
        certificate_type_get2.restype = _error_code
 
801
 
 
802
    # Remove non-public functions
 
803
    del _error_code, _retry_on_error
 
804
 
 
805
 
437
806
def call_pipe(connection,       # : multiprocessing.Connection
438
807
              func, *args, **kwargs):
439
808
    """This function is meant to be called by multiprocessing.Process
440
 
    
 
809
 
441
810
    This function runs func(*args, **kwargs), and writes the resulting
442
811
    return value on the provided multiprocessing.Connection.
443
812
    """
444
813
    connection.send(func(*args, **kwargs))
445
814
    connection.close()
446
815
 
 
816
 
447
817
class Client(object):
448
818
    """A representation of a client host served by this server.
449
 
    
 
819
 
450
820
    Attributes:
451
821
    approved:   bool(); 'None' if not yet approved/disapproved
452
822
    approval_delay: datetime.timedelta(); Time to wait for approval
454
824
    checker:    subprocess.Popen(); a running checker process used
455
825
                                    to see if the client lives.
456
826
                                    'None' if no process is running.
457
 
    checker_callback_tag: a gobject event source tag, or None
 
827
    checker_callback_tag: a GLib event source tag, or None
458
828
    checker_command: string; External command which is run to check
459
829
                     if client lives.  %() expansions are done at
460
830
                     runtime with vars(self) as dict, so that for
461
831
                     instance %(name)s can be used in the command.
462
 
    checker_initiator_tag: a gobject event source tag, or None
 
832
    checker_initiator_tag: a GLib event source tag, or None
463
833
    created:    datetime.datetime(); (UTC) object creation
464
834
    client_structure: Object describing what attributes a client has
465
835
                      and is used for storing the client at exit
466
836
    current_checker_command: string; current running checker_command
467
 
    disable_initiator_tag: a gobject event source tag, or None
 
837
    disable_initiator_tag: a GLib event source tag, or None
468
838
    enabled:    bool()
469
839
    fingerprint: string (40 or 32 hexadecimal digits); used to
470
 
                 uniquely identify the client
 
840
                 uniquely identify an OpenPGP client
 
841
    key_id: string (64 hexadecimal digits); used to uniquely identify
 
842
            a client using raw public keys
471
843
    host:       string; available for use by the checker command
472
844
    interval:   datetime.timedelta(); How often to start a new checker
473
845
    last_approval_request: datetime.datetime(); (UTC) or None
489
861
                disabled, or None
490
862
    server_settings: The server_settings dict from main()
491
863
    """
492
 
    
 
864
 
493
865
    runtime_expansions = ("approval_delay", "approval_duration",
494
 
                          "created", "enabled", "expires",
 
866
                          "created", "enabled", "expires", "key_id",
495
867
                          "fingerprint", "host", "interval",
496
868
                          "last_approval_request", "last_checked_ok",
497
869
                          "last_enabled", "name", "timeout")
506
878
        "approved_by_default": "True",
507
879
        "enabled": "True",
508
880
    }
509
 
    
 
881
 
510
882
    @staticmethod
511
883
    def config_parser(config):
512
884
        """Construct a new dict of client settings of this form:
519
891
        for client_name in config.sections():
520
892
            section = dict(config.items(client_name))
521
893
            client = settings[client_name] = {}
522
 
            
 
894
 
523
895
            client["host"] = section["host"]
524
896
            # Reformat values from string types to Python types
525
897
            client["approved_by_default"] = config.getboolean(
526
898
                client_name, "approved_by_default")
527
899
            client["enabled"] = config.getboolean(client_name,
528
900
                                                  "enabled")
529
 
            
530
 
            # Uppercase and remove spaces from fingerprint for later
531
 
            # comparison purposes with return value from the
532
 
            # fingerprint() function
 
901
 
 
902
            # Uppercase and remove spaces from key_id and fingerprint
 
903
            # for later comparison purposes with return value from the
 
904
            # key_id() and fingerprint() functions
 
905
            client["key_id"] = (section.get("key_id", "").upper()
 
906
                                .replace(" ", ""))
533
907
            client["fingerprint"] = (section["fingerprint"].upper()
534
908
                                     .replace(" ", ""))
535
909
            if "secret" in section:
536
 
                client["secret"] = section["secret"].decode("base64")
 
910
                client["secret"] = codecs.decode(section["secret"]
 
911
                                                 .encode("utf-8"),
 
912
                                                 "base64")
537
913
            elif "secfile" in section:
538
914
                with open(os.path.expanduser(os.path.expandvars
539
915
                                             (section["secfile"])),
554
930
            client["last_approval_request"] = None
555
931
            client["last_checked_ok"] = None
556
932
            client["last_checker_status"] = -2
557
 
        
 
933
 
558
934
        return settings
559
 
    
560
 
    def __init__(self, settings, name = None, server_settings=None):
 
935
 
 
936
    def __init__(self, settings, name=None, server_settings=None):
561
937
        self.name = name
562
938
        if server_settings is None:
563
939
            server_settings = {}
565
941
        # adding all client settings
566
942
        for setting, value in settings.items():
567
943
            setattr(self, setting, value)
568
 
        
 
944
 
569
945
        if self.enabled:
570
946
            if not hasattr(self, "last_enabled"):
571
947
                self.last_enabled = datetime.datetime.utcnow()
575
951
        else:
576
952
            self.last_enabled = None
577
953
            self.expires = None
578
 
        
 
954
 
579
955
        logger.debug("Creating client %r", self.name)
 
956
        logger.debug("  Key ID: %s", self.key_id)
580
957
        logger.debug("  Fingerprint: %s", self.fingerprint)
581
958
        self.created = settings.get("created",
582
959
                                    datetime.datetime.utcnow())
583
 
        
 
960
 
584
961
        # attributes specific for this server instance
585
962
        self.checker = None
586
963
        self.checker_initiator_tag = None
592
969
        self.changedstate = multiprocessing_manager.Condition(
593
970
            multiprocessing_manager.Lock())
594
971
        self.client_structure = [attr
595
 
                                 for attr in self.__dict__.iterkeys()
 
972
                                 for attr in self.__dict__.keys()
596
973
                                 if not attr.startswith("_")]
597
974
        self.client_structure.append("client_structure")
598
 
        
 
975
 
599
976
        for name, t in inspect.getmembers(
600
977
                type(self), lambda obj: isinstance(obj, property)):
601
978
            if not name.startswith("_"):
602
979
                self.client_structure.append(name)
603
 
    
 
980
 
604
981
    # Send notice to process children that client state has changed
605
982
    def send_changedstate(self):
606
983
        with self.changedstate:
607
984
            self.changedstate.notify_all()
608
 
    
 
985
 
609
986
    def enable(self):
610
987
        """Start this client's checker and timeout hooks"""
611
988
        if getattr(self, "enabled", False):
616
993
        self.last_enabled = datetime.datetime.utcnow()
617
994
        self.init_checker()
618
995
        self.send_changedstate()
619
 
    
 
996
 
620
997
    def disable(self, quiet=True):
621
998
        """Disable this client."""
622
999
        if not getattr(self, "enabled", False):
624
1001
        if not quiet:
625
1002
            logger.info("Disabling client %s", self.name)
626
1003
        if getattr(self, "disable_initiator_tag", None) is not None:
627
 
            gobject.source_remove(self.disable_initiator_tag)
 
1004
            GLib.source_remove(self.disable_initiator_tag)
628
1005
            self.disable_initiator_tag = None
629
1006
        self.expires = None
630
1007
        if getattr(self, "checker_initiator_tag", None) is not None:
631
 
            gobject.source_remove(self.checker_initiator_tag)
 
1008
            GLib.source_remove(self.checker_initiator_tag)
632
1009
            self.checker_initiator_tag = None
633
1010
        self.stop_checker()
634
1011
        self.enabled = False
635
1012
        if not quiet:
636
1013
            self.send_changedstate()
637
 
        # Do not run this again if called by a gobject.timeout_add
 
1014
        # Do not run this again if called by a GLib.timeout_add
638
1015
        return False
639
 
    
 
1016
 
640
1017
    def __del__(self):
641
1018
        self.disable()
642
 
    
 
1019
 
643
1020
    def init_checker(self):
644
1021
        # Schedule a new checker to be started an 'interval' from now,
645
1022
        # and every interval from then on.
646
1023
        if self.checker_initiator_tag is not None:
647
 
            gobject.source_remove(self.checker_initiator_tag)
648
 
        self.checker_initiator_tag = gobject.timeout_add(
 
1024
            GLib.source_remove(self.checker_initiator_tag)
 
1025
        self.checker_initiator_tag = GLib.timeout_add(
649
1026
            int(self.interval.total_seconds() * 1000),
650
1027
            self.start_checker)
651
1028
        # Schedule a disable() when 'timeout' has passed
652
1029
        if self.disable_initiator_tag is not None:
653
 
            gobject.source_remove(self.disable_initiator_tag)
654
 
        self.disable_initiator_tag = gobject.timeout_add(
 
1030
            GLib.source_remove(self.disable_initiator_tag)
 
1031
        self.disable_initiator_tag = GLib.timeout_add(
655
1032
            int(self.timeout.total_seconds() * 1000), self.disable)
656
1033
        # Also start a new checker *right now*.
657
1034
        self.start_checker()
658
 
    
 
1035
 
659
1036
    def checker_callback(self, source, condition, connection,
660
1037
                         command):
661
1038
        """The checker has completed, so take appropriate actions."""
664
1041
        # Read return code from connection (see call_pipe)
665
1042
        returncode = connection.recv()
666
1043
        connection.close()
667
 
        
 
1044
 
668
1045
        if returncode >= 0:
669
1046
            self.last_checker_status = returncode
670
1047
            self.last_checker_signal = None
680
1057
            logger.warning("Checker for %(name)s crashed?",
681
1058
                           vars(self))
682
1059
        return False
683
 
    
 
1060
 
684
1061
    def checked_ok(self):
685
1062
        """Assert that the client has been seen, alive and well."""
686
1063
        self.last_checked_ok = datetime.datetime.utcnow()
687
1064
        self.last_checker_status = 0
688
1065
        self.last_checker_signal = None
689
1066
        self.bump_timeout()
690
 
    
 
1067
 
691
1068
    def bump_timeout(self, timeout=None):
692
1069
        """Bump up the timeout for this client."""
693
1070
        if timeout is None:
694
1071
            timeout = self.timeout
695
1072
        if self.disable_initiator_tag is not None:
696
 
            gobject.source_remove(self.disable_initiator_tag)
 
1073
            GLib.source_remove(self.disable_initiator_tag)
697
1074
            self.disable_initiator_tag = None
698
1075
        if getattr(self, "enabled", False):
699
 
            self.disable_initiator_tag = gobject.timeout_add(
 
1076
            self.disable_initiator_tag = GLib.timeout_add(
700
1077
                int(timeout.total_seconds() * 1000), self.disable)
701
1078
            self.expires = datetime.datetime.utcnow() + timeout
702
 
    
 
1079
 
703
1080
    def need_approval(self):
704
1081
        self.last_approval_request = datetime.datetime.utcnow()
705
 
    
 
1082
 
706
1083
    def start_checker(self):
707
1084
        """Start a new checker subprocess if one is not running.
708
 
        
 
1085
 
709
1086
        If a checker already exists, leave it running and do
710
1087
        nothing."""
711
1088
        # The reason for not killing a running checker is that if we
716
1093
        # checkers alone, the checker would have to take more time
717
1094
        # than 'timeout' for the client to be disabled, which is as it
718
1095
        # should be.
719
 
        
 
1096
 
720
1097
        if self.checker is not None and not self.checker.is_alive():
721
1098
            logger.warning("Checker was not alive; joining")
722
1099
            self.checker.join()
726
1103
            # Escape attributes for the shell
727
1104
            escaped_attrs = {
728
1105
                attr: re.escape(str(getattr(self, attr)))
729
 
                for attr in self.runtime_expansions }
 
1106
                for attr in self.runtime_expansions}
730
1107
            try:
731
1108
                command = self.checker_command % escaped_attrs
732
1109
            except TypeError as error:
744
1121
            # The exception is when not debugging but nevertheless
745
1122
            # running in the foreground; use the previously
746
1123
            # created wnull.
747
 
            popen_args = { "close_fds": True,
748
 
                           "shell": True,
749
 
                           "cwd": "/" }
 
1124
            popen_args = {"close_fds": True,
 
1125
                          "shell": True,
 
1126
                          "cwd": "/"}
750
1127
            if (not self.server_settings["debug"]
751
1128
                and self.server_settings["foreground"]):
752
1129
                popen_args.update({"stdout": wnull,
753
 
                                   "stderr": wnull })
754
 
            pipe = multiprocessing.Pipe(duplex = False)
 
1130
                                   "stderr": wnull})
 
1131
            pipe = multiprocessing.Pipe(duplex=False)
755
1132
            self.checker = multiprocessing.Process(
756
 
                target = call_pipe,
757
 
                args = (pipe[1], subprocess.call, command),
758
 
                kwargs = popen_args)
 
1133
                target=call_pipe,
 
1134
                args=(pipe[1], subprocess.call, command),
 
1135
                kwargs=popen_args)
759
1136
            self.checker.start()
760
 
            self.checker_callback_tag = gobject.io_add_watch(
761
 
                pipe[0].fileno(), gobject.IO_IN,
 
1137
            self.checker_callback_tag = GLib.io_add_watch(
 
1138
                pipe[0].fileno(), GLib.IO_IN,
762
1139
                self.checker_callback, pipe[0], command)
763
 
        # Re-run this periodically if run by gobject.timeout_add
 
1140
        # Re-run this periodically if run by GLib.timeout_add
764
1141
        return True
765
 
    
 
1142
 
766
1143
    def stop_checker(self):
767
1144
        """Force the checker process, if any, to stop."""
768
1145
        if self.checker_callback_tag:
769
 
            gobject.source_remove(self.checker_callback_tag)
 
1146
            GLib.source_remove(self.checker_callback_tag)
770
1147
            self.checker_callback_tag = None
771
1148
        if getattr(self, "checker", None) is None:
772
1149
            return
781
1158
                          byte_arrays=False):
782
1159
    """Decorators for marking methods of a DBusObjectWithProperties to
783
1160
    become properties on the D-Bus.
784
 
    
 
1161
 
785
1162
    The decorated method will be called with no arguments by "Get"
786
1163
    and with one argument by "Set".
787
 
    
 
1164
 
788
1165
    The parameters, where they are supported, are the same as
789
1166
    dbus.service.method, except there is only "signature", since the
790
1167
    type from Get() and the type sent to Set() is the same.
794
1171
    if byte_arrays and signature != "ay":
795
1172
        raise ValueError("Byte arrays not supported for non-'ay'"
796
1173
                         " signature {!r}".format(signature))
797
 
    
 
1174
 
798
1175
    def decorator(func):
799
1176
        func._dbus_is_property = True
800
1177
        func._dbus_interface = dbus_interface
803
1180
        func._dbus_name = func.__name__
804
1181
        if func._dbus_name.endswith("_dbus_property"):
805
1182
            func._dbus_name = func._dbus_name[:-14]
806
 
        func._dbus_get_args_options = {'byte_arrays': byte_arrays }
 
1183
        func._dbus_get_args_options = {'byte_arrays': byte_arrays}
807
1184
        return func
808
 
    
 
1185
 
809
1186
    return decorator
810
1187
 
811
1188
 
812
1189
def dbus_interface_annotations(dbus_interface):
813
1190
    """Decorator for marking functions returning interface annotations
814
 
    
 
1191
 
815
1192
    Usage:
816
 
    
 
1193
 
817
1194
    @dbus_interface_annotations("org.example.Interface")
818
1195
    def _foo(self):  # Function name does not matter
819
1196
        return {"org.freedesktop.DBus.Deprecated": "true",
820
1197
                "org.freedesktop.DBus.Property.EmitsChangedSignal":
821
1198
                    "false"}
822
1199
    """
823
 
    
 
1200
 
824
1201
    def decorator(func):
825
1202
        func._dbus_is_interface = True
826
1203
        func._dbus_interface = dbus_interface
827
1204
        func._dbus_name = dbus_interface
828
1205
        return func
829
 
    
 
1206
 
830
1207
    return decorator
831
1208
 
832
1209
 
833
1210
def dbus_annotations(annotations):
834
1211
    """Decorator to annotate D-Bus methods, signals or properties
835
1212
    Usage:
836
 
    
 
1213
 
837
1214
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
838
1215
                       "org.freedesktop.DBus.Property."
839
1216
                       "EmitsChangedSignal": "false"})
841
1218
                           access="r")
842
1219
    def Property_dbus_property(self):
843
1220
        return dbus.Boolean(False)
844
 
    
 
1221
 
845
1222
    See also the DBusObjectWithAnnotations class.
846
1223
    """
847
 
    
 
1224
 
848
1225
    def decorator(func):
849
1226
        func._dbus_annotations = annotations
850
1227
        return func
851
 
    
 
1228
 
852
1229
    return decorator
853
1230
 
854
1231
 
872
1249
 
873
1250
class DBusObjectWithAnnotations(dbus.service.Object):
874
1251
    """A D-Bus object with annotations.
875
 
    
 
1252
 
876
1253
    Classes inheriting from this can use the dbus_annotations
877
1254
    decorator to add annotations to methods or signals.
878
1255
    """
879
 
    
 
1256
 
880
1257
    @staticmethod
881
1258
    def _is_dbus_thing(thing):
882
1259
        """Returns a function testing if an attribute is a D-Bus thing
883
 
        
 
1260
 
884
1261
        If called like _is_dbus_thing("method") it returns a function
885
1262
        suitable for use as predicate to inspect.getmembers().
886
1263
        """
887
1264
        return lambda obj: getattr(obj, "_dbus_is_{}".format(thing),
888
1265
                                   False)
889
 
    
 
1266
 
890
1267
    def _get_all_dbus_things(self, thing):
891
1268
        """Returns a generator of (name, attribute) pairs
892
1269
        """
895
1272
                for cls in self.__class__.__mro__
896
1273
                for name, athing in
897
1274
                inspect.getmembers(cls, self._is_dbus_thing(thing)))
898
 
    
 
1275
 
899
1276
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
900
 
                         out_signature = "s",
901
 
                         path_keyword = 'object_path',
902
 
                         connection_keyword = 'connection')
 
1277
                         out_signature="s",
 
1278
                         path_keyword='object_path',
 
1279
                         connection_keyword='connection')
903
1280
    def Introspect(self, object_path, connection):
904
1281
        """Overloading of standard D-Bus method.
905
 
        
 
1282
 
906
1283
        Inserts annotation tags on methods and signals.
907
1284
        """
908
1285
        xmlstring = dbus.service.Object.Introspect(self, object_path,
909
1286
                                                   connection)
910
1287
        try:
911
1288
            document = xml.dom.minidom.parseString(xmlstring)
912
 
            
 
1289
 
913
1290
            for if_tag in document.getElementsByTagName("interface"):
914
1291
                # Add annotation tags
915
1292
                for typ in ("method", "signal"):
942
1319
                    if_tag.appendChild(ann_tag)
943
1320
                # Fix argument name for the Introspect method itself
944
1321
                if (if_tag.getAttribute("name")
945
 
                                == dbus.INTROSPECTABLE_IFACE):
 
1322
                    == dbus.INTROSPECTABLE_IFACE):
946
1323
                    for cn in if_tag.getElementsByTagName("method"):
947
1324
                        if cn.getAttribute("name") == "Introspect":
948
1325
                            for arg in cn.getElementsByTagName("arg"):
961
1338
 
962
1339
class DBusObjectWithProperties(DBusObjectWithAnnotations):
963
1340
    """A D-Bus object with properties.
964
 
    
 
1341
 
965
1342
    Classes inheriting from this can use the dbus_service_property
966
1343
    decorator to expose methods as D-Bus properties.  It exposes the
967
1344
    standard Get(), Set(), and GetAll() methods on the D-Bus.
968
1345
    """
969
 
    
 
1346
 
970
1347
    def _get_dbus_property(self, interface_name, property_name):
971
1348
        """Returns a bound method if one exists which is a D-Bus
972
1349
        property with the specified name and interface.
977
1354
                if (value._dbus_name == property_name
978
1355
                    and value._dbus_interface == interface_name):
979
1356
                    return value.__get__(self)
980
 
        
 
1357
 
981
1358
        # No such property
982
1359
        raise DBusPropertyNotFound("{}:{}.{}".format(
983
1360
            self.dbus_object_path, interface_name, property_name))
984
 
    
 
1361
 
985
1362
    @classmethod
986
1363
    def _get_all_interface_names(cls):
987
1364
        """Get a sequence of all interfaces supported by an object"""
990
1367
                                     for x in (inspect.getmro(cls))
991
1368
                                     for attr in dir(x))
992
1369
                if name is not None)
993
 
    
 
1370
 
994
1371
    @dbus.service.method(dbus.PROPERTIES_IFACE,
995
1372
                         in_signature="ss",
996
1373
                         out_signature="v")
1004
1381
        if not hasattr(value, "variant_level"):
1005
1382
            return value
1006
1383
        return type(value)(value, variant_level=value.variant_level+1)
1007
 
    
 
1384
 
1008
1385
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
1009
1386
    def Set(self, interface_name, property_name, value):
1010
1387
        """Standard D-Bus property Set() method, see D-Bus standard.
1022
1399
            value = dbus.ByteArray(b''.join(chr(byte)
1023
1400
                                            for byte in value))
1024
1401
        prop(value)
1025
 
    
 
1402
 
1026
1403
    @dbus.service.method(dbus.PROPERTIES_IFACE,
1027
1404
                         in_signature="s",
1028
1405
                         out_signature="a{sv}")
1029
1406
    def GetAll(self, interface_name):
1030
1407
        """Standard D-Bus property GetAll() method, see D-Bus
1031
1408
        standard.
1032
 
        
 
1409
 
1033
1410
        Note: Will not include properties with access="write".
1034
1411
        """
1035
1412
        properties = {}
1046
1423
                properties[name] = value
1047
1424
                continue
1048
1425
            properties[name] = type(value)(
1049
 
                value, variant_level = value.variant_level + 1)
 
1426
                value, variant_level=value.variant_level + 1)
1050
1427
        return dbus.Dictionary(properties, signature="sv")
1051
 
    
 
1428
 
1052
1429
    @dbus.service.signal(dbus.PROPERTIES_IFACE, signature="sa{sv}as")
1053
1430
    def PropertiesChanged(self, interface_name, changed_properties,
1054
1431
                          invalidated_properties):
1056
1433
        standard.
1057
1434
        """
1058
1435
        pass
1059
 
    
 
1436
 
1060
1437
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1061
1438
                         out_signature="s",
1062
1439
                         path_keyword='object_path',
1063
1440
                         connection_keyword='connection')
1064
1441
    def Introspect(self, object_path, connection):
1065
1442
        """Overloading of standard D-Bus method.
1066
 
        
 
1443
 
1067
1444
        Inserts property tags and interface annotation tags.
1068
1445
        """
1069
1446
        xmlstring = DBusObjectWithAnnotations.Introspect(self,
1071
1448
                                                         connection)
1072
1449
        try:
1073
1450
            document = xml.dom.minidom.parseString(xmlstring)
1074
 
            
 
1451
 
1075
1452
            def make_tag(document, name, prop):
1076
1453
                e = document.createElement("property")
1077
1454
                e.setAttribute("name", name)
1078
1455
                e.setAttribute("type", prop._dbus_signature)
1079
1456
                e.setAttribute("access", prop._dbus_access)
1080
1457
                return e
1081
 
            
 
1458
 
1082
1459
            for if_tag in document.getElementsByTagName("interface"):
1083
1460
                # Add property tags
1084
1461
                for tag in (make_tag(document, name, prop)
1126
1503
                         exc_info=error)
1127
1504
        return xmlstring
1128
1505
 
 
1506
 
1129
1507
try:
1130
1508
    dbus.OBJECT_MANAGER_IFACE
1131
1509
except AttributeError:
1132
1510
    dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1133
1511
 
 
1512
 
1134
1513
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1135
1514
    """A D-Bus object with an ObjectManager.
1136
 
    
 
1515
 
1137
1516
    Classes inheriting from this exposes the standard
1138
1517
    GetManagedObjects call and the InterfacesAdded and
1139
1518
    InterfacesRemoved signals on the standard
1140
1519
    "org.freedesktop.DBus.ObjectManager" interface.
1141
 
    
 
1520
 
1142
1521
    Note: No signals are sent automatically; they must be sent
1143
1522
    manually.
1144
1523
    """
1145
1524
    @dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1146
 
                         out_signature = "a{oa{sa{sv}}}")
 
1525
                         out_signature="a{oa{sa{sv}}}")
1147
1526
    def GetManagedObjects(self):
1148
1527
        """This function must be overridden"""
1149
1528
        raise NotImplementedError()
1150
 
    
 
1529
 
1151
1530
    @dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1152
 
                         signature = "oa{sa{sv}}")
 
1531
                         signature="oa{sa{sv}}")
1153
1532
    def InterfacesAdded(self, object_path, interfaces_and_properties):
1154
1533
        pass
1155
 
    
1156
 
    @dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
 
1534
 
 
1535
    @dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature="oas")
1157
1536
    def InterfacesRemoved(self, object_path, interfaces):
1158
1537
        pass
1159
 
    
 
1538
 
1160
1539
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1161
 
                         out_signature = "s",
1162
 
                         path_keyword = 'object_path',
1163
 
                         connection_keyword = 'connection')
 
1540
                         out_signature="s",
 
1541
                         path_keyword='object_path',
 
1542
                         connection_keyword='connection')
1164
1543
    def Introspect(self, object_path, connection):
1165
1544
        """Overloading of standard D-Bus method.
1166
 
        
 
1545
 
1167
1546
        Override return argument name of GetManagedObjects to be
1168
1547
        "objpath_interfaces_and_properties"
1169
1548
        """
1172
1551
                                                         connection)
1173
1552
        try:
1174
1553
            document = xml.dom.minidom.parseString(xmlstring)
1175
 
            
 
1554
 
1176
1555
            for if_tag in document.getElementsByTagName("interface"):
1177
1556
                # Fix argument name for the GetManagedObjects method
1178
1557
                if (if_tag.getAttribute("name")
1179
 
                                == dbus.OBJECT_MANAGER_IFACE):
 
1558
                    == dbus.OBJECT_MANAGER_IFACE):
1180
1559
                    for cn in if_tag.getElementsByTagName("method"):
1181
1560
                        if (cn.getAttribute("name")
1182
1561
                            == "GetManagedObjects"):
1192
1571
        except (AttributeError, xml.dom.DOMException,
1193
1572
                xml.parsers.expat.ExpatError) as error:
1194
1573
            logger.error("Failed to override Introspection method",
1195
 
                         exc_info = error)
 
1574
                         exc_info=error)
1196
1575
        return xmlstring
1197
1576
 
 
1577
 
1198
1578
def datetime_to_dbus(dt, variant_level=0):
1199
1579
    """Convert a UTC datetime.datetime() to a D-Bus type."""
1200
1580
    if dt is None:
1201
 
        return dbus.String("", variant_level = variant_level)
 
1581
        return dbus.String("", variant_level=variant_level)
1202
1582
    return dbus.String(dt.isoformat(), variant_level=variant_level)
1203
1583
 
1204
1584
 
1207
1587
    dbus.service.Object, it will add alternate D-Bus attributes with
1208
1588
    interface names according to the "alt_interface_names" mapping.
1209
1589
    Usage:
1210
 
    
 
1590
 
1211
1591
    @alternate_dbus_interfaces({"org.example.Interface":
1212
1592
                                    "net.example.AlternateInterface"})
1213
1593
    class SampleDBusObject(dbus.service.Object):
1214
1594
        @dbus.service.method("org.example.Interface")
1215
1595
        def SampleDBusMethod():
1216
1596
            pass
1217
 
    
 
1597
 
1218
1598
    The above "SampleDBusMethod" on "SampleDBusObject" will be
1219
1599
    reachable via two interfaces: "org.example.Interface" and
1220
1600
    "net.example.AlternateInterface", the latter of which will have
1221
1601
    its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1222
1602
    "true", unless "deprecate" is passed with a False value.
1223
 
    
 
1603
 
1224
1604
    This works for methods and signals, and also for D-Bus properties
1225
1605
    (from DBusObjectWithProperties) and interfaces (from the
1226
1606
    dbus_interface_annotations decorator).
1227
1607
    """
1228
 
    
 
1608
 
1229
1609
    def wrapper(cls):
1230
1610
        for orig_interface_name, alt_interface_name in (
1231
1611
                alt_interface_names.items()):
1246
1626
                interface_names.add(alt_interface)
1247
1627
                # Is this a D-Bus signal?
1248
1628
                if getattr(attribute, "_dbus_is_signal", False):
 
1629
                    # Extract the original non-method undecorated
 
1630
                    # function by black magic
1249
1631
                    if sys.version_info.major == 2:
1250
 
                        # Extract the original non-method undecorated
1251
 
                        # function by black magic
1252
1632
                        nonmethod_func = (dict(
1253
1633
                            zip(attribute.func_code.co_freevars,
1254
1634
                                attribute.__closure__))
1255
1635
                                          ["func"].cell_contents)
1256
1636
                    else:
1257
 
                        nonmethod_func = attribute
 
1637
                        nonmethod_func = (dict(
 
1638
                            zip(attribute.__code__.co_freevars,
 
1639
                                attribute.__closure__))
 
1640
                                          ["func"].cell_contents)
1258
1641
                    # Create a new, but exactly alike, function
1259
1642
                    # object, and decorate it to be a new D-Bus signal
1260
1643
                    # with the alternate D-Bus interface name
1261
 
                    if sys.version_info.major == 2:
1262
 
                        new_function = types.FunctionType(
1263
 
                            nonmethod_func.func_code,
1264
 
                            nonmethod_func.func_globals,
1265
 
                            nonmethod_func.func_name,
1266
 
                            nonmethod_func.func_defaults,
1267
 
                            nonmethod_func.func_closure)
1268
 
                    else:
1269
 
                        new_function = types.FunctionType(
1270
 
                            nonmethod_func.__code__,
1271
 
                            nonmethod_func.__globals__,
1272
 
                            nonmethod_func.__name__,
1273
 
                            nonmethod_func.__defaults__,
1274
 
                            nonmethod_func.__closure__)
 
1644
                    new_function = copy_function(nonmethod_func)
1275
1645
                    new_function = (dbus.service.signal(
1276
1646
                        alt_interface,
1277
1647
                        attribute._dbus_signature)(new_function))
1281
1651
                            attribute._dbus_annotations)
1282
1652
                    except AttributeError:
1283
1653
                        pass
 
1654
 
1284
1655
                    # Define a creator of a function to call both the
1285
1656
                    # original and alternate functions, so both the
1286
1657
                    # original and alternate signals gets sent when
1289
1660
                        """This function is a scope container to pass
1290
1661
                        func1 and func2 to the "call_both" function
1291
1662
                        outside of its arguments"""
1292
 
                        
 
1663
 
1293
1664
                        @functools.wraps(func2)
1294
1665
                        def call_both(*args, **kwargs):
1295
1666
                            """This function will emit two D-Bus
1296
1667
                            signals by calling func1 and func2"""
1297
1668
                            func1(*args, **kwargs)
1298
1669
                            func2(*args, **kwargs)
1299
 
                        # Make wrapper function look like a D-Bus signal
 
1670
                        # Make wrapper function look like a D-Bus
 
1671
                        # signal
1300
1672
                        for name, attr in inspect.getmembers(func2):
1301
1673
                            if name.startswith("_dbus_"):
1302
1674
                                setattr(call_both, name, attr)
1303
 
                        
 
1675
 
1304
1676
                        return call_both
1305
1677
                    # Create the "call_both" function and add it to
1306
1678
                    # the class
1316
1688
                            alt_interface,
1317
1689
                            attribute._dbus_in_signature,
1318
1690
                            attribute._dbus_out_signature)
1319
 
                        (types.FunctionType(attribute.func_code,
1320
 
                                            attribute.func_globals,
1321
 
                                            attribute.func_name,
1322
 
                                            attribute.func_defaults,
1323
 
                                            attribute.func_closure)))
 
1691
                        (copy_function(attribute)))
1324
1692
                    # Copy annotations, if any
1325
1693
                    try:
1326
1694
                        attr[attrname]._dbus_annotations = dict(
1338
1706
                        attribute._dbus_access,
1339
1707
                        attribute._dbus_get_args_options
1340
1708
                        ["byte_arrays"])
1341
 
                                      (types.FunctionType(
1342
 
                                          attribute.func_code,
1343
 
                                          attribute.func_globals,
1344
 
                                          attribute.func_name,
1345
 
                                          attribute.func_defaults,
1346
 
                                          attribute.func_closure)))
 
1709
                                      (copy_function(attribute)))
1347
1710
                    # Copy annotations, if any
1348
1711
                    try:
1349
1712
                        attr[attrname]._dbus_annotations = dict(
1358
1721
                    # to the class.
1359
1722
                    attr[attrname] = (
1360
1723
                        dbus_interface_annotations(alt_interface)
1361
 
                        (types.FunctionType(attribute.func_code,
1362
 
                                            attribute.func_globals,
1363
 
                                            attribute.func_name,
1364
 
                                            attribute.func_defaults,
1365
 
                                            attribute.func_closure)))
 
1724
                        (copy_function(attribute)))
1366
1725
            if deprecate:
1367
1726
                # Deprecate all alternate interfaces
1368
 
                iname="_AlternateDBusNames_interface_annotation{}"
 
1727
                iname = "_AlternateDBusNames_interface_annotation{}"
1369
1728
                for interface_name in interface_names:
1370
 
                    
 
1729
 
1371
1730
                    @dbus_interface_annotations(interface_name)
1372
1731
                    def func(self):
1373
 
                        return { "org.freedesktop.DBus.Deprecated":
1374
 
                                 "true" }
 
1732
                        return {"org.freedesktop.DBus.Deprecated":
 
1733
                                "true"}
1375
1734
                    # Find an unused name
1376
1735
                    for aname in (iname.format(i)
1377
1736
                                  for i in itertools.count()):
1381
1740
            if interface_names:
1382
1741
                # Replace the class with a new subclass of it with
1383
1742
                # methods, signals, etc. as created above.
1384
 
                cls = type(b"{}Alternate".format(cls.__name__),
1385
 
                           (cls, ), attr)
 
1743
                if sys.version_info.major == 2:
 
1744
                    cls = type(b"{}Alternate".format(cls.__name__),
 
1745
                               (cls, ), attr)
 
1746
                else:
 
1747
                    cls = type("{}Alternate".format(cls.__name__),
 
1748
                               (cls, ), attr)
1386
1749
        return cls
1387
 
    
 
1750
 
1388
1751
    return wrapper
1389
1752
 
1390
1753
 
1392
1755
                            "se.bsnet.fukt.Mandos"})
1393
1756
class ClientDBus(Client, DBusObjectWithProperties):
1394
1757
    """A Client class using D-Bus
1395
 
    
 
1758
 
1396
1759
    Attributes:
1397
1760
    dbus_object_path: dbus.ObjectPath
1398
1761
    bus: dbus.SystemBus()
1399
1762
    """
1400
 
    
 
1763
 
1401
1764
    runtime_expansions = (Client.runtime_expansions
1402
1765
                          + ("dbus_object_path", ))
1403
 
    
 
1766
 
1404
1767
    _interface = "se.recompile.Mandos.Client"
1405
 
    
 
1768
 
1406
1769
    # dbus.service.Object doesn't use super(), so we can't either.
1407
 
    
1408
 
    def __init__(self, bus = None, *args, **kwargs):
 
1770
 
 
1771
    def __init__(self, bus=None, *args, **kwargs):
1409
1772
        self.bus = bus
1410
1773
        Client.__init__(self, *args, **kwargs)
1411
1774
        # Only now, when this client is initialized, can it show up on
1417
1780
            "/clients/" + client_object_name)
1418
1781
        DBusObjectWithProperties.__init__(self, self.bus,
1419
1782
                                          self.dbus_object_path)
1420
 
    
 
1783
 
1421
1784
    def notifychangeproperty(transform_func, dbus_name,
1422
1785
                             type_func=lambda x: x,
1423
1786
                             variant_level=1,
1425
1788
                             _interface=_interface):
1426
1789
        """ Modify a variable so that it's a property which announces
1427
1790
        its changes to DBus.
1428
 
        
 
1791
 
1429
1792
        transform_fun: Function that takes a value and a variant_level
1430
1793
                       and transforms it to a D-Bus type.
1431
1794
        dbus_name: D-Bus name of the variable
1434
1797
        variant_level: D-Bus variant level.  Default: 1
1435
1798
        """
1436
1799
        attrname = "_{}".format(dbus_name)
1437
 
        
 
1800
 
1438
1801
        def setter(self, value):
1439
1802
            if hasattr(self, "dbus_object_path"):
1440
1803
                if (not hasattr(self, attrname) or
1447
1810
                    else:
1448
1811
                        dbus_value = transform_func(
1449
1812
                            type_func(value),
1450
 
                            variant_level = variant_level)
 
1813
                            variant_level=variant_level)
1451
1814
                        self.PropertyChanged(dbus.String(dbus_name),
1452
1815
                                             dbus_value)
1453
1816
                        self.PropertiesChanged(
1454
1817
                            _interface,
1455
 
                            dbus.Dictionary({ dbus.String(dbus_name):
1456
 
                                              dbus_value }),
 
1818
                            dbus.Dictionary({dbus.String(dbus_name):
 
1819
                                             dbus_value}),
1457
1820
                            dbus.Array())
1458
1821
            setattr(self, attrname, value)
1459
 
        
 
1822
 
1460
1823
        return property(lambda self: getattr(self, attrname), setter)
1461
 
    
 
1824
 
1462
1825
    expires = notifychangeproperty(datetime_to_dbus, "Expires")
1463
1826
    approvals_pending = notifychangeproperty(dbus.Boolean,
1464
1827
                                             "ApprovalPending",
1465
 
                                             type_func = bool)
 
1828
                                             type_func=bool)
1466
1829
    enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1467
1830
    last_enabled = notifychangeproperty(datetime_to_dbus,
1468
1831
                                        "LastEnabled")
1469
1832
    checker = notifychangeproperty(
1470
1833
        dbus.Boolean, "CheckerRunning",
1471
 
        type_func = lambda checker: checker is not None)
 
1834
        type_func=lambda checker: checker is not None)
1472
1835
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
1473
1836
                                           "LastCheckedOK")
1474
1837
    last_checker_status = notifychangeproperty(dbus.Int16,
1479
1842
                                               "ApprovedByDefault")
1480
1843
    approval_delay = notifychangeproperty(
1481
1844
        dbus.UInt64, "ApprovalDelay",
1482
 
        type_func = lambda td: td.total_seconds() * 1000)
 
1845
        type_func=lambda td: td.total_seconds() * 1000)
1483
1846
    approval_duration = notifychangeproperty(
1484
1847
        dbus.UInt64, "ApprovalDuration",
1485
 
        type_func = lambda td: td.total_seconds() * 1000)
 
1848
        type_func=lambda td: td.total_seconds() * 1000)
1486
1849
    host = notifychangeproperty(dbus.String, "Host")
1487
1850
    timeout = notifychangeproperty(
1488
1851
        dbus.UInt64, "Timeout",
1489
 
        type_func = lambda td: td.total_seconds() * 1000)
 
1852
        type_func=lambda td: td.total_seconds() * 1000)
1490
1853
    extended_timeout = notifychangeproperty(
1491
1854
        dbus.UInt64, "ExtendedTimeout",
1492
 
        type_func = lambda td: td.total_seconds() * 1000)
 
1855
        type_func=lambda td: td.total_seconds() * 1000)
1493
1856
    interval = notifychangeproperty(
1494
1857
        dbus.UInt64, "Interval",
1495
 
        type_func = lambda td: td.total_seconds() * 1000)
 
1858
        type_func=lambda td: td.total_seconds() * 1000)
1496
1859
    checker_command = notifychangeproperty(dbus.String, "Checker")
1497
1860
    secret = notifychangeproperty(dbus.ByteArray, "Secret",
1498
1861
                                  invalidate_only=True)
1499
 
    
 
1862
 
1500
1863
    del notifychangeproperty
1501
 
    
 
1864
 
1502
1865
    def __del__(self, *args, **kwargs):
1503
1866
        try:
1504
1867
            self.remove_from_connection()
1507
1870
        if hasattr(DBusObjectWithProperties, "__del__"):
1508
1871
            DBusObjectWithProperties.__del__(self, *args, **kwargs)
1509
1872
        Client.__del__(self, *args, **kwargs)
1510
 
    
 
1873
 
1511
1874
    def checker_callback(self, source, condition,
1512
1875
                         connection, command, *args, **kwargs):
1513
1876
        ret = Client.checker_callback(self, source, condition,
1529
1892
                                      | self.last_checker_signal),
1530
1893
                                  dbus.String(command))
1531
1894
        return ret
1532
 
    
 
1895
 
1533
1896
    def start_checker(self, *args, **kwargs):
1534
1897
        old_checker_pid = getattr(self.checker, "pid", None)
1535
1898
        r = Client.start_checker(self, *args, **kwargs)
1539
1902
            # Emit D-Bus signal
1540
1903
            self.CheckerStarted(self.current_checker_command)
1541
1904
        return r
1542
 
    
 
1905
 
1543
1906
    def _reset_approved(self):
1544
1907
        self.approved = None
1545
1908
        return False
1546
 
    
 
1909
 
1547
1910
    def approve(self, value=True):
1548
1911
        self.approved = value
1549
 
        gobject.timeout_add(int(self.approval_duration.total_seconds()
1550
 
                                * 1000), self._reset_approved)
 
1912
        GLib.timeout_add(int(self.approval_duration.total_seconds()
 
1913
                             * 1000), self._reset_approved)
1551
1914
        self.send_changedstate()
1552
 
    
1553
 
    ## D-Bus methods, signals & properties
1554
 
    
1555
 
    ## Interfaces
1556
 
    
1557
 
    ## Signals
1558
 
    
 
1915
 
 
1916
    #  D-Bus methods, signals & properties
 
1917
 
 
1918
    #  Interfaces
 
1919
 
 
1920
    #  Signals
 
1921
 
1559
1922
    # CheckerCompleted - signal
1560
1923
    @dbus.service.signal(_interface, signature="nxs")
1561
1924
    def CheckerCompleted(self, exitcode, waitstatus, command):
1562
1925
        "D-Bus signal"
1563
1926
        pass
1564
 
    
 
1927
 
1565
1928
    # CheckerStarted - signal
1566
1929
    @dbus.service.signal(_interface, signature="s")
1567
1930
    def CheckerStarted(self, command):
1568
1931
        "D-Bus signal"
1569
1932
        pass
1570
 
    
 
1933
 
1571
1934
    # PropertyChanged - signal
1572
1935
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1573
1936
    @dbus.service.signal(_interface, signature="sv")
1574
1937
    def PropertyChanged(self, property, value):
1575
1938
        "D-Bus signal"
1576
1939
        pass
1577
 
    
 
1940
 
1578
1941
    # GotSecret - signal
1579
1942
    @dbus.service.signal(_interface)
1580
1943
    def GotSecret(self):
1583
1946
        server to mandos-client
1584
1947
        """
1585
1948
        pass
1586
 
    
 
1949
 
1587
1950
    # Rejected - signal
1588
1951
    @dbus.service.signal(_interface, signature="s")
1589
1952
    def Rejected(self, reason):
1590
1953
        "D-Bus signal"
1591
1954
        pass
1592
 
    
 
1955
 
1593
1956
    # NeedApproval - signal
1594
1957
    @dbus.service.signal(_interface, signature="tb")
1595
1958
    def NeedApproval(self, timeout, default):
1596
1959
        "D-Bus signal"
1597
1960
        return self.need_approval()
1598
 
    
1599
 
    ## Methods
1600
 
    
 
1961
 
 
1962
    #  Methods
 
1963
 
1601
1964
    # Approve - method
1602
1965
    @dbus.service.method(_interface, in_signature="b")
1603
1966
    def Approve(self, value):
1604
1967
        self.approve(value)
1605
 
    
 
1968
 
1606
1969
    # CheckedOK - method
1607
1970
    @dbus.service.method(_interface)
1608
1971
    def CheckedOK(self):
1609
1972
        self.checked_ok()
1610
 
    
 
1973
 
1611
1974
    # Enable - method
1612
1975
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1613
1976
    @dbus.service.method(_interface)
1614
1977
    def Enable(self):
1615
1978
        "D-Bus method"
1616
1979
        self.enable()
1617
 
    
 
1980
 
1618
1981
    # StartChecker - method
1619
1982
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1620
1983
    @dbus.service.method(_interface)
1621
1984
    def StartChecker(self):
1622
1985
        "D-Bus method"
1623
1986
        self.start_checker()
1624
 
    
 
1987
 
1625
1988
    # Disable - method
1626
1989
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1627
1990
    @dbus.service.method(_interface)
1628
1991
    def Disable(self):
1629
1992
        "D-Bus method"
1630
1993
        self.disable()
1631
 
    
 
1994
 
1632
1995
    # StopChecker - method
1633
1996
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1634
1997
    @dbus.service.method(_interface)
1635
1998
    def StopChecker(self):
1636
1999
        self.stop_checker()
1637
 
    
1638
 
    ## Properties
1639
 
    
 
2000
 
 
2001
    #  Properties
 
2002
 
1640
2003
    # ApprovalPending - property
1641
2004
    @dbus_service_property(_interface, signature="b", access="read")
1642
2005
    def ApprovalPending_dbus_property(self):
1643
2006
        return dbus.Boolean(bool(self.approvals_pending))
1644
 
    
 
2007
 
1645
2008
    # ApprovedByDefault - property
1646
2009
    @dbus_service_property(_interface,
1647
2010
                           signature="b",
1650
2013
        if value is None:       # get
1651
2014
            return dbus.Boolean(self.approved_by_default)
1652
2015
        self.approved_by_default = bool(value)
1653
 
    
 
2016
 
1654
2017
    # ApprovalDelay - property
1655
2018
    @dbus_service_property(_interface,
1656
2019
                           signature="t",
1660
2023
            return dbus.UInt64(self.approval_delay.total_seconds()
1661
2024
                               * 1000)
1662
2025
        self.approval_delay = datetime.timedelta(0, 0, 0, value)
1663
 
    
 
2026
 
1664
2027
    # ApprovalDuration - property
1665
2028
    @dbus_service_property(_interface,
1666
2029
                           signature="t",
1670
2033
            return dbus.UInt64(self.approval_duration.total_seconds()
1671
2034
                               * 1000)
1672
2035
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
1673
 
    
 
2036
 
1674
2037
    # Name - property
1675
2038
    @dbus_annotations(
1676
2039
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1677
2040
    @dbus_service_property(_interface, signature="s", access="read")
1678
2041
    def Name_dbus_property(self):
1679
2042
        return dbus.String(self.name)
1680
 
    
 
2043
 
 
2044
    # KeyID - property
 
2045
    @dbus_annotations(
 
2046
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
 
2047
    @dbus_service_property(_interface, signature="s", access="read")
 
2048
    def KeyID_dbus_property(self):
 
2049
        return dbus.String(self.key_id)
 
2050
 
1681
2051
    # Fingerprint - property
1682
2052
    @dbus_annotations(
1683
2053
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1684
2054
    @dbus_service_property(_interface, signature="s", access="read")
1685
2055
    def Fingerprint_dbus_property(self):
1686
2056
        return dbus.String(self.fingerprint)
1687
 
    
 
2057
 
1688
2058
    # Host - property
1689
2059
    @dbus_service_property(_interface,
1690
2060
                           signature="s",
1693
2063
        if value is None:       # get
1694
2064
            return dbus.String(self.host)
1695
2065
        self.host = str(value)
1696
 
    
 
2066
 
1697
2067
    # Created - property
1698
2068
    @dbus_annotations(
1699
2069
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1700
2070
    @dbus_service_property(_interface, signature="s", access="read")
1701
2071
    def Created_dbus_property(self):
1702
2072
        return datetime_to_dbus(self.created)
1703
 
    
 
2073
 
1704
2074
    # LastEnabled - property
1705
2075
    @dbus_service_property(_interface, signature="s", access="read")
1706
2076
    def LastEnabled_dbus_property(self):
1707
2077
        return datetime_to_dbus(self.last_enabled)
1708
 
    
 
2078
 
1709
2079
    # Enabled - property
1710
2080
    @dbus_service_property(_interface,
1711
2081
                           signature="b",
1717
2087
            self.enable()
1718
2088
        else:
1719
2089
            self.disable()
1720
 
    
 
2090
 
1721
2091
    # LastCheckedOK - property
1722
2092
    @dbus_service_property(_interface,
1723
2093
                           signature="s",
1727
2097
            self.checked_ok()
1728
2098
            return
1729
2099
        return datetime_to_dbus(self.last_checked_ok)
1730
 
    
 
2100
 
1731
2101
    # LastCheckerStatus - property
1732
2102
    @dbus_service_property(_interface, signature="n", access="read")
1733
2103
    def LastCheckerStatus_dbus_property(self):
1734
2104
        return dbus.Int16(self.last_checker_status)
1735
 
    
 
2105
 
1736
2106
    # Expires - property
1737
2107
    @dbus_service_property(_interface, signature="s", access="read")
1738
2108
    def Expires_dbus_property(self):
1739
2109
        return datetime_to_dbus(self.expires)
1740
 
    
 
2110
 
1741
2111
    # LastApprovalRequest - property
1742
2112
    @dbus_service_property(_interface, signature="s", access="read")
1743
2113
    def LastApprovalRequest_dbus_property(self):
1744
2114
        return datetime_to_dbus(self.last_approval_request)
1745
 
    
 
2115
 
1746
2116
    # Timeout - property
1747
2117
    @dbus_service_property(_interface,
1748
2118
                           signature="t",
1763
2133
                if (getattr(self, "disable_initiator_tag", None)
1764
2134
                    is None):
1765
2135
                    return
1766
 
                gobject.source_remove(self.disable_initiator_tag)
1767
 
                self.disable_initiator_tag = gobject.timeout_add(
 
2136
                GLib.source_remove(self.disable_initiator_tag)
 
2137
                self.disable_initiator_tag = GLib.timeout_add(
1768
2138
                    int((self.expires - now).total_seconds() * 1000),
1769
2139
                    self.disable)
1770
 
    
 
2140
 
1771
2141
    # ExtendedTimeout - property
1772
2142
    @dbus_service_property(_interface,
1773
2143
                           signature="t",
1777
2147
            return dbus.UInt64(self.extended_timeout.total_seconds()
1778
2148
                               * 1000)
1779
2149
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1780
 
    
 
2150
 
1781
2151
    # Interval - property
1782
2152
    @dbus_service_property(_interface,
1783
2153
                           signature="t",
1790
2160
            return
1791
2161
        if self.enabled:
1792
2162
            # Reschedule checker run
1793
 
            gobject.source_remove(self.checker_initiator_tag)
1794
 
            self.checker_initiator_tag = gobject.timeout_add(
 
2163
            GLib.source_remove(self.checker_initiator_tag)
 
2164
            self.checker_initiator_tag = GLib.timeout_add(
1795
2165
                value, self.start_checker)
1796
 
            self.start_checker() # Start one now, too
1797
 
    
 
2166
            self.start_checker()  # Start one now, too
 
2167
 
1798
2168
    # Checker - property
1799
2169
    @dbus_service_property(_interface,
1800
2170
                           signature="s",
1803
2173
        if value is None:       # get
1804
2174
            return dbus.String(self.checker_command)
1805
2175
        self.checker_command = str(value)
1806
 
    
 
2176
 
1807
2177
    # CheckerRunning - property
1808
2178
    @dbus_service_property(_interface,
1809
2179
                           signature="b",
1815
2185
            self.start_checker()
1816
2186
        else:
1817
2187
            self.stop_checker()
1818
 
    
 
2188
 
1819
2189
    # ObjectPath - property
1820
2190
    @dbus_annotations(
1821
2191
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const",
1822
2192
         "org.freedesktop.DBus.Deprecated": "true"})
1823
2193
    @dbus_service_property(_interface, signature="o", access="read")
1824
2194
    def ObjectPath_dbus_property(self):
1825
 
        return self.dbus_object_path # is already a dbus.ObjectPath
1826
 
    
 
2195
        return self.dbus_object_path  # is already a dbus.ObjectPath
 
2196
 
1827
2197
    # Secret = property
1828
2198
    @dbus_annotations(
1829
2199
        {"org.freedesktop.DBus.Property.EmitsChangedSignal":
1834
2204
                           byte_arrays=True)
1835
2205
    def Secret_dbus_property(self, value):
1836
2206
        self.secret = bytes(value)
1837
 
    
 
2207
 
1838
2208
    del _interface
1839
2209
 
1840
2210
 
1841
2211
class ProxyClient(object):
1842
 
    def __init__(self, child_pipe, fpr, address):
 
2212
    def __init__(self, child_pipe, key_id, fpr, address):
1843
2213
        self._pipe = child_pipe
1844
 
        self._pipe.send(('init', fpr, address))
 
2214
        self._pipe.send(('init', key_id, fpr, address))
1845
2215
        if not self._pipe.recv():
1846
 
            raise KeyError(fpr)
1847
 
    
 
2216
            raise KeyError(key_id or fpr)
 
2217
 
1848
2218
    def __getattribute__(self, name):
1849
2219
        if name == '_pipe':
1850
2220
            return super(ProxyClient, self).__getattribute__(name)
1853
2223
        if data[0] == 'data':
1854
2224
            return data[1]
1855
2225
        if data[0] == 'function':
1856
 
            
 
2226
 
1857
2227
            def func(*args, **kwargs):
1858
2228
                self._pipe.send(('funcall', name, args, kwargs))
1859
2229
                return self._pipe.recv()[1]
1860
 
            
 
2230
 
1861
2231
            return func
1862
 
    
 
2232
 
1863
2233
    def __setattr__(self, name, value):
1864
2234
        if name == '_pipe':
1865
2235
            return super(ProxyClient, self).__setattr__(name, value)
1868
2238
 
1869
2239
class ClientHandler(socketserver.BaseRequestHandler, object):
1870
2240
    """A class to handle client connections.
1871
 
    
 
2241
 
1872
2242
    Instantiated once for each connection to handle it.
1873
2243
    Note: This will run in its own forked process."""
1874
 
    
 
2244
 
1875
2245
    def handle(self):
1876
2246
        with contextlib.closing(self.server.child_pipe) as child_pipe:
1877
2247
            logger.info("TCP connection from: %s",
1878
2248
                        str(self.client_address))
1879
2249
            logger.debug("Pipe FD: %d",
1880
2250
                         self.server.child_pipe.fileno())
1881
 
            
1882
 
            session = gnutls.connection.ClientSession(
1883
 
                self.request, gnutls.connection.X509Credentials())
1884
 
            
1885
 
            # Note: gnutls.connection.X509Credentials is really a
1886
 
            # generic GnuTLS certificate credentials object so long as
1887
 
            # no X.509 keys are added to it.  Therefore, we can use it
1888
 
            # here despite using OpenPGP certificates.
1889
 
            
1890
 
            #priority = ':'.join(("NONE", "+VERS-TLS1.1",
1891
 
            #                      "+AES-256-CBC", "+SHA1",
1892
 
            #                      "+COMP-NULL", "+CTYPE-OPENPGP",
1893
 
            #                      "+DHE-DSS"))
 
2251
 
 
2252
            session = gnutls.ClientSession(self.request)
 
2253
 
 
2254
            # priority = ':'.join(("NONE", "+VERS-TLS1.1",
 
2255
            #                       "+AES-256-CBC", "+SHA1",
 
2256
            #                       "+COMP-NULL", "+CTYPE-OPENPGP",
 
2257
            #                       "+DHE-DSS"))
1894
2258
            # Use a fallback default, since this MUST be set.
1895
2259
            priority = self.server.gnutls_priority
1896
2260
            if priority is None:
1897
2261
                priority = "NORMAL"
1898
 
            gnutls.library.functions.gnutls_priority_set_direct(
1899
 
                session._c_object, priority, None)
1900
 
            
 
2262
            gnutls.priority_set_direct(session._c_object,
 
2263
                                       priority.encode("utf-8"),
 
2264
                                       None)
 
2265
 
1901
2266
            # Start communication using the Mandos protocol
1902
2267
            # Get protocol number
1903
2268
            line = self.request.makefile().readline()
1908
2273
            except (ValueError, IndexError, RuntimeError) as error:
1909
2274
                logger.error("Unknown protocol version: %s", error)
1910
2275
                return
1911
 
            
 
2276
 
1912
2277
            # Start GnuTLS connection
1913
2278
            try:
1914
2279
                session.handshake()
1915
 
            except gnutls.errors.GNUTLSError as error:
 
2280
            except gnutls.Error as error:
1916
2281
                logger.warning("Handshake failed: %s", error)
1917
2282
                # Do not run session.bye() here: the session is not
1918
2283
                # established.  Just abandon the request.
1919
2284
                return
1920
2285
            logger.debug("Handshake succeeded")
1921
 
            
 
2286
 
1922
2287
            approval_required = False
1923
2288
            try:
1924
 
                try:
1925
 
                    fpr = self.fingerprint(
1926
 
                        self.peer_certificate(session))
1927
 
                except (TypeError,
1928
 
                        gnutls.errors.GNUTLSError) as error:
1929
 
                    logger.warning("Bad certificate: %s", error)
1930
 
                    return
1931
 
                logger.debug("Fingerprint: %s", fpr)
1932
 
                
1933
 
                try:
1934
 
                    client = ProxyClient(child_pipe, fpr,
 
2289
                if gnutls.has_rawpk:
 
2290
                    fpr = b""
 
2291
                    try:
 
2292
                        key_id = self.key_id(
 
2293
                            self.peer_certificate(session))
 
2294
                    except (TypeError, gnutls.Error) as error:
 
2295
                        logger.warning("Bad certificate: %s", error)
 
2296
                        return
 
2297
                    logger.debug("Key ID: %s", key_id)
 
2298
 
 
2299
                else:
 
2300
                    key_id = b""
 
2301
                    try:
 
2302
                        fpr = self.fingerprint(
 
2303
                            self.peer_certificate(session))
 
2304
                    except (TypeError, gnutls.Error) as error:
 
2305
                        logger.warning("Bad certificate: %s", error)
 
2306
                        return
 
2307
                    logger.debug("Fingerprint: %s", fpr)
 
2308
 
 
2309
                try:
 
2310
                    client = ProxyClient(child_pipe, key_id, fpr,
1935
2311
                                         self.client_address)
1936
2312
                except KeyError:
1937
2313
                    return
1938
 
                
 
2314
 
1939
2315
                if client.approval_delay:
1940
2316
                    delay = client.approval_delay
1941
2317
                    client.approvals_pending += 1
1942
2318
                    approval_required = True
1943
 
                
 
2319
 
1944
2320
                while True:
1945
2321
                    if not client.enabled:
1946
2322
                        logger.info("Client %s is disabled",
1949
2325
                            # Emit D-Bus signal
1950
2326
                            client.Rejected("Disabled")
1951
2327
                        return
1952
 
                    
 
2328
 
1953
2329
                    if client.approved or not client.approval_delay:
1954
 
                        #We are approved or approval is disabled
 
2330
                        # We are approved or approval is disabled
1955
2331
                        break
1956
2332
                    elif client.approved is None:
1957
2333
                        logger.info("Client %s needs approval",
1968
2344
                            # Emit D-Bus signal
1969
2345
                            client.Rejected("Denied")
1970
2346
                        return
1971
 
                    
1972
 
                    #wait until timeout or approved
 
2347
 
 
2348
                    # wait until timeout or approved
1973
2349
                    time = datetime.datetime.now()
1974
2350
                    client.changedstate.acquire()
1975
2351
                    client.changedstate.wait(delay.total_seconds())
1988
2364
                            break
1989
2365
                    else:
1990
2366
                        delay -= time2 - time
1991
 
                
1992
 
                sent_size = 0
1993
 
                while sent_size < len(client.secret):
1994
 
                    try:
1995
 
                        sent = session.send(client.secret[sent_size:])
1996
 
                    except gnutls.errors.GNUTLSError as error:
1997
 
                        logger.warning("gnutls send failed",
1998
 
                                       exc_info=error)
1999
 
                        return
2000
 
                    logger.debug("Sent: %d, remaining: %d", sent,
2001
 
                                 len(client.secret) - (sent_size
2002
 
                                                       + sent))
2003
 
                    sent_size += sent
2004
 
                
 
2367
 
 
2368
                try:
 
2369
                    session.send(client.secret)
 
2370
                except gnutls.Error as error:
 
2371
                    logger.warning("gnutls send failed",
 
2372
                                   exc_info=error)
 
2373
                    return
 
2374
 
2005
2375
                logger.info("Sending secret to %s", client.name)
2006
2376
                # bump the timeout using extended_timeout
2007
2377
                client.bump_timeout(client.extended_timeout)
2008
2378
                if self.server.use_dbus:
2009
2379
                    # Emit D-Bus signal
2010
2380
                    client.GotSecret()
2011
 
            
 
2381
 
2012
2382
            finally:
2013
2383
                if approval_required:
2014
2384
                    client.approvals_pending -= 1
2015
2385
                try:
2016
2386
                    session.bye()
2017
 
                except gnutls.errors.GNUTLSError as error:
 
2387
                except gnutls.Error as error:
2018
2388
                    logger.warning("GnuTLS bye failed",
2019
2389
                                   exc_info=error)
2020
 
    
 
2390
 
2021
2391
    @staticmethod
2022
2392
    def peer_certificate(session):
2023
 
        "Return the peer's OpenPGP certificate as a bytestring"
2024
 
        # If not an OpenPGP certificate...
2025
 
        if (gnutls.library.functions.gnutls_certificate_type_get(
2026
 
                session._c_object)
2027
 
            != gnutls.library.constants.GNUTLS_CRT_OPENPGP):
2028
 
            # ...do the normal thing
2029
 
            return session.peer_certificate
 
2393
        "Return the peer's certificate as a bytestring"
 
2394
        try:
 
2395
            cert_type = gnutls.certificate_type_get2(session._c_object,
 
2396
                                                     gnutls.CTYPE_PEERS)
 
2397
        except AttributeError:
 
2398
            cert_type = gnutls.certificate_type_get(session._c_object)
 
2399
        if gnutls.has_rawpk:
 
2400
            valid_cert_types = frozenset((gnutls.CRT_RAWPK,))
 
2401
        else:
 
2402
            valid_cert_types = frozenset((gnutls.CRT_OPENPGP,))
 
2403
        # If not a valid certificate type...
 
2404
        if cert_type not in valid_cert_types:
 
2405
            logger.info("Cert type %r not in %r", cert_type,
 
2406
                        valid_cert_types)
 
2407
            # ...return invalid data
 
2408
            return b""
2030
2409
        list_size = ctypes.c_uint(1)
2031
 
        cert_list = (gnutls.library.functions
2032
 
                     .gnutls_certificate_get_peers
 
2410
        cert_list = (gnutls.certificate_get_peers
2033
2411
                     (session._c_object, ctypes.byref(list_size)))
2034
2412
        if not bool(cert_list) and list_size.value != 0:
2035
 
            raise gnutls.errors.GNUTLSError("error getting peer"
2036
 
                                            " certificate")
 
2413
            raise gnutls.Error("error getting peer certificate")
2037
2414
        if list_size.value == 0:
2038
2415
            return None
2039
2416
        cert = cert_list[0]
2040
2417
        return ctypes.string_at(cert.data, cert.size)
2041
 
    
 
2418
 
 
2419
    @staticmethod
 
2420
    def key_id(certificate):
 
2421
        "Convert a certificate bytestring to a hexdigit key ID"
 
2422
        # New GnuTLS "datum" with the public key
 
2423
        datum = gnutls.datum_t(
 
2424
            ctypes.cast(ctypes.c_char_p(certificate),
 
2425
                        ctypes.POINTER(ctypes.c_ubyte)),
 
2426
            ctypes.c_uint(len(certificate)))
 
2427
        # XXX all these need to be created in the gnutls "module"
 
2428
        # New empty GnuTLS certificate
 
2429
        pubkey = gnutls.pubkey_t()
 
2430
        gnutls.pubkey_init(ctypes.byref(pubkey))
 
2431
        # Import the raw public key into the certificate
 
2432
        gnutls.pubkey_import(pubkey,
 
2433
                             ctypes.byref(datum),
 
2434
                             gnutls.X509_FMT_DER)
 
2435
        # New buffer for the key ID
 
2436
        buf = ctypes.create_string_buffer(32)
 
2437
        buf_len = ctypes.c_size_t(len(buf))
 
2438
        # Get the key ID from the raw public key into the buffer
 
2439
        gnutls.pubkey_get_key_id(pubkey,
 
2440
                                 gnutls.KEYID_USE_SHA256,
 
2441
                                 ctypes.cast(ctypes.byref(buf),
 
2442
                                             ctypes.POINTER(ctypes.c_ubyte)),
 
2443
                                 ctypes.byref(buf_len))
 
2444
        # Deinit the certificate
 
2445
        gnutls.pubkey_deinit(pubkey)
 
2446
 
 
2447
        # Convert the buffer to a Python bytestring
 
2448
        key_id = ctypes.string_at(buf, buf_len.value)
 
2449
        # Convert the bytestring to hexadecimal notation
 
2450
        hex_key_id = binascii.hexlify(key_id).upper()
 
2451
        return hex_key_id
 
2452
 
2042
2453
    @staticmethod
2043
2454
    def fingerprint(openpgp):
2044
2455
        "Convert an OpenPGP bytestring to a hexdigit fingerprint"
2045
2456
        # New GnuTLS "datum" with the OpenPGP public key
2046
 
        datum = gnutls.library.types.gnutls_datum_t(
 
2457
        datum = gnutls.datum_t(
2047
2458
            ctypes.cast(ctypes.c_char_p(openpgp),
2048
2459
                        ctypes.POINTER(ctypes.c_ubyte)),
2049
2460
            ctypes.c_uint(len(openpgp)))
2050
2461
        # New empty GnuTLS certificate
2051
 
        crt = gnutls.library.types.gnutls_openpgp_crt_t()
2052
 
        gnutls.library.functions.gnutls_openpgp_crt_init(
2053
 
            ctypes.byref(crt))
 
2462
        crt = gnutls.openpgp_crt_t()
 
2463
        gnutls.openpgp_crt_init(ctypes.byref(crt))
2054
2464
        # Import the OpenPGP public key into the certificate
2055
 
        gnutls.library.functions.gnutls_openpgp_crt_import(
2056
 
            crt, ctypes.byref(datum),
2057
 
            gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
 
2465
        gnutls.openpgp_crt_import(crt, ctypes.byref(datum),
 
2466
                                  gnutls.OPENPGP_FMT_RAW)
2058
2467
        # Verify the self signature in the key
2059
2468
        crtverify = ctypes.c_uint()
2060
 
        gnutls.library.functions.gnutls_openpgp_crt_verify_self(
2061
 
            crt, 0, ctypes.byref(crtverify))
 
2469
        gnutls.openpgp_crt_verify_self(crt, 0,
 
2470
                                       ctypes.byref(crtverify))
2062
2471
        if crtverify.value != 0:
2063
 
            gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
2064
 
            raise gnutls.errors.CertificateSecurityError(
2065
 
                "Verify failed")
 
2472
            gnutls.openpgp_crt_deinit(crt)
 
2473
            raise gnutls.CertificateSecurityError(code
 
2474
                                                  =crtverify.value)
2066
2475
        # New buffer for the fingerprint
2067
2476
        buf = ctypes.create_string_buffer(20)
2068
2477
        buf_len = ctypes.c_size_t()
2069
2478
        # Get the fingerprint from the certificate into the buffer
2070
 
        gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint(
2071
 
            crt, ctypes.byref(buf), ctypes.byref(buf_len))
 
2479
        gnutls.openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
 
2480
                                           ctypes.byref(buf_len))
2072
2481
        # Deinit the certificate
2073
 
        gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
 
2482
        gnutls.openpgp_crt_deinit(crt)
2074
2483
        # Convert the buffer to a Python bytestring
2075
2484
        fpr = ctypes.string_at(buf, buf_len.value)
2076
2485
        # Convert the bytestring to hexadecimal notation
2080
2489
 
2081
2490
class MultiprocessingMixIn(object):
2082
2491
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
2083
 
    
 
2492
 
2084
2493
    def sub_process_main(self, request, address):
2085
2494
        try:
2086
2495
            self.finish_request(request, address)
2087
2496
        except Exception:
2088
2497
            self.handle_error(request, address)
2089
2498
        self.close_request(request)
2090
 
    
 
2499
 
2091
2500
    def process_request(self, request, address):
2092
2501
        """Start a new process to process the request."""
2093
 
        proc = multiprocessing.Process(target = self.sub_process_main,
2094
 
                                       args = (request, address))
 
2502
        proc = multiprocessing.Process(target=self.sub_process_main,
 
2503
                                       args=(request, address))
2095
2504
        proc.start()
2096
2505
        return proc
2097
2506
 
2098
2507
 
2099
2508
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
2100
2509
    """ adds a pipe to the MixIn """
2101
 
    
 
2510
 
2102
2511
    def process_request(self, request, client_address):
2103
2512
        """Overrides and wraps the original process_request().
2104
 
        
 
2513
 
2105
2514
        This function creates a new pipe in self.pipe
2106
2515
        """
2107
2516
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
2108
 
        
 
2517
 
2109
2518
        proc = MultiprocessingMixIn.process_request(self, request,
2110
2519
                                                    client_address)
2111
2520
        self.child_pipe.close()
2112
2521
        self.add_pipe(parent_pipe, proc)
2113
 
    
 
2522
 
2114
2523
    def add_pipe(self, parent_pipe, proc):
2115
2524
        """Dummy function; override as necessary"""
2116
2525
        raise NotImplementedError()
2119
2528
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
2120
2529
                     socketserver.TCPServer, object):
2121
2530
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
2122
 
    
 
2531
 
2123
2532
    Attributes:
2124
2533
        enabled:        Boolean; whether this server is activated yet
2125
2534
        interface:      None or a network interface name (string)
2126
2535
        use_ipv6:       Boolean; to use IPv6 or not
2127
2536
    """
2128
 
    
 
2537
 
2129
2538
    def __init__(self, server_address, RequestHandlerClass,
2130
2539
                 interface=None,
2131
2540
                 use_ipv6=True,
2141
2550
            self.socketfd = socketfd
2142
2551
            # Save the original socket.socket() function
2143
2552
            self.socket_socket = socket.socket
 
2553
 
2144
2554
            # To implement --socket, we monkey patch socket.socket.
2145
 
            # 
 
2555
            #
2146
2556
            # (When socketserver.TCPServer is a new-style class, we
2147
2557
            # could make self.socket into a property instead of monkey
2148
2558
            # patching socket.socket.)
2149
 
            # 
 
2559
            #
2150
2560
            # Create a one-time-only replacement for socket.socket()
2151
2561
            @functools.wraps(socket.socket)
2152
2562
            def socket_wrapper(*args, **kwargs):
2164
2574
        # socket_wrapper(), if socketfd was set.
2165
2575
        socketserver.TCPServer.__init__(self, server_address,
2166
2576
                                        RequestHandlerClass)
2167
 
    
 
2577
 
2168
2578
    def server_bind(self):
2169
2579
        """This overrides the normal server_bind() function
2170
2580
        to bind to an interface if one was specified, and also NOT to
2171
2581
        bind to an address or port if they were not specified."""
 
2582
        global SO_BINDTODEVICE
2172
2583
        if self.interface is not None:
2173
2584
            if SO_BINDTODEVICE is None:
2174
 
                logger.error("SO_BINDTODEVICE does not exist;"
2175
 
                             " cannot bind to interface %s",
2176
 
                             self.interface)
2177
 
            else:
2178
 
                try:
2179
 
                    self.socket.setsockopt(
2180
 
                        socket.SOL_SOCKET, SO_BINDTODEVICE,
2181
 
                        (self.interface + "\0").encode("utf-8"))
2182
 
                except socket.error as error:
2183
 
                    if error.errno == errno.EPERM:
2184
 
                        logger.error("No permission to bind to"
2185
 
                                     " interface %s", self.interface)
2186
 
                    elif error.errno == errno.ENOPROTOOPT:
2187
 
                        logger.error("SO_BINDTODEVICE not available;"
2188
 
                                     " cannot bind to interface %s",
2189
 
                                     self.interface)
2190
 
                    elif error.errno == errno.ENODEV:
2191
 
                        logger.error("Interface %s does not exist,"
2192
 
                                     " cannot bind", self.interface)
2193
 
                    else:
2194
 
                        raise
 
2585
                # Fall back to a hard-coded value which seems to be
 
2586
                # common enough.
 
2587
                logger.warning("SO_BINDTODEVICE not found, trying 25")
 
2588
                SO_BINDTODEVICE = 25
 
2589
            try:
 
2590
                self.socket.setsockopt(
 
2591
                    socket.SOL_SOCKET, SO_BINDTODEVICE,
 
2592
                    (self.interface + "\0").encode("utf-8"))
 
2593
            except socket.error as error:
 
2594
                if error.errno == errno.EPERM:
 
2595
                    logger.error("No permission to bind to"
 
2596
                                 " interface %s", self.interface)
 
2597
                elif error.errno == errno.ENOPROTOOPT:
 
2598
                    logger.error("SO_BINDTODEVICE not available;"
 
2599
                                 " cannot bind to interface %s",
 
2600
                                 self.interface)
 
2601
                elif error.errno == errno.ENODEV:
 
2602
                    logger.error("Interface %s does not exist,"
 
2603
                                 " cannot bind", self.interface)
 
2604
                else:
 
2605
                    raise
2195
2606
        # Only bind(2) the socket if we really need to.
2196
2607
        if self.server_address[0] or self.server_address[1]:
 
2608
            if self.server_address[1]:
 
2609
                self.allow_reuse_address = True
2197
2610
            if not self.server_address[0]:
2198
2611
                if self.address_family == socket.AF_INET6:
2199
 
                    any_address = "::" # in6addr_any
 
2612
                    any_address = "::"  # in6addr_any
2200
2613
                else:
2201
 
                    any_address = "0.0.0.0" # INADDR_ANY
 
2614
                    any_address = "0.0.0.0"  # INADDR_ANY
2202
2615
                self.server_address = (any_address,
2203
2616
                                       self.server_address[1])
2204
2617
            elif not self.server_address[1]:
2214
2627
 
2215
2628
class MandosServer(IPv6_TCPServer):
2216
2629
    """Mandos server.
2217
 
    
 
2630
 
2218
2631
    Attributes:
2219
2632
        clients:        set of Client objects
2220
2633
        gnutls_priority GnuTLS priority string
2221
2634
        use_dbus:       Boolean; to emit D-Bus signals or not
2222
 
    
2223
 
    Assumes a gobject.MainLoop event loop.
 
2635
 
 
2636
    Assumes a GLib.MainLoop event loop.
2224
2637
    """
2225
 
    
 
2638
 
2226
2639
    def __init__(self, server_address, RequestHandlerClass,
2227
2640
                 interface=None,
2228
2641
                 use_ipv6=True,
2238
2651
        self.gnutls_priority = gnutls_priority
2239
2652
        IPv6_TCPServer.__init__(self, server_address,
2240
2653
                                RequestHandlerClass,
2241
 
                                interface = interface,
2242
 
                                use_ipv6 = use_ipv6,
2243
 
                                socketfd = socketfd)
2244
 
    
 
2654
                                interface=interface,
 
2655
                                use_ipv6=use_ipv6,
 
2656
                                socketfd=socketfd)
 
2657
 
2245
2658
    def server_activate(self):
2246
2659
        if self.enabled:
2247
2660
            return socketserver.TCPServer.server_activate(self)
2248
 
    
 
2661
 
2249
2662
    def enable(self):
2250
2663
        self.enabled = True
2251
 
    
 
2664
 
2252
2665
    def add_pipe(self, parent_pipe, proc):
2253
2666
        # Call "handle_ipc" for both data and EOF events
2254
 
        gobject.io_add_watch(
 
2667
        GLib.io_add_watch(
2255
2668
            parent_pipe.fileno(),
2256
 
            gobject.IO_IN | gobject.IO_HUP,
 
2669
            GLib.IO_IN | GLib.IO_HUP,
2257
2670
            functools.partial(self.handle_ipc,
2258
 
                              parent_pipe = parent_pipe,
2259
 
                              proc = proc))
2260
 
    
 
2671
                              parent_pipe=parent_pipe,
 
2672
                              proc=proc))
 
2673
 
2261
2674
    def handle_ipc(self, source, condition,
2262
2675
                   parent_pipe=None,
2263
 
                   proc = None,
 
2676
                   proc=None,
2264
2677
                   client_object=None):
2265
2678
        # error, or the other end of multiprocessing.Pipe has closed
2266
 
        if condition & (gobject.IO_ERR | gobject.IO_HUP):
 
2679
        if condition & (GLib.IO_ERR | GLib.IO_HUP):
2267
2680
            # Wait for other process to exit
2268
2681
            proc.join()
2269
2682
            return False
2270
 
        
 
2683
 
2271
2684
        # Read a request from the child
2272
2685
        request = parent_pipe.recv()
2273
2686
        command = request[0]
2274
 
        
 
2687
 
2275
2688
        if command == 'init':
2276
 
            fpr = request[1]
2277
 
            address = request[2]
2278
 
            
2279
 
            for c in self.clients.itervalues():
2280
 
                if c.fingerprint == fpr:
 
2689
            key_id = request[1].decode("ascii")
 
2690
            fpr = request[2].decode("ascii")
 
2691
            address = request[3]
 
2692
 
 
2693
            for c in self.clients.values():
 
2694
                if key_id == "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855":
 
2695
                    continue
 
2696
                if key_id and c.key_id == key_id:
 
2697
                    client = c
 
2698
                    break
 
2699
                if fpr and c.fingerprint == fpr:
2281
2700
                    client = c
2282
2701
                    break
2283
2702
            else:
2284
 
                logger.info("Client not found for fingerprint: %s, ad"
2285
 
                            "dress: %s", fpr, address)
 
2703
                logger.info("Client not found for key ID: %s, address"
 
2704
                            ": %s", key_id or fpr, address)
2286
2705
                if self.use_dbus:
2287
2706
                    # Emit D-Bus signal
2288
 
                    mandos_dbus_service.ClientNotFound(fpr,
 
2707
                    mandos_dbus_service.ClientNotFound(key_id or fpr,
2289
2708
                                                       address[0])
2290
2709
                parent_pipe.send(False)
2291
2710
                return False
2292
 
            
2293
 
            gobject.io_add_watch(
 
2711
 
 
2712
            GLib.io_add_watch(
2294
2713
                parent_pipe.fileno(),
2295
 
                gobject.IO_IN | gobject.IO_HUP,
 
2714
                GLib.IO_IN | GLib.IO_HUP,
2296
2715
                functools.partial(self.handle_ipc,
2297
 
                                  parent_pipe = parent_pipe,
2298
 
                                  proc = proc,
2299
 
                                  client_object = client))
 
2716
                                  parent_pipe=parent_pipe,
 
2717
                                  proc=proc,
 
2718
                                  client_object=client))
2300
2719
            parent_pipe.send(True)
2301
2720
            # remove the old hook in favor of the new above hook on
2302
2721
            # same fileno
2305
2724
            funcname = request[1]
2306
2725
            args = request[2]
2307
2726
            kwargs = request[3]
2308
 
            
 
2727
 
2309
2728
            parent_pipe.send(('data', getattr(client_object,
2310
2729
                                              funcname)(*args,
2311
2730
                                                        **kwargs)))
2312
 
        
 
2731
 
2313
2732
        if command == 'getattr':
2314
2733
            attrname = request[1]
2315
2734
            if isinstance(client_object.__getattribute__(attrname),
2318
2737
            else:
2319
2738
                parent_pipe.send((
2320
2739
                    'data', client_object.__getattribute__(attrname)))
2321
 
        
 
2740
 
2322
2741
        if command == 'setattr':
2323
2742
            attrname = request[1]
2324
2743
            value = request[2]
2325
2744
            setattr(client_object, attrname, value)
2326
 
        
 
2745
 
2327
2746
        return True
2328
2747
 
2329
2748
 
2330
2749
def rfc3339_duration_to_delta(duration):
2331
2750
    """Parse an RFC 3339 "duration" and return a datetime.timedelta
2332
 
    
 
2751
 
2333
2752
    >>> rfc3339_duration_to_delta("P7D")
2334
2753
    datetime.timedelta(7)
2335
2754
    >>> rfc3339_duration_to_delta("PT60S")
2345
2764
    >>> rfc3339_duration_to_delta("P1DT3M20S")
2346
2765
    datetime.timedelta(1, 200)
2347
2766
    """
2348
 
    
 
2767
 
2349
2768
    # Parsing an RFC 3339 duration with regular expressions is not
2350
2769
    # possible - there would have to be multiple places for the same
2351
2770
    # values, like seconds.  The current code, while more esoteric, is
2352
2771
    # cleaner without depending on a parsing library.  If Python had a
2353
2772
    # built-in library for parsing we would use it, but we'd like to
2354
2773
    # avoid excessive use of external libraries.
2355
 
    
 
2774
 
2356
2775
    # New type for defining tokens, syntax, and semantics all-in-one
2357
2776
    Token = collections.namedtuple("Token", (
2358
2777
        "regexp",  # To match token; if "value" is not None, must have
2391
2810
                           frozenset((token_year, token_month,
2392
2811
                                      token_day, token_time,
2393
2812
                                      token_week)))
2394
 
    # Define starting values
2395
 
    value = datetime.timedelta() # Value so far
 
2813
    # Define starting values:
 
2814
    # Value so far
 
2815
    value = datetime.timedelta()
2396
2816
    found_token = None
2397
 
    followers = frozenset((token_duration, )) # Following valid tokens
2398
 
    s = duration                # String left to parse
 
2817
    # Following valid tokens
 
2818
    followers = frozenset((token_duration, ))
 
2819
    # String left to parse
 
2820
    s = duration
2399
2821
    # Loop until end token is found
2400
2822
    while found_token is not token_end:
2401
2823
        # Search for any currently valid tokens
2425
2847
 
2426
2848
def string_to_delta(interval):
2427
2849
    """Parse a string and return a datetime.timedelta
2428
 
    
 
2850
 
2429
2851
    >>> string_to_delta('7d')
2430
2852
    datetime.timedelta(7)
2431
2853
    >>> string_to_delta('60s')
2439
2861
    >>> string_to_delta('5m 30s')
2440
2862
    datetime.timedelta(0, 330)
2441
2863
    """
2442
 
    
 
2864
 
2443
2865
    try:
2444
2866
        return rfc3339_duration_to_delta(interval)
2445
2867
    except ValueError:
2446
2868
        pass
2447
 
    
 
2869
 
2448
2870
    timevalue = datetime.timedelta(0)
2449
2871
    for s in interval.split():
2450
2872
        try:
2468
2890
    return timevalue
2469
2891
 
2470
2892
 
2471
 
def daemon(nochdir = False, noclose = False):
 
2893
def daemon(nochdir=False, noclose=False):
2472
2894
    """See daemon(3).  Standard BSD Unix function.
2473
 
    
 
2895
 
2474
2896
    This should really exist as os.daemon, but it doesn't (yet)."""
2475
2897
    if os.fork():
2476
2898
        sys.exit()
2494
2916
 
2495
2917
 
2496
2918
def main():
2497
 
    
 
2919
 
2498
2920
    ##################################################################
2499
2921
    # Parsing of options, both command line and config file
2500
 
    
 
2922
 
2501
2923
    parser = argparse.ArgumentParser()
2502
2924
    parser.add_argument("-v", "--version", action="version",
2503
 
                        version = "%(prog)s {}".format(version),
 
2925
                        version="%(prog)s {}".format(version),
2504
2926
                        help="show version number and exit")
2505
2927
    parser.add_argument("-i", "--interface", metavar="IF",
2506
2928
                        help="Bind to interface IF")
2542
2964
    parser.add_argument("--no-zeroconf", action="store_false",
2543
2965
                        dest="zeroconf", help="Do not use Zeroconf",
2544
2966
                        default=None)
2545
 
    
 
2967
 
2546
2968
    options = parser.parse_args()
2547
 
    
 
2969
 
2548
2970
    if options.check:
2549
2971
        import doctest
2550
2972
        fail_count, test_count = doctest.testmod()
2551
2973
        sys.exit(os.EX_OK if fail_count == 0 else 1)
2552
 
    
 
2974
 
2553
2975
    # Default values for config file for server-global settings
2554
 
    server_defaults = { "interface": "",
2555
 
                        "address": "",
2556
 
                        "port": "",
2557
 
                        "debug": "False",
2558
 
                        "priority":
2559
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2560
 
                        ":+SIGN-DSA-SHA256",
2561
 
                        "servicename": "Mandos",
2562
 
                        "use_dbus": "True",
2563
 
                        "use_ipv6": "True",
2564
 
                        "debuglevel": "",
2565
 
                        "restore": "True",
2566
 
                        "socket": "",
2567
 
                        "statedir": "/var/lib/mandos",
2568
 
                        "foreground": "False",
2569
 
                        "zeroconf": "True",
2570
 
                    }
2571
 
    
 
2976
    if gnutls.has_rawpk:
 
2977
        priority = ("SECURE128:!CTYPE-X.509:+CTYPE-RAWPK:!RSA"
 
2978
                    ":!VERS-ALL:+VERS-TLS1.3:%PROFILE_ULTRA")
 
2979
    else:
 
2980
        priority = ("SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
 
2981
                    ":+SIGN-DSA-SHA256")
 
2982
    server_defaults = {"interface": "",
 
2983
                       "address": "",
 
2984
                       "port": "",
 
2985
                       "debug": "False",
 
2986
                       "priority": priority,
 
2987
                       "servicename": "Mandos",
 
2988
                       "use_dbus": "True",
 
2989
                       "use_ipv6": "True",
 
2990
                       "debuglevel": "",
 
2991
                       "restore": "True",
 
2992
                       "socket": "",
 
2993
                       "statedir": "/var/lib/mandos",
 
2994
                       "foreground": "False",
 
2995
                       "zeroconf": "True",
 
2996
                       }
 
2997
    del priority
 
2998
 
2572
2999
    # Parse config file for server-global settings
2573
3000
    server_config = configparser.SafeConfigParser(server_defaults)
2574
3001
    del server_defaults
2576
3003
    # Convert the SafeConfigParser object to a dict
2577
3004
    server_settings = server_config.defaults()
2578
3005
    # Use the appropriate methods on the non-string config options
2579
 
    for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
 
3006
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
 
3007
                   "foreground", "zeroconf"):
2580
3008
        server_settings[option] = server_config.getboolean("DEFAULT",
2581
3009
                                                           option)
2582
3010
    if server_settings["port"]:
2592
3020
            server_settings["socket"] = os.dup(server_settings
2593
3021
                                               ["socket"])
2594
3022
    del server_config
2595
 
    
 
3023
 
2596
3024
    # Override the settings from the config file with command line
2597
3025
    # options, if set.
2598
3026
    for option in ("interface", "address", "port", "debug",
2616
3044
    if server_settings["debug"]:
2617
3045
        server_settings["foreground"] = True
2618
3046
    # Now we have our good server settings in "server_settings"
2619
 
    
 
3047
 
2620
3048
    ##################################################################
2621
 
    
 
3049
 
2622
3050
    if (not server_settings["zeroconf"]
2623
3051
        and not (server_settings["port"]
2624
3052
                 or server_settings["socket"] != "")):
2625
3053
        parser.error("Needs port or socket to work without Zeroconf")
2626
 
    
 
3054
 
2627
3055
    # For convenience
2628
3056
    debug = server_settings["debug"]
2629
3057
    debuglevel = server_settings["debuglevel"]
2633
3061
                                     stored_state_file)
2634
3062
    foreground = server_settings["foreground"]
2635
3063
    zeroconf = server_settings["zeroconf"]
2636
 
    
 
3064
 
2637
3065
    if debug:
2638
3066
        initlogger(debug, logging.DEBUG)
2639
3067
    else:
2642
3070
        else:
2643
3071
            level = getattr(logging, debuglevel.upper())
2644
3072
            initlogger(debug, level)
2645
 
    
 
3073
 
2646
3074
    if server_settings["servicename"] != "Mandos":
2647
3075
        syslogger.setFormatter(
2648
3076
            logging.Formatter('Mandos ({}) [%(process)d]:'
2649
3077
                              ' %(levelname)s: %(message)s'.format(
2650
3078
                                  server_settings["servicename"])))
2651
 
    
 
3079
 
2652
3080
    # Parse config file with clients
2653
3081
    client_config = configparser.SafeConfigParser(Client
2654
3082
                                                  .client_defaults)
2655
3083
    client_config.read(os.path.join(server_settings["configdir"],
2656
3084
                                    "clients.conf"))
2657
 
    
 
3085
 
2658
3086
    global mandos_dbus_service
2659
3087
    mandos_dbus_service = None
2660
 
    
 
3088
 
2661
3089
    socketfd = None
2662
3090
    if server_settings["socket"] != "":
2663
3091
        socketfd = server_settings["socket"]
2679
3107
        except IOError as e:
2680
3108
            logger.error("Could not open file %r", pidfilename,
2681
3109
                         exc_info=e)
2682
 
    
2683
 
    for name in ("_mandos", "mandos", "nobody"):
 
3110
 
 
3111
    for name, group in (("_mandos", "_mandos"),
 
3112
                        ("mandos", "mandos"),
 
3113
                        ("nobody", "nogroup")):
2684
3114
        try:
2685
3115
            uid = pwd.getpwnam(name).pw_uid
2686
 
            gid = pwd.getpwnam(name).pw_gid
 
3116
            gid = pwd.getpwnam(group).pw_gid
2687
3117
            break
2688
3118
        except KeyError:
2689
3119
            continue
2693
3123
    try:
2694
3124
        os.setgid(gid)
2695
3125
        os.setuid(uid)
 
3126
        if debug:
 
3127
            logger.debug("Did setuid/setgid to {}:{}".format(uid,
 
3128
                                                             gid))
2696
3129
    except OSError as error:
 
3130
        logger.warning("Failed to setuid/setgid to {}:{}: {}"
 
3131
                       .format(uid, gid, os.strerror(error.errno)))
2697
3132
        if error.errno != errno.EPERM:
2698
3133
            raise
2699
 
    
 
3134
 
2700
3135
    if debug:
2701
3136
        # Enable all possible GnuTLS debugging
2702
 
        
 
3137
 
2703
3138
        # "Use a log level over 10 to enable all debugging options."
2704
3139
        # - GnuTLS manual
2705
 
        gnutls.library.functions.gnutls_global_set_log_level(11)
2706
 
        
2707
 
        @gnutls.library.types.gnutls_log_func
 
3140
        gnutls.global_set_log_level(11)
 
3141
 
 
3142
        @gnutls.log_func
2708
3143
        def debug_gnutls(level, string):
2709
3144
            logger.debug("GnuTLS: %s", string[:-1])
2710
 
        
2711
 
        gnutls.library.functions.gnutls_global_set_log_function(
2712
 
            debug_gnutls)
2713
 
        
 
3145
 
 
3146
        gnutls.global_set_log_function(debug_gnutls)
 
3147
 
2714
3148
        # Redirect stdin so all checkers get /dev/null
2715
3149
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2716
3150
        os.dup2(null, sys.stdin.fileno())
2717
3151
        if null > 2:
2718
3152
            os.close(null)
2719
 
    
 
3153
 
2720
3154
    # Need to fork before connecting to D-Bus
2721
3155
    if not foreground:
2722
3156
        # Close all input and output, do double fork, etc.
2723
3157
        daemon()
2724
 
    
2725
 
    # multiprocessing will use threads, so before we use gobject we
2726
 
    # need to inform gobject that threads will be used.
2727
 
    gobject.threads_init()
2728
 
    
 
3158
 
 
3159
    # multiprocessing will use threads, so before we use GLib we need
 
3160
    # to inform GLib that threads will be used.
 
3161
    GLib.threads_init()
 
3162
 
2729
3163
    global main_loop
2730
3164
    # From the Avahi example code
2731
3165
    DBusGMainLoop(set_as_default=True)
2732
 
    main_loop = gobject.MainLoop()
 
3166
    main_loop = GLib.MainLoop()
2733
3167
    bus = dbus.SystemBus()
2734
3168
    # End of Avahi example code
2735
3169
    if use_dbus:
2748
3182
    if zeroconf:
2749
3183
        protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2750
3184
        service = AvahiServiceToSyslog(
2751
 
            name = server_settings["servicename"],
2752
 
            servicetype = "_mandos._tcp",
2753
 
            protocol = protocol,
2754
 
            bus = bus)
 
3185
            name=server_settings["servicename"],
 
3186
            servicetype="_mandos._tcp",
 
3187
            protocol=protocol,
 
3188
            bus=bus)
2755
3189
        if server_settings["interface"]:
2756
3190
            service.interface = if_nametoindex(
2757
3191
                server_settings["interface"].encode("utf-8"))
2758
 
    
 
3192
 
2759
3193
    global multiprocessing_manager
2760
3194
    multiprocessing_manager = multiprocessing.Manager()
2761
 
    
 
3195
 
2762
3196
    client_class = Client
2763
3197
    if use_dbus:
2764
 
        client_class = functools.partial(ClientDBus, bus = bus)
2765
 
    
 
3198
        client_class = functools.partial(ClientDBus, bus=bus)
 
3199
 
2766
3200
    client_settings = Client.config_parser(client_config)
2767
3201
    old_client_settings = {}
2768
3202
    clients_data = {}
2769
 
    
 
3203
 
2770
3204
    # This is used to redirect stdout and stderr for checker processes
2771
3205
    global wnull
2772
 
    wnull = open(os.devnull, "w") # A writable /dev/null
 
3206
    wnull = open(os.devnull, "w")  # A writable /dev/null
2773
3207
    # Only used if server is running in foreground but not in debug
2774
3208
    # mode
2775
3209
    if debug or not foreground:
2776
3210
        wnull.close()
2777
 
    
 
3211
 
2778
3212
    # Get client data and settings from last running state.
2779
3213
    if server_settings["restore"]:
2780
3214
        try:
2781
3215
            with open(stored_state_path, "rb") as stored_state:
2782
 
                clients_data, old_client_settings = pickle.load(
2783
 
                    stored_state)
 
3216
                if sys.version_info.major == 2:
 
3217
                    clients_data, old_client_settings = pickle.load(
 
3218
                        stored_state)
 
3219
                else:
 
3220
                    bytes_clients_data, bytes_old_client_settings = (
 
3221
                        pickle.load(stored_state, encoding="bytes"))
 
3222
                    #   Fix bytes to strings
 
3223
                    #  clients_data
 
3224
                    # .keys()
 
3225
                    clients_data = {(key.decode("utf-8")
 
3226
                                     if isinstance(key, bytes)
 
3227
                                     else key): value
 
3228
                                    for key, value in
 
3229
                                    bytes_clients_data.items()}
 
3230
                    del bytes_clients_data
 
3231
                    for key in clients_data:
 
3232
                        value = {(k.decode("utf-8")
 
3233
                                  if isinstance(k, bytes) else k): v
 
3234
                                 for k, v in
 
3235
                                 clients_data[key].items()}
 
3236
                        clients_data[key] = value
 
3237
                        # .client_structure
 
3238
                        value["client_structure"] = [
 
3239
                            (s.decode("utf-8")
 
3240
                             if isinstance(s, bytes)
 
3241
                             else s) for s in
 
3242
                            value["client_structure"]]
 
3243
                        # .name & .host
 
3244
                        for k in ("name", "host"):
 
3245
                            if isinstance(value[k], bytes):
 
3246
                                value[k] = value[k].decode("utf-8")
 
3247
                        if "key_id" not in value:
 
3248
                            value["key_id"] = ""
 
3249
                        elif "fingerprint" not in value:
 
3250
                            value["fingerprint"] = ""
 
3251
                    #  old_client_settings
 
3252
                    # .keys()
 
3253
                    old_client_settings = {
 
3254
                        (key.decode("utf-8")
 
3255
                         if isinstance(key, bytes)
 
3256
                         else key): value
 
3257
                        for key, value in
 
3258
                        bytes_old_client_settings.items()}
 
3259
                    del bytes_old_client_settings
 
3260
                    # .host
 
3261
                    for value in old_client_settings.values():
 
3262
                        if isinstance(value["host"], bytes):
 
3263
                            value["host"] = (value["host"]
 
3264
                                             .decode("utf-8"))
2784
3265
            os.remove(stored_state_path)
2785
3266
        except IOError as e:
2786
3267
            if e.errno == errno.ENOENT:
2794
3275
            logger.warning("Could not load persistent state: "
2795
3276
                           "EOFError:",
2796
3277
                           exc_info=e)
2797
 
    
 
3278
 
2798
3279
    with PGPEngine() as pgp:
2799
3280
        for client_name, client in clients_data.items():
2800
3281
            # Skip removed clients
2801
3282
            if client_name not in client_settings:
2802
3283
                continue
2803
 
            
 
3284
 
2804
3285
            # Decide which value to use after restoring saved state.
2805
3286
            # We have three different values: Old config file,
2806
3287
            # new config file, and saved state.
2817
3298
                        client[name] = value
2818
3299
                except KeyError:
2819
3300
                    pass
2820
 
            
 
3301
 
2821
3302
            # Clients who has passed its expire date can still be
2822
3303
            # enabled if its last checker was successful.  A Client
2823
3304
            # whose checker succeeded before we stored its state is
2856
3337
                    client_name))
2857
3338
                client["secret"] = (client_settings[client_name]
2858
3339
                                    ["secret"])
2859
 
    
 
3340
 
2860
3341
    # Add/remove clients based on new changes made to config
2861
3342
    for client_name in (set(old_client_settings)
2862
3343
                        - set(client_settings)):
2864
3345
    for client_name in (set(client_settings)
2865
3346
                        - set(old_client_settings)):
2866
3347
        clients_data[client_name] = client_settings[client_name]
2867
 
    
 
3348
 
2868
3349
    # Create all client objects
2869
3350
    for client_name, client in clients_data.items():
2870
3351
        tcp_server.clients[client_name] = client_class(
2871
 
            name = client_name,
2872
 
            settings = client,
2873
 
            server_settings = server_settings)
2874
 
    
 
3352
            name=client_name,
 
3353
            settings=client,
 
3354
            server_settings=server_settings)
 
3355
 
2875
3356
    if not tcp_server.clients:
2876
3357
        logger.warning("No clients defined")
2877
 
    
 
3358
 
2878
3359
    if not foreground:
2879
3360
        if pidfile is not None:
2880
3361
            pid = os.getpid()
2886
3367
                             pidfilename, pid)
2887
3368
        del pidfile
2888
3369
        del pidfilename
2889
 
    
2890
 
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2891
 
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2892
 
    
 
3370
 
 
3371
    for termsig in (signal.SIGHUP, signal.SIGTERM):
 
3372
        GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig,
 
3373
                             lambda: main_loop.quit() and False)
 
3374
 
2893
3375
    if use_dbus:
2894
 
        
 
3376
 
2895
3377
        @alternate_dbus_interfaces(
2896
 
            { "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
 
3378
            {"se.recompile.Mandos": "se.bsnet.fukt.Mandos"})
2897
3379
        class MandosDBusService(DBusObjectWithObjectManager):
2898
3380
            """A D-Bus proxy object"""
2899
 
            
 
3381
 
2900
3382
            def __init__(self):
2901
3383
                dbus.service.Object.__init__(self, bus, "/")
2902
 
            
 
3384
 
2903
3385
            _interface = "se.recompile.Mandos"
2904
 
            
 
3386
 
2905
3387
            @dbus.service.signal(_interface, signature="o")
2906
3388
            def ClientAdded(self, objpath):
2907
3389
                "D-Bus signal"
2908
3390
                pass
2909
 
            
 
3391
 
2910
3392
            @dbus.service.signal(_interface, signature="ss")
2911
 
            def ClientNotFound(self, fingerprint, address):
 
3393
            def ClientNotFound(self, key_id, address):
2912
3394
                "D-Bus signal"
2913
3395
                pass
2914
 
            
 
3396
 
2915
3397
            @dbus_annotations({"org.freedesktop.DBus.Deprecated":
2916
3398
                               "true"})
2917
3399
            @dbus.service.signal(_interface, signature="os")
2918
3400
            def ClientRemoved(self, objpath, name):
2919
3401
                "D-Bus signal"
2920
3402
                pass
2921
 
            
 
3403
 
2922
3404
            @dbus_annotations({"org.freedesktop.DBus.Deprecated":
2923
3405
                               "true"})
2924
3406
            @dbus.service.method(_interface, out_signature="ao")
2925
3407
            def GetAllClients(self):
2926
3408
                "D-Bus method"
2927
3409
                return dbus.Array(c.dbus_object_path for c in
2928
 
                                  tcp_server.clients.itervalues())
2929
 
            
 
3410
                                  tcp_server.clients.values())
 
3411
 
2930
3412
            @dbus_annotations({"org.freedesktop.DBus.Deprecated":
2931
3413
                               "true"})
2932
3414
            @dbus.service.method(_interface,
2934
3416
            def GetAllClientsWithProperties(self):
2935
3417
                "D-Bus method"
2936
3418
                return dbus.Dictionary(
2937
 
                    { c.dbus_object_path: c.GetAll(
 
3419
                    {c.dbus_object_path: c.GetAll(
2938
3420
                        "se.recompile.Mandos.Client")
2939
 
                      for c in tcp_server.clients.itervalues() },
 
3421
                     for c in tcp_server.clients.values()},
2940
3422
                    signature="oa{sv}")
2941
 
            
 
3423
 
2942
3424
            @dbus.service.method(_interface, in_signature="o")
2943
3425
            def RemoveClient(self, object_path):
2944
3426
                "D-Bus method"
2945
 
                for c in tcp_server.clients.itervalues():
 
3427
                for c in tcp_server.clients.values():
2946
3428
                    if c.dbus_object_path == object_path:
2947
3429
                        del tcp_server.clients[c.name]
2948
3430
                        c.remove_from_connection()
2952
3434
                        self.client_removed_signal(c)
2953
3435
                        return
2954
3436
                raise KeyError(object_path)
2955
 
            
 
3437
 
2956
3438
            del _interface
2957
 
            
 
3439
 
2958
3440
            @dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
2959
 
                                 out_signature = "a{oa{sa{sv}}}")
 
3441
                                 out_signature="a{oa{sa{sv}}}")
2960
3442
            def GetManagedObjects(self):
2961
3443
                """D-Bus method"""
2962
3444
                return dbus.Dictionary(
2963
 
                    { client.dbus_object_path:
2964
 
                      dbus.Dictionary(
2965
 
                          { interface: client.GetAll(interface)
2966
 
                            for interface in
2967
 
                                 client._get_all_interface_names()})
2968
 
                      for client in tcp_server.clients.values()})
2969
 
            
 
3445
                    {client.dbus_object_path:
 
3446
                     dbus.Dictionary(
 
3447
                         {interface: client.GetAll(interface)
 
3448
                          for interface in
 
3449
                          client._get_all_interface_names()})
 
3450
                     for client in tcp_server.clients.values()})
 
3451
 
2970
3452
            def client_added_signal(self, client):
2971
3453
                """Send the new standard signal and the old signal"""
2972
3454
                if use_dbus:
2974
3456
                    self.InterfacesAdded(
2975
3457
                        client.dbus_object_path,
2976
3458
                        dbus.Dictionary(
2977
 
                            { interface: client.GetAll(interface)
2978
 
                              for interface in
2979
 
                              client._get_all_interface_names()}))
 
3459
                            {interface: client.GetAll(interface)
 
3460
                             for interface in
 
3461
                             client._get_all_interface_names()}))
2980
3462
                    # Old signal
2981
3463
                    self.ClientAdded(client.dbus_object_path)
2982
 
            
 
3464
 
2983
3465
            def client_removed_signal(self, client):
2984
3466
                """Send the new standard signal and the old signal"""
2985
3467
                if use_dbus:
2990
3472
                    # Old signal
2991
3473
                    self.ClientRemoved(client.dbus_object_path,
2992
3474
                                       client.name)
2993
 
        
 
3475
 
2994
3476
        mandos_dbus_service = MandosDBusService()
2995
 
    
 
3477
 
 
3478
    # Save modules to variables to exempt the modules from being
 
3479
    # unloaded before the function registered with atexit() is run.
 
3480
    mp = multiprocessing
 
3481
    wn = wnull
 
3482
 
2996
3483
    def cleanup():
2997
3484
        "Cleanup function; run on exit"
2998
3485
        if zeroconf:
2999
3486
            service.cleanup()
3000
 
        
3001
 
        multiprocessing.active_children()
3002
 
        wnull.close()
 
3487
 
 
3488
        mp.active_children()
 
3489
        wn.close()
3003
3490
        if not (tcp_server.clients or client_settings):
3004
3491
            return
3005
 
        
 
3492
 
3006
3493
        # Store client before exiting. Secrets are encrypted with key
3007
3494
        # based on what config file has. If config file is
3008
3495
        # removed/edited, old secret will thus be unrecovable.
3009
3496
        clients = {}
3010
3497
        with PGPEngine() as pgp:
3011
 
            for client in tcp_server.clients.itervalues():
 
3498
            for client in tcp_server.clients.values():
3012
3499
                key = client_settings[client.name]["secret"]
3013
3500
                client.encrypted_secret = pgp.encrypt(client.secret,
3014
3501
                                                      key)
3015
3502
                client_dict = {}
3016
 
                
 
3503
 
3017
3504
                # A list of attributes that can not be pickled
3018
3505
                # + secret.
3019
 
                exclude = { "bus", "changedstate", "secret",
3020
 
                            "checker", "server_settings" }
 
3506
                exclude = {"bus", "changedstate", "secret",
 
3507
                           "checker", "server_settings"}
3021
3508
                for name, typ in inspect.getmembers(dbus.service
3022
3509
                                                    .Object):
3023
3510
                    exclude.add(name)
3024
 
                
 
3511
 
3025
3512
                client_dict["encrypted_secret"] = (client
3026
3513
                                                   .encrypted_secret)
3027
3514
                for attr in client.client_structure:
3028
3515
                    if attr not in exclude:
3029
3516
                        client_dict[attr] = getattr(client, attr)
3030
 
                
 
3517
 
3031
3518
                clients[client.name] = client_dict
3032
3519
                del client_settings[client.name]["secret"]
3033
 
        
 
3520
 
3034
3521
        try:
3035
3522
            with tempfile.NamedTemporaryFile(
3036
3523
                    mode='wb',
3038
3525
                    prefix='clients-',
3039
3526
                    dir=os.path.dirname(stored_state_path),
3040
3527
                    delete=False) as stored_state:
3041
 
                pickle.dump((clients, client_settings), stored_state)
 
3528
                pickle.dump((clients, client_settings), stored_state,
 
3529
                            protocol=2)
3042
3530
                tempname = stored_state.name
3043
3531
            os.rename(tempname, stored_state_path)
3044
3532
        except (IOError, OSError) as e:
3054
3542
                logger.warning("Could not save persistent state:",
3055
3543
                               exc_info=e)
3056
3544
                raise
3057
 
        
 
3545
 
3058
3546
        # Delete all clients, and settings from config
3059
3547
        while tcp_server.clients:
3060
3548
            name, client = tcp_server.clients.popitem()
3066
3554
            if use_dbus:
3067
3555
                mandos_dbus_service.client_removed_signal(client)
3068
3556
        client_settings.clear()
3069
 
    
 
3557
 
3070
3558
    atexit.register(cleanup)
3071
 
    
3072
 
    for client in tcp_server.clients.itervalues():
 
3559
 
 
3560
    for client in tcp_server.clients.values():
3073
3561
        if use_dbus:
3074
3562
            # Emit D-Bus signal for adding
3075
3563
            mandos_dbus_service.client_added_signal(client)
3076
3564
        # Need to initiate checking of clients
3077
3565
        if client.enabled:
3078
3566
            client.init_checker()
3079
 
    
 
3567
 
3080
3568
    tcp_server.enable()
3081
3569
    tcp_server.server_activate()
3082
 
    
 
3570
 
3083
3571
    # Find out what port we got
3084
3572
    if zeroconf:
3085
3573
        service.port = tcp_server.socket.getsockname()[1]
3090
3578
    else:                       # IPv4
3091
3579
        logger.info("Now listening on address %r, port %d",
3092
3580
                    *tcp_server.socket.getsockname())
3093
 
    
3094
 
    #service.interface = tcp_server.socket.getsockname()[3]
3095
 
    
 
3581
 
 
3582
    # service.interface = tcp_server.socket.getsockname()[3]
 
3583
 
3096
3584
    try:
3097
3585
        if zeroconf:
3098
3586
            # From the Avahi example code
3103
3591
                cleanup()
3104
3592
                sys.exit(1)
3105
3593
            # End of Avahi example code
3106
 
        
3107
 
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
3108
 
                             lambda *args, **kwargs:
3109
 
                             (tcp_server.handle_request
3110
 
                              (*args[2:], **kwargs) or True))
3111
 
        
 
3594
 
 
3595
        GLib.io_add_watch(tcp_server.fileno(), GLib.IO_IN,
 
3596
                          lambda *args, **kwargs:
 
3597
                          (tcp_server.handle_request
 
3598
                           (*args[2:], **kwargs) or True))
 
3599
 
3112
3600
        logger.debug("Starting main loop")
3113
3601
        main_loop.run()
3114
3602
    except AvahiError as error: