diff options
Diffstat (limited to 'src/nm-dbus-manager.c')
-rw-r--r-- | src/nm-dbus-manager.c | 1030 |
1 files changed, 1030 insertions, 0 deletions
diff --git a/src/nm-dbus-manager.c b/src/nm-dbus-manager.c new file mode 100644 index 0000000000..965bcec78f --- /dev/null +++ b/src/nm-dbus-manager.c @@ -0,0 +1,1030 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2006 - 2013 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-dbus-manager.h" + +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <errno.h> +#include <string.h> + +#include "nm-utils/c-list.h" +#include "nm-dbus-interface.h" +#include "nm-core-internal.h" +#include "nm-dbus-compat.h" +#include "nm-exported-object.h" +#include "NetworkManagerUtils.h" + +/* The base path for our GDBusObjectManagerServers. They do not contain + * "NetworkManager" because GDBusObjectManagerServer requires that all + * exported objects be *below* the base path, and eg the Manager object + * is the base path already. + */ +#define OBJECT_MANAGER_SERVER_BASE_PATH "/org/freedesktop" + +/*****************************************************************************/ + +enum { + DBUS_CONNECTION_CHANGED = 0, + PRIVATE_CONNECTION_NEW, + PRIVATE_CONNECTION_DISCONNECTED, + NUMBER_OF_SIGNALS, +}; + +static guint signals[NUMBER_OF_SIGNALS]; + +typedef struct { + GDBusConnection *connection; + GDBusObjectManagerServer *obj_manager; + gboolean started; + + CList private_servers_lst_head; + + GDBusProxy *proxy; + + gulong bus_closed_id; + guint reconnect_id; +} NMBusManagerPrivate; + +struct _NMBusManager { + GObject parent; + NMBusManagerPrivate _priv; +}; + +struct _NMBusManagerClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMBusManager, nm_bus_manager, G_TYPE_OBJECT) + +#define NM_BUS_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMBusManager, NM_IS_BUS_MANAGER) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_CORE +#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "bus-manager", __VA_ARGS__) + +/*****************************************************************************/ + +static gboolean nm_bus_manager_init_bus (NMBusManager *self); +static void nm_bus_manager_cleanup (NMBusManager *self); +static void start_reconnection_timeout (NMBusManager *self); + +/*****************************************************************************/ + +NM_DEFINE_SINGLETON_REGISTER (NMBusManager); + +NMBusManager * +nm_bus_manager_get (void) +{ + if (G_UNLIKELY (!singleton_instance)) { + nm_bus_manager_setup (g_object_new (NM_TYPE_BUS_MANAGER, NULL)); + if (!nm_bus_manager_init_bus (singleton_instance)) + start_reconnection_timeout (singleton_instance); + } + return singleton_instance; +} + +void +nm_bus_manager_setup (NMBusManager *instance) +{ + static char already_setup = FALSE; + + g_assert (NM_IS_BUS_MANAGER (instance)); + g_assert (!already_setup); + g_assert (!singleton_instance); + + already_setup = TRUE; + singleton_instance = instance; + nm_singleton_instance_register (); + _LOGD ("setup %s singleton (%p)", "NMBusManager", singleton_instance); +} + +/*****************************************************************************/ + +typedef struct { + CList private_servers_lst; + + const char *tag; + GQuark detail; + char *address; + GDBusServer *server; + + /* With peer bus connections, we'll get a new connection for each + * client. For each connection we create an ObjectManager for + * that connection to handle exporting our objects. + * + * Note that even for connections that don't export any objects + * we'll still create GDBusObjectManager since that's where we store + * the pointer to the GDBusConnection. + */ + CList object_mgr_lst_head; + + NMBusManager *manager; +} PrivateServer; + +typedef struct { + CList object_mgr_lst; + GDBusObjectManagerServer *manager; + char *fake_sender; +} ObjectMgrData; + +typedef struct { + GDBusConnection *connection; + PrivateServer *server; + gboolean remote_peer_vanished; +} CloseConnectionInfo; + +/*****************************************************************************/ + +static void +_object_mgr_data_free (ObjectMgrData *obj_mgr_data) +{ + GDBusConnection *connection; + + c_list_unlink_stale (&obj_mgr_data->object_mgr_lst); + + connection = g_dbus_object_manager_server_get_connection (obj_mgr_data->manager); + if (!g_dbus_connection_is_closed (connection)) + g_dbus_connection_close (connection, NULL, NULL, NULL); + g_dbus_object_manager_server_set_connection (obj_mgr_data->manager, NULL); + g_object_unref (obj_mgr_data->manager); + g_object_unref (connection); + + g_free (obj_mgr_data->fake_sender); + + g_slice_free (ObjectMgrData, obj_mgr_data); +} + +/*****************************************************************************/ + +static gboolean +close_connection_in_idle (gpointer user_data) +{ + CloseConnectionInfo *info = user_data; + PrivateServer *server = info->server; + ObjectMgrData *obj_mgr_data, *obj_mgr_data_safe; + + /* Emit this for the manager */ + g_signal_emit (server->manager, + signals[PRIVATE_CONNECTION_DISCONNECTED], + server->detail, + info->connection); + + /* FIXME: there's a bug (754730) in GLib for which the connection + * is marked as closed when the remote peer vanishes but its + * resources are not cleaned up. Work around it by explicitly + * closing the connection in that case. */ + if (info->remote_peer_vanished) + g_dbus_connection_close (info->connection, NULL, NULL, NULL); + + c_list_for_each_entry_safe (obj_mgr_data, obj_mgr_data_safe, &server->object_mgr_lst_head, object_mgr_lst) { + gs_unref_object GDBusConnection *connection = NULL; + + connection = g_dbus_object_manager_server_get_connection (obj_mgr_data->manager); + if (connection == info->connection) { + _object_mgr_data_free (obj_mgr_data); + break; + } + } + + g_object_unref (server->manager); + g_slice_free (CloseConnectionInfo, info); + + return G_SOURCE_REMOVE; +} + +static void +private_server_closed_connection (GDBusConnection *conn, + gboolean remote_peer_vanished, + GError *error, + gpointer user_data) +{ + PrivateServer *s = user_data; + CloseConnectionInfo *info; + + /* Clean up after the connection */ + _LOGD ("(%s) closed connection %p on private socket", s->tag, conn); + + info = g_slice_new0 (CloseConnectionInfo); + info->connection = conn; + info->server = s; + info->remote_peer_vanished = remote_peer_vanished; + + g_object_ref (s->manager); + + /* Delay the close of connection to ensure that D-Bus signals + * are handled */ + g_idle_add (close_connection_in_idle, info); +} + +static gboolean +private_server_new_connection (GDBusServer *server, + GDBusConnection *conn, + gpointer user_data) +{ + PrivateServer *s = user_data; + ObjectMgrData *obj_mgr_data; + static guint32 counter = 0; + GDBusObjectManagerServer *manager; + char *sender; + + g_signal_connect (conn, "closed", G_CALLBACK (private_server_closed_connection), s); + + /* Fake a sender since private connections don't have one */ + sender = g_strdup_printf ("x:y:%d", counter++); + + manager = g_dbus_object_manager_server_new (OBJECT_MANAGER_SERVER_BASE_PATH); + g_dbus_object_manager_server_set_connection (manager, conn); + + obj_mgr_data = g_slice_new (ObjectMgrData); + obj_mgr_data->manager = manager; + obj_mgr_data->fake_sender = sender; + c_list_link_tail (&s->object_mgr_lst_head, &obj_mgr_data->object_mgr_lst); + + _LOGD ("(%s) accepted connection %p on private socket", s->tag, conn); + + /* Emit this for the manager. + * + * It is essential to do this from the "new-connection" signal handler, as + * at that point no messages from the connection are yet processed + * (which avoids races with registering objects). */ + g_signal_emit (s->manager, + signals[PRIVATE_CONNECTION_NEW], + s->detail, + conn, + manager); + return TRUE; +} + +static gboolean +private_server_authorize (GDBusAuthObserver *observer, + GIOStream *stream, + GCredentials *credentials, + gpointer user_data) +{ + return g_credentials_get_unix_user (credentials, NULL) == 0; +} + +static void +private_server_free (gpointer ptr) +{ + PrivateServer *s = ptr; + ObjectMgrData *obj_mgr_data, *obj_mgr_data_safe; + + c_list_unlink_stale (&s->private_servers_lst); + + unlink (s->address); + g_free (s->address); + + c_list_for_each_entry_safe (obj_mgr_data, obj_mgr_data_safe, &s->object_mgr_lst_head, object_mgr_lst) + _object_mgr_data_free (obj_mgr_data); + + g_dbus_server_stop (s->server); + + g_signal_handlers_disconnect_by_func (s->server, G_CALLBACK (private_server_new_connection), s); + + g_object_unref (s->server); + + g_slice_free (PrivateServer, s); +} + +void +nm_bus_manager_private_server_register (NMBusManager *self, + const char *path, + const char *tag) +{ + NMBusManagerPrivate *priv; + PrivateServer *s; + gs_unref_object GDBusAuthObserver *auth_observer = NULL; + GDBusServer *server; + GError *error = NULL; + gs_free char *address = NULL; + gs_free char *guid = NULL; + + g_return_if_fail (NM_IS_BUS_MANAGER (self)); + g_return_if_fail (path); + g_return_if_fail (tag); + + priv = NM_BUS_MANAGER_GET_PRIVATE (self); + + /* Only one instance per tag; but don't warn */ + c_list_for_each_entry (s, &priv->private_servers_lst_head, private_servers_lst) { + if (nm_streq0 (tag, s->tag)) + return; + } + + unlink (path); + address = g_strdup_printf ("unix:path=%s", path); + + _LOGD ("(%s) creating private socket %s", tag, address); + + guid = g_dbus_generate_guid (); + auth_observer = g_dbus_auth_observer_new (); + g_signal_connect (auth_observer, "authorize-authenticated-peer", + G_CALLBACK (private_server_authorize), NULL); + server = g_dbus_server_new_sync (address, + G_DBUS_SERVER_FLAGS_NONE, + guid, + auth_observer, + NULL, &error); + + if (!server) { + _LOGW ("(%s) failed to set up private socket %s: %s", + tag, address, error->message); + g_error_free (error); + return; + } + + s = g_slice_new0 (PrivateServer); + s->address = g_steal_pointer (&address); + s->server = server; + g_signal_connect (server, "new-connection", + G_CALLBACK (private_server_new_connection), s); + + c_list_init (&s->object_mgr_lst_head); + + s->manager = self; + s->detail = g_quark_from_string (tag); + s->tag = g_quark_to_string (s->detail); + + c_list_link_tail (&priv->private_servers_lst_head, &s->private_servers_lst); + + g_dbus_server_start (server); +} + +static const char * +private_server_get_connection_owner (PrivateServer *s, GDBusConnection *connection) +{ + ObjectMgrData *obj_mgr_data; + + nm_assert (s); + nm_assert (G_IS_DBUS_CONNECTION (connection)); + + c_list_for_each_entry (obj_mgr_data, &s->object_mgr_lst_head, object_mgr_lst) { + gs_unref_object GDBusConnection *c = NULL; + + c = g_dbus_object_manager_server_get_connection (obj_mgr_data->manager); + if (c == connection) + return obj_mgr_data->fake_sender; + } + return NULL; +} + +static GDBusConnection * +private_server_get_connection_by_owner (PrivateServer *s, const char *owner) +{ + ObjectMgrData *obj_mgr_data; + + nm_assert (s); + nm_assert (owner); + + c_list_for_each_entry (obj_mgr_data, &s->object_mgr_lst_head, object_mgr_lst) { + if (nm_streq (owner, obj_mgr_data->fake_sender)) + return g_dbus_object_manager_server_get_connection (obj_mgr_data->manager); + } + return NULL; +} + +/*****************************************************************************/ + +static gboolean +_bus_get_unix_pid (NMBusManager *self, + const char *sender, + gulong *out_pid, + GError **error) +{ + guint32 unix_pid = G_MAXUINT32; + gs_unref_variant GVariant *ret = NULL; + + ret = _nm_dbus_proxy_call_sync (NM_BUS_MANAGER_GET_PRIVATE (self)->proxy, + "GetConnectionUnixProcessID", + g_variant_new ("(s)", sender), + G_VARIANT_TYPE ("(u)"), + G_DBUS_CALL_FLAGS_NONE, 2000, + NULL, error); + if (!ret) + return FALSE; + + g_variant_get (ret, "(u)", &unix_pid); + + *out_pid = (gulong) unix_pid; + return TRUE; +} + +static gboolean +_bus_get_unix_user (NMBusManager *self, + const char *sender, + gulong *out_user, + GError **error) +{ + guint32 unix_uid = G_MAXUINT32; + gs_unref_variant GVariant *ret = NULL; + + ret = _nm_dbus_proxy_call_sync (NM_BUS_MANAGER_GET_PRIVATE (self)->proxy, + "GetConnectionUnixUser", + g_variant_new ("(s)", sender), + G_VARIANT_TYPE ("(u)"), + G_DBUS_CALL_FLAGS_NONE, 2000, + NULL, error); + if (!ret) + return FALSE; + + g_variant_get (ret, "(u)", &unix_uid); + + *out_user = (gulong) unix_uid; + return TRUE; +} + +/** + * _get_caller_info(): + * + * Given a GDBus method invocation, or a GDBusConnection + GDBusMessage, + * return the sender and the UID of the sender. + */ +static gboolean +_get_caller_info (NMBusManager *self, + GDBusMethodInvocation *context, + GDBusConnection *connection, + GDBusMessage *message, + char **out_sender, + gulong *out_uid, + gulong *out_pid) +{ + NMBusManagerPrivate *priv = NM_BUS_MANAGER_GET_PRIVATE (self); + const char *sender; + + if (context) { + connection = g_dbus_method_invocation_get_connection (context); + + /* only bus connections will have a sender */ + sender = g_dbus_method_invocation_get_sender (context); + } else { + g_assert (message); + sender = g_dbus_message_get_sender (message); + } + g_assert (connection); + + if (!sender) { + PrivateServer *s; + + /* Might be a private connection, for which we fake a sender */ + c_list_for_each_entry (s, &priv->private_servers_lst_head, private_servers_lst) { + sender = private_server_get_connection_owner (s, connection); + if (sender) { + if (out_uid) + *out_uid = 0; + if (out_sender) + *out_sender = g_strdup (sender); + if (out_pid) { + GCredentials *creds; + + creds = g_dbus_connection_get_peer_credentials (connection); + if (creds) { + pid_t pid; + + pid = g_credentials_get_unix_pid (creds, NULL); + if (pid == -1) + *out_pid = G_MAXULONG; + else + *out_pid = pid; + } else + *out_pid = G_MAXULONG; + } + return TRUE; + } + } + return FALSE; + } + + /* Bus connections always have a sender */ + g_assert (sender); + if (out_uid) { + if (!_bus_get_unix_user (self, sender, out_uid, NULL)) { + *out_uid = G_MAXULONG; + return FALSE; + } + } + + if (out_pid) { + if (!_bus_get_unix_pid (self, sender, out_pid, NULL)) { + *out_pid = G_MAXULONG; + return FALSE; + } + } + + if (out_sender) + *out_sender = g_strdup (sender); + + return TRUE; +} + +gboolean +nm_bus_manager_get_caller_info (NMBusManager *self, + GDBusMethodInvocation *context, + char **out_sender, + gulong *out_uid, + gulong *out_pid) +{ + return _get_caller_info (self, context, NULL, NULL, out_sender, out_uid, out_pid); +} + +gboolean +nm_bus_manager_get_caller_info_from_message (NMBusManager *self, + GDBusConnection *connection, + GDBusMessage *message, + char **out_sender, + gulong *out_uid, + gulong *out_pid) +{ + return _get_caller_info (self, NULL, connection, message, out_sender, out_uid, out_pid); +} + +/** + * nm_bus_manager_ensure_uid: + * + * @self: bus manager instance + * @context: D-Bus method invocation + * @uid: a user-id + * @error_domain: error domain to return on failure + * @error_code: error code to return on failure + * + * Retrieves the uid of the D-Bus method caller and + * checks that it matches @uid, unless @uid is G_MAXULONG. + * In case of failure the function returns FALSE and finishes + * handling the D-Bus method with an error. + * + * Returns: %TRUE if the check succeeded, %FALSE otherwise + */ +gboolean +nm_bus_manager_ensure_uid (NMBusManager *self, + GDBusMethodInvocation *context, + gulong uid, + GQuark error_domain, + int error_code) +{ + gulong caller_uid; + GError *error = NULL; + + g_return_val_if_fail (NM_IS_BUS_MANAGER (self), FALSE); + g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (context), FALSE); + + if (!nm_bus_manager_get_caller_info (self, context, NULL, &caller_uid, NULL)) { + error = g_error_new_literal (error_domain, + error_code, + "Unable to determine request UID."); + g_dbus_method_invocation_take_error (context, error); + return FALSE; + } + + if (uid != G_MAXULONG && caller_uid != uid) { + error = g_error_new_literal (error_domain, + error_code, + "Permission denied"); + g_dbus_method_invocation_take_error (context, error); + return FALSE; + } + + return TRUE; +} + +gboolean +nm_bus_manager_get_unix_user (NMBusManager *self, + const char *sender, + gulong *out_uid) +{ + NMBusManagerPrivate *priv = NM_BUS_MANAGER_GET_PRIVATE (self); + PrivateServer *s; + GError *error = NULL; + + g_return_val_if_fail (sender != NULL, FALSE); + g_return_val_if_fail (out_uid != NULL, FALSE); + + /* Check if it's a private connection sender, which we fake */ + c_list_for_each_entry (s, &priv->private_servers_lst_head, private_servers_lst) { + gs_unref_object GDBusConnection *connection = NULL; + + connection = private_server_get_connection_by_owner (s, sender); + if (connection) { + *out_uid = 0; + return TRUE; + } + } + + /* Otherwise, a bus connection */ + if (!_bus_get_unix_user (self, sender, out_uid, &error)) { + _LOGW ("failed to get unix user for dbus sender '%s': %s", + sender, error->message); + g_error_free (error); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +/* Only cleanup a specific dbus connection, not all our private data */ +static void +nm_bus_manager_cleanup (NMBusManager *self) +{ + NMBusManagerPrivate *priv = NM_BUS_MANAGER_GET_PRIVATE (self); + + g_clear_object (&priv->proxy); + + if (priv->connection) { + g_signal_handler_disconnect (priv->connection, priv->bus_closed_id); + priv->bus_closed_id = 0; + g_clear_object (&priv->connection); + } + + g_dbus_object_manager_server_set_connection (priv->obj_manager, NULL); + priv->started = FALSE; +} + +static gboolean +nm_bus_manager_reconnect (gpointer user_data) +{ + NMBusManager *self = NM_BUS_MANAGER (user_data); + NMBusManagerPrivate *priv = NM_BUS_MANAGER_GET_PRIVATE (self); + + g_assert (self != NULL); + + if (nm_bus_manager_init_bus (self)) { + if (nm_bus_manager_start_service (self)) { + _LOGI ("reconnected to the system bus"); + g_signal_emit (self, signals[DBUS_CONNECTION_CHANGED], + 0, priv->connection); + priv->reconnect_id = 0; + return FALSE; + } + } + + /* Try again */ + nm_bus_manager_cleanup (self); + return TRUE; +} + +static void +start_reconnection_timeout (NMBusManager *self) +{ + NMBusManagerPrivate *priv = NM_BUS_MANAGER_GET_PRIVATE (self); + + if (priv->reconnect_id) + g_source_remove (priv->reconnect_id); + + /* Schedule timeout for reconnection attempts */ + priv->reconnect_id = g_timeout_add_seconds (3, nm_bus_manager_reconnect, self); +} + +static void +closed_cb (GDBusConnection *connection, + gboolean remote_peer_vanished, + GError *error, + gpointer user_data) +{ + NMBusManager *self = NM_BUS_MANAGER (user_data); + + /* Clean up existing connection */ + _LOGW ("disconnected by the system bus"); + + nm_bus_manager_cleanup (self); + + g_signal_emit (G_OBJECT (self), signals[DBUS_CONNECTION_CHANGED], 0, NULL); + + start_reconnection_timeout (self); +} + +static gboolean +nm_bus_manager_init_bus (NMBusManager *self) +{ + NMBusManagerPrivate *priv = NM_BUS_MANAGER_GET_PRIVATE (self); + GError *error = NULL; + + if (priv->connection) { + _LOGW ("DBus Manager already has a valid connection"); + return FALSE; + } + + priv->connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (!priv->connection) { + /* Log with 'info' severity; there won't be a bus daemon in minimal + * environments (eg, initrd) where we only want to use the private + * socket. + */ + _LOGI ("could not connect to the system bus (%s); only the " + "private D-Bus socket will be available", + error->message); + g_error_free (error); + return FALSE; + } + + g_dbus_connection_set_exit_on_close (priv->connection, FALSE); + priv->bus_closed_id = g_signal_connect (priv->connection, "closed", + G_CALLBACK (closed_cb), self); + + priv->proxy = g_dbus_proxy_new_sync (priv->connection, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, + NULL, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + NULL, &error); + if (!priv->proxy) { + g_clear_object (&priv->connection); + _LOGW ("could not create org.freedesktop.DBus proxy (%s); only the " + "private D-Bus socket will be available", + error->message); + g_error_free (error); + return FALSE; + } + + g_dbus_object_manager_server_set_connection (priv->obj_manager, priv->connection); + return TRUE; +} + +/* Register our service on the bus; shouldn't be called until + * all necessary message handlers have been registered, because + * when we register on the bus, clients may start to call. + */ +gboolean +nm_bus_manager_start_service (NMBusManager *self) +{ + NMBusManagerPrivate *priv; + gs_unref_variant GVariant *ret = NULL; + int result; + GError *err = NULL; + + g_return_val_if_fail (NM_IS_BUS_MANAGER (self), FALSE); + + priv = NM_BUS_MANAGER_GET_PRIVATE (self); + + if (priv->started) { + _LOGE ("service has already started"); + return FALSE; + } + + /* Pointless to request a name when we aren't connected to the bus */ + if (!priv->proxy) + return FALSE; + + ret = _nm_dbus_proxy_call_sync (priv->proxy, + "RequestName", + g_variant_new ("(su)", + NM_DBUS_SERVICE, + DBUS_NAME_FLAG_DO_NOT_QUEUE), + G_VARIANT_TYPE ("(u)"), + G_DBUS_CALL_FLAGS_NONE, -1, + NULL, &err); + if (!ret) { + _LOGE ("could not acquire the NetworkManager service: '%s'", err->message); + g_error_free (err); + return FALSE; + } + + g_variant_get (ret, "(u)", &result); + + if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + _LOGE ("could not acquire the NetworkManager service as it is already taken"); + return FALSE; + } + + priv->started = TRUE; + return priv->started; +} + +GDBusConnection * +nm_bus_manager_get_connection (NMBusManager *self) +{ + g_return_val_if_fail (NM_IS_BUS_MANAGER (self), NULL); + + return NM_BUS_MANAGER_GET_PRIVATE (self)->connection; +} + +void +nm_bus_manager_register_object (NMBusManager *self, + GDBusObjectSkeleton *object) +{ + NMBusManagerPrivate *priv; + + g_return_if_fail (NM_IS_BUS_MANAGER (self)); + g_return_if_fail (NM_IS_EXPORTED_OBJECT (object)); + + priv = NM_BUS_MANAGER_GET_PRIVATE (self); + +#if NM_MORE_ASSERTS >= 1 + if (g_dbus_object_manager_server_is_exported (priv->obj_manager, object)) + g_return_if_reached (); +#endif + + g_dbus_object_manager_server_export (priv->obj_manager, object); +} + +GDBusObjectSkeleton * +nm_bus_manager_get_registered_object (NMBusManager *self, + const char *path) +{ + NMBusManagerPrivate *priv = NM_BUS_MANAGER_GET_PRIVATE (self); + + return G_DBUS_OBJECT_SKELETON (g_dbus_object_manager_get_object ((GDBusObjectManager *) priv->obj_manager, path)); +} + +void +nm_bus_manager_unregister_object (NMBusManager *self, + GDBusObjectSkeleton *object) +{ + NMBusManagerPrivate *priv; + gs_free char *path = NULL; + + g_return_if_fail (NM_IS_BUS_MANAGER (self)); + g_return_if_fail (NM_IS_EXPORTED_OBJECT (object)); + + priv = NM_BUS_MANAGER_GET_PRIVATE (self); + +#if NM_MORE_ASSERTS >= 1 + if (!g_dbus_object_manager_server_is_exported (priv->obj_manager, object)) + g_return_if_reached (); +#endif + + g_object_get (G_OBJECT (object), "g-object-path", &path, NULL); + g_return_if_fail (path != NULL); + + g_dbus_object_manager_server_unexport (priv->obj_manager, path); +} + +const char * +nm_bus_manager_connection_get_private_name (NMBusManager *self, + GDBusConnection *connection) +{ + NMBusManagerPrivate *priv; + PrivateServer *s; + const char *owner; + + g_return_val_if_fail (NM_IS_BUS_MANAGER (self), FALSE); + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE); + + if (g_dbus_connection_get_unique_name (connection)) { + /* Shortcut. The connection is not a private connection. */ + return NULL; + } + + priv = NM_BUS_MANAGER_GET_PRIVATE (self); + c_list_for_each_entry (s, &priv->private_servers_lst_head, private_servers_lst) { + if ((owner = private_server_get_connection_owner (s, connection))) + return owner; + } + g_return_val_if_reached (NULL); +} + +/** + * nm_bus_manager_new_proxy: + * @self: the #NMBusManager + * @connection: the GDBusConnection for which this connection should be created + * @proxy_type: the type of #GDBusProxy to create + * @name: any name on the message bus + * @path: name of the object instance to call methods on + * @iface: name of the interface to call methods on + * + * Creates a new proxy (of type @proxy_type) for a name on a given bus. Since + * the process which called the D-Bus method could be coming from a private + * connection or the system bus connection, different proxies must be created + * for each case. This function abstracts that. + * + * Returns: a #GDBusProxy capable of calling D-Bus methods of the calling process + */ +GDBusProxy * +nm_bus_manager_new_proxy (NMBusManager *self, + GDBusConnection *connection, + GType proxy_type, + const char *name, + const char *path, + const char *iface) +{ + const char *owner; + GDBusProxy *proxy; + GError *error = NULL; + + g_return_val_if_fail (g_type_is_a (proxy_type, G_TYPE_DBUS_PROXY), NULL); + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + + /* Might be a private connection, for which @name is fake */ + owner = nm_bus_manager_connection_get_private_name (self, connection); + if (owner) { + g_return_val_if_fail (!g_strcmp0 (owner, name), NULL); + name = NULL; + } + + proxy = g_initable_new (proxy_type, NULL, &error, + "g-connection", connection, + "g-flags", (G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS), + "g-name", name, + "g-object-path", path, + "g-interface-name", iface, + NULL); + if (!proxy) { + _LOGW ("could not create proxy for %s on connection %s: %s", + iface, name, error->message); + g_error_free (error); + } + return proxy; +} + +/*****************************************************************************/ + +static void +nm_bus_manager_init (NMBusManager *self) +{ + NMBusManagerPrivate *priv = NM_BUS_MANAGER_GET_PRIVATE (self); + + c_list_init (&priv->private_servers_lst_head); + priv->obj_manager = g_dbus_object_manager_server_new (OBJECT_MANAGER_SERVER_BASE_PATH); +} + +static void +dispose (GObject *object) +{ + NMBusManager *self = NM_BUS_MANAGER (object); + NMBusManagerPrivate *priv = NM_BUS_MANAGER_GET_PRIVATE (self); + GList *exported, *iter; + PrivateServer *s, *s_safe; + + c_list_for_each_entry_safe (s, s_safe, &priv->private_servers_lst_head, private_servers_lst) + private_server_free (s); + + nm_bus_manager_cleanup (self); + + if (priv->obj_manager) { + /* The ObjectManager owns the last reference to many exported + * objects, and when that reference is dropped the objects unregister + * themselves via nm_bus_manager_unregister_object(). By that time + * priv->obj_manager is already NULL and that prints warnings. Unregister + * them before clearing the ObjectManager instead. + */ + exported = g_dbus_object_manager_get_objects ((GDBusObjectManager *) priv->obj_manager); + for (iter = exported; iter; iter = iter->next) { + nm_bus_manager_unregister_object (self, iter->data); + g_object_unref (iter->data); + } + g_list_free (exported); + g_clear_object (&priv->obj_manager); + } + + nm_clear_g_source (&priv->reconnect_id); + + G_OBJECT_CLASS (nm_bus_manager_parent_class)->dispose (object); +} + +static void +nm_bus_manager_class_init (NMBusManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = dispose; + + signals[DBUS_CONNECTION_CHANGED] = + g_signal_new (NM_BUS_MANAGER_DBUS_CONNECTION_CHANGED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, G_TYPE_POINTER); + + signals[PRIVATE_CONNECTION_NEW] = + g_signal_new (NM_BUS_MANAGER_PRIVATE_CONNECTION_NEW, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 2, G_TYPE_DBUS_CONNECTION, G_TYPE_DBUS_OBJECT_MANAGER_SERVER); + + signals[PRIVATE_CONNECTION_DISCONNECTED] = + g_signal_new (NM_BUS_MANAGER_PRIVATE_CONNECTION_DISCONNECTED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, G_TYPE_POINTER); +} + + + |