/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2016-07-03 03:32:28 UTC
  • Revision ID: teddy@recompile.se-20160703033228-cafpnnfs53kdg52g
Change all http:// URLs to https:// wherever possible.

* INSTALL (Prerequisites/Libraries/Mandos Server): Change "http://" to
  "https://" wherever possible.
* Makefile (FORTIFY): - '' -
* mandos.xml (SEE ALSO): - '' -
* plugin-runner.c (main): - '' -
* plugins.d/mandos-client.c (start_mandos_communication): - '' -
* plugins.d/mandos-client.xml (SEE ALSO): - '' -

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python2.7
 
1
#!/usr/bin/python
2
2
# -*- mode: python; coding: utf-8 -*-
3
3
4
4
# Mandos server - give out binary blobs to connecting clients.
11
11
# "AvahiService" class, and some lines in "main".
12
12
13
13
# Everything else is
14
 
# Copyright © 2008-2015 Teddy Hogeborn
15
 
# Copyright © 2008-2015 Björn Påhlsson
 
14
# Copyright © 2008-2016 Teddy Hogeborn
 
15
# Copyright © 2008-2016 Björn Påhlsson
16
16
17
17
# This program is free software: you can redistribute it and/or modify
18
18
# it under the terms of the GNU General Public License as published by
34
34
from __future__ import (division, absolute_import, print_function,
35
35
                        unicode_literals)
36
36
 
37
 
from future_builtins import *
 
37
try:
 
38
    from future_builtins import *
 
39
except ImportError:
 
40
    pass
38
41
 
39
42
try:
40
43
    import SocketServer as socketserver
76
79
 
77
80
import dbus
78
81
import dbus.service
79
 
try:
80
 
    import gobject
81
 
except ImportError:
82
 
    from gi.repository import GObject as gobject
83
 
import avahi
 
82
from gi.repository import GLib
84
83
from dbus.mainloop.glib import DBusGMainLoop
85
84
import ctypes
86
85
import ctypes.util
87
86
import xml.dom.minidom
88
87
import inspect
89
88
 
 
89
# Try to find the value of SO_BINDTODEVICE:
90
90
try:
 
91
    # This is where SO_BINDTODEVICE is in Python 3.3 (or 3.4?) and
 
92
    # newer, and it is also the most natural place for it:
91
93
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
92
94
except AttributeError:
93
95
    try:
 
96
        # This is where SO_BINDTODEVICE was up to and including Python
 
97
        # 2.6, and also 3.2:
94
98
        from IN import SO_BINDTODEVICE
95
99
    except ImportError:
96
 
        SO_BINDTODEVICE = None
 
100
        # In Python 2.7 it seems to have been removed entirely.
 
101
        # Try running the C preprocessor:
 
102
        try:
 
103
            cc = subprocess.Popen(["cc", "--language=c", "-E",
 
104
                                   "/dev/stdin"],
 
105
                                  stdin=subprocess.PIPE,
 
106
                                  stdout=subprocess.PIPE)
 
107
            stdout = cc.communicate(
 
108
                "#include <sys/socket.h>\nSO_BINDTODEVICE\n")[0]
 
109
            SO_BINDTODEVICE = int(stdout.splitlines()[-1])
 
110
        except (OSError, ValueError, IndexError):
 
111
            # No value found
 
112
            SO_BINDTODEVICE = None
97
113
 
98
114
if sys.version_info.major == 2:
99
115
    str = unicode
100
116
 
101
 
version = "1.7.1"
 
117
version = "1.7.10"
102
118
stored_state_file = "clients.pickle"
103
119
 
104
120
logger = logging.getLogger()
119
135
        return interface_index
120
136
 
121
137
 
 
138
def copy_function(func):
 
139
    """Make a copy of a function"""
 
140
    if sys.version_info.major == 2:
 
141
        return types.FunctionType(func.func_code,
 
142
                                  func.func_globals,
 
143
                                  func.func_name,
 
144
                                  func.func_defaults,
 
145
                                  func.func_closure)
 
146
    else:
 
147
        return types.FunctionType(func.__code__,
 
148
                                  func.__globals__,
 
149
                                  func.__name__,
 
150
                                  func.__defaults__,
 
151
                                  func.__closure__)
 
152
 
 
153
 
122
154
def initlogger(debug, level=logging.WARNING):
123
155
    """init logger and add loglevel"""
124
156
    
151
183
    
152
184
    def __init__(self):
153
185
        self.tempdir = tempfile.mkdtemp(prefix="mandos-")
 
186
        self.gpg = "gpg"
 
187
        try:
 
188
            output = subprocess.check_output(["gpgconf"])
 
189
            for line in output.splitlines():
 
190
                name, text, path = line.split(b":")
 
191
                if name == "gpg":
 
192
                    self.gpg = path
 
193
                    break
 
194
        except OSError as e:
 
195
            if e.errno != errno.ENOENT:
 
196
                raise
