diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 05:31:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 05:31:45 +0000 |
commit | 74aa0bc6779af38018a03fd2cf4419fe85917904 (patch) | |
tree | 9cb0681aac9a94a49c153d5823e7a55d1513d91f /src/sbus/server | |
parent | Initial commit. (diff) | |
download | sssd-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.c | 778 | ||||
-rw-r--r-- | src/sbus/server/sbus_server_handler.c | 190 | ||||
-rw-r--r-- | src/sbus/server/sbus_server_interface.c | 429 | ||||
-rw-r--r-- | src/sbus/server/sbus_server_match.c | 450 |
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; +} |