>From ff0c92647428807c1a9faf17b4eaf9406a9c06b0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrezina@redhat.com>
Date: Thu, 11 Dec 2014 22:23:32 +0100
Subject: [PATCH 4/9] sbus: support multiple interfaces on single path

This patch removes the old message handler which is replaced
with a new one that supports multiple interfaces registered
on single object path.

A hash table is used to store registered object paths and their
interfaces. When an entry or the table itself is destroyed,
registered object path is unregistered through delete callback.

It temporarily removes support of Introspect and Properties
standard D-Bus interfaces and disables unit tests of those
interfaces. The support is brought back by following patches.

Resolves:
https://fedorahosted.org/sssd/ticket/2339
---
 src/sbus/sssd_dbus.h            |   9 +-
 src/sbus/sssd_dbus_connection.c | 197 +--------------
 src/sbus/sssd_dbus_interface.c  | 546 ++++++++++++++++++++++++++++++++--------
 src/sbus/sssd_dbus_private.h    |  20 +-
 src/tests/sbus_codegen_tests.c  |   2 +
 src/tests/sbus_tests.c          |   2 +
 6 files changed, 465 insertions(+), 311 deletions(-)

diff --git a/src/sbus/sssd_dbus.h b/src/sbus/sssd_dbus.h
index 7552fbcf1a4fd630970eec378f2f725c6d00eaa4..c50b4e9b659a3a35b50e38d8295dbccd9e1a062d 100644
--- a/src/sbus/sssd_dbus.h
+++ b/src/sbus/sssd_dbus.h
@@ -170,6 +170,9 @@ int sbus_conn_register_iface(struct sbus_connection *conn,
                              const char *object_path,
                              void *pvt);
 
+errno_t
+sbus_conn_reregister_paths(struct sbus_connection *conn);
+
 bool sbus_conn_disconnecting(struct sbus_connection *conn);
 
 /* max_retries < 0: retry forever
@@ -181,12 +184,6 @@ void sbus_reconnect_init(struct sbus_connection *conn,
                          sbus_conn_reconn_callback_fn callback,
                          void *pvt);
 
-/* Default message handler
- * Should be usable for most cases */
-DBusHandlerResult sbus_message_handler(DBusConnection *conn,
-                                  DBusMessage *message,
-                                  void *user_data);
-
 /*
  * Send a message across the SBUS
  * If requested, the DBusPendingCall object will
diff --git a/src/sbus/sssd_dbus_connection.c b/src/sbus/sssd_dbus_connection.c
index d27fd2fadfec989935b238a2719bd34048844d8a..8ff279c1bf5f20d5396e1e41c6a162328657091f 100644
--- a/src/sbus/sssd_dbus_connection.c
+++ b/src/sbus/sssd_dbus_connection.c
@@ -161,6 +161,13 @@ int sbus_init_connection(TALLOC_CTX *ctx,
     conn->dbus.conn = dbus_conn;
     conn->connection_type = connection_type;
 
+    ret = sbus_opath_hash_init(conn, conn, &conn->managed_paths);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Cannot create object paths hash table\n");
+        talloc_free(conn);
+        return EIO;
+    }
+
     ret = sss_hash_create(conn, 32, &conn->clients);
     if (ret != EOK) {
         DEBUG(SSSDBG_CRIT_FAILURE, "Cannot create clients hash table\n");
@@ -313,7 +320,7 @@ void sbus_disconnect(struct sbus_connection *conn)
     conn->disconnect = 1;
 
     /* Unregister object paths */
-    sbus_unreg_object_paths(conn);
+    talloc_zfree(conn->managed_paths);
 
     /* Disable watch functions */
     dbus_connection_set_watch_functions(conn->dbus.conn,
@@ -342,185 +349,12 @@ void sbus_disconnect(struct sbus_connection *conn)
     DEBUG(SSSDBG_TRACE_FUNC ,"Disconnected %p\n", conn->dbus.conn);
 }
 
