/* SSSD Service monitor - D-BUS features Copyright (C) Stephen Gallagher 2008 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 . */ #include #include #include #include "tevent.h" #include "util/util.h" #include "dbus/dbus.h" #include "sbus/sssd_dbus.h" #include "sbus/sssd_dbus_private.h" static int sbus_server_destructor(void *ctx); /* * new_connection_callback * Actions to be run upon each new client connection * Must either perform dbus_connection_ref() on the * new connection or else close the connection with * dbus_connection_close() */ static void sbus_server_init_new_connection(DBusServer *dbus_server, DBusConnection *dbus_conn, void *data) { struct sbus_connection *server; struct sbus_connection *conn; int ret; DEBUG(5,("Entering.\n")); server = talloc_get_type(data, struct sbus_connection); if (!server) { return; } DEBUG(5,("Adding connection %p.\n", dbus_conn)); ret = sbus_init_connection(server, server->ev, dbus_conn, server->server_intf, SBUS_CONN_TYPE_PRIVATE, &conn); if (ret != 0) { dbus_connection_close(dbus_conn); DEBUG(5,("Closing connection (failed setup)")); return; } dbus_connection_ref(dbus_conn); DEBUG(5,("Got a connection\n")); /* * Initialize connection-specific features * This may set a more detailed destructor, but * the default destructor will always be chained * to handle connection cleanup. * This function (or its callbacks) should also * set up connection-specific methods. */ ret = server->srv_init_fn(conn, server->srv_init_data); if (ret != EOK) { DEBUG(1,("Initialization failed!\n")); dbus_connection_close(dbus_conn); talloc_zfree(conn); } } const char * get_socket_address(TALLOC_CTX *mem_ctx, const char *address, bool use_symlink) { if (!use_symlink) { return talloc_strdup(mem_ctx, address); } return talloc_asprintf(mem_ctx, "%s.%lu", address, (unsigned long) getpid()); } static errno_t create_socket_symlink(const char *filename, const char *symlink_filename) { errno_t ret; DEBUG(7, ("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(1, ("Cannot remove old symlink: [%d][%s].\n", ret, strerror(ret))); return EIO; } errno = 0; ret = symlink(filename, symlink_filename); } if (ret != 0) { ret = errno; DEBUG(1, ("symlink() failed on file '%s': [%d][%s].\n", filename, ret, strerror(ret))); return EIO; } return EOK; } static errno_t remove_socket_symlink(const char *symlink_name) { errno_t ret; char target[PATH_MAX]; char pidpath[PATH_MAX]; ssize_t numread = 0; errno = 0; numread = readlink(symlink_name, target, PATH_MAX-1); if (numread < 0) { ret = errno; DEBUG(2, ("readlink failed [%d]: %s\n", ret, strerror(ret))); return ret; } target[numread] = '\0'; DEBUG(9, ("The symlink points to [%s]\n", target)); /* We can only remove the symlink if it points to a socket with * the same PID */ ret = snprintf(pidpath, PATH_MAX, "%s.%lu", symlink_name, (unsigned long) getpid()); if (ret < 0) { DEBUG(2, ("snprintf failed")); return EIO; } else if (ret >= PATH_MAX) { DEBUG(2, ("path too long?!?!\n")); return EIO; } DEBUG(9, ("The path including our pid is [%s]\n", pidpath)); if (strcmp(pidpath, target) != 0) { DEBUG(4, ("Will not remove symlink, seems to be owned by " "another process\n")); return EOK; } ret = unlink(symlink_name); if (ret != 0) { ret = errno; DEBUG(2, ("unlink failed to remove [%s] [%d]: %s\n", symlink, ret, strerror(ret))); return ret; } DEBUG(9, ("Removed the symlink\n")); return EOK; } /* * dbus_new_server * Set up a D-BUS server, integrate with the event loop * for handling file descriptor and timed events */ int sbus_new_server(TALLOC_CTX *mem_ctx, struct tevent_context *ev, const char *address, struct sbus_interface *intf, bool use_symlink, struct sbus_connection **_server, sbus_server_conn_init_fn init_fn, void *init_pvt_data) { struct sbus_connection *server; DBusServer *dbus_server; DBusError dbus_error; dbus_bool_t dbret; char *tmp; int ret; char *filename; char *symlink_filename = NULL; const char *socket_address; struct stat stat_buf; TALLOC_CTX *tmp_ctx; *_server = NULL; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) return ENOMEM; socket_address = get_socket_address(tmp_ctx, address, use_symlink); if (!socket_address) { ret = ENOMEM; goto done; } /* Set up D-BUS server */ dbus_error_init(&dbus_error); dbus_server = dbus_server_listen(socket_address, &dbus_error); if (!dbus_server) { DEBUG(1,("dbus_server_listen failed! (name=%s, message=%s)\n", dbus_error.name, dbus_error.message)); if (dbus_error_is_set(&dbus_error)) dbus_error_free(&dbus_error); ret = EIO; goto done; } filename = strchr(socket_address, '/'); if (filename == NULL) { DEBUG(1, ("Unexpected dbus address [%s].\n", socket_address)); ret = EIO; goto done; } if (use_symlink) { symlink_filename = strchr(address, '/'); if (symlink_filename == NULL) { DEBUG(1, ("Unexpected dbus address [%s].\n", address)); ret = EIO; goto done; } ret = create_socket_symlink(filename, symlink_filename); if (ret != EOK) { DEBUG(1, ("Could not create symlink [%d]: %s\n", ret, strerror(ret))); ret = EIO; goto done; } } /* Both check_file and chmod can handle both the symlink and * the socket */ ret = check_file(filename, 0, 0, -1, CHECK_SOCK, &stat_buf, true); if (ret != EOK) { DEBUG(1, ("check_file failed for [%s].\n", filename)); ret = EIO; goto done; } if ((stat_buf.st_mode & ~S_IFMT) != 0600) { ret = chmod(filename, 0600); if (ret != EOK) { DEBUG(1, ("chmod failed for [%s]: [%d][%s].\n", filename, errno, strerror(errno))); ret = EIO; goto done; } } tmp = dbus_server_get_address(dbus_server); DEBUG(3, ("D-BUS Server listening on %s\n", tmp)); free(tmp); server = talloc_zero(tmp_ctx, struct sbus_connection); if (!server) { ret = ENOMEM; goto done; } server->ev = ev; server->type = SBUS_SERVER; server->dbus.server = dbus_server; server->server_intf = intf; server->srv_init_fn = init_fn; server->srv_init_data = init_pvt_data; talloc_set_destructor((TALLOC_CTX *)server, sbus_server_destructor); if (use_symlink) { server->symlink = talloc_strdup(server, symlink_filename); if (!server->symlink) { ret = ENOMEM; goto done; } } /* Set up D-BUS new connection handler */ dbus_server_set_new_connection_function(server->dbus.server, sbus_server_init_new_connection, server, NULL); /* Set up DBusWatch functions */ dbret = dbus_server_set_watch_functions(server->dbus.server, sbus_add_watch, sbus_remove_watch, sbus_toggle_watch, server, NULL); if (!dbret) { DEBUG(4, ("Error setting up D-BUS server watch functions\n")); ret = EIO; goto done; } /* Set up DBusTimeout functions */ dbret = dbus_server_set_timeout_functions(server->dbus.server, sbus_add_timeout, sbus_remove_timeout, sbus_toggle_timeout, server, NULL); if (!dbret) { DEBUG(4,("Error setting up D-BUS server timeout functions\n")); dbus_server_set_watch_functions(server->dbus.server, NULL, NULL, NULL, NULL, NULL); ret = EIO; goto done; } *_server = talloc_steal(mem_ctx, server); ret = EOK; done: if (ret != EOK && symlink_filename) { unlink(symlink_filename); } talloc_free(tmp_ctx); return ret; } static int sbus_server_destructor(void *ctx) { struct sbus_connection *server; errno_t ret; server = talloc_get_type(ctx, struct sbus_connection); dbus_server_disconnect(server->dbus.server); if (server->symlink) { ret = remove_socket_symlink(server->symlink); if (ret != EOK) { DEBUG(3, ("Could not remove the server symlink\n")); } } return 0; }