34
34
from __future__ import (division, absolute_import, print_function,
 
38
 
    from future_builtins import *
 
 
37
from future_builtins import *
 
43
40
    import SocketServer as socketserver
 
 
118
119
        return interface_index
 
121
 
def copy_function(func):
 
122
 
    """Make a copy of a function"""
 
123
 
    if sys.version_info.major == 2:
 
124
 
        return types.FunctionType(func.func_code,
 
130
 
        return types.FunctionType(func.__code__,
 
137
122
def initlogger(debug, level=logging.WARNING):
 
138
123
    """init logger and add loglevel"""
 
 
171
156
            output = subprocess.check_output(["gpgconf"])
 
172
157
            for line in output.splitlines():
 
173
 
                name, text, path = line.split(b":")
 
 
158
                name, text, path = line.split(":")
 
174
159
                if name == "gpg":
 
 
253
238
            raise PGPError(err)
 
254
239
        return decrypted_plaintext
 
256
 
# Pretend that we have an Avahi module
 
258
 
    """This isn't so much a class as it is a module-like namespace.
 
259
 
    It is instantiated once, and simulates having an Avahi module."""
 
260
 
    IF_UNSPEC = -1              # avahi-common/address.h
 
261
 
    PROTO_UNSPEC = -1           # avahi-common/address.h
 
262
 
    PROTO_INET = 0              # avahi-common/address.h
 
263
 
    PROTO_INET6 = 1             # avahi-common/address.h
 
264
 
    DBUS_NAME = "org.freedesktop.Avahi"
 
265
 
    DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"
 
266
 
    DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
 
267
 
    DBUS_PATH_SERVER = "/"
 
268
 
    def string_array_to_txt_array(self, t):
 
269
 
        return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
 
270
 
                           for s in t), signature="ay")
 
271
 
    ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h
 
272
 
    ENTRY_GROUP_COLLISION = 3   # avahi-common/defs.h
 
273
 
    ENTRY_GROUP_FAILURE = 4     # avahi-common/defs.h
 
274
 
    SERVER_INVALID = 0          # avahi-common/defs.h
 
275
 
    SERVER_REGISTERING = 1      # avahi-common/defs.h
 
276
 
    SERVER_RUNNING = 2          # avahi-common/defs.h
 
277
 
    SERVER_COLLISION = 3        # avahi-common/defs.h
 
278
 
    SERVER_FAILURE = 4          # avahi-common/defs.h
 
281
242
class AvahiError(Exception):
 
282
243
    def __init__(self, value, *args, **kwargs):
 
 
487
448
    _library = ctypes.cdll.LoadLibrary(
 
488
449
        ctypes.util.find_library("gnutls"))
 
489
 
    _need_version = b"3.3.0"
 
 
450
    _need_version = "3.3.0"
 
490
451
    def __init__(self):
 
491
452
        # Need to use class name "GnuTLS" here, since this method is
 
492
453
        # called before the assignment to the "gnutls" global variable
 
 
526
487
    openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
 
527
488
    openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
 
528
489
    log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
 
529
 
    credentials_type_t = ctypes.c_int
 
 
490
    credentials_type_t = ctypes.c_int # 
 
530
491
    transport_ptr_t = ctypes.c_void_p
 
531
492
    close_request_t = ctypes.c_int
 
 
754
715
    checker:    subprocess.Popen(); a running checker process used
 
755
716
                                    to see if the client lives.
 
756
717
                                    'None' if no process is running.
 
757
 
    checker_callback_tag: a GLib event source tag, or None
 
 
718
    checker_callback_tag: a gobject event source tag, or None
 
758
719
    checker_command: string; External command which is run to check
 
759
720
                     if client lives.  %() expansions are done at
 
760
721
                     runtime with vars(self) as dict, so that for
 
761
722
                     instance %(name)s can be used in the command.
 
762
 
    checker_initiator_tag: a GLib event source tag, or None
 
 
723
    checker_initiator_tag: a gobject event source tag, or None
 
763
724
    created:    datetime.datetime(); (UTC) object creation
 
764
725
    client_structure: Object describing what attributes a client has
 
765
726
                      and is used for storing the client at exit
 
766
727
    current_checker_command: string; current running checker_command
 
767
 
    disable_initiator_tag: a GLib event source tag, or None
 
 
728
    disable_initiator_tag: a gobject event source tag, or None
 
769
730
    fingerprint: string (40 or 32 hexadecimal digits); used to
 
770
731
                 uniquely identify the client
 
 
833
794
            client["fingerprint"] = (section["fingerprint"].upper()
 
834
795
                                     .replace(" ", ""))
 
835
796
            if "secret" in section:
 
836
 
                client["secret"] = codecs.decode(section["secret"]
 
 
797
                client["secret"] = section["secret"].decode("base64")
 
839
798
            elif "secfile" in section:
 
840
799
                with open(os.path.expanduser(os.path.expandvars
 
841
800
                                             (section["secfile"])),
 
 
894
853
        self.changedstate = multiprocessing_manager.Condition(
 
895
854
            multiprocessing_manager.Lock())
 
896
855
        self.client_structure = [attr
 
897
 
                                 for attr in self.__dict__.keys()
 
 
856
                                 for attr in self.__dict__.iterkeys()
 
898
857
                                 if not attr.startswith("_")]
 
899
858
        self.client_structure.append("client_structure")
 
 
927
886
            logger.info("Disabling client %s", self.name)
 
928
887
        if getattr(self, "disable_initiator_tag", None) is not None:
 
929
 
            GLib.source_remove(self.disable_initiator_tag)
 
 
888
            gobject.source_remove(self.disable_initiator_tag)
 
930
889
            self.disable_initiator_tag = None
 
931
890
        self.expires = None
 
932
891
        if getattr(self, "checker_initiator_tag", None) is not None:
 
933
 
            GLib.source_remove(self.checker_initiator_tag)
 
 
892
            gobject.source_remove(self.checker_initiator_tag)
 
934
893
            self.checker_initiator_tag = None
 
935
894
        self.stop_checker()
 
936
895
        self.enabled = False
 
938
897
            self.send_changedstate()
 
939
 
        # Do not run this again if called by a GLib.timeout_add
 
 
898
        # Do not run this again if called by a gobject.timeout_add
 
942
901
    def __del__(self):
 
 
946
905
        # Schedule a new checker to be started an 'interval' from now,
 
947
906
        # and every interval from then on.
 
948
907
        if self.checker_initiator_tag is not None:
 
949
 
            GLib.source_remove(self.checker_initiator_tag)
 
950
 
        self.checker_initiator_tag = GLib.timeout_add(
 
 
908
            gobject.source_remove(self.checker_initiator_tag)
 
 
909
        self.checker_initiator_tag = gobject.timeout_add(
 
951
910
            int(self.interval.total_seconds() * 1000),
 
952
911
            self.start_checker)
 
953
912
        # Schedule a disable() when 'timeout' has passed
 
954
913
        if self.disable_initiator_tag is not None:
 
955
 
            GLib.source_remove(self.disable_initiator_tag)
 
956
 
        self.disable_initiator_tag = GLib.timeout_add(
 
 
914
            gobject.source_remove(self.disable_initiator_tag)
 
 
915
        self.disable_initiator_tag = gobject.timeout_add(
 
957
916
            int(self.timeout.total_seconds() * 1000), self.disable)
 
958
917
        # Also start a new checker *right now*.
 
959
918
        self.start_checker()
 
 
995
954
        if timeout is None:
 
996
955
            timeout = self.timeout
 
997
956
        if self.disable_initiator_tag is not None:
 
998
 
            GLib.source_remove(self.disable_initiator_tag)
 
 
957
            gobject.source_remove(self.disable_initiator_tag)
 
999
958
            self.disable_initiator_tag = None
 
1000
959
        if getattr(self, "enabled", False):
 
1001
 
            self.disable_initiator_tag = GLib.timeout_add(
 
 
960
            self.disable_initiator_tag = gobject.timeout_add(
 
1002
961
                int(timeout.total_seconds() * 1000), self.disable)
 
1003
962
            self.expires = datetime.datetime.utcnow() + timeout
 
 
1059
1018
                args = (pipe[1], subprocess.call, command),
 
1060
1019
                kwargs = popen_args)
 
1061
1020
            self.checker.start()
 
1062
 
            self.checker_callback_tag = GLib.io_add_watch(
 
1063
 
                pipe[0].fileno(), GLib.IO_IN,
 
 
1021
            self.checker_callback_tag = gobject.io_add_watch(
 
 
1022
                pipe[0].fileno(), gobject.IO_IN,
 
1064
1023
                self.checker_callback, pipe[0], command)
 
1065
 
        # Re-run this periodically if run by GLib.timeout_add
 
 
1024
        # Re-run this periodically if run by gobject.timeout_add
 
1068
1027
    def stop_checker(self):
 
1069
1028
        """Force the checker process, if any, to stop."""
 
1070
1029
        if self.checker_callback_tag:
 
1071
 
            GLib.source_remove(self.checker_callback_tag)
 
 
1030
            gobject.source_remove(self.checker_callback_tag)
 
1072
1031
            self.checker_callback_tag = None
 
1073
1032
        if getattr(self, "checker", None) is None:
 
 
1548
1507
                interface_names.add(alt_interface)
 
1549
1508
                # Is this a D-Bus signal?
 
1550
1509
                if getattr(attribute, "_dbus_is_signal", False):
 
1551
 
                    # Extract the original non-method undecorated
 
1552
 
                    # function by black magic
 
1553
1510
                    if sys.version_info.major == 2:
 
 
1511
                        # Extract the original non-method undecorated
 
 
1512
                        # function by black magic
 
1554
1513
                        nonmethod_func = (dict(
 
1555
1514
                            zip(attribute.func_code.co_freevars,
 
1556
1515
                                attribute.__closure__))
 
1557
1516
                                          ["func"].cell_contents)
 
1559
 
                        nonmethod_func = (dict(
 
1560
 
                            zip(attribute.__code__.co_freevars,
 
1561
 
                                attribute.__closure__))
 
1562
 
                                          ["func"].cell_contents)
 
 
1518
                        nonmethod_func = attribute
 
1563
1519
                    # Create a new, but exactly alike, function
 
1564
1520
                    # object, and decorate it to be a new D-Bus signal
 
1565
1521
                    # with the alternate D-Bus interface name
 
1566
 
                    new_function = copy_function(nonmethod_func)
 
 
1522
                    if sys.version_info.major == 2:
 
 
1523
                        new_function = types.FunctionType(
 
 
1524
                            nonmethod_func.func_code,
 
 
1525
                            nonmethod_func.func_globals,
 
 
1526
                            nonmethod_func.func_name,
 
 
1527
                            nonmethod_func.func_defaults,
 
 
1528
                            nonmethod_func.func_closure)
 
 
1530
                        new_function = types.FunctionType(
 
 
1531
                            nonmethod_func.__code__,
 
 
1532
                            nonmethod_func.__globals__,
 
 
1533
                            nonmethod_func.__name__,
 
 
1534
                            nonmethod_func.__defaults__,
 
 
1535
                            nonmethod_func.__closure__)
 
1567
1536
                    new_function = (dbus.service.signal(
 
1569
1538
                        attribute._dbus_signature)(new_function))
 
 
1609
1578
                            attribute._dbus_in_signature,
 
1610
1579
                            attribute._dbus_out_signature)
 
1611
 
                        (copy_function(attribute)))
 
 
1580
                        (types.FunctionType(attribute.func_code,
 
 
1581
                                            attribute.func_globals,
 
 
1582
                                            attribute.func_name,
 
 
1583
                                            attribute.func_defaults,
 
 
1584
                                            attribute.func_closure)))
 
1612
1585
                    # Copy annotations, if any
 
1614
1587
                        attr[attrname]._dbus_annotations = dict(
 
 
1626
1599
                        attribute._dbus_access,
 
1627
1600
                        attribute._dbus_get_args_options
 
1628
1601
                        ["byte_arrays"])
 
1629
 
                                      (copy_function(attribute)))
 
 
1602
                                      (types.FunctionType(
 
 
1603
                                          attribute.func_code,
 
 
1604
                                          attribute.func_globals,
 
 
1605
                                          attribute.func_name,
 
 
1606
                                          attribute.func_defaults,
 
 
1607
                                          attribute.func_closure)))
 
1630
1608
                    # Copy annotations, if any
 
1632
1610
                        attr[attrname]._dbus_annotations = dict(
 
 
1641
1619
                    # to the class.
 
1642
1620
                    attr[attrname] = (
 
1643
1621
                        dbus_interface_annotations(alt_interface)
 
1644
 
                        (copy_function(attribute)))
 
 
1622
                        (types.FunctionType(attribute.func_code,
 
 
1623
                                            attribute.func_globals,
 
 
1624
                                            attribute.func_name,
 
 
1625
                                            attribute.func_defaults,
 
 
1626
                                            attribute.func_closure)))
 
1646
1628
                # Deprecate all alternate interfaces
 
1647
1629
                iname="_AlternateDBusNames_interface_annotation{}"
 
 
1660
1642
            if interface_names:
 
1661
1643
                # Replace the class with a new subclass of it with
 
1662
1644
                # methods, signals, etc. as created above.
 
1663
 
                if sys.version_info.major == 2:
 
1664
 
                    cls = type(b"{}Alternate".format(cls.__name__),
 
1667
 
                    cls = type("{}Alternate".format(cls.__name__),
 
 
1645
                cls = type(b"{}Alternate".format(cls.__name__),
 
 
1830
1808
    def approve(self, value=True):
 
1831
1809
        self.approved = value
 
1832
 
        GLib.timeout_add(int(self.approval_duration.total_seconds()
 
1833
 
                             * 1000), self._reset_approved)
 
 
1810
        gobject.timeout_add(int(self.approval_duration.total_seconds()
 
 
1811
                                * 1000), self._reset_approved)
 
1834
1812
        self.send_changedstate()
 
1836
1814
    ## D-Bus methods, signals & properties
 
 
2046
2024
                if (getattr(self, "disable_initiator_tag", None)
 
2049
 
                GLib.source_remove(self.disable_initiator_tag)
 
2050
 
                self.disable_initiator_tag = GLib.timeout_add(
 
 
2027
                gobject.source_remove(self.disable_initiator_tag)
 
 
2028
                self.disable_initiator_tag = gobject.timeout_add(
 
2051
2029
                    int((self.expires - now).total_seconds() * 1000),
 
 
2074
2052
        if self.enabled:
 
2075
2053
            # Reschedule checker run
 
2076
 
            GLib.source_remove(self.checker_initiator_tag)
 
2077
 
            self.checker_initiator_tag = GLib.timeout_add(
 
 
2054
            gobject.source_remove(self.checker_initiator_tag)
 
 
2055
            self.checker_initiator_tag = gobject.timeout_add(
 
2078
2056
                value, self.start_checker)
 
2079
2057
            self.start_checker() # Start one now, too
 
 
2484
2462
        gnutls_priority GnuTLS priority string
 
2485
2463
        use_dbus:       Boolean; to emit D-Bus signals or not
 
2487
 
    Assumes a GLib.MainLoop event loop.
 
 
2465
    Assumes a gobject.MainLoop event loop.
 
2490
2468
    def __init__(self, server_address, RequestHandlerClass,
 
 
2516
2494
    def add_pipe(self, parent_pipe, proc):
 
2517
2495
        # Call "handle_ipc" for both data and EOF events
 
 
2496
        gobject.io_add_watch(
 
2519
2497
            parent_pipe.fileno(),
 
2520
 
            GLib.IO_IN | GLib.IO_HUP,
 
 
2498
            gobject.IO_IN | gobject.IO_HUP,
 
2521
2499
            functools.partial(self.handle_ipc,
 
2522
2500
                              parent_pipe = parent_pipe,
 
 
2528
2506
                   client_object=None):
 
2529
2507
        # error, or the other end of multiprocessing.Pipe has closed
 
2530
 
        if condition & (GLib.IO_ERR | GLib.IO_HUP):
 
 
2508
        if condition & (gobject.IO_ERR | gobject.IO_HUP):
 
2531
2509
            # Wait for other process to exit
 
 
2554
2532
                parent_pipe.send(False)
 
 
2535
            gobject.io_add_watch(
 
2558
2536
                parent_pipe.fileno(),
 
2559
 
                GLib.IO_IN | GLib.IO_HUP,
 
 
2537
                gobject.IO_IN | gobject.IO_HUP,
 
2560
2538
                functools.partial(self.handle_ipc,
 
2561
2539
                                  parent_pipe = parent_pipe,
 
 
2944
2922
            logger.error("Could not open file %r", pidfilename,
 
2947
 
    for name, group in (("_mandos", "_mandos"),
 
2948
 
                        ("mandos", "mandos"),
 
2949
 
                        ("nobody", "nogroup")):
 
 
2925
    for name in ("_mandos", "mandos", "nobody"):
 
2951
2927
            uid = pwd.getpwnam(name).pw_uid
 
2952
 
            gid = pwd.getpwnam(group).pw_gid
 
 
2928
            gid = pwd.getpwnam(name).pw_gid
 
2954
2930
        except KeyError:
 
 
2963
 
            logger.debug("Did setuid/setgid to {}:{}".format(uid,
 
2965
2938
    except OSError as error:
 
2966
 
        logger.warning("Failed to setuid/setgid to {}:{}: {}"
 
2967
 
                       .format(uid, gid, os.strerror(error.errno)))
 
2968
2939
        if error.errno != errno.EPERM:
 
 
2992
2963
        # Close all input and output, do double fork, etc.
 
2995
 
    # multiprocessing will use threads, so before we use GLib we need
 
2996
 
    # to inform GLib that threads will be used.
 
 
2966
    # multiprocessing will use threads, so before we use gobject we
 
 
2967
    # need to inform gobject that threads will be used.
 
 
2968
    gobject.threads_init()
 
2999
2970
    global main_loop
 
3000
2971
    # From the Avahi example code
 
3001
2972
    DBusGMainLoop(set_as_default=True)
 
3002
 
    main_loop = GLib.MainLoop()
 
 
2973
    main_loop = gobject.MainLoop()
 
3003
2974
    bus = dbus.SystemBus()
 
3004
2975
    # End of Avahi example code
 
 
3049
3020
    if server_settings["restore"]:
 
3051
3022
            with open(stored_state_path, "rb") as stored_state:
 
3052
 
                if sys.version_info.major == 2:                
 
3053
 
                    clients_data, old_client_settings = pickle.load(
 
3056
 
                    bytes_clients_data, bytes_old_client_settings = (
 
3057
 
                        pickle.load(stored_state, encoding = "bytes"))
 
3058
 
                    ### Fix bytes to strings
 
3061
 
                    clients_data = { (key.decode("utf-8")
 
3062
 
                                      if isinstance(key, bytes)
 
3065
 
                                     bytes_clients_data.items() }
 
3066
 
                    del bytes_clients_data
 
3067
 
                    for key in clients_data:
 
3068
 
                        value = { (k.decode("utf-8")
 
3069
 
                                   if isinstance(k, bytes) else k): v
 
3071
 
                                  clients_data[key].items() }
 
3072
 
                        clients_data[key] = value
 
3074
 
                        value["client_structure"] = [
 
3076
 
                             if isinstance(s, bytes)
 
3078
 
                            value["client_structure"] ]
 
3080
 
                        for k in ("name", "host"):
 
3081
 
                            if isinstance(value[k], bytes):
 
3082
 
                                value[k] = value[k].decode("utf-8")
 
3083
 
                    ## old_client_settings
 
3085
 
                    old_client_settings = {
 
3086
 
                        (key.decode("utf-8")
 
3087
 
                         if isinstance(key, bytes)
 
3090
 
                        bytes_old_client_settings.items() }
 
3091
 
                    del bytes_old_client_settings
 
3093
 
                    for value in old_client_settings.values():
 
3094
 
                        if isinstance(value["host"], bytes):
 
3095
 
                            value["host"] = (value["host"]
 
 
3023
                clients_data, old_client_settings = pickle.load(
 
3097
3025
            os.remove(stored_state_path)
 
3098
3026
        except IOError as e:
 
3099
3027
            if e.errno == errno.ENOENT:
 
 
3201
3129
        del pidfilename
 
3203
 
    for termsig in (signal.SIGHUP, signal.SIGTERM):
 
3204
 
        GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig,
 
3205
 
                             lambda: main_loop.quit() and False)
 
 
3131
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
 
 
3132
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
 
 
3250
3177
                return dbus.Dictionary(
 
3251
3178
                    { c.dbus_object_path: c.GetAll(
 
3252
3179
                        "se.recompile.Mandos.Client")
 
3253
 
                      for c in tcp_server.clients.values() },
 
 
3180
                      for c in tcp_server.clients.itervalues() },
 
3254
3181
                    signature="oa{sv}")
 
3256
3183
            @dbus.service.method(_interface, in_signature="o")
 
3257
3184
            def RemoveClient(self, object_path):
 
3259
 
                for c in tcp_server.clients.values():
 
 
3186
                for c in tcp_server.clients.itervalues():
 
3260
3187
                    if c.dbus_object_path == object_path:
 
3261
3188
                        del tcp_server.clients[c.name]
 
3262
3189
                        c.remove_from_connection()
 
 
3322
3249
        # removed/edited, old secret will thus be unrecovable.
 
3324
3251
        with PGPEngine() as pgp:
 
3325
 
            for client in tcp_server.clients.values():
 
 
3252
            for client in tcp_server.clients.itervalues():
 
3326
3253
                key = client_settings[client.name]["secret"]
 
3327
3254
                client.encrypted_secret = pgp.encrypt(client.secret,
 
 
3352
3279
                    prefix='clients-',
 
3353
3280
                    dir=os.path.dirname(stored_state_path),
 
3354
3281
                    delete=False) as stored_state:
 
3355
 
                pickle.dump((clients, client_settings), stored_state,
 
 
3282
                pickle.dump((clients, client_settings), stored_state)
 
3357
3283
                tempname = stored_state.name
 
3358
3284
            os.rename(tempname, stored_state_path)
 
3359
3285
        except (IOError, OSError) as e:
 
 
3420
3346
            # End of Avahi example code
 
3422
 
        GLib.io_add_watch(tcp_server.fileno(), GLib.IO_IN,
 
3423
 
                          lambda *args, **kwargs:
 
3424
 
                          (tcp_server.handle_request
 
3425
 
                           (*args[2:], **kwargs) or True))
 
 
3348
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
 
 
3349
                             lambda *args, **kwargs:
 
 
3350
                             (tcp_server.handle_request
 
 
3351
                              (*args[2:], **kwargs) or True))
 
3427
3353
        logger.debug("Starting main loop")
 
3428
3354
        main_loop.run()