-static void sbus_handler_got_caller_id(struct tevent_req *req);
-
-/* messsage_handler
- * Receive messages and process them
- */
-DBusHandlerResult sbus_message_handler(DBusConnection *dbus_conn,
-                                       DBusMessage *message,
-                                       void *user_data)
-{
-    struct sbus_interface_p *intf_p;
-    const char *msg_method;
-    const char *path;
-    const char *msg_interface;
-    const char *sender;
-    struct sbus_request *dbus_req = NULL;
-    struct tevent_req *req;
-
-    if (!user_data) {
-        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-    }
-    intf_p = talloc_get_type(user_data, struct sbus_interface_p);
-
-    msg_method = dbus_message_get_member(message);
-    DEBUG(SSSDBG_TRACE_ALL, "Received SBUS method [%s]\n", msg_method);
-    path = dbus_message_get_path(message);
-    msg_interface = dbus_message_get_interface(message);
-    sender = dbus_message_get_sender(message);
-
-    if (!msg_method || !path || !msg_interface) {
-        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-    }
-
-    /* Validate the D-BUS path */
-    if (!sbus_iface_handles_path(intf_p, path)) {
-        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-    }
-
-    /* Validate the method interface */
-    if (strcmp(msg_interface, intf_p->intf->vtable->meta->name) == 0 ||
-             strcmp(msg_interface, DBUS_PROPERTIES_INTERFACE) == 0 ||
-            (strcmp(msg_interface, DBUS_INTROSPECT_INTERFACE) == 0 &&
-                strcmp(msg_method, DBUS_INTROSPECT_METHOD) == 0)) {
-
-        /* OK, this message for us. Get the sender ID if applicable */
-        dbus_req = sbus_new_request(intf_p->conn, intf_p->intf, message);
-        if (dbus_req == NULL) {
-            return DBUS_HANDLER_RESULT_NEED_MEMORY;
-        }
-
-        req = sbus_get_sender_id_send(dbus_req, dbus_req->conn->ev,
-                                      dbus_req->conn, sender);
-        if (req == NULL) {
-            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-        }
-        tevent_req_set_callback(req, sbus_handler_got_caller_id, dbus_req);
-        return DBUS_HANDLER_RESULT_HANDLED;
-    }
-
-    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-}
-
-static void sbus_handler_got_caller_id(struct tevent_req *req)
-{
-    struct sbus_request *dbus_req = \
-                      tevent_req_callback_data(req,
-                                               struct sbus_request);
-    errno_t ret;
-    DBusError *dberr;
-    DBusMessage *reply = NULL;
-    const struct sbus_method_meta *method;
-    const struct sbus_interface_meta *interface;
-    const char *msg_method;
-    const char *msg_interface;
-    sbus_msg_handler_fn handler_fn = NULL;
-    void *handler_data = NULL; /* Must be a talloc pointer! */
-    struct sbus_introspect_ctx *ictx = NULL;
-    const char *dbus_error = NULL;
-
-    ret = sbus_get_sender_id_recv(req, &dbus_req->client);
-    if (ret != EOK) {
-        dberr = sbus_error_new(dbus_req,
-                               DBUS_ERROR_FAILED,
-                               "Failed to retrieve called ID: %s\n",
-                               sss_strerror(ret));
-        sbus_request_fail_and_finish(dbus_req, dberr);
-        return;
-    }
-
-    msg_method = dbus_message_get_member(dbus_req->message);
-    msg_interface = dbus_message_get_interface(dbus_req->message);
-    DEBUG(SSSDBG_TRACE_ALL, "Received SBUS method [%s]\n", msg_method);
-
-    /* Prepare the handler */
-    interface = dbus_req->intf->vtable->meta;
-    if (strcmp(msg_interface, interface->name) == 0) {
-        method = sbus_meta_find_method(interface, msg_method);
-        if (method && method->vtable_offset)
-            handler_fn = VTABLE_FUNC(dbus_req->intf->vtable,
-                                     method->vtable_offset);
-
-        if (!method) {
-            /* Reply DBUS_ERROR_UNKNOWN_METHOD */
-            DEBUG(SSSDBG_CRIT_FAILURE,
-                  "No matching method found for %s.\n", msg_method);
-            dbus_error = DBUS_ERROR_UNKNOWN_METHOD;
-            goto fail;
-        } else if (!handler_fn) {
-            /* Reply DBUS_ERROR_NOT_SUPPORTED */
-            DEBUG(SSSDBG_CRIT_FAILURE,
-                  "No handler provided found for %s.\n", msg_method);
-            dbus_error = DBUS_ERROR_NOT_SUPPORTED;
-            goto fail;
-        }
-    } else if (strcmp(msg_interface, DBUS_INTROSPECT_INTERFACE) == 0 &&
-                strcmp(msg_method, DBUS_INTROSPECT_METHOD) == 0) {
-            DEBUG(SSSDBG_TRACE_LIBS, "Got introspection request\n");
-            ictx = talloc(dbus_req->conn, struct sbus_introspect_ctx);
-            if (ictx == NULL) {
-                dbus_error = DBUS_ERROR_NO_MEMORY;
-                goto fail;
-            }
-
-            handler_fn = sbus_introspect;
-            ictx->iface = interface;
-            handler_data = ictx;
-            method = &introspect_method;
-    } else if (strcmp(msg_interface, DBUS_PROPERTIES_INTERFACE) == 0) {
-        ret = sbus_properties_dispatch(dbus_req);
-        if (ret == ERR_SBUS_NOSUP) {
-            /* No known method matched */
-            dbus_error = DBUS_ERROR_NOT_SUPPORTED;
-            goto fail;
-        }
-        /* sbus_properties_dispatch handles all other errors
-         * or success internally
-         */
-        return;
-    }
-
-    if (handler_fn == NULL) {
-        DEBUG(SSSDBG_MINOR_FAILURE, "No handler matched!\n");
-        dbus_error = DBUS_ERROR_NOT_SUPPORTED;
-        goto fail;
-    }
-
-    dbus_req->method = method;
-    if (handler_data) {
-        /* If the handler uses private instance data, make
-         * sure they go away when the request does
-         */
-        talloc_steal(dbus_req, handler_data);
-    } else {
-        /* If no custom handler data is set, pass on the
-         * interface data
-         */
-        handler_data = dbus_req->intf->instance_data;
-    }
-
-    sbus_request_invoke_or_finish(dbus_req,
-                                  handler_fn,
-                                  handler_data,
-                                  method->invoker);
-    return;
-
-fail:
-    reply = dbus_message_new_error(dbus_req->message,
-                                   dbus_error ? dbus_error : DBUS_ERROR_FAILED,
-                                   NULL);
-    sbus_request_finish(dbus_req, reply);
-}
-
 static void sbus_reconnect(struct tevent_context *ev,
                            struct tevent_timer *te,
                            struct timeval tv, void *data)
 {
     struct sbus_connection *conn;
-    struct sbus_interface_p *iter;
     DBusError dbus_error;
-    dbus_bool_t dbret;
     int ret;
 
     conn = talloc_get_type(data, struct sbus_connection);
@@ -539,20 +373,7 @@ static void sbus_reconnect(struct tevent_context *ev,
         }
 
         /* Re-register object paths */
-        iter = conn->intf_list;
-        while (iter) {
-            dbret = dbus_connection_register_object_path(conn->dbus.conn,
-                                                         iter->intf->path,
-                                                         &dbus_object_path_vtable,
-                                                         iter);
-            if (!dbret) {
-                DEBUG(SSSDBG_FATAL_FAILURE,
-                      "Could not register object path.\n");
-                dbus_connection_unref(conn->dbus.conn);
-                goto failed;
-            }
-            iter = iter->next;
-        }
+        sbus_conn_reregister_paths(conn);
 
         /* Reset retries to 0 to resume dispatch processing */
         conn->retries = 0;
diff --git a/src/sbus/sssd_dbus_interface.c b/src/sbus/sssd_dbus_interface.c
index acede4d32b780a7b4ae70a52839755eadc42e4a9..9a26a26b6ce7f14829d2120bcf18407eb14460c4 100644
--- a/src/sbus/sssd_dbus_interface.c
+++ b/src/sbus/sssd_dbus_interface.c
@@ -20,43 +20,31 @@
 
 #include <talloc.h>
 #include <dbus/dbus.h>
+#include <dhash.h>
 
 #include "util/util.h"
 #include "sbus/sssd_dbus.h"
+#include "sbus/sssd_dbus_meta.h"
 #include "sbus/sssd_dbus_private.h"
 
-DBusObjectPathVTable dbus_object_path_vtable =
-    { NULL, sbus_message_handler, NULL, NULL, NULL, NULL };
+struct sbus_interface_list {
+    struct sbus_interface_list *prev, *next;
+    struct sbus_interface *interface;
+};
 
-static bool path_in_interface_list(struct sbus_interface_p *list,
-                                   const char *path)
+static struct sbus_interface *
+sbus_iface_list_lookup(struct sbus_interface_list *list,
+                       const char *iface)
 {
-    struct sbus_interface_p *iter;
+    struct sbus_interface_list *item;
 
-    if (!list || !path) {
-        return false;
-    }
-
-    iter = list;
-    while (iter != NULL) {
-        if (strcmp(iter->intf->path, path) == 0) {
-            return true;
+    DLIST_FOR_EACH(item, list) {
+        if (strcmp(item->interface->vtable->meta->name, iface) == 0) {
+            return item->interface;
         }
-        iter = iter->next;
     }
 
-    return false;
-}
-
-void sbus_unreg_object_paths(struct sbus_connection *conn)
-{
-    struct sbus_interface_p *iter = conn->intf_list;
-
-    while (iter != NULL) {
-        dbus_connection_unregister_object_path(conn->dbus.conn,
-                                               iter->intf->path);
-        iter = iter->next;
-    }
+    return NULL;
 }
 
 /**
@@ -76,24 +64,6 @@ static bool sbus_opath_is_subtree(const char *path)
     return path[len - 2] == '/' && path[len - 1] == '*';
 }
 
-static bool sbus_opath_match_tree(const char *object_path,
-                                  const char *tree_path)
-{
-    if (object_path == NULL || tree_path == NULL || tree_path[0] == '\0') {
-        return false;
-    };
-
-    /* first check if tree is a base path or a subtree path */
-    if (!sbus_opath_is_subtree(tree_path)) {
-        return strcmp(object_path, tree_path) == 0;
-    }
-
-    /* Compare without the asterisk, which is the last character.
-     * Slash, that has to be present before the asterisk, will ensure that only
-     * subtree object path matches. */
-    return strncmp(object_path, tree_path, strlen(tree_path) - 1) == 0;
-}
-
 /**
  * If the path represents a subtree object path, this function will
  * remove /~* from the end.
@@ -121,14 +91,226 @@ static char *sbus_opath_get_base_path(TALLOC_CTX *mem_ctx,
     return tree_path;
 }
 
-bool sbus_iface_handles_path(struct sbus_interface_p *intf_p,
-                             const char *path)
+static char *sbus_opath_parent_subtree(TALLOC_CTX *mem_ctx,
+                                       const char *path)
 {
-    if (sbus_opath_is_subtree(intf_p->intf->path)) {
-        return sbus_opath_match_tree(path, intf_p->intf->path);
+    char *subtree;
+    char *slash;
+
+    /* first remove /~* from the end, stop when we have reached the root i.e.
+     * subtree == "/" */
+    subtree = sbus_opath_get_base_path(mem_ctx, path);
+    if (subtree == NULL || subtree[1] == '\0') {
+        return NULL;
+    }
+
+    /* Find the first separator and replace the part with asterisk. */
+    slash = strrchr(subtree, '/');
+    if (slash == NULL) {
+        /* we cannot continue up */
+        talloc_free(subtree);
+        return NULL;
+    }
+
+    if (*(slash + 1) == '\0') {
+        /* this object path is invalid since it cannot end with slash */
+        DEBUG(SSSDBG_CRIT_FAILURE, "Invalid object path '%s'?\n", path);
+        talloc_free(subtree);
+        return NULL;
+    }
+
+    /* because object path cannot end with / there is enough space for
+     * asterisk and terminating zero */
+    *(slash + 1) = '*';
+    *(slash + 2) = '\0';
+
+    return subtree;
+}
+
+static void
+sbus_opath_hash_delete_cb(hash_entry_t *item,
+                          hash_destroy_enum deltype,
+                          void *pvt)
+{
+    struct sbus_connection *conn;
+    char *path;
+
+    conn = talloc_get_type(pvt, struct sbus_connection);
+    path = sbus_opath_get_base_path(NULL, item->key.str);
+
+    dbus_connection_unregister_object_path(conn->dbus.conn, path);
+}
+
+errno_t
+sbus_opath_hash_init(TALLOC_CTX *mem_ctx,
+                     struct sbus_connection *conn,
+                     hash_table_t **_table)
+{
+    return sss_hash_create_ex(mem_ctx, 10, _table, 0, 0, 0, 0,
+                              sbus_opath_hash_delete_cb, conn);
+}
+
+static errno_t
+sbus_opath_hash_add_iface(hash_table_t *table,
+                          const char *object_path,
+                          struct sbus_interface *iface,
+                          bool *_path_known)
+{
+    TALLOC_CTX *tmp_ctx = NULL;
+    struct sbus_interface_list *list = NULL;
+    struct sbus_interface_list *item = NULL;
+    const char *iface_name = iface->vtable->meta->name;
+    hash_key_t key;
+    hash_value_t value;
+    bool path_known;
+    errno_t ret;
+    int hret;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        return ENOMEM;
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC, "Registering interface %s with path %s\n",
+          iface_name, object_path);
+
+    /* create new list item */
+
+    item = talloc_zero(tmp_ctx, struct sbus_interface_list);
+    if (item == NULL) {
+        return ENOMEM;
+    }
+
+    item->interface = iface;
+
+    /* first lookup existing list in hash table */
+
+    key.type = HASH_KEY_STRING;
+    key.str = talloc_strdup(tmp_ctx, object_path);
+    if (key.str == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    hret = hash_lookup(table, &key, &value);
+    if (hret == HASH_SUCCESS) {
+        /* This object path has already some interface registered. We will
+         * check for existence of the interface currently being added and
+         * add it if missing. */
+
+        path_known = true;
+
+        list = talloc_get_type(value.ptr, struct sbus_interface_list);
+        if (sbus_iface_list_lookup(list, iface_name) != NULL) {
+            DEBUG(SSSDBG_MINOR_FAILURE, "Trying to register the same interface"
+                  " twice: iface=%s, opath=%s\n", iface_name, object_path);
+            ret = EEXIST;
+            goto done;
+        }
+
+        DLIST_ADD_END(list, item, struct sbus_interface_list *);
+        ret = EOK;
+        goto done;
+    } else if (hret != HASH_ERROR_KEY_NOT_FOUND) {
+        ret = EIO;
+        goto done;
+    }
+
+    /* otherwise create new hash entry and new list */
+
+    path_known = false;
+    list = item;
+
+    value.type = HASH_VALUE_PTR;
+    value.ptr = list;
+
+    hret = hash_enter(table, &key, &value);
+    if (hret != HASH_SUCCESS) {
+        ret = EIO;
+        goto done;
+    }
+
+    talloc_steal(table, key.str);
+    ret = EOK;
+
+done:
+    if (ret == EOK) {
+        talloc_steal(item, iface);
+        talloc_steal(table, item);
+        *_path_known = path_known;
+    } else {
+        talloc_free(item);
+    }
+
+    return ret;
+}
+
+static bool
+sbus_opath_hash_has_path(hash_table_t *table,
+                         const char *object_path)
+{
+    hash_key_t key;
+
+    key.type = HASH_KEY_STRING;
+    key.str = discard_const(object_path);
+
+    return hash_has_key(table, &key);
+}
+
+/**
+ * First @object_path is looked up in @table, if it is not found it steps up
+ * in the path hierarchy and try to lookup the parent node. This continues
+ * until the root is reached.
+ */
+static struct sbus_interface *
+sbus_opath_hash_lookup_iface(hash_table_t *table,
+                             const char *object_path,
+                             const char *iface_name)
+{
+    TALLOC_CTX *tmp_ctx = NULL;
+    struct sbus_interface_list *list = NULL;
+    struct sbus_interface *iface = NULL;
+    char *lookup_path = NULL;
+    hash_key_t key;
+    hash_value_t value;
+    int hret;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        return NULL;
+    }
+
+    lookup_path = talloc_strdup(tmp_ctx, object_path);
+    if (lookup_path == NULL) {
+        goto done;
+    }
+
+    while (lookup_path != NULL) {
+        key.type = HASH_KEY_STRING;
+        key.str = lookup_path;
+
+        hret = hash_lookup(table, &key, &value);
+        if (hret == HASH_SUCCESS) {
+            list = talloc_get_type(value.ptr, struct sbus_interface_list);
+            iface = sbus_iface_list_lookup(list, iface_name);
+            if (iface != NULL) {
+                goto done;
+            }
+        } else if (hret != HASH_ERROR_KEY_NOT_FOUND) {
+            DEBUG(SSSDBG_OP_FAILURE,
+                  "Unable to search hash table: hret=%d\n", hret);
+            iface = NULL;
+            goto done;
+        }
+
+        /* we will not free lookup path since it is freed with tmp_ctx
+         * and the object paths are supposed to be small */
+        lookup_path = sbus_opath_parent_subtree(tmp_ctx, lookup_path);
     }
 
-    return strcmp(path, intf_p->intf->path) == 0;
+done:
+    talloc_free(tmp_ctx);
+    return iface;
 }
 
 static struct sbus_interface *
