/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-06-23 20:10:40 UTC
  • mto: This revision was merged to the branch mainline in revision 861.
  • Revision ID: teddy@recompile.se-20160623201040-33k2tpdbnxjqfjcp
Tags: version-1.7.10-1
* Makefile (version): Change to 1.7.10.
* NEWS (Version 1.7.10): Add new entry.
* debian/changelog (1.7.10-1): - '' -

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
476
545
    openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
477
546
    openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
478
547
    log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
479
 
    credentials_type_t = ctypes.c_int # 
 
548
    credentials_type_t = ctypes.c_int
480
549
    transport_ptr_t = ctypes.c_void_p
481
550
    close_request_t = ctypes.c_int
482
551
    
704
773
    checker:    subprocess.Popen(); a running checker process used
705
774
                                    to see if the client lives.
706
775
                                    'None' if no process is running.
707
 
    checker_callback_tag: a gobject event source tag, or None
 
776
    checker_callback_tag: a GLib event source tag, or None
708
777
    checker_command: string; External command which is run to check
709
778
                     if client lives.  %() expansions are done at
710
779
                     runtime with vars(self) as dict, so that for
711
780
                     instance %(name)s can be used in the command.
712
 
    checker_initiator_tag: a gobject event source tag, or None
 
781
    checker_initiator_tag: a GLib event source tag, or None
713
782
    created:    datetime.datetime(); (UTC) object creation
714
783
    client_structure: Object describing what attributes a client has
715
784
                      and is used for storing the client at exit
716
785
    current_checker_command: string; current running checker_command
717
 
    disable_initiator_tag: a gobject event source tag, or None
 
786
    disable_initiator_tag: a GLib event source tag, or None
718
787
    enabled:    bool()
719
788
    fingerprint: string (40 or 32 hexadecimal digits); used to
720
789
                 uniquely identify the client
783
852
            client["fingerprint"] = (section["fingerprint"].upper()
784
853
                                     .replace(" ", ""))
785
854
            if "secret" in section:
786
 
                client["secret"] = section["secret"].decode("base64")
 
855
                client["secret"] = codecs.decode(section["secret"]
 
856
                                                 .encode("utf-8"),
 
857
                                                 "base64")
787
858
            elif "secfile" in section:
788
859
                with open(os.path.expanduser(os.path.expandvars
789
860
                                             (section["secfile"])),
842
913
        self.changedstate = multiprocessing_manager.Condition(
843
914
            multiprocessing_manager.Lock())
844
915
        self.client_structure = [attr
845
 
                                 for attr in self.__dict__.iterkeys()
 
916
                                 for attr in self.__dict__.keys()
846
917
                                 if not attr.startswith("_")]
847
918
        self.client_structure.append("client_structure")
848
919
        
874
945
        if not quiet:
875
946
            logger.info("Disabling client %s", self.name)
876
947
        if getattr(self, "disable_initiator_tag", None) is not None:
877
 
            gobject.source_remove(self.disable_initiator_tag)
 
948
            GLib.source_remove(self.disable_initiator_tag)
878
949
            self.disable_initiator_tag = None
879
950
        self.expires = None
880
951
        if getattr(self, "checker_initiator_tag", None) is not None:
881
 
            gobject.source_remove(self.checker_initiator_tag)
 
952
            GLib.source_remove(self.checker_initiator_tag)
882
953
            self.checker_initiator_tag = None
883
954
        self.stop_checker()
884
955
        self.enabled = False
885
956
        if not quiet:
886
957
            self.send_changedstate()
887
 
        # 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
888
959
        return False
889
960
    
890
961
    def __del__(self):
894
965
        # Schedule a new checker to be started an 'interval' from now,
895
966
        # and every interval from then on.
896
967
        if self.checker_initiator_tag is not None:
897
 
            gobject.source_remove(self.checker_initiator_tag)
898
 
        self.checker_initiator_tag = gobject.timeout_add(
 
968
            GLib.source_remove(self.checker_initiator_tag)
 
969
        self.checker_initiator_tag = GLib.timeout_add(
899
970
            int(self.interval.total_seconds() * 1000),
900
971
            self.start_checker)
901
972
        # Schedule a disable() when 'timeout' has passed
902
973
        if self.disable_initiator_tag is not None:
903
 
            gobject.source_remove(self.disable_initiator_tag)
904
 
        self.disable_initiator_tag = gobject.timeout_add(
 
974
            GLib.source_remove(self.disable_initiator_tag)
 
975
        self.disable_initiator_tag = GLib.timeout_add(
905
976
            int(self.timeout.total_seconds() * 1000), self.disable)
906
977
        # Also start a new checker *right now*.
907
978
        self.start_checker()
943
1014
        if timeout is None:
944
1015
            timeout = self.timeout
945
1016
        if self.disable_initiator_tag is not None:
946
 
            gobject.source_remove(self.disable_initiator_tag)
 
1017
            GLib.source_remove(self.disable_initiator_tag)
947
1018
            self.disable_initiator_tag = None
948
1019
        if getattr(self, "enabled", False):
949
 
            self.disable_initiator_tag = gobject.timeout_add(
 
1020
            self.disable_initiator_tag = GLib.timeout_add(
950
1021
                int(timeout.total_seconds() * 1000), self.disable)
951
1022
            self.expires = datetime.datetime.utcnow() + timeout
952
1023
    
1007
1078
                args = (pipe[1], subprocess.call, command),
1008
1079
                kwargs = popen_args)
1009
1080
            self.checker.start()
1010
 
            self.checker_callback_tag = gobject.io_add_watch(
1011
 
                pipe[0].fileno(), gobject.IO_IN,
 
1081
            self.checker_callback_tag = GLib.io_add_watch(
 
1082
                pipe[0].fileno(), GLib.IO_IN,
1012
1083
                self.checker_callback, pipe[0], command)
1013
 
        # Re-run this periodically if run by gobject.timeout_add
 
1084
        # Re-run this periodically if run by GLib.timeout_add
1014
1085
        return True
1015
1086
    
1016
1087
    def stop_checker(self):
1017
1088
        """Force the checker process, if any, to stop."""
1018
1089
        if self.checker_callback_tag:
1019
 
            gobject.source_remove(self.checker_callback_tag)
 
1090
            GLib.source_remove(self.checker_callback_tag)
1020
1091
            self.checker_callback_tag = None
1021
1092
        if getattr(self, "checker", None) is None:
1022
1093
            return
1496
1567
                interface_names.add(alt_interface)
1497
1568
                # Is this a D-Bus signal?
1498
1569
                if getattr(attribute, "_dbus_is_signal", False):
 
1570
                    # Extract the original non-method undecorated
 
1571
                    # function by black magic
1499
1572
                    if sys.version_info.major == 2:
1500
 
                        # Extract the original non-method undecorated
1501
 
                        # function by black magic
1502
1573
                        nonmethod_func = (dict(
1503
1574
                            zip(attribute.func_code.co_freevars,
1504
1575
                                attribute.__closure__))
1505
1576
                                          ["func"].cell_contents)
1506
1577
                    else:
1507
 
                        nonmethod_func = attribute
 
1578
                        nonmethod_func = (dict(
 
1579
                            zip(attribute.__code__.co_freevars,
 
1580
                                attribute.__closure__))
 
1581
                                          ["func"].cell_contents)
1508
1582
                    # Create a new, but exactly alike, function
1509
1583
                    # object, and decorate it to be a new D-Bus signal
1510
1584
                    # with the alternate D-Bus interface name
1511
 
                    if sys.version_info.major == 2:
1512
 
                        new_function = types.FunctionType(
1513
 
                            nonmethod_func.func_code,
1514
 
                            nonmethod_func.func_globals,
1515
 
                            nonmethod_func.func_name,
1516
 
                            nonmethod_func.func_defaults,
1517
 
                            nonmethod_func.func_closure)
1518
 
                    else:
1519
 
                        new_function = types.FunctionType(
1520
 
                            nonmethod_func.__code__,
1521
 
                            nonmethod_func.__globals__,
1522
 
                            nonmethod_func.__name__,
1523
 
                            nonmethod_func.__defaults__,
1524
 
                            nonmethod_func.__closure__)
 
1585
                    new_function = copy_function(nonmethod_func)
1525
1586
                    new_function = (dbus.service.signal(
1526
1587
                        alt_interface,
1527
1588
                        attribute._dbus_signature)(new_function))
1566
1627
                            alt_interface,
1567
1628
                            attribute._dbus_in_signature,
1568
1629
                            attribute._dbus_out_signature)
1569
 
                        (types.FunctionType(attribute.func_code,
1570
 
                                            attribute.func_globals,
1571
 
                                            attribute.func_name,
1572
 
                                            attribute.func_defaults,
1573
 
                                            attribute.func_closure)))
 
1630
                        (copy_function(attribute)))
1574
1631
                    # Copy annotations, if any
1575
1632
                    try:
1576
1633
                        attr[attrname]._dbus_annotations = dict(
1588
1645
                        attribute._dbus_access,
1589
1646
                        attribute._dbus_get_args_options
1590
1647
                        ["byte_arrays"])
1591
 
                                      (types.FunctionType(
1592
 
                                          attribute.func_code,
1593
 
                                          attribute.func_globals,
1594
 
                                          attribute.func_name,
1595
 
                                          attribute.func_defaults,
1596
 
                                          attribute.func_closure)))
 