154
197
        self.gnupgargs = ['--batch',
155
 
                          '--home', self.tempdir,
 
198
                          '--homedir', self.tempdir,
156
199
                          '--force-mdc',
157
 
                          '--quiet',
158
 
                          '--no-use-agent']
 
200
                          '--quiet']
 
201
        # Only GPG version 1 has the --no-use-agent option.
 
202
        if self.gpg == "gpg" or self.gpg.endswith("/gpg"):
 
203
            self.gnupgargs.append("--no-use-agent")
159
204
    
160
205
    def __enter__(self):
161
206
        return self
197
242
                dir=self.tempdir) as passfile:
198
243
            passfile.write(passphrase)
199
244
            passfile.flush()
200
 
            proc = subprocess.Popen(['gpg', '--symmetric',
 
245
            proc = subprocess.Popen([self.gpg, '--symmetric',
201
246
                                     '--passphrase-file',
202
247
                                     passfile.name]
203
248
                                    + self.gnupgargs,
215
260
                dir = self.tempdir) as passfile:
216
261
            passfile.write(passphrase)
217
262
            passfile.flush()
218
 
            proc = subprocess.Popen(['gpg', '--decrypt',
 
263
            proc = subprocess.Popen([self.gpg, '--decrypt',
219
264
                                     '--passphrase-file',
220
265
                                     passfile.name]
221
266
                                    + self.gnupgargs,
227
272
            raise PGPError(err)
228
273
        return decrypted_plaintext
229
274
 
 
275
# Pretend that we have an Avahi module
 
276
class Avahi(object):
 
277
    """This isn't so much a class as it is a module-like namespace.
 
278
    It is instantiated once, and simulates having an Avahi module."""
 
279
    IF_UNSPEC = -1              # avahi-common/address.h
 
280
    PROTO_UNSPEC = -1           # avahi-common/address.h
 
281
    PROTO_INET = 0              # avahi-common/address.h
 
282
    PROTO_INET6 = 1             # avahi-common/address.h
 
283
    DBUS_NAME = "org.freedesktop.Avahi"
 
284
    DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"
 
285
    DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
 
286
    DBUS_PATH_SERVER = "/"
 
287
    def string_array_to_txt_array(self, t):
 
288
        return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
 
289
                           for s in t), signature="ay")
 
290
    ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h
 
291
    ENTRY_GROUP_COLLISION = 3   # avahi-common/defs.h
 
292
    ENTRY_GROUP_FAILURE = 4     # avahi-common/defs.h
 
293
    SERVER_INVALID = 0          # avahi-common/defs.h
 
294
    SERVER_REGISTERING = 1      # avahi-common/defs.h
 
295
    SERVER_RUNNING = 2          # avahi-common/defs.h
 
296
    SERVER_COLLISION = 3        # avahi-common/defs.h
 
297
    SERVER_FAILURE = 4          # avahi-common/defs.h
 
298
avahi = Avahi()
230
299
 
231
300
class AvahiError(Exception):
232
301
    def __init__(self, value, *args, **kwargs):
436
505
    
437
506
    _library = ctypes.cdll.LoadLibrary(
438
507
        ctypes.util.find_library("gnutls"))
439
 
    _need_version = "3.3.0"
 
508
    _need_version = b"3.3.0"
440
509
    def __init__(self):
441
510
        # Need to use class name "GnuTLS" here, since this method is
442
511
        # called before the assignment to the "gnutls" global variable
450
519
    
451
520
    # Constants
452
521
    E_SUCCESS = 0
 
522
    E_INTERRUPTED = -52
 
523
    E_AGAIN = -28
453
524
    CRT_OPENPGP = 2
454
525
    CLIENT = 2
455
526
    SHUT_RDWR = 0
474
545
    openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
475
546
    openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
476
547
    log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
477
 
    credentials_type_t = ctypes.c_int # 
 
548
    credentials_type_t = ctypes.c_int
478
549
    transport_ptr_t = ctypes.c_void_p
479
550
    close_request_t = ctypes.c_int
480
551
    
483
554
        # We need to use the class name "GnuTLS" here, since this
484
555
        # exception might be raised from within GnuTLS.__init__,
485
556
        # which is called before the assignment to the "gnutls"
486
 
        # global variable happens.
 
557
        # global variable has happened.
487
558
        def __init__(self, message = None, code = None, args=()):
488
559
            # Default usage is by a message string, but if a return
489
560
            # code is passed, convert it to a string with
490
561
            # gnutls.strerror()
 
562
            self.code = code
491
563
            if message is None and code is not None:
492
564
                message = GnuTLS.strerror(code)
493
565
            return super(GnuTLS.Error, self).__init__(
531
603
        
532
604
        def send(self, data):
533
605
            data = bytes(data)
534
 
            if not data:
535
 
                return 0
536
 
            return gnutls.record_send(self._c_object, data, len(data))
 
606
            data_len = len(data)
 
607
            while data_len > 0:
 
608
                data_len -= gnutls.record_send(self._c_object,
 
609
                                               data[-data_len:],
 
610
                                               data_len)
537
611
        
538
612
        def bye(self):
539
613
            return gnutls.bye(self._c_object, gnutls.SHUT_RDWR)
540
614
    
541
 
    # Error handling function
 
615
    # Error handling functions
542
616
    def _error_code(result):
543
617
        """A function to raise exceptions on errors, suitable
544
618
        for the 'restype' attribute on ctypes functions"""
548
622
            raise gnutls.CertificateSecurityError(code = result)
549
623
        raise gnutls.Error(code = result)
550
624
    
 
625
    def _retry_on_error(result, func, arguments):
 
626
        """A function to retry on some errors, suitable
 
627
        for the 'errcheck' attribute on ctypes functions"""
 
628
        while result < 0:
 
629
            if result not in (gnutls.E_INTERRUPTED, gnutls.E_AGAIN):
 
630
                return _error_code(result)
 
631
            result = func(*arguments)
 
632
        return result
 
633
    
551
634
    # Unless otherwise indicated, the function declarations below are
552
635
    # all from the gnutls/gnutls.h C header file.
553
636
    
569
652
    record_send.argtypes = [session_t, ctypes.c_void_p,
570
653
                            ctypes.c_size_t]
571
654
    record_send.restype = ctypes.c_ssize_t
 
655
    record_send.errcheck = _retry_on_error
572
656
    
573
657
    certificate_allocate_credentials = (
574
658
        _library.gnutls_certificate_allocate_credentials)
620
704
    handshake = _library.gnutls_handshake
621
705
    handshake.argtypes = [session_t]
622
706
    handshake.restype = _error_code
 
707
    handshake.errcheck = _retry_on_error
623
708
    
624
709
    transport_set_ptr = _library.gnutls_transport_set_ptr
625
710
    transport_set_ptr.argtypes = [session_t, transport_ptr_t]
628
713
    bye = _library.gnutls_bye
629
714
    bye.argtypes = [session_t, close_request_t]
630
715
    bye.restype = _error_code
 
716
    bye.errcheck = _retry_on_error
631
717
    
632
718
    check_version = _library.gnutls_check_version
633
719
    check_version.argtypes = [ctypes.c_char_p]
662
748
                                                ctypes.c_size_t)]
663
749
    openpgp_crt_get_fingerprint.restype = _error_code
664
750
    
665
 
    # Remove non-public function
666
 
    del _error_code
 
751
    # Remove non-public functions
 
752
    del _error_code, _retry_on_error
667
753
# Create the global "gnutls" object, simulating a module
668
754
gnutls = GnuTLS()
669
755
 
687
773
    checker:    subprocess.Popen(); a running checker process used
688
774
                                    to see if the client lives.
689
775
                                    'None' if no process is running.
690
 
    checker_callback_tag: a gobject event source tag, or None
 
776
    checker_callback_tag: a GLib event source tag, or None
691
777
    checker_command: string; External command which is run to check
692
778
                     if client lives.  %() expansions are done at
693
779
                     runtime with vars(self) as dict, so that for
694
780
                     instance %(name)s can be used in the command.
695
 
    checker_initiator_tag: a gobject event source tag, or None
 
781
    checker_initiator_tag: a GLib event source tag, or None
696
782
    created:    datetime.datetime(); (UTC) object creation
697
783
    client_structure: Object describing what attributes a client has
698
784
                      and is used for storing the client at exit
699
785
    current_checker_command: string; current running checker_command
700
 
    disable_initiator_tag: a gobject event source tag, or None
 
786
    disable_initiator_tag: a GLib event source tag, or None
701
787
    enabled:    bool()
702
788
    fingerprint: string (40 or 32 hexadecimal digits); used to
703
789
                 uniquely identify the client
766
852
            client["fingerprint"] = (section["fingerprint"].upper()
767
853
                                     .replace(" ", ""))
768
854
            if "secret" in section:
769
 
                client["secret"] = section["secret"].decode("base64")
 
855
                client["secret"] = codecs.decode(section["secret"]
 
856
                                                 .encode("utf-8"),
 
857
                                                 "base64")
770
858
            elif "secfile" in section:
771
859
                with open(os.path.expanduser(os.path.expandvars
772
860
                                             (section["secfile"])),
825
913
        self.changedstate = multiprocessing_manager.Condition(
826
914
            multiprocessing_manager.Lock())
827
915
        self.client_structure = [attr
828
 
                                 for attr in self.__dict__.iterkeys()
 
916
                                 for attr in self.__dict__.keys()
829
917
                                 if not attr.startswith("_")]
830
918
        self.client_structure.append("client_structure")
831
919
        
857
945
        if not quiet:
858
946
            logger.info("Disabling client %s", self.name)
859
947
        if getattr(self, "disable_initiator_tag", None) is not None:
860
 
            gobject.source_remove(self.disable_initiator_tag)
 
948
            GLib.source_remove(self.disable_initiator_tag)
861
949
            self.disable_initiator_tag = None
862
950
        self.expires = None
863
951
        if getattr(self, "checker_initiator_tag", None) is not None:
864
 
            gobject.source_remove(self.checker_initiator_tag)
 
952
            GLib.source_remove(self.checker_initiator_tag)
865
953
            self.checker_initiator_tag = None
866
954
        self.stop_checker()
867
955
        self.enabled = False
868
956
        if not quiet:
869
957
            self.send_changedstate()
870
 
        # Do not run this again if called by a gobject.timeout_add
 
958
        # Do not run this again if called by a GLib.timeout_add
871
959
        return False
872
960
    
873
961
    def __del__(self):
877
965
        # Schedule a new checker to be started an 'interval' from now,
878
966
        # and every interval from then on.
879
967
        if self.checker_initiator_tag is not None:
880
 
            gobject.source_remove(self.checker_initiator_tag)
881
 
        self.checker_initiator_tag = gobject.timeout_add(
 
968
            GLib.source_remove(self.checker_initiator_tag)
 
969
        self.checker_initiator_tag = GLib.timeout_add(
882
970
            int(self.interval.total_seconds() * 1000),
883
971
            self.start_checker)
884
972
        # Schedule a disable() when 'timeout' has passed
885
973
        if self.disable_initiator_tag is not None:
886
 
            gobject.source_remove(self.disable_initiator_tag)
887
 
        self.disable_initiator_tag = gobject.timeout_add(
 
974
            GLib.source_remove(self.disable_initiator_tag)
 
975
        self.disable_initiator_tag = GLib.timeout_add(
888
976
            int(self.timeout.total_seconds() * 1000), self.disable)
889
977
        # Also start a new checker *right now*.
890
978
        self.start_checker()
926
1014
        if timeout is None:
927
1015
            timeout = self.timeout
928
1016
        if self.disable_initiator_tag is not None:
929
 
            gobject.source_remove(self.disable_initiator_tag)
 
1017
            GLib.source_remove(self.disable_initiator_tag)
930
1018
            self.disable_initiator_tag = None
931
1019
        if getattr(self, "enabled", False):
932
 
            self.disable_initiator_tag = gobject.timeout_add(
 
1020
            self.disable_initiator_tag = GLib.timeout_add(
933
1021
                int(timeout.total_seconds() * 1000), self.disable)
934
1022
            self.expires = datetime.datetime.utcnow() + timeout
935
1023
    
990
1078
                args = (pipe[1], subprocess.call, command),
991
1079
                kwargs = popen_args)
992
1080
            self.checker.start()
993
 
            self.checker_callback_tag = gobject.io_add_watch(
994
 
                pipe[0].fileno(), gobject.IO_IN,
 
1081
            self.checker_callback_tag = GLib.io_add_watch(
 
1082
                pipe[0].fileno(), GLib.IO_IN,
995
1083
                self.checker_callback, pipe[0], command)
996
 
        # Re-run this periodically if run by gobject.timeout_add
 
1084
        # Re-run this periodically if run by GLib.timeout_add
997
1085
        return True
998
1086
    
999
1087
    def stop_checker(self):
1000
1088
        """Force the checker process, if any, to stop."""
1001
1089
        if self.checker_callback_tag:
1002
 
            gobject.source_remove(self.checker_callback_tag)
 
1090
            GLib.source_remove(self.checker_callback_tag)
1003
1091
            self.checker_callback_tag = None
1004
1092
        if getattr(self, "checker", None) is None:
1005
1093
            return
1479
1567
                interface_names.add(alt_interface)
1480
1568
                # Is this a D-Bus signal?
1481
1569
                if getattr(attribute, "_dbus_is_signal", False):
 
1570
                    # Extract the original non-method undecorated
 
1571
                    # function by black magic
1482
1572
                    if sys.version_info.major == 2:
1483
 
                        # Extract the original non-method undecorated
1484
 
                        # function by black magic
1485
1573
                        nonmethod_func = (dict(
1486
1574
                            zip(attribute.func_code.co_freevars,
1487
1575
                                attribute.__closure__))
1488
1576
                                          ["func"].cell_contents)
1489
1577
                    else:
1490
 
                        nonmethod_func = attribute
 
1578
                        nonmethod_func = (dict(
 
1579
                            zip(attribute.__code__.co_freevars,
 
1580
                                attribute.__closure__))
 
1581
                                          ["func"].cell_contents)
1491
1582
                    # Create a new, but exactly alike, function
1492
1583
                    # object, and decorate it to be a new D-Bus signal
1493
1584
                    # with the alternate D-Bus interface name
1494
 
                    if sys.version_info.major == 2:
1495
 
                        new_function = types.FunctionType(
1496
 
                            nonmethod_func.func_code,
1497
 
                            nonmethod_func.func_globals,
1498
 
                            nonmethod_func.func_name,
1499
 
                            nonmethod_func.func_defaults,
1500
 
                            nonmethod_func.func_closure)
1501
 
                    else:
1502
 
                        new_function = types.FunctionType(
1503
 
                            nonmethod_func.__code__,
1504
 
                            nonmethod_func.__globals__,
1505
 
                            nonmethod_func.__name__,
1506
 
                            nonmethod_func.__defaults__,
1507
 
                            nonmethod_func.__closure__)
 
1585
                    new_function = copy_function(nonmethod_func)
1508
1586
                    new_function = (dbus.service.signal(
1509
1587
                        alt_interface,
1510
1588
                        attribute._dbus_signature)(new_function))
1549
1627
                            alt_interface,
1550
1628
                            attribute._dbus_in_signature,
1551
1629
                            attribute._dbus_out_signature)
1552
 
                        (types.FunctionType(attribute.func_code,
1553
 
                                            attribute.func_globals,
1554
 
                                            attribute.func_name,
1555
 
                                            attribute.func_defaults,
1556
 
                                            attribute.func_closure)))
 
1630
                        (copy_function(attribute)))
1557
1631
                    # Copy annotations, if any
1558
1632
                    try:
1559
1633
                        attr[attrname]._dbus_annotations = dict(
1571
1645
                        attribute._dbus_access,
1572
1646
                        attribute._dbus_get_args_options
1573
1647
                        ["byte_arrays"])
1574
 
                                      (types.FunctionType(
1575
 
                                          attribute.func_code,
1576
 
                                          attribute.func_globals,
1577
 
                                          attribute.func_name,
1578
 
                                          attribute.func_defaults,
1579
 
                                          attribute.func_closure)))
 
1648
                                      (copy_function(attribute)))
1580
1649
                    # Copy annotations, if any
1581
1650
                    try:
1582
1651
                        attr[attrname]._dbus_annotations = dict(
1591
1660
                    # to the class.
1592
1661
                    attr[attrname] = (
1593
1662
                        dbus_interface_annotations(alt_interface)
1594
 
                        (types.FunctionType(attribute.func_code,
1595
 
                                            attribute.func_globals,
1596
 
                                            attribute.func_name,
1597
 
                                            attribute.func_defaults,
1598
 
                                            attribute.func_closure)))
 
1663
                        (copy_function(attribute)))
1599
1664
            if deprecate:
1600
1665
                # Deprecate all alternate interfaces
1601
1666
                iname="_AlternateDBusNames_interface_annotation{}"
1614
1679
            if interface_names:
1615
1680
                # Replace the class with a new subclass of it with
1616
1681
                # methods, signals, etc. as created above.
1617
 
                cls = type(b"{}Alternate".format(cls.__name__),
1618
 
                           (cls, ), attr)
 
1682
                if sys.version_info.major == 2:
 
1683
                    cls = type(b"{}Alternate".format(cls.__name__),
 
1684
                               (cls, ), attr)
 
1685
                else:
 
1686
                    cls = type("{}Alternate".format(cls.__name__),
 
1687
                               (cls, ), attr)
1619
1688
        return cls
1620
1689
    
1621
1690
    return wrapper
1779
1848
    
1780
1849
    def approve(self, value=True):
1781
1850
        self.approved = value
1782
 
        gobject.timeout_add(int(self.approval_duration.total_seconds()
1783
 
                                * 1000), self._reset_approved)
 
1851
        GLib.timeout_add(int(self.approval_duration.total_seconds()
 
1852
                             * 1000), self._reset_approved)
1784
1853
        self.send_changedstate()
1785
1854
    
1786
1855
    ## D-Bus methods, signals & properties
1996
2065
                if (getattr(self, "disable_initiator_tag", None)
1997
2066
                    is None):
1998
2067
                    return
1999
 
                gobject.source_remove(self.disable_initiator_tag)
2000
 
                self.disable_initiator_tag = gobject.timeout_add(
 
2068
                GLib.source_remove(self.disable_initiator_tag)
 
2069
                self.disable_initiator_tag = GLib.timeout_add(
2001
2070
                    int((self.expires - now).total_seconds() * 1000),
2002
2071
                    self.disable)
2003
2072
    
2023
2092
            return
2024
2093
        if self.enabled:
2025
2094
            # Reschedule checker run
2026
 
            gobject.source_remove(self.checker_initiator_tag)
2027
 
            self.checker_initiator_tag = gobject.timeout_add(
 
2095
            GLib.source_remove(self.checker_initiator_tag)
 
2096
            self.checker_initiator_tag = GLib.timeout_add(
2028
2097
                value, self.start_checker)
2029
2098
            self.start_checker() # Start one now, too
2030
2099
    
2122
2191
            priority = self.server.gnutls_priority
2123
2192
            if priority is None:
2124
2193
                priority = "NORMAL"
2125
 
            gnutls.priority_set_direct(session._c_object, priority,
 
2194
            gnutls.priority_set_direct(session._c_object,
 
2195
                                       priority.encode("utf-8"),
2126
2196
                                       None)
2127
2197
            
2128
2198
            # Start communication using the Mandos protocol
2215
2285
                    else:
2216
2286
                        delay -= time2 - time
2217
2287
                
2218
 
                sent_size = 0
2219
 
                while sent_size < len(client.secret):
2220
 
                    try:
2221
 
                        sent = session.send(client.secret[sent_size:])
2222
 
                    except gnutls.Error as error:
2223
 
                        logger.warning("gnutls send failed",
2224
 
                                       exc_info=error)
2225
 
                        return
2226
 
                    logger.debug("Sent: %d, remaining: %d", sent,
2227
 
                                 len(client.secret) - (sent_size
2228
 
                                                       + sent))
2229
 
                    sent_size += sent
 
2288
                try:
 
2289
                    session.send(client.secret)
 
2290
                except gnutls.Error as error:
 
2291
                    logger.warning("gnutls send failed",
 
2292
                                   exc_info = error)
 
2293
                    return
2230
2294
                
2231
2295
                logger.info("Sending secret to %s", client.name)
2232
2296
                # bump the timeout using extended_timeout
2389
2453
        """This overrides the normal server_bind() function
2390
2454
        to bind to an interface if one was specified, and also NOT to
2391
2455
        bind to an address or port if they were not specified."""
 
2456
        global SO_BINDTODEVICE
2392
2457
        if self.interface is not None:
2393
2458
            if SO_BINDTODEVICE is None:
2394
 
                logger.error("SO_BINDTODEVICE does not exist;"
2395
 
                             " cannot bind to interface %s",
2396
 
                             self.interface)
2397
 
            else:
2398
 
                try:
2399
 
                    self.socket.setsockopt(
2400
 
                        socket.SOL_SOCKET, SO_BINDTODEVICE,
2401
 
                        (self.interface + "\0").encode("utf-8"))
2402
 
                except socket.error as error:
2403
 
                    if error.errno == errno.EPERM:
2404
 
                        logger.error("No permission to bind to"
2405
 
                                     " interface %s", self.interface)
2406
 
                    elif error.errno == errno.ENOPROTOOPT:
2407
 
                        logger.error("SO_BINDTODEVICE not available;"
2408
 
                                     " cannot bind to interface %s",
2409
 
                                     self.interface)
2410
 
                    elif error.errno == errno.ENODEV:
2411
 
                        logger.error("Interface %s does not exist,"
2412
 
                                     " cannot bind", self.interface)
2413
 
                    else:
2414
 
                        raise
 
2459
                # Fall back to a hard-coded value which seems to be
 
2460
                # common enough.
 
2461
                logger.warning("SO_BINDTODEVICE not found, trying 25")
 
2462
                SO_BINDTODEVICE = 25
 
2463
            try:
 
2464
                self.socket.setsockopt(
 
2465
                    socket.SOL_SOCKET, SO_BINDTODEVICE,
 
2466
                    (self.interface + "\0").encode("utf-8"))
 
2467
            except socket.error as error:
 
2468
                if error.errno == errno.EPERM:
 
2469
                    logger.error("No permission to bind to"
 
2470
                                 " interface %s", self.interface)
 
2471
                elif error.errno == errno.ENOPROTOOPT:
 
2472
                    logger.error("SO_BINDTODEVICE not available;"
 
2473
                                 " cannot bind to interface %s",
 
2474
                                 self.interface)
 
2475
                elif error.errno == errno.ENODEV:
 
2476
                    logger.error("Interface %s does not exist,"
 
2477
                                 " cannot bind", self.interface)
 
2478
                else:
 
2479
                    raise
2415
2480
        # Only bind(2) the socket if we really need to.
2416
2481
        if self.server_address[0] or self.server_address[1]:
2417
2482
            if not self.server_address[0]:
2440
2505
        gnutls_priority GnuTLS priority string
2441
2506
        use_dbus:       Boolean; to emit D-Bus signals or not
2442
2507
    
2443
 
    Assumes a gobject.MainLoop event loop.
 
2508
    Assumes a GLib.MainLoop event loop.
2444
2509
    """
2445
2510
    
2446
2511
    def __init__(self, server_address, RequestHandlerClass,
2471
2536
    
2472
2537
    def add_pipe(self, parent_pipe, proc):
2473
2538
        # Call "handle_ipc" for both data and EOF events
2474
 
        gobject.io_add_watch(
 
2539
        GLib.io_add_watch(
2475
2540
            parent_pipe.fileno(),
2476
 
            gobject.IO_IN | gobject.IO_HUP,
 
2541
            GLib.IO_IN | GLib.IO_HUP,
2477
2542
            functools.partial(self.handle_ipc,
2478
2543
                              parent_pipe = parent_pipe,
2479
2544
                              proc = proc))
2483
2548
                   proc = None,
2484
2549
                   client_object=None):
2485
2550
        # error, or the other end of multiprocessing.Pipe has closed
2486
 
        if condition & (gobject.IO_ERR | gobject.IO_HUP):
 
2551
        if condition & (GLib.IO_ERR | GLib.IO_HUP):
2487
2552
            # Wait for other process to exit
2488
2553
            proc.join()
2489
2554
            return False
2496
2561
            fpr = request[1]
2497
2562
            address = request[2]
2498
2563
            
2499
 
            for c in self.clients.itervalues():
 
2564
            for c in self.clients.values():
2500
2565
                if c.fingerprint == fpr:
2501
2566
                    client = c
2502
2567
                    break
2510
2575
                parent_pipe.send(False)
2511
2576
                return False
2512
2577
            
2513
 
            gobject.io_add_watch(
 
2578
            GLib.io_add_watch(
2514
2579
                parent_pipe.fileno(),
2515
 
                gobject.IO_IN | gobject.IO_HUP,
 
2580
                GLib.IO_IN | GLib.IO_HUP,
2516
2581
                functools.partial(self.handle_ipc,
2517
2582
                                  parent_pipe = parent_pipe,
2518
2583
                                  proc = proc,
2900
2965
            logger.error("Could not open file %r", pidfilename,
2901
2966
                         exc_info=e)
2902
2967
    
2903
 
    for name in ("_mandos", "mandos", "nobody"):
 
2968
    for name, group in (("_mandos", "_mandos"),
 
2969
                        ("mandos", "mandos"),
 
2970
                        ("nobody", "nogroup")):
2904
2971
        try:
2905
2972
            uid = pwd.getpwnam(name).pw_uid
2906
 
            gid = pwd.getpwnam(name).pw_gid
 
2973
            gid = pwd.getpwnam(group).pw_gid
2907
2974
            break
2908
2975
        except KeyError:
2909
2976
            continue
2913
2980
    try:
2914
2981
        os.setgid(gid)
2915
2982
        os.setuid(uid)
 
2983
        if debug:
 
2984
            logger.debug("Did setuid/setgid to {}:{}".format(uid,
 
2985
                                                             gid))
2916
2986
    except OSError as error:
 
2987
        logger.warning("Failed to setuid/setgid to {}:{}: {}"
 
2988
                       .format(uid, gid, os.strerror(error.errno)))
2917
2989
        if error.errno != errno.EPERM:
2918
2990
            raise
2919
2991
    
2941
3013
        # Close all input and output, do double fork, etc.
2942
3014
        daemon()
2943
3015
    
2944
 
    # multiprocessing will use threads, so before we use gobject we
2945
 
    # need to inform gobject that threads will be used.
2946
 
    gobject.threads_init()
 
3016
    # multiprocessing will use threads, so before we use GLib we need
 
3017
    # to inform GLib that threads will be used.
 
3018
    GLib.threads_init()
2947
3019
    
2948
3020
    global main_loop
2949
3021
    # From the Avahi example code
2950
3022
    DBusGMainLoop(set_as_default=True)
2951
 
    main_loop = gobject.MainLoop()
 
3023
    main_loop = GLib.MainLoop()
2952
3024
    bus = dbus.SystemBus()
2953
3025
    # End of Avahi example code
2954
3026
    if use_dbus:
2998
3070
    if server_settings["restore"]:
2999
3071
        try:
3000
3072
            with open(stored_state_path, "rb") as stored_state:
3001
 
                clients_data, old_client_settings = pickle.load(
3002
 
                    stored_state)
 
3073
                if sys.version_info.major == 2:                
 
3074
                    clients_data, old_client_settings = pickle.load(
 
3075
                        stored_state)
 
3076
                else:
 
3077
                    bytes_clients_data, bytes_old_client_settings = (
 
3078
                        pickle.load(stored_state, encoding = "bytes"))
 
3079
                    ### Fix bytes to strings
 
3080
                    ## clients_data
 
3081
                    # .keys()
 
3082
                    clients_data = { (key.decode("utf-8")
 
3083
                                      if isinstance(key, bytes)
 
3084
                                      else key): value
 
3085
                                     for key, value in
 
3086
                                     bytes_clients_data.items() }
 
3087
                    del bytes_clients_data
 
3088
                    for key in clients_data:
 
3089
                        value = { (k.decode("utf-8")
 
3090
                                   if isinstance(k, bytes) else k): v
 
3091
                                  for k, v in
 
3092
                                  clients_data[key].items() }
 
3093
                        clients_data[key] = value
 
3094
                        # .client_structure
 
3095
                        value["client_structure"] = [
 
3096
                            (s.decode("utf-8")
 
3097
                             if isinstance(s, bytes)
 
3098
                             else s) for s in
 
3099
                            value["client_structure"] ]
 
3100
                        # .name & .host
 
3101
                        for k in ("name", "host"):
 
3102
                            if isinstance(value[k], bytes):
 
3103
                                value[k] = value[k].decode("utf-8")
 
3104
                    ## old_client_settings
 
3105
                    # .keys()
 
3106
                    old_client_settings = {
 
3107
                        (key.decode("utf-8")
 
3108
                         if isinstance(key, bytes)
 
3109
                         else key): value
 
3110
                        for key, value in
 
3111
                        bytes_old_client_settings.items() }
 
3112
                    del bytes_old_client_settings
 
3113
                    # .host
 
3114
                    for value in old_client_settings.values():
 
3115
                        if isinstance(value["host"], bytes):
 
3116
                            value["host"] = (value["host"]
 
3117
                                             .decode("utf-8"))
3003
3118
            os.remove(stored_state_path)
3004
3119
        except IOError as e:
3005
3120
            if e.errno == errno.ENOENT:
3106
3221
        del pidfile
3107
3222
        del pidfilename
3108
3223
    
3109
 
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
3110
 
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
 
3224
    for termsig in (signal.SIGHUP, signal.SIGTERM):
 
3225
        GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig,
 
3226
                             lambda: main_loop.quit() and False)
3111
3227
    
3112
3228
    if use_dbus:
3113
3229
        
3144
3260
            def GetAllClients(self):
3145
3261
                "D-Bus method"
3146
3262
                return dbus.Array(c.dbus_object_path for c in
3147
 
                                  tcp_server.clients.itervalues())
 
3263
                                  tcp_server.clients.values())
3148
3264
            
3149
3265
            @dbus_annotations({"org.freedesktop.DBus.Deprecated":
3150
3266
                               "true"})
3155
3271
                return dbus.Dictionary(
3156
3272
                    { c.dbus_object_path: c.GetAll(
3157
3273
                        "se.recompile.Mandos.Client")
3158
 
                      for c in tcp_server.clients.itervalues() },
 
3274
                      for c in tcp_server.clients.values() },
3159
3275
                    signature="oa{sv}")
3160
3276
            
3161
3277
            @dbus.service.method(_interface, in_signature="o")
3162
3278
            def RemoveClient(self, object_path):
3163
3279
                "D-Bus method"
3164
 
                for c in tcp_server.clients.itervalues():
 
3280
                for c in tcp_server.clients.values():
3165
3281
                    if c.dbus_object_path == object_path:
3166
3282
                        del tcp_server.clients[c.name]
3167
3283
                        c.remove_from_connection()
3212
3328
        
3213
3329
        mandos_dbus_service = MandosDBusService()
3214
3330
    
 
3331
    # Save modules to variables to exempt the modules from being
 
3332
    # unloaded before the function registered with atexit() is run.
 
3333
    mp = multiprocessing
 
3334
    wn = wnull
3215
3335
    def cleanup():
3216
3336
        "Cleanup function; run on exit"
3217
3337
        if zeroconf:
3218
3338
            service.cleanup()
3219
3339
        
3220
 
        multiprocessing.active_children()
3221
 
        wnull.close()
 
3340
        mp.active_children()
 
3341
        wn.close()
3222
3342
        if not (tcp_server.clients or client_settings):
3223
3343
            return
3224
3344
        
3227
3347
        # removed/edited, old secret will thus be unrecovable.
3228
3348
        clients = {}
3229
3349
        with PGPEngine() as pgp:
3230
 
            for client in tcp_server.clients.itervalues():
 
3350
            for client in tcp_server.clients.values():
3231
3351
                key = client_settings[client.name]["secret"]
3232
3352
                client.encrypted_secret = pgp.encrypt(client.secret,
3233
3353
                                                      key)
3257
3377
                    prefix='clients-',
3258
3378
                    dir=os.path.dirname(stored_state_path),
3259
3379
                    delete=False) as stored_state:
3260
 
                pickle.dump((clients, client_settings), stored_state)
 
3380
                pickle.dump((clients, client_settings), stored_state,
 
3381
                            protocol = 2)
3261
3382
                tempname = stored_state.name
3262
3383
            os.rename(tempname, stored_state_path)
3263
3384
        except (IOError, OSError) as e:
3288
3409
    
3289
3410
    atexit.register(cleanup)
3290
3411
    
3291
 
    for client in tcp_server.clients.itervalues():
 
3412
    for client in tcp_server.clients.values():
3292
3413
        if use_dbus:
3293
3414
            # Emit D-Bus signal for adding
3294
3415
            mandos_dbus_service.client_added_signal(client)
3323
3444
                sys.exit(1)
3324
3445
            # End of Avahi example code
3325
3446
        
3326
 
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
3327
 
                             lambda *args, **kwargs:
3328
 
                             (tcp_server.handle_request
3329
 
                              (*args[2:], **kwargs) or True))
 
3447
        GLib.io_add_watch(tcp_server.fileno(), GLib.IO_IN,
 
3448
                          lambda *args, **kwargs:
 
3449
                          (tcp_server.handle_request
 
3450
                           (*args[2:], **kwargs) or True))
3330
3451
        
3331
3452
        logger.debug("Starting main loop")
3332
3453
        main_loop.run()