@@ -157,67 +339,227 @@ sbus_new_interface(TALLOC_CTX *mem_ctx,
     return intf;
 }
 
-int sbus_conn_register_iface(struct sbus_connection *conn,
-                             struct sbus_vtable *iface_vtable,
-                             const char *object_path,
-                             void *pvt)
+static DBusHandlerResult
+sbus_message_handler(DBusConnection *dbus_conn,
+                     DBusMessage *message,
+                     void *user_data);
+
+static errno_t
+sbus_conn_register_path(struct sbus_connection *conn,
+                        const char *path)
 {
-    struct sbus_interface_p *intf_p;
-    struct sbus_interface *intf;
+    static DBusObjectPathVTable vtable = {NULL, sbus_message_handler,
+                                          NULL, NULL, NULL, NULL};
+    DBusError error;
+    char *reg_path = NULL;
     dbus_bool_t dbret;
-    const char *path;
-    bool fallback;
 
-    intf = sbus_new_interface(conn, object_path, iface_vtable, pvt);
-    if (intf == NULL) {
-        return ENOMEM;
-    }
-
-    if (!conn || !intf->vtable || !intf->vtable->meta) {
-        return EINVAL;
-    }
-
-    path = intf->path;
-    fallback = sbus_opath_is_subtree(path);
-
-    if (path_in_interface_list(conn->intf_list, path)) {
-        DEBUG(SSSDBG_FATAL_FAILURE,
-              "Cannot add method context with identical path.\n");
-        return EINVAL;
-    }
-
-    intf_p = talloc_zero(conn, struct sbus_interface_p);
-    if (!intf_p) {
-        return ENOMEM;
-    }
-    intf_p->conn = conn;
-    intf_p->intf = intf;
-    intf_p->reg_path = sbus_opath_get_base_path(intf_p, path);
-    if (intf_p->reg_path == NULL) {
-        return ENOMEM;
-    }
-
-    DLIST_ADD(conn->intf_list, intf_p);
-
-    DEBUG(SSSDBG_TRACE_LIBS, "Will register path %s with%s fallback\n",
-                             intf_p->reg_path, fallback ? "" : "out");
-
-    if (fallback) {
-        dbret = dbus_connection_register_fallback(conn->dbus.conn,
-                                                  intf_p->reg_path,
-                                                  &dbus_object_path_vtable,
-                                                  intf_p);
+    DEBUG(SSSDBG_TRACE_FUNC, "Registering object path %s with D-Bus "
+          "connection\n", path);
+
+    if (sbus_opath_is_subtree(path)) {
+        reg_path = sbus_opath_get_base_path(conn, path);
+        if (reg_path == NULL) {
+            return ENOMEM;
+        }
+
+        /* D-Bus does not allow to have both object path and fallback
+         * registered. Since we handle the real message handlers ourselves
+         * we will register fallback only in this case. */
+        if (sbus_opath_hash_has_path(conn->managed_paths, reg_path)) {
+            dbus_connection_unregister_object_path(conn->dbus.conn, reg_path);
+        }
+
+        dbret = dbus_connection_register_fallback(conn->dbus.conn, reg_path,
+                                                  &vtable, conn);
+        talloc_free(reg_path);
     } else {
-        dbret = dbus_connection_register_object_path(conn->dbus.conn,
-                                                     intf_p->reg_path,
-                                                     &dbus_object_path_vtable,
-                                                     intf_p);
+        dbus_error_init(&error);
+
+        dbret = dbus_connection_try_register_object_path(conn->dbus.conn, path,
+                                                         &vtable, conn, &error);
+
+        if (dbus_error_is_set(&error) &&
+                strcmp(error.name, DBUS_ERROR_OBJECT_PATH_IN_USE) == 0) {
+            /* A fallback is probably already registered. Just return. */
+            dbus_error_free(&error);
+            return EOK;
+        }
     }
+
     if (!dbret) {
-        DEBUG(SSSDBG_FATAL_FAILURE,
-              "Could not register object path to the connection.\n");
+        DEBUG(SSSDBG_FATAL_FAILURE, "Unable to register object path "
+              "%s with D-Bus connection.\n", path);
         return ENOMEM;
     }
 
     return EOK;
 }
+
+errno_t
+sbus_conn_register_iface(struct sbus_connection *conn,
+                         struct sbus_vtable *iface_vtable,
+                         const char *object_path,
+                         void *pvt)
+{
+    struct sbus_interface *iface = NULL;
+    bool path_known;
+    errno_t ret;
+
+    if (conn == NULL || iface_vtable == NULL || object_path == NULL) {
+        return EINVAL;
+    }
+
+    iface = sbus_new_interface(conn, object_path, iface_vtable, pvt);
+    if (iface == NULL) {
+        return ENOMEM;
+    }
+
+    ret = sbus_opath_hash_add_iface(conn->managed_paths, object_path, iface,
+                                    &path_known);
+    if (ret != EOK) {
+        talloc_free(iface);
+        return ret;
+    }
+
+    if (path_known) {
+        /* this object path is already registered */
+        return EOK;
+    }
+
+    ret = sbus_conn_register_path(conn, object_path);
+
+    /* if ret != EOK we will still leave iface in the table, since
+     * we probably don't have enough memory to remove it correctly anyway */
+    return ret;
+}
+
+errno_t
+sbus_conn_reregister_paths(struct sbus_connection *conn)
+{
+    hash_key_t *keys = NULL;
+    unsigned long count;
+    unsigned long i;
+    errno_t ret;
+    int hret;
+
+    hret = hash_keys(conn->managed_paths, &count, &keys);
+    if (hret != HASH_SUCCESS) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    for (i = 0; i < count; i++) {
+        ret = sbus_conn_register_path(conn, keys[i].str);
+        if (ret != EOK) {
+            goto done;
+        }
+    }
+
+    ret = EOK;
+
+done:
+    talloc_free(keys);
+    return ret;
+}
+
+static void
+sbus_message_handler_got_caller_id(struct tevent_req *req);
+
+static DBusHandlerResult
+sbus_message_handler(DBusConnection *dbus_conn,
+                     DBusMessage *message,
+                     void *user_data)
+{
+    struct tevent_req *req;
+    struct sbus_connection *conn;
+    struct sbus_interface *iface;
+    struct sbus_request *sbus_req;
+    const struct sbus_method_meta *method;
+    const char *iface_name;
+    const char *method_name;
+    const char *path;
+    const char *sender;
+
+    conn = talloc_get_type(user_data, struct sbus_connection);
+
+    /* header information */
+    iface_name = dbus_message_get_interface(message);
+    method_name = dbus_message_get_member(message);
+    path = dbus_message_get_path(message);
+    sender = dbus_message_get_sender(message);
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Received SBUS method %s.%s on path %s\n",
+          iface_name, method_name, path);
+
+    /* try to find the interface */
+    iface = sbus_opath_hash_lookup_iface(conn->managed_paths,
+                                         path, iface_name);
+    if (iface == NULL) {
+        goto fail;
+    }
+
+    method = sbus_meta_find_method(iface->vtable->meta, method_name);
+    if (method == NULL || method->vtable_offset == 0) {
+        goto fail;
+    }
+
+    /* we have a valid handler, create D-Bus request */
+    sbus_req = sbus_new_request(conn, iface, message);
+    if (sbus_req == NULL) {
+        return DBUS_HANDLER_RESULT_NEED_MEMORY;
+    }
+
+    sbus_req->method = method;
+
+    /* now get the sender ID */
+    req = sbus_get_sender_id_send(sbus_req, conn->ev, conn, sender);
+    if (req == NULL) {
+        talloc_free(sbus_req);
+        return DBUS_HANDLER_RESULT_NEED_MEMORY;
+    }
+    tevent_req_set_callback(req, sbus_message_handler_got_caller_id, sbus_req);
+
+    return DBUS_HANDLER_RESULT_HANDLED;
+
+fail: ;
+    DBusMessage *reply;
+
+    DEBUG(SSSDBG_CRIT_FAILURE, "No matching handler found for method %s.%s "
+          "on path %s\n", iface_name, method_name, path);
+
+    reply = dbus_message_new_error(message, DBUS_ERROR_UNKNOWN_METHOD, NULL);
+    sbus_conn_send_reply(conn, reply);
+
+    return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static void
+sbus_message_handler_got_caller_id(struct tevent_req *req)
+{
+    struct sbus_request *sbus_req;
+    const struct sbus_method_meta *method;
+    sbus_msg_handler_fn handler;
+    sbus_method_invoker_fn invoker;
+    void *pvt;
+    DBusError *error;
+    errno_t ret;
+
+    sbus_req = tevent_req_callback_data(req, struct sbus_request);
+    method = sbus_req->method;
+
+    ret = sbus_get_sender_id_recv(req, &sbus_req->client);
+    if (ret != EOK) {
+        error = sbus_error_new(sbus_req, DBUS_ERROR_FAILED, "Failed to "
+                               "resolve caller's ID: %s\n", sss_strerror(ret));
+        sbus_request_fail_and_finish(sbus_req, error);
+        return;
+    }
+
+    handler = VTABLE_FUNC(sbus_req->intf->vtable, method->vtable_offset);
+    invoker = method->invoker;
+    pvt = sbus_req->intf->instance_data;
+
+    sbus_request_invoke_or_finish(sbus_req, handler, pvt, invoker);
+    return;
+}
diff --git a/src/sbus/sssd_dbus_private.h b/src/sbus/sssd_dbus_private.h
index fc63405a13d417a17aa6b9c4803e74f2f165e494..23d2898f8377e188f94d2356e290626439dc763e 100644
--- a/src/sbus/sssd_dbus_private.h
+++ b/src/sbus/sssd_dbus_private.h
@@ -37,14 +37,6 @@ enum dbus_conn_type {
 
 struct sbus_watch_ctx;
 
-struct sbus_interface_p {
-    struct sbus_interface_p *prev, *next;
-    struct sbus_connection *conn;
-    struct sbus_interface *intf;
-
-    const char *reg_path;
-};
-
 struct sbus_connection {
     struct tevent_context *ev;
 
@@ -55,8 +47,7 @@ struct sbus_connection {
     int connection_type;
     int disconnect;
 
-    /* dbus tables and handlers */
-    struct sbus_interface_p *intf_list;
+    hash_table_t *managed_paths;
 
     /* reconnect settings */
     int retries;
@@ -75,8 +66,6 @@ struct sbus_connection {
     struct sbus_watch_ctx *watch_list;
 };
 
-extern DBusObjectPathVTable dbus_object_path_vtable;
-
 /* =Watches=============================================================== */
 
 struct sbus_watch_ctx {
@@ -114,9 +103,10 @@ sbus_new_request(struct sbus_connection *conn, struct sbus_interface *intf,
 
 /* =Interface=and=object=paths============================================ */
 
-void sbus_unreg_object_paths(struct sbus_connection *conn);
-bool sbus_iface_handles_path(struct sbus_interface_p *intf_p,
-                             const char *path);
+errno_t
+sbus_opath_hash_init(TALLOC_CTX *mem_ctx,
+                     struct sbus_connection *conn,
+                     hash_table_t **_table);
 
 /* =Interface=introspection=============================================== */
 extern const struct sbus_method_meta introspect_method;
diff --git a/src/tests/sbus_codegen_tests.c b/src/tests/sbus_codegen_tests.c
index dccb5e3147e61c3d6f40137927f8724804a37b97..07c62c5ef53dd8ff1ad33611762e530475bdf835 100644
--- a/src/tests/sbus_codegen_tests.c
+++ b/src/tests/sbus_codegen_tests.c
@@ -1353,9 +1353,11 @@ TCase *create_handler_tests(void)
     TCase *tc = tcase_create("handler");
 
     tcase_add_test(tc, test_marshal_basic_types);
+#if false
     tcase_add_test(tc, test_get_basic_types);
     tcase_add_test(tc, test_getall_basic_types);
     tcase_add_test(tc, test_get_basic_array_types);
+#endif
 
     return tc;
 }
diff --git a/src/tests/sbus_tests.c b/src/tests/sbus_tests.c
index 486e39221c619414c315399b1257d417bea44d77..6086a42bfc8b53ae0a7047345b509d515abab38f 100644
--- a/src/tests/sbus_tests.c
+++ b/src/tests/sbus_tests.c
@@ -372,7 +372,9 @@ TCase *create_sbus_tests(void)
     tcase_add_test(tc, test_raw_handler);
     tcase_add_test(tc, test_request_parse_ok);
     tcase_add_test(tc, test_request_parse_bad_args);
+#if false
     tcase_add_test(tc, test_introspection);
+#endif
     tcase_add_test(tc, test_sbus_new_error);
 
     return tc;
-- 
1.9.3