1648
                                      (copy_function(attribute)))
1597
1649
                    # Copy annotations, if any
1598
1650
                    try:
1599
1651
                        attr[attrname]._dbus_annotations = dict(
1608
1660
                    # to the class.
1609
1661
                    attr[attrname] = (
1610
1662
                        dbus_interface_annotations(alt_interface)
1611
 
                        (types.FunctionType(attribute.func_code,
1612
 
                                            attribute.func_globals,
1613
 
                                            attribute.func_name,
1614
 
                                            attribute.func_defaults,
1615
 
                                            attribute.func_closure)))
 
1663
                        (copy_function(attribute)))
1616
1664
            if deprecate:
1617
1665
                # Deprecate all alternate interfaces
1618
1666
                iname="_AlternateDBusNames_interface_annotation{}"
1631
1679
            if interface_names:
1632
1680
                # Replace the class with a new subclass of it with
1633
1681
                # methods, signals, etc. as created above.
1634
 
                cls = type(b"{}Alternate".format(cls.__name__),
1635
 
                           (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)
1636
1688
        return cls
1637
1689
    
1638
1690
    return wrapper
1796
1848
    
1797
1849
    def approve(self, value=True):
1798
1850
        self.approved = value
1799
 
        gobject.timeout_add(int(self.approval_duration.total_seconds()
1800
 
                                * 1000), self._reset_approved)
 
1851
        GLib.timeout_add(int(self.approval_duration.total_seconds()
 
1852
                             * 1000), self._reset_approved)
1801
1853
        self.send_changedstate()
1802
1854
    
1803
1855
    ## D-Bus methods, signals & properties
2013
2065
                if (getattr(self, "disable_initiator_tag", None)
2014
2066
                    is None):
2015
2067
                    return
2016
 
                gobject.source_remove(self.disable_initiator_tag)
2017
 
                self.disable_initiator_tag = gobject.timeout_add(
 
2068
                GLib.source_remove(self.disable_initiator_tag)
 
2069
                self.disable_initiator_tag = GLib.timeout_add(
2018
2070
                    int((self.expires - now).total_seconds() * 1000),
2019
2071
                    self.disable)
2020
2072
    
2040
2092
            return
2041
2093
        if self.enabled:
2042
2094
            # Reschedule checker run
2043
 
            gobject.source_remove(self.checker_initiator_tag)
2044
 
            self.checker_initiator_tag = gobject.timeout_add(
 
2095
            GLib.source_remove(self.checker_initiator_tag)
 
2096
            self.checker_initiator_tag = GLib.timeout_add(
2045
2097
                value, self.start_checker)
2046
2098
            self.start_checker() # Start one now, too
2047
2099
    
2139
2191
            priority = self.server.gnutls_priority
2140
2192
            if priority is None:
2141
2193
                priority = "NORMAL"
2142
 
            gnutls.priority_set_direct(session._c_object, priority,
 
2194
            gnutls.priority_set_direct(session._c_object,
 
2195
                                       priority.encode("utf-8"),
2143
2196
                                       None)
2144
2197
            
2145
2198
            # Start communication using the Mandos protocol
2400
2453
        """This overrides the normal server_bind() function
2401
2454
        to bind to an interface if one was specified, and also NOT to
2402
2455
        bind to an address or port if they were not specified."""
 
2456
        global SO_BINDTODEVICE
2403
2457
        if self.interface is not None:
2404
2458
            if SO_BINDTODEVICE is None:
2405
 
                logger.error("SO_BINDTODEVICE does not exist;"
2406
 
                             " cannot bind to interface %s",
2407
 
                             self.interface)
2408
 
            else:
2409
 
                try:
2410
 
                    self.socket.setsockopt(
2411
 
                        socket.SOL_SOCKET, SO_BINDTODEVICE,
2412
 
                        (self.interface + "\0").encode("utf-8"))
2413
 
                except socket.error as error:
2414
 
                    if error.errno == errno.EPERM:
2415
 
                        logger.error("No permission to bind to"
2416
 
                                     " interface %s", self.interface)
2417
 
                    elif error.errno == errno.ENOPROTOOPT:
2418
 
                        logger.error("SO_BINDTODEVICE not available;"
2419
 
                                     " cannot bind to interface %s",
2420
 
                                     self.interface)
2421
 
                    elif error.errno == errno.ENODEV:
2422
 
                        logger.error("Interface %s does not exist,"
2423
 
                                     " cannot bind", self.interface)
2424
 
                    else:
2425
 
                        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
2426
2480
        # Only bind(2) the socket if we really need to.
2427
2481
        if self.server_address[0] or self.server_address[1]:
2428
2482
            if not self.server_address[0]:
2451
2505
        gnutls_priority GnuTLS priority string
2452
2506
        use_dbus:       Boolean; to emit D-Bus signals or not
2453
2507
    
2454
 
    Assumes a gobject.MainLoop event loop.
 
2508
    Assumes a GLib.MainLoop event loop.
2455
2509
    """
2456
2510
    
2457
2511
    def __init__(self, server_address, RequestHandlerClass,
2482
2536
    
2483
2537
    def add_pipe(self, parent_pipe, proc):
2484
2538
        # Call "handle_ipc" for both data and EOF events
2485
 
        gobject.io_add_watch(
 
2539
        GLib.io_add_watch(
2486
2540
            parent_pipe.fileno(),
2487
 
            gobject.IO_IN | gobject.IO_HUP,
 
2541
            GLib.IO_IN | GLib.IO_HUP,
2488
2542
            functools.partial(self.handle_ipc,
2489
2543
                              parent_pipe = parent_pipe,
2490
2544
                              proc = proc))
2494
2548
                   proc = None,
2495
2549
                   client_object=None):
2496
2550
        # error, or the other end of multiprocessing.Pipe has closed
2497
 
        if condition & (gobject.IO_ERR | gobject.IO_HUP):
 
2551
        if condition & (GLib.IO_ERR | GLib.IO_HUP):
2498
2552
            # Wait for other process to exit
2499
2553
            proc.join()
2500
2554
            return False
2507
2561
            fpr = request[1]
2508
2562
            address = request[2]
2509
2563
            
2510
 
            for c in self.clients.itervalues():
 
2564
            for c in self.clients.values():
2511
2565
                if c.fingerprint == fpr:
2512
2566
                    client = c
2513
2567
                    break
2521
2575
                parent_pipe.send(False)
2522
2576
                return False
2523
2577
            
2524
 
            gobject.io_add_watch(
 
2578
            GLib.io_add_watch(
2525
2579
                parent_pipe.fileno(),
2526
 
                gobject.IO_IN | gobject.IO_HUP,
 
2580
                GLib.IO_IN | GLib.IO_HUP,
2527
2581
                functools.partial(self.handle_ipc,
2528
2582
                                  parent_pipe = parent_pipe,
2529
2583
                                  proc = proc,
2911
2965
            logger.error("Could not open file %r", pidfilename,
2912
2966
                         exc_info=e)
2913
2967
    
2914
 
    for name in ("_mandos", "mandos", "nobody"):
 
2968
    for name, group in (("_mandos", "_mandos"),
 
2969
                        ("mandos", "mandos"),
 
2970
                        ("nobody", "nogroup")):
2915
2971
        try:
2916
2972
            uid = pwd.getpwnam(name).pw_uid
2917
 
            gid = pwd.getpwnam(name).pw_gid
 
2973
            gid = pwd.getpwnam(group).pw_gid
2918
2974
            break
2919
2975
        except KeyError:
2920
2976
            continue
2924
2980
    try:
2925
2981
        os.setgid(gid)
2926
2982
        os.setuid(uid)
 
2983
        if debug:
 
2984
            logger.debug("Did setuid/setgid to {}:{}".format(uid,
 
2985
                                                             gid))
2927
2986
    except OSError as error:
 
2987
        logger.warning("Failed to setuid/setgid to {}:{}: {}"
 
2988
                       .format(uid, gid, os.strerror(error.errno)))
2928
2989
        if error.errno != errno.EPERM:
2929
2990
            raise
2930
2991
    
2952
3013
        # Close all input and output, do double fork, etc.
2953
3014
        daemon()
2954
3015
    
2955
 
    # multiprocessing will use threads, so before we use gobject we
2956
 
    # need to inform gobject that threads will be used.
2957
 
    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()
2958
3019
    
2959
3020
    global main_loop
2960
3021
    # From the Avahi example code
2961
3022
    DBusGMainLoop(set_as_default=True)
2962
 
    main_loop = gobject.MainLoop()
 
3023
    main_loop = GLib.MainLoop()
2963
3024
    bus = dbus.SystemBus()
2964
3025
    # End of Avahi example code
2965
3026
    if use_dbus:
3009
3070
    if server_settings["restore"]:
3010
3071
        try:
3011
3072
            with open(stored_state_path, "rb") as stored_state:
3012
 
                clients_data, old_client_settings = pickle.load(
3013
 
                    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"))
3014
3118
            os.remove(stored_state_path)
3015
3119
        except IOError as e:
3016
3120
            if e.errno == errno.ENOENT:
3117
3221
        del pidfile
3118
3222
        del pidfilename
3119
3223
    
3120
 
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
3121
 
    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)
3122
3227
    
3123
3228
    if use_dbus:
3124
3229
        
3155
3260
            def GetAllClients(self):
3156
3261
                "D-Bus method"
3157
3262
                return dbus.Array(c.dbus_object_path for c in
3158
 
                                  tcp_server.clients.itervalues())
 
3263
                                  tcp_server.clients.values())
3159
3264
            
3160
3265
            @dbus_annotations({"org.freedesktop.DBus.Deprecated":
3161
3266
                               "true"})
3166
3271
                return dbus.Dictionary(
3167
3272
                    { c.dbus_object_path: c.GetAll(
3168
3273
                        "se.recompile.Mandos.Client")
3169
 
                      for c in tcp_server.clients.itervalues() },
 
3274
                      for c in tcp_server.clients.values() },
3170
3275
                    signature="oa{sv}")
3171
3276
            
3172
3277
            @dbus.service.method(_interface, in_signature="o")
3173
3278
            def RemoveClient(self, object_path):
3174
3279
                "D-Bus method"
3175
 
                for c in tcp_server.clients.itervalues():
 
3280
                for c in tcp_server.clients.values():
3176
3281
                    if c.dbus_object_path == object_path:
3177
3282
                        del tcp_server.clients[c.name]
3178
3283
                        c.remove_from_connection()
3223
3328
        
3224
3329
        mandos_dbus_service = MandosDBusService()
3225
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
3226
3335
    def cleanup():
3227
3336
        "Cleanup function; run on exit"
3228
3337
        if zeroconf:
3229
3338
            service.cleanup()
3230
3339
        
3231
 
        multiprocessing.active_children()
3232
 
        wnull.close()
 
3340
        mp.active_children()
 
3341
        wn.close()
3233
3342
        if not (tcp_server.clients or client_settings):
3234
3343
            return
3235
3344
        
3238
3347
        # removed/edited, old secret will thus be unrecovable.
3239
3348
        clients = {}
3240
3349
        with PGPEngine() as pgp:
3241
 
            for client in tcp_server.clients.itervalues():
 
3350
            for client in tcp_server.clients.values():
3242
3351
                key = client_settings[client.name]["secret"]
3243
3352
                client.encrypted_secret = pgp.encrypt(client.secret,
3244
3353
                                                      key)
3268
3377
                    prefix='clients-',
3269
3378
                    dir=os.path.dirname(stored_state_path),
3270
3379
                    delete=False) as stored_state:
3271
 
                pickle.dump((clients, client_settings), stored_state)
 
3380
                pickle.dump((clients, client_settings), stored_state,
 
3381
                            protocol = 2)
3272
3382
                tempname = stored_state.name
3273
3383
            os.rename(tempname, stored_state_path)
3274
3384
        except (IOError, OSError) as e:
3299
3409
    
3300
3410
    atexit.register(cleanup)
3301
3411
    
3302
 
    for client in tcp_server.clients.itervalues():
 
3412
    for client in tcp_server.clients.values():
3303
3413
        if use_dbus:
3304
3414
            # Emit D-Bus signal for adding
3305
3415
            mandos_dbus_service.client_added_signal(client)
3334
3444
                sys.exit(1)
3335
3445
            # End of Avahi example code
3336
3446
        
3337
 
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
3338
 
                             lambda *args, **kwargs:
3339
 
                             (tcp_server.handle_request
3340
 
                              (*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))
3341
3451
        
3342
3452
        logger.debug("Starting main loop")
3343
3453
        main_loop.run()