=== modified file 'INSTALL' --- INSTALL 2016-02-28 20:26:27 +0000 +++ INSTALL 2016-03-13 00:37:02 +0000 @@ -42,7 +42,7 @@ + Avahi 0.6.16 http://www.avahi.org/ + Python 2.7 https://www.python.org/ + dbus-python 0.82.4 http://dbus.freedesktop.org/doc/dbus-python/ - + PyGObject 2.14.2 https://developer.gnome.org/pygobject/ + + PyGObject 3.7.1 https://wiki.gnome.org/Projects/PyGObject + pkg-config http://www.freedesktop.org/wiki/Software/pkg-config/ + Urwid 1.0.1 http://urwid.org/ (Only needed by the "mandos-monitor" tool.) @@ -52,8 +52,8 @@ + ssh-keyscan from OpenSSH http://www.openssh.com/ Package names: - avahi-daemon python python-avahi python-dbus python-gobject - python-urwid pkg-config fping ssh-client + avahi-daemon python python-dbus python-gi python-urwid pkg-config + fping ssh-client *** Mandos Client + GNU C Library 2.16 https://gnu.org/software/libc/ === modified file 'debian/control' --- debian/control 2016-03-05 21:05:11 +0000 +++ debian/control 2016-03-12 23:42:38 +0000 @@ -9,7 +9,7 @@ | gnutls-dev (>= 3.3.0), xsltproc, pkg-config, libnl-route-3-dev Build-Depends-Indep: systemd, python (>= 2.7), python (<< 3), - python-dbus, python-avahi, python-gi | python-gobject + python-dbus, python-gi Standards-Version: 3.9.7 Vcs-Bzr: http://ftp.recompile.se/pub/mandos/trunk Vcs-Browser: http://bzr.recompile.se/loggerhead/mandos/trunk/files @@ -19,8 +19,8 @@ Architecture: all Depends: ${misc:Depends}, python (>= 2.7), python (<< 3), libgnutls28-dev (>= 3.3.0) | libgnutls30 (>= 3.3.0), - python-dbus, python-avahi, python-gi | python-gobject, - avahi-daemon, adduser, python-urwid, gnupg + python-dbus, python-gi, avahi-daemon, adduser, python-urwid, + gnupg Recommends: ssh-client | fping Description: server giving encrypted passwords to Mandos clients This is the server part of the Mandos system, which allows === modified file 'init.d-mandos' --- init.d-mandos 2016-03-08 00:03:43 +0000 +++ init.d-mandos 2016-03-13 00:37:02 +0000 @@ -77,9 +77,7 @@ # 1 if daemon was already stopped # 2 if daemon could not be stopped # other if a failure occurred - start-stop-daemon --stop --quiet --pidfile $PIDFILE --name $NAME - mandos-ctl >/dev/null 2>&1 - start-stop-daemon --stop --quiet --retry=30/KILL/5 --pidfile $PIDFILE --name $NAME + start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME RETVAL="$?" [ "$RETVAL" = 2 ] && return 2 # Wait for children to finish too if this is a daemon that forks === modified file 'mandos' --- mandos 2016-03-08 00:14:50 +0000 +++ mandos 2016-03-13 00:37:02 +0000 @@ -34,7 +34,10 @@ from __future__ import (division, absolute_import, print_function, unicode_literals) -from future_builtins import * +try: + from future_builtins import * +except ImportError: + pass try: import SocketServer as socketserver @@ -76,11 +79,7 @@ import dbus import dbus.service -try: - from gi.repository import GObject -except ImportError: - import gobject as GObject -import avahi +from gi.repository import GLib from dbus.mainloop.glib import DBusGMainLoop import ctypes import ctypes.util @@ -119,6 +118,22 @@ return interface_index +def copy_function(func): + """Make a copy of a function""" + if sys.version_info.major == 2: + return types.FunctionType(func.func_code, + func.func_globals, + func.func_name, + func.func_defaults, + func.func_closure) + else: + return types.FunctionType(func.__code__, + func.__globals__, + func.__name__, + func.__defaults__, + func.__closure__) + + def initlogger(debug, level=logging.WARNING): """init logger and add loglevel""" @@ -155,7 +170,7 @@ try: output = subprocess.check_output(["gpgconf"]) for line in output.splitlines(): - name, text, path = line.split(":") + name, text, path = line.split(b":") if name == "gpg": self.gpg = path break @@ -238,6 +253,30 @@ raise PGPError(err) return decrypted_plaintext +# Pretend that we have an Avahi module +class Avahi(object): + """This isn't so much a class as it is a module-like namespace. + It is instantiated once, and simulates having an Avahi module.""" + IF_UNSPEC = -1 # avahi-common/address.h + PROTO_UNSPEC = -1 # avahi-common/address.h + PROTO_INET = 0 # avahi-common/address.h + PROTO_INET6 = 1 # avahi-common/address.h + DBUS_NAME = "org.freedesktop.Avahi" + DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup" + DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server" + DBUS_PATH_SERVER = "/" + def string_array_to_txt_array(self, t): + return dbus.Array((dbus.ByteArray(s.encode("utf-8")) + for s in t), signature="ay") + ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h + ENTRY_GROUP_COLLISION = 3 # avahi-common/defs.h + ENTRY_GROUP_FAILURE = 4 # avahi-common/defs.h + SERVER_INVALID = 0 # avahi-common/defs.h + SERVER_REGISTERING = 1 # avahi-common/defs.h + SERVER_RUNNING = 2 # avahi-common/defs.h + SERVER_COLLISION = 3 # avahi-common/defs.h + SERVER_FAILURE = 4 # avahi-common/defs.h +avahi = Avahi() class AvahiError(Exception): def __init__(self, value, *args, **kwargs): @@ -447,7 +486,7 @@ _library = ctypes.cdll.LoadLibrary( ctypes.util.find_library("gnutls")) - _need_version = "3.3.0" + _need_version = b"3.3.0" def __init__(self): # Need to use class name "GnuTLS" here, since this method is # called before the assignment to the "gnutls" global variable @@ -715,17 +754,17 @@ checker: subprocess.Popen(); a running checker process used to see if the client lives. 'None' if no process is running. - checker_callback_tag: a GObject event source tag, or None + checker_callback_tag: a GLib event source tag, or None checker_command: string; External command which is run to check if client lives. %() expansions are done at runtime with vars(self) as dict, so that for instance %(name)s can be used in the command. - checker_initiator_tag: a GObject event source tag, or None + checker_initiator_tag: a GLib event source tag, or None created: datetime.datetime(); (UTC) object creation client_structure: Object describing what attributes a client has and is used for storing the client at exit current_checker_command: string; current running checker_command - disable_initiator_tag: a GObject event source tag, or None + disable_initiator_tag: a GLib event source tag, or None enabled: bool() fingerprint: string (40 or 32 hexadecimal digits); used to uniquely identify the client @@ -794,7 +833,9 @@ client["fingerprint"] = (section["fingerprint"].upper() .replace(" ", "")) if "secret" in section: - client["secret"] = section["secret"].decode("base64") + client["secret"] = codecs.decode(section["secret"] + .encode("utf-8"), + "base64") elif "secfile" in section: with open(os.path.expanduser(os.path.expandvars (section["secfile"])), @@ -853,7 +894,7 @@ self.changedstate = multiprocessing_manager.Condition( multiprocessing_manager.Lock()) self.client_structure = [attr - for attr in self.__dict__.iterkeys() + for attr in self.__dict__.keys() if not attr.startswith("_")] self.client_structure.append("client_structure") @@ -885,17 +926,17 @@ if not quiet: logger.info("Disabling client %s", self.name) if getattr(self, "disable_initiator_tag", None) is not None: - GObject.source_remove(self.disable_initiator_tag) + GLib.source_remove(self.disable_initiator_tag) self.disable_initiator_tag = None self.expires = None if getattr(self, "checker_initiator_tag", None) is not None: - GObject.source_remove(self.checker_initiator_tag) + GLib.source_remove(self.checker_initiator_tag) self.checker_initiator_tag = None self.stop_checker() self.enabled = False if not quiet: self.send_changedstate() - # Do not run this again if called by a GObject.timeout_add + # Do not run this again if called by a GLib.timeout_add return False def __del__(self): @@ -905,14 +946,14 @@ # Schedule a new checker to be started an 'interval' from now, # and every interval from then on. if self.checker_initiator_tag is not None: - GObject.source_remove(self.checker_initiator_tag) - self.checker_initiator_tag = GObject.timeout_add( + GLib.source_remove(self.checker_initiator_tag) + self.checker_initiator_tag = GLib.timeout_add( int(self.interval.total_seconds() * 1000), self.start_checker) # Schedule a disable() when 'timeout' has passed if self.disable_initiator_tag is not None: - GObject.source_remove(self.disable_initiator_tag) - self.disable_initiator_tag = GObject.timeout_add( + GLib.source_remove(self.disable_initiator_tag) + self.disable_initiator_tag = GLib.timeout_add( int(self.timeout.total_seconds() * 1000), self.disable) # Also start a new checker *right now*. self.start_checker() @@ -954,10 +995,10 @@ if timeout is None: timeout = self.timeout if self.disable_initiator_tag is not None: - GObject.source_remove(self.disable_initiator_tag) + GLib.source_remove(self.disable_initiator_tag) self.disable_initiator_tag = None if getattr(self, "enabled", False): - self.disable_initiator_tag = GObject.timeout_add( + self.disable_initiator_tag = GLib.timeout_add( int(timeout.total_seconds() * 1000), self.disable) self.expires = datetime.datetime.utcnow() + timeout @@ -1018,16 +1059,16 @@ args = (pipe[1], subprocess.call, command), kwargs = popen_args) self.checker.start() - self.checker_callback_tag = GObject.io_add_watch( - pipe[0].fileno(), GObject.IO_IN, + self.checker_callback_tag = GLib.io_add_watch( + pipe[0].fileno(), GLib.IO_IN, self.checker_callback, pipe[0], command) - # Re-run this periodically if run by GObject.timeout_add + # Re-run this periodically if run by GLib.timeout_add return True def stop_checker(self): """Force the checker process, if any, to stop.""" if self.checker_callback_tag: - GObject.source_remove(self.checker_callback_tag) + GLib.source_remove(self.checker_callback_tag) self.checker_callback_tag = None if getattr(self, "checker", None) is None: return @@ -1507,32 +1548,22 @@ interface_names.add(alt_interface) # Is this a D-Bus signal? if getattr(attribute, "_dbus_is_signal", False): + # Extract the original non-method undecorated + # function by black magic if sys.version_info.major == 2: - # Extract the original non-method undecorated - # function by black magic nonmethod_func = (dict( zip(attribute.func_code.co_freevars, attribute.__closure__)) ["func"].cell_contents) else: - nonmethod_func = attribute + nonmethod_func = (dict( + zip(attribute.__code__.co_freevars, + attribute.__closure__)) + ["func"].cell_contents) # Create a new, but exactly alike, function # object, and decorate it to be a new D-Bus signal # with the alternate D-Bus interface name - if sys.version_info.major == 2: - new_function = types.FunctionType( - nonmethod_func.func_code, - nonmethod_func.func_globals, - nonmethod_func.func_name, - nonmethod_func.func_defaults, - nonmethod_func.func_closure) - else: - new_function = types.FunctionType( - nonmethod_func.__code__, - nonmethod_func.__globals__, - nonmethod_func.__name__, - nonmethod_func.__defaults__, - nonmethod_func.__closure__) + new_function = copy_function(nonmethod_func) new_function = (dbus.service.signal( alt_interface, attribute._dbus_signature)(new_function)) @@ -1577,11 +1608,7 @@ alt_interface, attribute._dbus_in_signature, attribute._dbus_out_signature) - (types.FunctionType(attribute.func_code, - attribute.func_globals, - attribute.func_name, - attribute.func_defaults, - attribute.func_closure))) + (copy_function(attribute))) # Copy annotations, if any try: attr[attrname]._dbus_annotations = dict( @@ -1599,12 +1626,7 @@ attribute._dbus_access, attribute._dbus_get_args_options ["byte_arrays"]) - (types.FunctionType( - attribute.func_code, - attribute.func_globals, - attribute.func_name, - attribute.func_defaults, - attribute.func_closure))) + (copy_function(attribute))) # Copy annotations, if any try: attr[attrname]._dbus_annotations = dict( @@ -1619,11 +1641,7 @@ # to the class. attr[attrname] = ( dbus_interface_annotations(alt_interface) - (types.FunctionType(attribute.func_code, - attribute.func_globals, - attribute.func_name, - attribute.func_defaults, - attribute.func_closure))) + (copy_function(attribute))) if deprecate: # Deprecate all alternate interfaces iname="_AlternateDBusNames_interface_annotation{}" @@ -1642,8 +1660,12 @@ if interface_names: # Replace the class with a new subclass of it with # methods, signals, etc. as created above. - cls = type(b"{}Alternate".format(cls.__name__), - (cls, ), attr) + if sys.version_info.major == 2: + cls = type(b"{}Alternate".format(cls.__name__), + (cls, ), attr) + else: + cls = type("{}Alternate".format(cls.__name__), + (cls, ), attr) return cls return wrapper @@ -1807,8 +1829,8 @@ def approve(self, value=True): self.approved = value - GObject.timeout_add(int(self.approval_duration.total_seconds() - * 1000), self._reset_approved) + GLib.timeout_add(int(self.approval_duration.total_seconds() + * 1000), self._reset_approved) self.send_changedstate() ## D-Bus methods, signals & properties @@ -2024,8 +2046,8 @@ if (getattr(self, "disable_initiator_tag", None) is None): return - GObject.source_remove(self.disable_initiator_tag) - self.disable_initiator_tag = GObject.timeout_add( + GLib.source_remove(self.disable_initiator_tag) + self.disable_initiator_tag = GLib.timeout_add( int((self.expires - now).total_seconds() * 1000), self.disable) @@ -2051,8 +2073,8 @@ return if self.enabled: # Reschedule checker run - GObject.source_remove(self.checker_initiator_tag) - self.checker_initiator_tag = GObject.timeout_add( + GLib.source_remove(self.checker_initiator_tag) + self.checker_initiator_tag = GLib.timeout_add( value, self.start_checker) self.start_checker() # Start one now, too @@ -2462,7 +2484,7 @@ gnutls_priority GnuTLS priority string use_dbus: Boolean; to emit D-Bus signals or not - Assumes a GObject.MainLoop event loop. + Assumes a GLib.MainLoop event loop. """ def __init__(self, server_address, RequestHandlerClass, @@ -2493,9 +2515,9 @@ def add_pipe(self, parent_pipe, proc): # Call "handle_ipc" for both data and EOF events - GObject.io_add_watch( + GLib.io_add_watch( parent_pipe.fileno(), - GObject.IO_IN | GObject.IO_HUP, + GLib.IO_IN | GLib.IO_HUP, functools.partial(self.handle_ipc, parent_pipe = parent_pipe, proc = proc)) @@ -2505,7 +2527,7 @@ proc = None, client_object=None): # error, or the other end of multiprocessing.Pipe has closed - if condition & (GObject.IO_ERR | GObject.IO_HUP): + if condition & (GLib.IO_ERR | GLib.IO_HUP): # Wait for other process to exit proc.join() return False @@ -2518,7 +2540,7 @@ fpr = request[1] address = request[2] - for c in self.clients.itervalues(): + for c in self.clients.values(): if c.fingerprint == fpr: client = c break @@ -2532,9 +2554,9 @@ parent_pipe.send(False) return False - GObject.io_add_watch( + GLib.io_add_watch( parent_pipe.fileno(), - GObject.IO_IN | GObject.IO_HUP, + GLib.IO_IN | GLib.IO_HUP, functools.partial(self.handle_ipc, parent_pipe = parent_pipe, proc = proc, @@ -2970,14 +2992,14 @@ # Close all input and output, do double fork, etc. daemon() - # multiprocessing will use threads, so before we use GObject we - # need to inform GObject that threads will be used. - GObject.threads_init() + # multiprocessing will use threads, so before we use GLib we need + # to inform GLib that threads will be used. + GLib.threads_init() global main_loop # From the Avahi example code DBusGMainLoop(set_as_default=True) - main_loop = GObject.MainLoop() + main_loop = GLib.MainLoop() bus = dbus.SystemBus() # End of Avahi example code if use_dbus: @@ -3027,8 +3049,51 @@ if server_settings["restore"]: try: with open(stored_state_path, "rb") as stored_state: - clients_data, old_client_settings = pickle.load( - stored_state) + if sys.version_info.major == 2: + clients_data, old_client_settings = pickle.load( + stored_state) + else: + bytes_clients_data, bytes_old_client_settings = ( + pickle.load(stored_state, encoding = "bytes")) + ### Fix bytes to strings + ## clients_data + # .keys() + clients_data = { (key.decode("utf-8") + if isinstance(key, bytes) + else key): value + for key, value in + bytes_clients_data.items() } + del bytes_clients_data + for key in clients_data: + value = { (k.decode("utf-8") + if isinstance(k, bytes) else k): v + for k, v in + clients_data[key].items() } + clients_data[key] = value + # .client_structure + value["client_structure"] = [ + (s.decode("utf-8") + if isinstance(s, bytes) + else s) for s in + value["client_structure"] ] + # .name & .host + for k in ("name", "host"): + if isinstance(value[k], bytes): + value[k] = value[k].decode("utf-8") + ## old_client_settings + # .keys() + old_client_settings = { + (key.decode("utf-8") + if isinstance(key, bytes) + else key): value + for key, value in + bytes_old_client_settings.items() } + del bytes_old_client_settings + # .host + for value in old_client_settings.values(): + if isinstance(value["host"], bytes): + value["host"] = (value["host"] + .decode("utf-8")) os.remove(stored_state_path) except IOError as e: if e.errno == errno.ENOENT: @@ -3135,8 +3200,9 @@ del pidfile del pidfilename - signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit()) - signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit()) + for termsig in (signal.SIGHUP, signal.SIGTERM): + GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig, + lambda: main_loop.quit() and False) if use_dbus: @@ -3173,7 +3239,7 @@ def GetAllClients(self): "D-Bus method" return dbus.Array(c.dbus_object_path for c in - tcp_server.clients.itervalues()) + tcp_server.clients.values()) @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"}) @@ -3184,13 +3250,13 @@ return dbus.Dictionary( { c.dbus_object_path: c.GetAll( "se.recompile.Mandos.Client") - for c in tcp_server.clients.itervalues() }, + for c in tcp_server.clients.values() }, signature="oa{sv}") @dbus.service.method(_interface, in_signature="o") def RemoveClient(self, object_path): "D-Bus method" - for c in tcp_server.clients.itervalues(): + for c in tcp_server.clients.values(): if c.dbus_object_path == object_path: del tcp_server.clients[c.name] c.remove_from_connection() @@ -3256,7 +3322,7 @@ # removed/edited, old secret will thus be unrecovable. clients = {} with PGPEngine() as pgp: - for client in tcp_server.clients.itervalues(): + for client in tcp_server.clients.values(): key = client_settings[client.name]["secret"] client.encrypted_secret = pgp.encrypt(client.secret, key) @@ -3286,7 +3352,8 @@ prefix='clients-', dir=os.path.dirname(stored_state_path), delete=False) as stored_state: - pickle.dump((clients, client_settings), stored_state) + pickle.dump((clients, client_settings), stored_state, + protocol = 2) tempname = stored_state.name os.rename(tempname, stored_state_path) except (IOError, OSError) as e: @@ -3317,7 +3384,7 @@ atexit.register(cleanup) - for client in tcp_server.clients.itervalues(): + for client in tcp_server.clients.values(): if use_dbus: # Emit D-Bus signal for adding mandos_dbus_service.client_added_signal(client) @@ -3352,10 +3419,10 @@ sys.exit(1) # End of Avahi example code - GObject.io_add_watch(tcp_server.fileno(), GObject.IO_IN, - lambda *args, **kwargs: - (tcp_server.handle_request - (*args[2:], **kwargs) or True)) + GLib.io_add_watch(tcp_server.fileno(), GLib.IO_IN, + lambda *args, **kwargs: + (tcp_server.handle_request + (*args[2:], **kwargs) or True)) logger.debug("Starting main loop") main_loop.run() === modified file 'mandos-monitor' --- mandos-monitor 2016-03-08 00:14:50 +0000 +++ mandos-monitor 2016-03-12 23:42:38 +0000 @@ -39,10 +39,7 @@ import urwid from dbus.mainloop.glib import DBusGMainLoop -try: - from gi.repository import GObject -except ImportError: - import gobject as GObject +from gi.repository import GLib import dbus @@ -172,11 +169,11 @@ """ if flag and self._update_timer_callback_tag is None: # Will update the shown timer value every second - self._update_timer_callback_tag = (GObject.timeout_add + self._update_timer_callback_tag = (GLib.timeout_add (1000, self.update_timer)) elif not (flag or self._update_timer_callback_tag is None): - GObject.source_remove(self._update_timer_callback_tag) + GLib.source_remove(self._update_timer_callback_tag) self._update_timer_callback_tag = None def checker_completed(self, exitstatus, condition, command): @@ -309,14 +306,15 @@ self.update_hook() def update_timer(self): - """called by GObject. Will indefinitely loop until - GObject.source_remove() on tag is called""" + """called by GLib. Will indefinitely loop until + GLib.source_remove() on tag is called + """ self.update() return True # Keep calling this def delete(self, **kwargs): if self._update_timer_callback_tag is not None: - GObject.source_remove(self._update_timer_callback_tag) + GLib.source_remove(self._update_timer_callback_tag) self._update_timer_callback_tag = None for match in self.match_objects: match.remove() @@ -465,7 +463,7 @@ "q: Quit ?: Help")) self.busname = domain + '.Mandos' - self.main_loop = GObject.MainLoop() + self.main_loop = GLib.MainLoop() def client_not_found(self, fingerprint, address): self.log_message("Client with address {} and fingerprint {}" @@ -640,13 +638,13 @@ path=path) self.refresh() - self._input_callback_tag = (GObject.io_add_watch + self._input_callback_tag = (GLib.io_add_watch (sys.stdin.fileno(), - GObject.IO_IN, + GLib.IO_IN, self.process_input)) self.main_loop.run() # Main loop has finished, we should close everything now - GObject.source_remove(self._input_callback_tag) + GLib.source_remove(self._input_callback_tag) self.screen.stop() def stop(self): === modified file 'mandos.service' --- mandos.service 2016-03-08 00:03:43 +0000 +++ mandos.service 2016-03-13 00:37:02 +0000 @@ -28,9 +28,6 @@ ProtectSystem=full ProtectHome=yes CapabilityBoundingSet=CAP_KILL CAP_SETGID CAP_SETUID CAP_DAC_OVERRIDE CAP_NET_RAW -# Bug workaround; the daemon does not stop on SIGTERM until poked via -# D-Bus; cause is unknown at this time -ExecStop=/bin/kill -TERM $MAINPID ; /usr/sbin/mandos-ctl >/dev/null 2>&1 [Install] WantedBy=multi-user.target