=== modified file 'DBUS-API' --- DBUS-API 2015-08-10 07:34:37 +0000 +++ DBUS-API 2015-08-10 09:00:23 +0000 @@ -13,34 +13,25 @@ | Path | Object | |-----------------------+-------------------| | "/" | The Mandos Server | - | "/clients/CLIENTNAME" | Mandos Client | - - + + (To get a list of paths to client objects, use the standard D-Bus + org.freedesktop.DBus.ObjectManager interface, which the server + object supports.) + + * Mandos Server Interface: Interface name: "se.recompile.Mandos" ** Methods: -*** GetAllClients() → (ao: Clients) - Returns an array of all client D-Bus object paths - -*** GetAllClientsWithProperties() → (a{oa{sv}}: ClientProperties) - Returns an array of all clients and all their properties - *** RemoveClient(o: ObjectPath) → nothing Removes a client ** Signals: -*** ClientAdded(o: ObjectPath) - A new client was added. - *** ClientNotFound(s: Fingerprint, s: Address) A client connected from Address using Fingerprint, but was rejected because it was not found in the server. The fingerprint is represented as a string of hexadecimal digits. The address is an IPv4 or IPv6 address in its normal string format. - -*** ClientRemoved(o: ObjectPath, s: Name) - A client named Name on ObjectPath was removed. * Mandos Client Interface: === modified file 'TODO' --- TODO 2015-07-08 21:18:49 +0000 +++ TODO 2015-08-10 09:00:23 +0000 @@ -75,9 +75,6 @@ ** TODO Use python-tlslite? ** TODO D-Bus AddClient() method on server object ** TODO Use org.freedesktop.DBus.Method.NoReply annotation on async methods. :2: -** TODO Support [[http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager][org.freedesktop.DBus.ObjectManager]] interface on server object :2: - Deprecate methods GetAllClients(), GetAllClientsWithProperties() - and signals ClientAdded and ClientRemoved. ** TODO Save state periodically to recover better from hard shutdowns ** TODO CheckerCompleted method, deprecate CheckedOK ** TODO Secret Service API? === modified file 'mandos' --- mandos 2015-08-10 08:25:01 +0000 +++ mandos 2015-08-10 09:00:23 +0000 @@ -983,6 +983,15 @@ raise DBusPropertyNotFound("{}:{}.{}".format( self.dbus_object_path, interface_name, property_name)) + @classmethod + def _get_all_interface_names(cls): + """Get a sequence of all interfaces supported by an object""" + return (name for name in set(getattr(getattr(x, attr), + "_dbus_interface", None) + for x in (inspect.getmro(cls)) + for attr in dir(x)) + if name is not None) + @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss", out_signature="v") @@ -1118,6 +1127,74 @@ exc_info=error) return xmlstring +try: + dbus.OBJECT_MANAGER_IFACE +except AttributeError: + dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager" + +class DBusObjectWithObjectManager(DBusObjectWithAnnotations): + """A D-Bus object with an ObjectManager. + + Classes inheriting from this exposes the standard + GetManagedObjects call and the InterfacesAdded and + InterfacesRemoved signals on the standard + "org.freedesktop.DBus.ObjectManager" interface. + + Note: No signals are sent automatically; they must be sent + manually. + """ + @dbus.service.method(dbus.OBJECT_MANAGER_IFACE, + out_signature = "a{oa{sa{sv}}}") + def GetManagedObjects(self): + """This function must be overridden""" + raise NotImplementedError() + + @dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, + signature = "oa{sa{sv}}") + def InterfacesAdded(self, object_path, interfaces_and_properties): + pass + + @dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, + signature = "oas") + def InterfacesRemoved(self, object_path, interfaces): + pass + + @dbus.service.method(dbus.INTROSPECTABLE_IFACE, + out_signature = "s", + path_keyword = 'object_path', + connection_keyword = 'connection') + def Introspect(self, object_path, connection): + """Overloading of standard D-Bus method. + + Override return argument name of GetManagedObjects to be + "objpath_interfaces_and_properties" + """ + xmlstring = DBusObjectWithAnnotations(self, object_path, + connection) + try: + document = xml.dom.minidom.parseString(xmlstring) + + for if_tag in document.getElementsByTagName("interface"): + # Fix argument name for the GetManagedObjects method + if (if_tag.getAttribute("name") + == dbus.OBJECT_MANAGER_IFACE): + for cn in if_tag.getElementsByTagName("method"): + if (cn.getAttribute("name") + == "GetManagedObjects"): + for arg in cn.getElementsByTagName("arg"): + if (arg.getAttribute("direction") + == "out"): + arg.setAttribute( + "name", + "objpath_interfaces" + "_and_properties") + xmlstring = document.toxml("utf-8") + document.unlink() + except (AttributeError, xml.dom.DOMException, + xml.parsers.expat.ExpatError) as error: + logger.error("Failed to override Introspection method", + exc_info = error) + return xmlstring def datetime_to_dbus(dt, variant_level=0): """Convert a UTC datetime.datetime() to a D-Bus type.""" @@ -2813,7 +2890,7 @@ @alternate_dbus_interfaces( { "se.recompile.Mandos": "se.bsnet.fukt.Mandos" }) - class MandosDBusService(DBusObjectWithAnnotations): + class MandosDBusService(DBusObjectWithObjectManager): """A D-Bus proxy object""" def __init__(self): @@ -2831,23 +2908,30 @@ "D-Bus signal" pass + @dbus_annotations({"org.freedesktop.DBus.Deprecated": + "true"}) @dbus.service.signal(_interface, signature="os") def ClientRemoved(self, objpath, name): "D-Bus signal" pass + @dbus_annotations({"org.freedesktop.DBus.Deprecated": + "true"}) @dbus.service.method(_interface, out_signature="ao") def GetAllClients(self): "D-Bus method" return dbus.Array(c.dbus_object_path for c in tcp_server.clients.itervalues()) + @dbus_annotations({"org.freedesktop.DBus.Deprecated": + "true"}) @dbus.service.method(_interface, out_signature="a{oa{sv}}") def GetAllClientsWithProperties(self): "D-Bus method" return dbus.Dictionary( - { c.dbus_object_path: c.GetAll("") + { c.dbus_object_path: c.GetAll( + "se.recompile.Mandos.Client") for c in tcp_server.clients.itervalues() }, signature="oa{sv}") @@ -2858,14 +2942,50 @@ if c.dbus_object_path == object_path: del tcp_server.clients[c.name] c.remove_from_connection() - # Don't signal anything except ClientRemoved + # Don't signal the disabling c.disable(quiet=True) - # Emit D-Bus signal - self.ClientRemoved(object_path, c.name) + # Emit D-Bus signal for removal + self.client_removed_signal(c) return raise KeyError(object_path) del _interface + + @dbus.service.method(dbus.OBJECT_MANAGER_IFACE, + out_signature = "a{oa{sa{sv}}}") + def GetManagedObjects(self): + """D-Bus method""" + return dbus.Dictionary( + { client.dbus_object_path: + dbus.Dictionary( + { interface: client.GetAll(interface) + for interface in + client._get_all_interface_names()}) + for client in tcp_server.clients.values()}) + + def client_added_signal(self, client): + """Send the new standard signal and the old signal""" + if use_dbus: + # New standard signal + self.InterfacesAdded( + client.dbus_object_path, + dbus.Dictionary( + { interface: client.GetAll(interface) + for interface in + client._get_all_interface_names()})) + # Old signal + self.ClientAdded(client.dbus_object_path) + + def client_removed_signal(self, client): + """Send the new standard signal and the old signal""" + if use_dbus: + # New standard signal + self.InterfacesRemoved( + client.dbus_object_path, + client._get_all_interface_names()) + # Old signal + self.ClientRemoved(client.dbus_object_path, + client.name) mandos_dbus_service = MandosDBusService() @@ -2936,20 +3056,18 @@ name, client = tcp_server.clients.popitem() if use_dbus: client.remove_from_connection() - # Don't signal anything except ClientRemoved + # Don't signal the disabling client.disable(quiet=True) - if use_dbus: - # Emit D-Bus signal - mandos_dbus_service.ClientRemoved( - client.dbus_object_path, client.name) + # Emit D-Bus signal for removal + mandos_dbus_service.client_removed_signal(client) client_settings.clear() atexit.register(cleanup) for client in tcp_server.clients.itervalues(): if use_dbus: - # Emit D-Bus signal - mandos_dbus_service.ClientAdded(client.dbus_object_path) + # Emit D-Bus signal for adding + mandos_dbus_service.client_added_signal(client) # Need to initiate checking of clients if client.enabled: client.init_checker() === modified file 'mandos-ctl' --- mandos-ctl 2015-05-31 16:13:39 +0000 +++ mandos-ctl 2015-08-10 09:00:23 +0000 @@ -75,6 +75,11 @@ version = "1.6.9" +try: + dbus.OBJECT_MANAGER_IFACE +except AttributeError: + dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager" + def milliseconds_to_string(ms): td = datetime.timedelta(0, 0, 0, ms) return ("{days}{hours:02}:{minutes:02}:{seconds:02}".format( @@ -341,6 +346,8 @@ mandos_serv = dbus.Interface(mandos_dbus_objc, dbus_interface = server_interface) + mandos_serv_object_manager = dbus.Interface( + mandos_dbus_objc, dbus_interface = dbus.OBJECT_MANAGER_IFACE) #block stderr since dbus library prints to stderr null = os.open(os.path.devnull, os.O_RDWR) @@ -349,14 +356,18 @@ os.close(null) try: try: - mandos_clients = mandos_serv.GetAllClientsWithProperties() + mandos_clients = { path: ifs_and_props[client_interface] + for path, ifs_and_props in + mandos_serv_object_manager + .GetManagedObjects().items() + if client_interface in ifs_and_props } finally: #restore stderr os.dup2(stderrcopy, sys.stderr.fileno()) os.close(stderrcopy) - except dbus.exceptions.DBusException: - print("Access denied: Accessing mandos server through dbus.", - file=sys.stderr) + except dbus.exceptions.DBusException as e: + print("Access denied: Accessing mandos server through D-Bus: {}" + .format(e), file=sys.stderr) sys.exit(1) # Compile dict of (clients: properties) to process === modified file 'mandos-monitor' --- mandos-monitor 2015-08-10 07:34:37 +0000 +++ mandos-monitor 2015-08-10 09:00:23 +0000 @@ -62,6 +62,11 @@ client_interface = domain + '.Mandos.Client' version = "1.6.9" +try: + dbus.OBJECT_MANAGER_IFACE +except AttributeError: + dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager" + def isoformat_to_datetime(iso): "Parse an ISO 8601 date string to a datetime.datetime()" if not iso: @@ -105,7 +110,8 @@ It updates the changed properties in the "properties" dict. """ # Update properties dict with new value - self.properties.update(properties) + if interface == client_interface: + self.properties.update(properties) def delete(self): self.property_changed_match.remove() @@ -521,21 +527,32 @@ self.log_message("Wrap mode: {}".format(self.log_wrap), level=0) - def find_and_remove_client(self, path, name): + def find_and_remove_client(self, path, interfaces): """Find a client by its object path and remove it. - This is connected to the ClientRemoved signal from the + This is connected to the InterfacesRemoved signal from the Mandos server object.""" + if client_interface not in interfaces: + # Not a Mandos client object; ignore + return try: client = self.clients_dict[path] except KeyError: # not found? - self.log_message("Unknown client {!r} ({!r}) removed" - .format(name, path)) + self.log_message("Unknown client {!r} removed" + .format(path)) return client.delete() - def add_new_client(self, path): + def add_new_client(self, path, ifs_and_props): + """Find a client by its object path and remove it. + + This is connected to the InterfacesAdded signal from the + Mandos server object. + """ + if client_interface not in ifs_and_props: + # Not a Mandos client object; ignore + return client_proxy_object = self.bus.get_object(self.busname, path) self.add_client(MandosClientWidget(server_proxy_object =self.mandos_serv, @@ -546,7 +563,10 @@ delete_hook =self.remove_client, logger - =self.log_message), + =self.log_message, + properties + = dict(ifs_and_props[ + client_interface])), path=path) def add_client(self, client, path=None): @@ -587,14 +607,16 @@ mandos_clients = dbus.Dictionary() (self.mandos_serv - .connect_to_signal("ClientRemoved", + .connect_to_signal("InterfacesRemoved", self.find_and_remove_client, - dbus_interface=server_interface, + dbus_interface + = dbus.OBJECT_MANAGER_IFACE, byte_arrays=True)) (self.mandos_serv - .connect_to_signal("ClientAdded", + .connect_to_signal("InterfacesAdded", self.add_new_client, - dbus_interface=server_interface, + dbus_interface + = dbus.OBJECT_MANAGER_IFACE, byte_arrays=True)) (self.mandos_serv .connect_to_signal("ClientNotFound",