summaryrefslogtreecommitdiffstats
path: root/src/sbus/server
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 05:31:45 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 05:31:45 +0000
commit74aa0bc6779af38018a03fd2cf4419fe85917904 (patch)
tree9cb0681aac9a94a49c153d5823e7a55d1513d91f /src/sbus/server
parentInitial commit. (diff)
downloadsssd-74aa0bc6779af38018a03fd2cf4419fe85917904.tar.xz
sssd-74aa0bc6779af38018a03fd2cf4419fe85917904.zip
Adding upstream version 2.9.4.upstream/2.9.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/sbus/server')
-rw-r--r--src/sbus/server/sbus_server.c778
-rw-r--r--src/sbus/server/sbus_server_handler.c190
-rw-r--r--src/sbus/server/sbus_server_interface.c429
-rw-r--r--src/sbus/server/sbus_server_match.c450
4 files changed, 1847 insertions, 0 deletions
diff --git a/src/sbus/server/sbus_server.c b/src/sbus/server/sbus_server.c
new file mode 100644
index 0000000..9c9ddc8
--- /dev/null
+++ b/src/sbus/server/sbus_server.c
@@ -0,0 +1,778 @@
+/*
+ Authors:
+ Pavel Březina <pbrezina@redhat.com>
+ Stephen Gallagher <sgallagh@redhat.com>
+ Simo Sorce <ssorce@redhat.com>
+
+ Copyright (C) 2017 Red Hat
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include <tevent.h>
+#include <talloc.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <dbus/dbus.h>
+
+#include "util/util.h"
+#include "util/sss_ptr_hash.h"
+#include "sbus/sbus_private.h"
+
+struct sbus_server_on_connection {
+ const char *name;
+ sbus_server_on_connection_cb callback;
+ sbus_server_on_connection_data data;
+};
+
+static const char *
+sbus_server_get_filename(const char *address)
+{
+ const char *filename;
+
+ filename = strchr(address, '/');
+ if (filename == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected dbus address [%s].\n", address);
+ return NULL;
+ }
+
+ return filename;
+}
+
+static const char *
+sbus_server_get_socket_address(TALLOC_CTX *mem_ctx,
+ const char *address,
+ bool use_symlink)
+{
+ unsigned long pid;
+
+ if (!use_symlink) {
+ return talloc_strdup(mem_ctx, address);
+ }
+
+ pid = getpid();
+ return talloc_asprintf(mem_ctx, "%s.%lu", address, pid);
+}
+
+static errno_t
+sbus_server_get_socket(TALLOC_CTX *mem_ctx,
+ const char *address,
+ bool use_symlink,
+ const char **_socket_address,
+ const char **_filename,
+ const char **_symlink)
+{
+ const char *symlink = NULL;
+ const char *socket_address;
+ const char *filename;
+
+ /* Get D-Bus socket address. */
+ socket_address = sbus_server_get_socket_address(mem_ctx, address,
+ use_symlink);
+ if (socket_address == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n");
+ return ENOMEM;
+ }
+
+ /* Get system files names. */
+ filename = sbus_server_get_filename(socket_address);
+ if (filename == NULL) {
+ return EINVAL;
+ }
+
+ if (use_symlink) {
+ symlink = sbus_server_get_filename(address);
+ if (symlink == NULL) {
+ return EINVAL;
+ }
+ }
+
+ if (_socket_address != NULL) {
+ *_socket_address = socket_address;
+ }
+
+ if (_filename != NULL) {
+ *_filename = filename;
+ }
+
+ if (_symlink != NULL) {
+ *_symlink = symlink;
+ }
+
+ return EOK;
+}
+
+static DBusServer *
+sbus_server_socket_listen(const char *socket_address)
+{
+ DBusServer *server;
+ DBusError error;
+ char *server_address;
+
+ dbus_error_init(&error);
+
+ server = dbus_server_listen(socket_address, &error);
+ if (server == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to start a D-Bus server at "
+ "%s [%s]: %s\n", socket_address, error.name, error.message);
+ } else {
+ server_address = dbus_server_get_address(server);
+ DEBUG(SSSDBG_TRACE_FUNC, "D-BUS Server listening on %s\n", server_address);
+ free(server_address);
+ }
+
+ dbus_error_free(&error);
+
+ return server;
+}
+
+static errno_t
+sbus_server_symlink_create(const char *filename,
+ const char *symlink_filename)
+{
+ errno_t ret;
+
+ if (symlink_filename == NULL) {
+ return EINVAL;
+ }
+
+ DEBUG(SSSDBG_TRACE_LIBS, "Symlinking the dbus path %s to a link %s\n",
+ filename, symlink_filename);
+ errno = 0;
+ ret = symlink(filename, symlink_filename);
+ if (ret != 0 && errno == EEXIST) {
+ /* Perhaps cruft after a previous server? */
+ errno = 0;
+ ret = unlink(symlink_filename);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Cannot remove old symlink '%s': [%d][%s].\n",
+ symlink_filename, ret, strerror(ret));
+ return EIO;
+ }
+ errno = 0;
+ ret = symlink(filename, symlink_filename);
+ }
+
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE, "symlink() failed on file '%s': [%d][%s].\n",
+ filename, ret, strerror(ret));
+ return EIO;
+ }
+
+ return EOK;
+}
+
+static errno_t
+sbus_server_symlink_read(const char *name, char *buf, size_t buf_len)
+{
+ ssize_t num_read = 0;
+ errno_t ret;
+
+ errno = 0;
+ num_read = readlink(name, buf, buf_len - 1);
+ if (num_read < 0) {
+ ret = errno;
+ DEBUG(SSSDBG_OP_FAILURE, "Unable to read link target [%d]: %s\n",
+ ret, sss_strerror(ret));
+ return ret;
+ }
+
+ buf[num_read] = '\0';
+
+ return EOK;
+}
+
+static errno_t
+sbus_server_symlink_pidpath(const char *name, char *buf, size_t buf_len)
+{
+ int ret;
+
+ ret = snprintf(buf, buf_len, "%s.%lu", name, (unsigned long)getpid());
+ if (ret < 0) {
+ DEBUG(SSSDBG_OP_FAILURE, "snprintf failed\n");
+ return EIO;
+ } else if (ret >= PATH_MAX) {
+ DEBUG(SSSDBG_OP_FAILURE, "path too long?!?!\n");
+ return EIO;
+ }
+
+ return EOK;
+}
+
+static void
+sbus_server_symlink_remove(const char *name)
+{
+ char target[PATH_MAX];
+ char pidpath[PATH_MAX];
+ errno_t ret;
+
+ ret = sbus_server_symlink_read(name, target, PATH_MAX);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL, "The symlink points to [%s]\n", target);
+
+ ret = sbus_server_symlink_pidpath(name, pidpath, PATH_MAX);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL, "The path including our pid is [%s]\n", pidpath);
+
+ /* We can only remove the symlink if it points to
+ * a socket with the same PID. */
+
+ if (strcmp(pidpath, target) != 0) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "Will not remove symlink, seems to be "
+ "owned by another process\n");
+ ret = ERR_INTERNAL;
+ goto done;
+ }
+
+ ret = unlink(name);
+ if (ret != 0) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE, "unlink failed to remove [%s] [%d]: %s\n",
+ name, ret, sss_strerror(ret));
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Unable to remove symlink [%s]\n", name);
+ return;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Symlink removed [%s]\n", name);
+}
+
+static errno_t
+sbus_server_check_file(const char *filename, uid_t uid, gid_t gid)
+{
+ struct stat stat_buf;
+ errno_t ret;
+
+ /* Both check_file and chmod can handle both the symlink and the socket */
+ ret = check_file(filename, getuid(), getgid(), S_IFSOCK, S_IFMT,
+ &stat_buf, true);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "check_file failed for [%s].\n", filename);
+ return ret;
+ }
+
+ if ((stat_buf.st_mode & ~S_IFMT) != (S_IRUSR | S_IWUSR)) {
+ ret = chmod(filename, (S_IRUSR | S_IWUSR));
+ if (ret != EOK) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE, "chmod failed for [%s] [%d]: %s\n",
+ filename, ret, sss_strerror(ret));
+ return ret;
+ }
+ }
+
+ if (stat_buf.st_uid != uid || stat_buf.st_gid != gid) {
+ ret = chown(filename, uid, gid);
+ if (ret != EOK) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE, "chown failed for [%s] [%d]: %s\n",
+ filename, ret, sss_strerror(ret));
+ return ret;
+ }
+ }
+
+ return EOK;
+}
+
+static DBusServer *
+sbus_server_setup_dbus(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *address,
+ bool use_symlink,
+ uid_t uid,
+ gid_t gid,
+ const char **_symlink)
+{
+ TALLOC_CTX *tmp_ctx;
+ DBusServer *dbus_server = NULL;
+ bool symlink_created = false;
+ const char *symlink = NULL;
+ const char *socket_address;
+ const char *filename;
+ errno_t ret;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n");
+ return NULL;
+ }
+
+ /* Get socket address. */
+ ret = sbus_server_get_socket(tmp_ctx, address, use_symlink,
+ &socket_address, &filename, &symlink);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ /* Start listening on this socket. This will also create the socket. */
+ dbus_server = sbus_server_socket_listen(socket_address);
+ if (dbus_server == NULL) {
+ ret = EIO;
+ goto done;
+ }
+
+ /* Create symlink if requested. */
+ if (use_symlink) {
+ ret = sbus_server_symlink_create(filename, symlink);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Could not create symlink [%d]: %s\n",
+ ret, sss_strerror(ret));
+ ret = EIO;
+ goto done;
+ }
+
+ symlink_created = true;
+ }
+
+ /* Check file permissions and setup proper owner. */
+ ret = sbus_server_check_file(filename, uid, gid);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ if (use_symlink) {
+ *_symlink = talloc_strdup(mem_ctx, symlink);
+ if (*_symlink == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ } else {
+ *_symlink = NULL;
+ }
+
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+
+ if (ret != EOK && dbus_server != NULL) {
+ dbus_server_disconnect(dbus_server);
+ dbus_server_unref(dbus_server);
+
+ if (symlink_created) {
+ sbus_server_symlink_remove(symlink);
+ }
+
+ return NULL;
+ }
+
+ return dbus_server;
+}
+
+static bool
+sbus_server_filter_add(struct sbus_server *server,
+ DBusConnection *dbus_conn)
+{
+ dbus_bool_t dbret;
+
+ /* Add a connection filter that is used to process input messages. */
+ dbret = dbus_connection_add_filter(dbus_conn, sbus_server_filter,
+ server, NULL);
+ if (dbret == false) {
+ return false;
+ }
+
+ return true;
+}
+
+static dbus_bool_t
+sbus_server_check_connection_uid(DBusConnection *dbus_conn,
+ unsigned long uid,
+ void *data)
+{
+ struct sbus_server *sbus_server;
+
+ sbus_server = talloc_get_type(data, struct sbus_server);
+
+ if (uid == 0 || uid == sbus_server->uid) {
+ return true;
+ }
+
+ return false;
+}
+
+static void
+sbus_server_new_connection(DBusServer *dbus_server,
+ DBusConnection *dbus_conn,
+ void *data)
+{
+ struct sbus_server *sbus_server;
+ struct sbus_connection *sbus_conn;
+ dbus_bool_t dbret;
+ errno_t ret;
+ bool bret;
+
+ sbus_server = talloc_get_type(data, struct sbus_server);
+
+ DEBUG(SSSDBG_FUNC_DATA, "Adding connection %p.\n", dbus_conn);
+
+ /* Allow access from uid that is associated with this sbus server. */
+ dbus_connection_set_unix_user_function(dbus_conn,
+ sbus_server_check_connection_uid,
+ sbus_server, NULL);
+
+ /* First, add a message filter that will take care of routing messages
+ * between connections. */
+ bret = sbus_server_filter_add(sbus_server, dbus_conn);
+ if (!bret) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to add server filter!\n");
+ return;
+ }
+
+ /**
+ * @dbus_conn is unreferenced in libdbus by the caller of this new
+ * connection function thus we must not unreference it here. Its
+ * reference counter is increased in @sbus_connection_init.
+ */
+
+ sbus_conn = sbus_connection_init(sbus_server, sbus_server->ev, dbus_conn,
+ NULL, NULL, SBUS_CONNECTION_CLIENT,
+ NULL);
+ if (sbus_conn == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Closing connection, unable to setup\n");
+ dbus_connection_close(dbus_conn);
+ return;
+ }
+
+ dbret = dbus_connection_set_data(dbus_conn, sbus_server->data_slot,
+ sbus_conn, NULL);
+ if (!dbret) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Closing connection, unable to set data\n");
+ talloc_free(sbus_conn);
+ return;
+ }
+
+ if (sbus_server->on_connection->callback != NULL) {
+ ret = sbus_server->on_connection->callback(sbus_conn,
+ sbus_server->on_connection->data);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Closing connection, new connection "
+ "callback failed [%d]: %s\n", ret, sss_strerror(ret));
+ talloc_free(sbus_conn);
+ return;
+ }
+ }
+}
+
+static errno_t
+sbus_server_tevent_enable(struct sbus_server *server)
+{
+ errno_t ret;
+
+ ret = sbus_watch_server(server, server->ev, server->server,
+ &server->watch_ctx);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup D-Bus watch [%d]: %s\n",
+ ret, sss_strerror(ret));
+ return ret;
+ }
+
+ /* Set function that is called each time a new client is connected. */
+ dbus_server_set_new_connection_function(server->server,
+ sbus_server_new_connection,
+ server, NULL);
+
+ return EOK;
+}
+
+static void
+sbus_server_tevent_disable(struct sbus_server *server)
+{
+ dbus_server_set_new_connection_function(server->server, NULL, NULL, NULL);
+ talloc_zfree(server->watch_ctx);
+}
+
+static void
+sbus_server_name_owner_changed(struct sbus_server *server,
+ const char *name,
+ const char *new_owner,
+ const char *old_owner)
+{
+ DBusMessage *message;
+
+ /* We can't really send signals when the server is being destroyed. */
+ if (server == NULL || server->disconnecting) {
+ return;
+ }
+
+ message = sbus_signal_create(NULL, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS,
+ "NameOwnerChanged",
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_STRING, &new_owner,
+ DBUS_TYPE_STRING, &old_owner);
+ if (message == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n");
+ return;
+ }
+
+ dbus_message_set_sender(message, DBUS_SERVICE_DBUS);
+
+ /* Send the signal. */
+ sbus_server_matchmaker(server, NULL, name, message);
+}
+
+void
+sbus_server_name_acquired(struct sbus_server *server,
+ struct sbus_connection *conn,
+ const char *name)
+{
+ DBusMessage *message;
+
+ message = sbus_signal_create(NULL, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS,
+ "NameAcquired", DBUS_TYPE_STRING, &name);
+ if (message == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n");
+ return;
+ }
+
+ dbus_message_set_sender(message, DBUS_SERVICE_DBUS);
+ dbus_message_set_destination(message, conn->unique_name);
+ dbus_connection_send(conn->connection, message, NULL);
+
+ sbus_server_name_owner_changed(server, name, name, "");
+}
+
+void
+sbus_server_name_lost(struct sbus_server *server,
+ struct sbus_connection *conn,
+ const char *name)
+{
+ DBusMessage *message;
+
+ if (name[0] == ':') {
+ /* The connection is being terminated. Do not send the signal. */
+ return;
+ }
+
+ message = sbus_signal_create(NULL, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS,
+ "NameLost", DBUS_TYPE_STRING, &name);
+ if (message == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n");
+ return;
+ }
+
+ dbus_message_set_sender(message, DBUS_SERVICE_DBUS);
+ dbus_message_set_destination(message, conn->unique_name);
+ dbus_connection_send(conn->connection, message, NULL);
+
+ sbus_server_name_owner_changed(server, name, "", name);
+}
+
+static void
+sbus_server_name_remove_from_table_cb(hash_entry_t *item,
+ hash_destroy_enum type,
+ void *pvt)
+{
+ struct sbus_server *server;
+ const char *name;
+
+ /* We can't really send signals when the server is being destroyed. */
+ if (type == HASH_TABLE_DESTROY) {
+ return;
+ }
+
+ server = talloc_get_type(pvt, struct sbus_server);
+ name = item->key.str;
+
+ sbus_server_name_owner_changed(server, name, "", name);
+}
+
+static int sbus_server_destructor(struct sbus_server *server)
+{
+ if (server->server == NULL) {
+ return 0;
+ }
+
+ server->disconnecting = true;
+
+ /* Remove tevent integration first. */
+ sbus_server_tevent_disable(server);
+
+ if (server->data_slot != -1) {
+ dbus_connection_free_data_slot(&server->data_slot);
+ }
+
+ /* Release server. */
+ dbus_server_disconnect(server->server);
+ dbus_server_unref(server->server);
+
+ if (server->symlink != NULL) {
+ sbus_server_symlink_remove(server->symlink);
+ }
+
+ return 0;
+}
+
+struct sbus_server *
+sbus_server_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *address,
+ bool use_symlink,
+ uint32_t max_connections,
+ uid_t uid,
+ gid_t gid,
+ sbus_server_on_connection_cb on_conn_cb,
+ sbus_server_on_connection_data on_conn_data)
+{
+ DBusServer *dbus_server;
+ struct sbus_server *sbus_server;
+ const char *symlink;
+ dbus_bool_t dbret;
+ errno_t ret;
+
+ sbus_server = talloc_zero(mem_ctx, struct sbus_server);
+ if (sbus_server == NULL) {
+ return NULL;
+ }
+
+ sbus_server->data_slot = -1;
+ talloc_set_destructor(sbus_server, sbus_server_destructor);
+
+ dbus_server = sbus_server_setup_dbus(sbus_server, ev, address,
+ use_symlink, uid, gid, &symlink);
+ if (dbus_server == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup a D-Bus server!\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ sbus_server->ev = ev;
+ sbus_server->server = dbus_server;
+ sbus_server->symlink = talloc_steal(sbus_server, symlink);
+ sbus_server->max_connections = max_connections;
+ sbus_server->name.major = 1;
+ sbus_server->name.minor = 0;
+ sbus_server->uid = uid;
+ sbus_server->gid = gid;
+
+ sbus_server->on_connection = talloc_zero(sbus_server,
+ struct sbus_server_on_connection);
+ if (sbus_server->on_connection == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ if (on_conn_cb != NULL) {
+ _sbus_server_set_on_connection(sbus_server, "on-connection", on_conn_cb,
+ on_conn_data);
+ }
+
+ sbus_server->names = sss_ptr_hash_create(sbus_server,
+ sbus_server_name_remove_from_table_cb, sbus_server);
+ if (sbus_server->names == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ sbus_server->match_rules = sss_ptr_hash_create(sbus_server, NULL, NULL);
+ if (sbus_server->match_rules == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ sbus_server->router = sbus_router_init(sbus_server, NULL);
+ if (sbus_server->router == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = sbus_server_setup_interface(sbus_server);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup bus interface [%d]: %s\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ dbret = dbus_connection_allocate_data_slot(&sbus_server->data_slot);
+ if (!dbret) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = sbus_server_tevent_enable(sbus_server);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to integrate with tevent [%d]: %s\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(sbus_server);
+ return NULL;
+ }
+
+ return sbus_server;
+}
+
+struct sbus_connection *
+sbus_server_find_connection(struct sbus_server *server, const char *name)
+{
+ return sss_ptr_hash_lookup(server->names, name, struct sbus_connection);
+}
+
+void
+_sbus_server_set_on_connection(struct sbus_server *server,
+ const char *name,
+ sbus_server_on_connection_cb on_connection_cb,
+ sbus_server_on_connection_data data)
+{
+ if (server == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Bug: server is NULL\n");
+ return;
+ }
+
+ if (name == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Bug: name is NULL\n");
+ return;
+ }
+
+ if (on_connection_cb == NULL) {
+ DEBUG(SSSDBG_TRACE_FUNC, "Unsetting on connectoin callback\n");
+ server->on_connection->callback = NULL;
+ server->on_connection->data = NULL;
+ server->on_connection->name = NULL;
+ return;
+ }
+
+ if (server->on_connection->callback != NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Bug: on connection callback is "
+ "already set to %s\n", server->on_connection->name);
+ return;
+ }
+
+ server->on_connection->callback = on_connection_cb;
+ server->on_connection->data = data;
+ server->on_connection->name = name;
+}
diff --git a/src/sbus/server/sbus_server_handler.c b/src/sbus/server/sbus_server_handler.c
new file mode 100644
index 0000000..d4e4547
--- /dev/null
+++ b/src/sbus/server/sbus_server_handler.c
@@ -0,0 +1,190 @@
+/*
+ Authors:
+ Pavel Březina <pbrezina@redhat.com>
+ Stephen Gallagher <sgallagh@redhat.com>
+ Simo Sorce <ssorce@redhat.com>
+
+ Copyright (C) 2017 Red Hat
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <errno.h>
+#include <string.h>
+#include <tevent.h>
+#include <talloc.h>
+#include <dbus/dbus.h>
+
+#include "util/util.h"
+#include "util/sss_ptr_hash.h"
+#include "sbus/sbus_private.h"
+
+static DBusHandlerResult
+sbus_server_resend_message(struct sbus_server *server,
+ struct sbus_connection *conn,
+ DBusMessage *message,
+ const char *destination)
+{
+ struct sbus_connection *destconn;
+
+ destconn = sbus_server_find_connection(server, destination);
+ if (destconn == NULL) {
+ DEBUG(SSSDBG_TRACE_ALL, "Trying to send a message to an unknown "
+ "destination: %s\n", destination);
+ sbus_reply_error(conn, message, DBUS_ERROR_SERVICE_UNKNOWN, destination);
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+
+ /* Message is unreferenced by libdbus. */
+ dbus_connection_send(destconn->connection, message, NULL);
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+DBusHandlerResult
+sbus_server_route_signal(struct sbus_server *server,
+ struct sbus_connection *conn,
+ DBusMessage *message,
+ const char *destination)
+{
+ errno_t ret;
+
+ /* If a destination is set (unusual but possible) we simply send the
+ * signal to its desired destination. */
+ if (destination != NULL) {
+ return sbus_server_resend_message(server, conn, message, destination);
+ }
+
+ /* Otherwise we need to send it to all connections that listen to it. */
+ ret = sbus_server_matchmaker(server, conn, NULL, message);
+ if (ret == EOK) {
+ return DBUS_HANDLER_RESULT_HANDLED;
+ } else if (ret != ENOENT) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send signal [%d]: %s\n",
+ ret, sss_strerror(ret));
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static DBusHandlerResult
+sbus_server_route_message(struct sbus_server *server,
+ struct sbus_connection *conn,
+ DBusMessage *message,
+ const char *destination)
+{
+ if (strcmp(destination, DBUS_SERVICE_DBUS) == 0) {
+ /* This message is addressed to D-Bus service. We must reply to it. */
+ return sbus_router_filter(conn, server->router, message);
+ }
+
+ return sbus_server_resend_message(server, conn, message, destination);
+}
+
+static bool
+sbus_server_check_access(struct sbus_connection *conn,
+ DBusMessage *message)
+{
+ const char *destination;
+ const char *interface;
+ const char *member;
+ int type;
+
+ /* Connection must first obtain its unique name through Hello method. */
+ if (conn->unique_name != NULL) {
+ return true;
+ }
+
+ destination = dbus_message_get_destination(message);
+ interface = dbus_message_get_interface(message);
+ member = dbus_message_get_member(message);
+ type = dbus_message_get_type(message);
+
+ if (type != DBUS_MESSAGE_TYPE_METHOD_CALL) {
+ return false;
+ }
+
+ if (strcmp(destination, DBUS_SERVICE_DBUS) != 0) {
+ return false;
+ }
+
+ if (strcmp(interface, DBUS_INTERFACE_DBUS) != 0) {
+ return false;
+ }
+
+ if (strcmp(member, "Hello") != 0) {
+ return false;
+ }
+
+ return true;
+}
+
+DBusHandlerResult
+sbus_server_filter(DBusConnection *dbus_conn,
+ DBusMessage *message,
+ void *handler_data)
+{
+ struct sbus_server *server;
+ struct sbus_connection *conn;
+ const char *destination;
+ const char *sender;
+ dbus_bool_t dbret;
+ int type;
+
+ server = talloc_get_type(handler_data, struct sbus_server);
+
+ /* We can't really send signals when the server is being destroyed. */
+ if (server == NULL || server->disconnecting) {
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+
+ conn = dbus_connection_get_data(dbus_conn, server->data_slot);
+ if (conn == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unknown connection!\n");
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+
+ if (!sbus_server_check_access(conn, message)) {
+ sbus_reply_error(conn, message, DBUS_ERROR_ACCESS_DENIED,
+ "Connection did not call org.freedesktop.DBus.Hello");
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+
+ /* We always require a sender but it may not be assigned yet. We prefer
+ * well known name if set. */
+ sender = sbus_connection_get_name(conn);
+ dbret = dbus_message_set_sender(message, sender);
+ if (!dbret) {
+ sbus_reply_error(conn, message, DBUS_ERROR_FAILED,
+ "Unable to set sender");
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+
+ /* Set sender may reallocate internal fields so this needs to be read
+ * after we call dbus_message_set_sender(). */
+ destination = dbus_message_get_destination(message);
+ type = dbus_message_get_type(message);
+
+ if (type == DBUS_MESSAGE_TYPE_SIGNAL) {
+ return sbus_server_route_signal(server, conn, message, destination);
+ }
+
+ /* We do not allow method calls without destination. */
+ if (destination == NULL) {
+ sbus_reply_error(conn, message, DBUS_ERROR_FAILED,
+ "Non-signal multicast calls are not supported");
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+
+ return sbus_server_route_message(server, conn, message, destination);
+}
diff --git a/src/sbus/server/sbus_server_interface.c b/src/sbus/server/sbus_server_interface.c
new file mode 100644
index 0000000..9c0ba0a
--- /dev/null
+++ b/src/sbus/server/sbus_server_interface.c
@@ -0,0 +1,429 @@
+/*
+ Authors:
+ Pavel Březina <pbrezina@redhat.com>
+ Stephen Gallagher <sgallagh@redhat.com>
+ Simo Sorce <ssorce@redhat.com>
+
+ Copyright (C) 2017 Red Hat
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <errno.h>
+#include <dhash.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <string.h>
+#include <tevent.h>
+#include <talloc.h>
+#include <dbus/dbus.h>
+
+#include "util/util.h"
+#include "util/sss_ptr_hash.h"
+#include "sbus/sbus_private.h"
+#include "sbus/interface_dbus/sbus_dbus_server.h"
+
+static errno_t
+sbus_server_bus_hello(TALLOC_CTX *mem_ctx,
+ struct sbus_request *sbus_req,
+ struct sbus_server *server,
+ const char **_out)
+{
+ struct sbus_connection *conn;
+ uint32_t attempts;
+ errno_t ret;
+ char *name;
+
+ /* Generation of unique names is inspired by libdbus source:
+ * create_unique_client_name() from bus/driver.c */
+
+ conn = sbus_req->conn;
+ if (conn->unique_name != NULL) {
+ return EEXIST;
+ }
+
+ for (attempts = 0; attempts < server->max_connections; attempts++) {
+ server->name.minor++;
+ if (server->name.minor == 0) {
+ /* Overflow of minor version. Increase major version. */
+ server->name.major++;
+ server->name.minor = 1;
+ if (server->name.major == 0) {
+ /* Overflow of major version. D-Bus would die here,
+ * we will just start over. */
+ server->name.major = 1;
+ server->name.minor = 0;
+ continue;
+ }
+ }
+
+ name = talloc_asprintf(NULL, ":%u.%u",
+ server->name.major, server->name.minor);
+ if (name == NULL) {
+ return ENOMEM;
+ }
+
+ ret = sss_ptr_hash_add(server->names, name, conn,
+ struct sbus_connection);
+ if (ret == EEXIST) {
+ talloc_free(name);
+ continue;
+ }
+
+ DEBUG(SSSDBG_TRACE_ALL, "Assigning unique name %s to connection %p\n",
+ name, conn);
+
+ conn->unique_name = talloc_steal(conn, name);
+ sbus_server_name_acquired(server, conn, name);
+ *_out = name;
+
+ return EOK;
+ }
+
+ DEBUG(SSSDBG_CRIT_FAILURE, "Maximum number [%u] of active connections "
+ "has been reached.\n", server->max_connections);
+
+ return ERR_SBUS_CONNECTION_LIMIT;
+}
+
+static errno_t
+sbus_server_bus_request_name(TALLOC_CTX *mem_ctx,
+ struct sbus_request *sbus_req,
+ struct sbus_server *server,
+ const char *name,
+ uint32_t flags,
+ uint32_t *_result)
+{
+ struct sbus_connection *conn;
+ errno_t ret;
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Requesting name: %s\n", name);
+
+ if (name[0] == ':') {
+ DEBUG(SSSDBG_OP_FAILURE, "Can not assign unique name: %s\n", name);
+ return EINVAL;
+ }
+
+ conn = sss_ptr_hash_lookup(server->names, name, struct sbus_connection);
+ if (conn == NULL) {
+ /* We want to remember only the first well known name. */
+ if (sbus_req->conn->wellknown_name == NULL) {
+ ret = sbus_connection_set_name(sbus_req->conn, name);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set well known name "
+ "[%d]: %s\n", ret, sss_strerror(ret));
+ return ret;
+ }
+ }
+
+ ret = sss_ptr_hash_add(server->names, name, sbus_req->conn,
+ struct sbus_connection);
+ if (ret == EOK) {
+ sbus_server_name_acquired(server, sbus_req->conn, name);
+ *_result = DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER;
+ }
+
+ return ret;
+ }
+
+ if (conn == sbus_req->conn) {
+ *_result = DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER;
+ return EOK;
+ }
+
+ *_result = DBUS_REQUEST_NAME_REPLY_EXISTS;
+ return EOK;
+}
+
+static errno_t
+sbus_server_bus_release_name(TALLOC_CTX *mem_ctx,
+ struct sbus_request *sbus_req,
+ struct sbus_server *server,
+ const char *name,
+ uint32_t *_result)
+{
+ struct sbus_connection *conn;
+
+ if (name[0] == ':') {
+ DEBUG(SSSDBG_OP_FAILURE, "Can not release unique name: %s\n", name);
+ return EINVAL;
+ }
+
+ conn = sss_ptr_hash_lookup(server->names, name, struct sbus_connection);
+ if (conn == NULL) {
+ *_result = DBUS_RELEASE_NAME_REPLY_NON_EXISTENT;
+ return EOK;
+ }
+
+ if (conn != sbus_req->conn) {
+ *_result = DBUS_RELEASE_NAME_REPLY_NOT_OWNER;
+ return EOK;
+ }
+
+ sss_ptr_hash_delete(server->names, name, false);
+ sbus_server_name_lost(server, conn, name);
+ *_result = DBUS_RELEASE_NAME_REPLY_RELEASED;
+ return EOK;
+}
+
+static errno_t
+sbus_server_bus_name_has_owner(TALLOC_CTX *mem_ctx,
+ struct sbus_request *sbus_req,
+ struct sbus_server *server,
+ const char *name,
+ bool *_result)
+{
+ struct sbus_connection *conn;
+
+ conn = sss_ptr_hash_lookup(server->names, name, struct sbus_connection);
+ if (conn == NULL) {
+ *_result = false;
+ return EOK;
+ }
+
+ *_result = true;
+ return EOK;
+}
+
+static errno_t
+sbus_server_bus_list_names(TALLOC_CTX *mem_ctx,
+ struct sbus_request *sbus_req,
+ struct sbus_server *server,
+ const char ***_names)
+{
+ hash_key_t *keys;
+ const char **names;
+ unsigned long count;
+ unsigned long i;
+ int hret;
+
+ hret = hash_keys(server->names, &count, &keys);
+ if (hret != HASH_SUCCESS) {
+ return ENOMEM;
+ }
+
+ names = talloc_zero_array(mem_ctx, const char *, count + 2);
+ if (names == NULL) {
+ talloc_free(keys);
+ return ENOMEM;
+ }
+
+ names[0] = DBUS_SERVICE_DBUS;
+ for (i = 1; i < count + 1; i++) {
+ names[i] = keys[i - 1].str;
+ }
+
+ *_names = names;
+
+ talloc_free(keys);
+
+ return EOK;
+}
+
+static errno_t
+sbus_server_bus_list_activatable_names(TALLOC_CTX *mem_ctx,
+ struct sbus_request *sbus_req,
+ struct sbus_server *server,
+ const char ***_names)
+{
+ /* We do not support activatable services. */
+ *_names = NULL;
+
+ return EOK;
+}
+
+static errno_t
+sbus_server_bus_get_name_owner(TALLOC_CTX *mem_ctx,
+ struct sbus_request *sbus_req,
+ struct sbus_server *server,
+ const char *name,
+ const char **_unique_name)
+{
+ struct sbus_connection *conn;
+
+ /* The bus service owns itself. */
+ if (strcmp(name, DBUS_SERVICE_DBUS) == 0) {
+ *_unique_name = DBUS_SERVICE_DBUS;
+ return EOK;
+ }
+
+ conn = sss_ptr_hash_lookup(server->names, name, struct sbus_connection);
+ if (conn == NULL) {
+ return ERR_SBUS_UNKNOWN_OWNER;
+ }
+
+ *_unique_name = conn->unique_name;
+ return EOK;
+}
+
+static errno_t
+sbus_server_bus_list_queued_owners(TALLOC_CTX *mem_ctx,
+ struct sbus_request *sbus_req,
+ struct sbus_server *server,
+ const char *name,
+ const char ***_names)
+{
+ /* We do not support queued name requests. */
+ *_names = NULL;
+
+ return EOK;
+}
+
+static errno_t
+sbus_server_bus_get_connection_unix_user(TALLOC_CTX *mem_ctx,
+ struct sbus_request *sbus_req,
+ struct sbus_server *server,
+ const char *name,
+ uint32_t *_uid)
+{
+ struct sbus_connection *conn;
+ unsigned long uid;
+ dbus_bool_t dbret;
+
+ if (strcmp(name, DBUS_SERVICE_DBUS) == 0) {
+ *_uid = server->uid;
+ return EOK;
+ }
+
+ conn = sss_ptr_hash_lookup(server->names, name, struct sbus_connection);
+ if (conn == NULL) {
+ return ERR_SBUS_UNKNOWN_OWNER;
+ }
+
+ dbret = dbus_connection_get_unix_user(conn->connection, &uid);
+ if (!dbret) {
+ return EIO;
+ }
+
+ *_uid = (uint32_t)uid;
+ return EOK;
+}
+
+static errno_t
+sbus_server_bus_get_connection_unix_process_id(TALLOC_CTX *mem_ctx,
+ struct sbus_request *sbus_req,
+ struct sbus_server *server,
+ const char *name,
+ uint32_t *_pid)
+{
+ struct sbus_connection *conn;
+ unsigned long pid;
+ dbus_bool_t dbret;
+
+ if (strcmp(name, DBUS_SERVICE_DBUS) == 0) {
+ *_pid = getpid();
+ return EOK;
+ }
+
+ conn = sss_ptr_hash_lookup(server->names, name, struct sbus_connection);
+ if (conn == NULL) {
+ return ERR_SBUS_UNKNOWN_OWNER;
+ }
+
+ dbret = dbus_connection_get_unix_process_id(conn->connection, &pid);
+ if (!dbret) {
+ return EIO;
+ }
+
+ *_pid = (uint32_t)pid;
+ return EOK;
+}
+
+static errno_t
+sbus_server_bus_start_service_by_name(TALLOC_CTX *mem_ctx,
+ struct sbus_request *sbus_req,
+ struct sbus_server *server,
+ const char *name,
+ uint32_t flags,
+ uint32_t *_result)
+{
+ struct sbus_connection *conn;
+
+ if (strcmp(name, DBUS_SERVICE_DBUS) == 0) {
+ *_result = DBUS_START_REPLY_ALREADY_RUNNING;
+ return EOK;
+ }
+
+ conn = sss_ptr_hash_lookup(server->names, name, struct sbus_connection);
+ if (conn == NULL) {
+ return ERR_SBUS_UNKNOWN_OWNER;
+ }
+
+ *_result = DBUS_START_REPLY_ALREADY_RUNNING;
+ return EOK;
+}
+
+static errno_t
+sbus_server_bus_add_match(TALLOC_CTX *mem_ctx,
+ struct sbus_request *sbus_req,
+ struct sbus_server *server,
+ const char *rule)
+{
+ return sbus_server_add_match(server, sbus_req->conn, rule);
+}
+
+static errno_t
+sbus_server_bus_remove_match(TALLOC_CTX *mem_ctx,
+ struct sbus_request *sbus_req,
+ struct sbus_server *server,
+ const char *rule)
+{
+ return sbus_server_remove_match(server, sbus_req->conn, rule);
+}
+
+errno_t
+sbus_server_setup_interface(struct sbus_server *server)
+{
+ errno_t ret;
+
+ SBUS_INTERFACE(bus,
+ org_freedesktop_DBus,
+ SBUS_METHODS(
+ SBUS_SYNC(METHOD, org_freedesktop_DBus, Hello, sbus_server_bus_hello, server),
+ SBUS_SYNC(METHOD, org_freedesktop_DBus, RequestName, sbus_server_bus_request_name, server),
+ SBUS_SYNC(METHOD, org_freedesktop_DBus, ReleaseName, sbus_server_bus_release_name, server),
+ SBUS_SYNC(METHOD, org_freedesktop_DBus, NameHasOwner, sbus_server_bus_name_has_owner, server),
+ SBUS_SYNC(METHOD, org_freedesktop_DBus, ListNames, sbus_server_bus_list_names, server),
+ SBUS_SYNC(METHOD, org_freedesktop_DBus, ListActivatableNames, sbus_server_bus_list_activatable_names, server),
+ SBUS_SYNC(METHOD, org_freedesktop_DBus, GetNameOwner, sbus_server_bus_get_name_owner, server),
+ SBUS_SYNC(METHOD, org_freedesktop_DBus, ListQueuedOwners, sbus_server_bus_list_queued_owners, server),
+ SBUS_SYNC(METHOD, org_freedesktop_DBus, GetConnectionUnixUser, sbus_server_bus_get_connection_unix_user, server),
+ SBUS_SYNC(METHOD, org_freedesktop_DBus, GetConnectionUnixProcessID, sbus_server_bus_get_connection_unix_process_id, server),
+ SBUS_SYNC(METHOD, org_freedesktop_DBus, StartServiceByName, sbus_server_bus_start_service_by_name, server),
+ SBUS_SYNC(METHOD, org_freedesktop_DBus, AddMatch, sbus_server_bus_add_match, server),
+ SBUS_SYNC(METHOD, org_freedesktop_DBus, RemoveMatch, sbus_server_bus_remove_match, server)
+ ),
+ SBUS_SIGNALS(
+ SBUS_EMITS(org_freedesktop_DBus, NameOwnerChanged),
+ SBUS_EMITS(org_freedesktop_DBus, NameAcquired),
+ SBUS_EMITS(org_freedesktop_DBus, NameLost)
+ ),
+ SBUS_WITHOUT_PROPERTIES
+ );
+
+ /* Here we register interfaces on some object paths. */
+ struct sbus_path paths[] = {
+ {DBUS_PATH_DBUS, &bus},
+ {NULL, NULL}
+ };
+
+ ret = sbus_router_add_path_map(server->router, paths);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to add paths [%d]: %s\n",
+ ret, sss_strerror(ret));
+ return ret;
+ }
+
+ return EOK;
+}
diff --git a/src/sbus/server/sbus_server_match.c b/src/sbus/server/sbus_server_match.c
new file mode 100644
index 0000000..4467c3b
--- /dev/null
+++ b/src/sbus/server/sbus_server_match.c
@@ -0,0 +1,450 @@
+/*
+ Authors:
+ Pavel Březina <pbrezina@redhat.com>
+ Stephen Gallagher <sgallagh@redhat.com>
+ Simo Sorce <ssorce@redhat.com>
+
+ Copyright (C) 2017 Red Hat
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <errno.h>
+#include <string.h>
+#include <tevent.h>
+#include <talloc.h>
+
+#include "util/util.h"
+#include "util/sss_ptr_hash.h"
+#include "util/sss_ptr_list.h"
+#include "sbus/sbus_private.h"
+
+struct sbus_rule {
+ const char *type;
+ const char *interface;
+ const char *member;
+};
+
+static struct sbus_connection *
+sbus_match_find(struct sss_ptr_list *list,
+ struct sbus_connection *conn)
+{
+ struct sbus_connection *match_conn;
+
+ SSS_PTR_LIST_FOR_EACH(list, match_conn, struct sbus_connection) {
+ if (match_conn == conn) {
+ return match_conn;
+ }
+ }
+
+ return NULL;
+}
+
+static char *
+sbus_match_rule_key(TALLOC_CTX *mem_ctx,
+ const char *interface,
+ const char *member)
+{
+ if (interface == NULL) {
+ return NULL;
+ }
+
+ if (member == NULL) {
+ return talloc_strdup(mem_ctx, interface);
+ }
+
+ return talloc_asprintf(mem_ctx, "%s.%s", interface, member);
+}
+
+static struct sss_ptr_list *
+sbus_match_rule_create(struct sbus_server *server,
+ const char *key)
+{
+ struct sss_ptr_list *list;
+ errno_t ret;
+
+ list = sss_ptr_list_create(NULL, false);
+ if (list == NULL) {
+ return NULL;
+ }
+
+ ret = sss_ptr_hash_add(server->match_rules, key, list, struct sss_ptr_list);
+ if (ret != EOK) {
+ talloc_free(list);
+ return NULL;
+ }
+
+ talloc_steal(server->match_rules, list);
+
+ return list;
+}
+
+
+static struct sss_ptr_list *
+sbus_match_rule_get(struct sbus_server *server,
+ const char *interface,
+ const char *member,
+ bool create,
+ bool *_created)
+{
+ struct sss_ptr_list *list;
+ char *key;
+
+ key = sbus_match_rule_key(NULL, interface, member);
+ if (key == NULL) {
+ return NULL;
+ }
+
+ list = sss_ptr_hash_lookup(server->match_rules, key, struct sss_ptr_list);
+ if (!create || list != NULL) {
+ if (_created != NULL) {
+ *_created = false;
+ }
+ goto done;
+ }
+
+ list = sbus_match_rule_create(server, key);
+ if (list != NULL && _created != NULL) {
+ *_created = true;
+ }
+
+done:
+ talloc_free(key);
+ return list;
+}
+
+static errno_t
+sbus_match_rule_add(struct sbus_server *server,
+ struct sbus_connection *conn,
+ struct sbus_rule *rule)
+{
+ struct sbus_connection *match_conn;
+ struct sss_ptr_list *list;
+ bool created = false;
+ errno_t ret;
+
+ DEBUG(SSSDBG_TRACE_ALL, "Adding match rule for %s: %s.%s\n",
+ conn->unique_name, rule->interface, rule->member);
+
+ list = sbus_match_rule_get(server, rule->interface, rule->member,
+ true, &created);
+ if (list == NULL) {
+ return ENOMEM;
+ }
+
+ match_conn = sbus_match_find(list, conn);
+ if (match_conn != NULL) {
+ /* Match was already added. */
+ return EOK;
+ }
+
+ ret = sss_ptr_list_add(list, conn);
+ if (ret != EOK && created) {
+ talloc_free(list);
+ }
+
+ return ret;
+}
+
+static errno_t
+sbus_match_rule_remove(struct sbus_server *server,
+ struct sbus_connection *conn,
+ struct sbus_rule *rule)
+{
+ struct sbus_connection *match_conn;
+ struct sss_ptr_list *list;
+
+ DEBUG(SSSDBG_TRACE_ALL, "Removing match rule for %s: %s.%s\n",
+ conn->unique_name, rule->interface, rule->member);
+
+ list = sbus_match_rule_get(server, rule->interface, rule->member,
+ false, NULL);
+ if (list == NULL) {
+ return EOK;
+ }
+
+ match_conn = sbus_match_find(list, conn);
+ if (match_conn == NULL) {
+ return EOK;
+ }
+
+ sss_ptr_list_remove(list, match_conn);
+
+ if (sss_ptr_list_is_empty(list)) {
+ /* This will remove the list from the hash table. */
+ talloc_free(list);
+ }
+
+ return EOK;
+}
+
+static struct sss_ptr_list *
+sbus_match_rule_find(struct sbus_server *server,
+ const char *interface,
+ const char *member)
+{
+ return sbus_match_rule_get(server, interface, member, false, NULL);
+}
+
+static errno_t
+sbus_match_rule_parse_value(TALLOC_CTX *mem_ctx,
+ const char *item,
+ const char *name,
+ const char **_value)
+{
+ size_t name_len = strlen(name);
+ size_t iter_len;
+ const char *iter;
+ char quote;
+
+ if (strncmp(item, name, name_len) != 0) {
+ return ENOENT;
+ }
+
+ iter = item + name_len;
+
+ if (*iter == '=') {
+ iter++;
+ } else {
+ return ENOENT;
+ }
+
+ if (*iter == '"' || *iter == '\'') {
+ quote = *iter;
+ iter++;
+ } else {
+ return EINVAL;
+ }
+
+ iter_len = strlen(iter);
+ if (iter[iter_len - 1] != quote) {
+ return EINVAL;
+ }
+
+ *_value = talloc_strndup(mem_ctx, iter, iter_len - 1);
+ if (*_value == NULL) {
+ return ENOMEM;
+ }
+
+ return EOK;
+}
+
+static errno_t
+sbus_match_rule_parse_keys(TALLOC_CTX *mem_ctx,
+ char **tokens,
+ struct sbus_rule **_rule)
+{
+ struct sbus_rule *rule;
+ errno_t ret;
+ int i, j;
+
+ rule = talloc_zero(mem_ctx, struct sbus_rule);
+ if (rule == NULL) {
+ return ENOMEM;
+ }
+
+ struct {
+ const char *name;
+ const char **value;
+ } keys[] = {
+ {"type", &rule->type},
+ {"interface", &rule->interface},
+ {"member", &rule->member},
+ /* There are more keys in D-Bus specification, such as sender, path
+ * and destination. But we are not interested in them yet. */
+ {NULL, NULL}
+ };
+
+ for (i = 0; tokens[i] != NULL; i++) {
+ for (j = 0; keys[j].name != NULL; j++) {
+ ret = sbus_match_rule_parse_value(rule, tokens[i],
+ keys[j].name, keys[j].value);
+ if (ret == EOK) {
+ break;
+ } else if (ret == ENOENT) {
+ continue;
+ }
+
+ /* Error. */
+ talloc_free(rule);
+ return ret;
+ }
+ }
+
+ *_rule = rule;
+ return EOK;
+}
+
+static errno_t
+sbus_match_rule_parse_check(struct sbus_rule *rule)
+{
+ if (rule->type == NULL || strcmp(rule->type, "signal") != 0) {
+ return EINVAL;
+ }
+
+ if (rule->interface == NULL || rule->member == NULL) {
+ return EINVAL;
+ }
+
+ return EOK;
+}
+
+static errno_t
+sbus_match_rule_parse(TALLOC_CTX *mem_ctx,
+ const char *dbus_rule,
+ struct sbus_rule **_rule)
+{
+ struct sbus_rule *sbus_rule;
+ char **tokens;
+ errno_t ret;
+ int count;
+
+ ret = split_on_separator(NULL, dbus_rule, ',', true, true, &tokens, &count);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = sbus_match_rule_parse_keys(mem_ctx, tokens, &sbus_rule);
+ talloc_free(tokens);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = sbus_match_rule_parse_check(sbus_rule);
+ if (ret != EOK) {
+ talloc_free(sbus_rule);
+ goto done;
+ }
+
+ *_rule = sbus_rule;
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Unable to parse rule [%s] [%d]: %s\n",
+ dbus_rule, ret, sss_strerror(ret));
+ }
+
+
+ return ret;
+}
+
+errno_t
+sbus_server_add_match(struct sbus_server *server,
+ struct sbus_connection *conn,
+ const char *dbus_rule)
+{
+ struct sbus_rule *sbus_rule;
+ errno_t ret;
+
+ ret = sbus_match_rule_parse(NULL, dbus_rule, &sbus_rule);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ ret = sbus_match_rule_add(server, conn, sbus_rule);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Unable to add rule [%s] [%d]: %s\n",
+ dbus_rule, ret, sss_strerror(ret));
+ }
+
+ talloc_free(sbus_rule);
+ return ret;
+}
+
+errno_t
+sbus_server_remove_match(struct sbus_server *server,
+ struct sbus_connection *conn,
+ const char *dbus_rule)
+{
+ struct sbus_rule *sbus_rule;
+ errno_t ret;
+
+ ret = sbus_match_rule_parse(NULL, dbus_rule, &sbus_rule);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ ret = sbus_match_rule_remove(server, conn, sbus_rule);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Unable to remove rule [%s] [%d]: %s\n",
+ dbus_rule, ret, sss_strerror(ret));
+ }
+
+ talloc_free(sbus_rule);
+ return ret;
+}
+
+static bool
+sbus_server_connection_has_name(struct sbus_server *server,
+ struct sbus_connection *conn,
+ const char *name)
+{
+ struct sbus_connection *named_conn;
+
+ named_conn = sss_ptr_hash_lookup(server->names, name,
+ struct sbus_connection);
+
+ if (named_conn == NULL || named_conn != conn) {
+ return false;
+ }
+
+ return true;
+}
+
+errno_t
+sbus_server_matchmaker(struct sbus_server *server,
+ struct sbus_connection *conn,
+ const char *avoid_name,
+ DBusMessage *message)
+{
+ struct sss_ptr_list *list;
+ struct sbus_connection *match_conn;
+ bool has_name;
+
+ /* We can't really send signals when the server is being destroyed. */
+ if (server == NULL || server->disconnecting) {
+ return EOK;
+ }
+
+ list = sbus_match_rule_find(server,
+ dbus_message_get_interface(message),
+ dbus_message_get_member(message));
+ if (list == NULL) {
+ /* No connection listens for this signal. */
+ return EOK;
+ }
+
+ SSS_PTR_LIST_FOR_EACH(list, match_conn, struct sbus_connection) {
+ /* Do not send signal back to the sender. */
+ if (match_conn == conn) {
+ continue;
+ }
+
+ /* Sometimes (e.g. when a name is being deleted), we do not want to
+ * send the signal to a specific name. */
+ if (avoid_name != NULL) {
+ has_name = sbus_server_connection_has_name(server, match_conn,
+ avoid_name);
+ if (has_name) {
+ continue;
+ }
+ }
+
+ dbus_connection_send(match_conn->connection, message, NULL);
+ }
+
+ return EOK;
+}