/* SPDX-License-Identifier: GPL-2.0+ */ /* * Copyright (C) 2006 - 2013 Red Hat, Inc. * Copyright (C) 2006 - 2008 Novell, Inc. */ #include "nm-default.h" #include "nm-dbus-manager.h" #include #include #include #include "c-list/src/c-list.h" #include "nm-glib-aux/nm-c-list.h" #include "nm-dbus-interface.h" #include "nm-core-internal.h" #include "nm-std-aux/nm-dbus-compat.h" #include "nm-dbus-object.h" #include "NetworkManagerUtils.h" #include "nm-libnm-core-intern/nm-auth-subject.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" /*****************************************************************************/ typedef struct { CList caller_info_lst; gulong uid; gulong pid; gint64 uid_checked_at; gint64 pid_checked_at; bool uid_valid : 1; bool pid_valid : 1; char sender[0]; } CallerInfo; typedef struct { GVariant *value; } PropertyCacheData; typedef struct { CList registration_lst; NMDBusObject * obj; NMDBusObjectClass *klass; guint info_idx; guint registration_id; PropertyCacheData property_cache[]; } RegistrationData; /* we require that @path is the first member of NMDBusManagerData * because _objects_by_path_hash() requires that. */ G_STATIC_ASSERT(G_STRUCT_OFFSET(struct _NMDBusObjectInternal, path) == 0); enum { PRIVATE_CONNECTION_NEW, PRIVATE_CONNECTION_DISCONNECTED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; typedef struct { GHashTable *objects_by_path; CList objects_lst_head; CList private_servers_lst_head; NMDBusManagerSetPropertyHandler set_property_handler; gpointer set_property_handler_data; GDBusConnection *main_dbus_connection; CList caller_info_lst_head; guint objmgr_registration_id; bool started : 1; bool shutting_down : 1; } NMDBusManagerPrivate; struct _NMDBusManager { GObject parent; NMDBusManagerPrivate _priv; }; struct _NMDBusManagerClass { GObjectClass parent; }; G_DEFINE_TYPE(NMDBusManager, nm_dbus_manager, G_TYPE_OBJECT) #define NM_DBUS_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDBusManager, NM_IS_DBUS_MANAGER) /*****************************************************************************/ #define _NMLOG_DOMAIN LOGD_CORE #define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "bus-manager", __VA_ARGS__) NM_DEFINE_SINGLETON_GETTER(NMDBusManager, nm_dbus_manager_get, NM_TYPE_DBUS_MANAGER); /*****************************************************************************/ static const GDBusInterfaceInfo interface_info_objmgr; static const GDBusSignalInfo signal_info_objmgr_interfaces_added; static const GDBusSignalInfo signal_info_objmgr_interfaces_removed; static GVariantBuilder *_obj_collect_properties_all(NMDBusObject *obj, GVariantBuilder *builder); /*****************************************************************************/ static guint _objects_by_path_hash(gconstpointer user_data) { const char *const *p_data = user_data; nm_assert(p_data); nm_assert(*p_data); nm_assert((*p_data)[0] == '/'); return nm_hash_str(*p_data); } static gboolean _objects_by_path_equal(gconstpointer user_data_a, gconstpointer user_data_b) { const char *const *p_data_a = user_data_a; const char *const *p_data_b = user_data_b; nm_assert(p_data_a); nm_assert(*p_data_a); nm_assert((*p_data_a)[0] == '/'); nm_assert(p_data_b); nm_assert(*p_data_b); nm_assert((*p_data_b)[0] == '/'); return nm_streq(*p_data_a, *p_data_b); } /*****************************************************************************/ 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; NMDBusManager *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 " NM_HASH_OBFUSCATE_PTR_FMT " on private socket", s->tag, NM_HASH_OBFUSCATE_PTR(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 " NM_HASH_OBFUSCATE_PTR_FMT " on private socket", s->tag, NM_HASH_OBFUSCATE_PTR(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 gboolean private_server_allow_mechanism(GDBusAuthObserver *observer, const char * mechanism, gpointer user_data) { return NM_IN_STRSET(mechanism, "EXTERNAL"); } 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_dbus_manager_private_server_register(NMDBusManager *self, const char *path, const char *tag) { NMDBusManagerPrivate *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_DBUS_MANAGER(self)); g_return_if_fail(path); g_return_if_fail(tag); priv = NM_DBUS_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); g_signal_connect(auth_observer, "allow-mechanism", G_CALLBACK(private_server_allow_mechanism), 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 void _caller_info_free(CallerInfo *caller_info) { c_list_unlink_stale(&caller_info->caller_info_lst); g_free(caller_info); } static gboolean _bus_get_unix_pid(NMDBusManager *self, const char *sender, gulong *out_pid) { NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE(self); guint32 unix_pid = G_MAXUINT32; gs_unref_variant GVariant *ret = NULL; if (!priv->main_dbus_connection) return FALSE; ret = g_dbus_connection_call_sync(priv->main_dbus_connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "GetConnectionUnixProcessID", g_variant_new("(s)", sender), G_VARIANT_TYPE("(u)"), G_DBUS_CALL_FLAGS_NONE, 2000, NULL, NULL); if (!ret) return FALSE; g_variant_get(ret, "(u)", &unix_pid); *out_pid = (gulong) unix_pid; return TRUE; } static gboolean _bus_get_unix_user(NMDBusManager *self, const char *sender, gulong *out_user) { NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE(self); guint32 unix_uid = G_MAXUINT32; gs_unref_variant GVariant *ret = NULL; if (!priv->main_dbus_connection) return FALSE; ret = g_dbus_connection_call_sync(priv->main_dbus_connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "GetConnectionUnixUser", g_variant_new("(s)", sender), G_VARIANT_TYPE("(u)"), G_DBUS_CALL_FLAGS_NONE, 2000, NULL, NULL); if (!ret) return FALSE; g_variant_get(ret, "(u)", &unix_uid); *out_user = (gulong) unix_uid; return TRUE; } static const CallerInfo * _get_caller_info_ensure(NMDBusManager *self, const char * sender, gboolean ensure_uid, gboolean ensure_pid) { NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE(self); CallerInfo * caller_info; CallerInfo * ci; gint64 now_ns; gsize num; #define CALLER_INFO_MAX_AGE (NM_UTILS_NSEC_PER_SEC * 1) /* Linear search the cache for the sender. * * The number of cached caller-infos is limited. Hence, it's O(1) and * the list is reasonably short. * Also, the entire caching assumes that we repeatedly ask for the * same sender. That means, we expect to find the right caller info * at the front of the list. */ num = 1; caller_info = NULL; c_list_for_each_entry (ci, &priv->caller_info_lst_head, caller_info_lst) { if (nm_streq(sender, ci->sender)) { caller_info = ci; break; } num++; } if (caller_info) nm_c_list_move_front(&priv->caller_info_lst_head, &caller_info->caller_info_lst); else { gsize l = strlen(sender) + 1; caller_info = g_malloc(sizeof(CallerInfo) + l); *caller_info = (CallerInfo){ .uid_checked_at = -CALLER_INFO_MAX_AGE, .pid_checked_at = -CALLER_INFO_MAX_AGE, }; memcpy(caller_info->sender, sender, l); c_list_link_front(&priv->caller_info_lst_head, &caller_info->caller_info_lst); /* only cache the last few entries. */ while (TRUE) { nm_assert(num > 0 && num == c_list_length(&priv->caller_info_lst_head)); if (num-- <= 5) break; _caller_info_free( c_list_last_entry(&priv->caller_info_lst_head, CallerInfo, caller_info_lst)); } } now_ns = nm_utils_get_monotonic_timestamp_nsec(); if (ensure_uid && (now_ns - caller_info->uid_checked_at) > CALLER_INFO_MAX_AGE) { caller_info->uid_checked_at = now_ns; if (!(caller_info->uid_valid = _bus_get_unix_user(self, sender, &caller_info->uid))) caller_info->uid = G_MAXULONG; } if (ensure_pid && (now_ns - caller_info->pid_checked_at) > CALLER_INFO_MAX_AGE) { caller_info->pid_checked_at = now_ns; if (!(caller_info->pid_valid = _bus_get_unix_pid(self, sender, &caller_info->pid))) caller_info->pid = G_MAXULONG; } return caller_info; } static gboolean _get_caller_info(NMDBusManager * self, GDBusMethodInvocation *context, GDBusConnection * connection, GDBusMessage * message, const char ** out_sender, gulong * out_uid, gulong * out_pid) { NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE(self); const CallerInfo * caller_info; const char * sender; if (context) { nm_assert(G_IS_DBUS_METHOD_INVOCATION(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 { nm_assert(G_IS_DBUS_MESSAGE(message)); sender = g_dbus_message_get_sender(message); } nm_assert(G_IS_DBUS_CONNECTION(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) { NM_SET_OUT(out_uid, 0); NM_SET_OUT(out_sender, 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; } } NM_SET_OUT(out_sender, NULL); NM_SET_OUT(out_uid, G_MAXULONG); NM_SET_OUT(out_pid, G_MAXULONG); return FALSE; } caller_info = _get_caller_info_ensure(self, sender, !!out_uid, !!out_pid); NM_SET_OUT(out_sender, caller_info->sender); NM_SET_OUT(out_uid, caller_info->uid); NM_SET_OUT(out_pid, caller_info->pid); if (out_uid && !caller_info->uid_valid) return FALSE; if (out_pid && !caller_info->pid_valid) return FALSE; return TRUE; } gboolean nm_dbus_manager_get_caller_info(NMDBusManager * self, GDBusMethodInvocation *context, const 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_dbus_manager_get_caller_info_from_message(NMDBusManager * self, GDBusConnection *connection, GDBusMessage * message, const char ** out_sender, gulong * out_uid, gulong * out_pid) { return _get_caller_info(self, NULL, connection, message, out_sender, out_uid, out_pid); } /** * nm_dbus_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_dbus_manager_ensure_uid(NMDBusManager * self, GDBusMethodInvocation *context, gulong uid, GQuark error_domain, int error_code) { gulong caller_uid; GError *error = NULL; g_return_val_if_fail(NM_IS_DBUS_MANAGER(self), FALSE); g_return_val_if_fail(G_IS_DBUS_METHOD_INVOCATION(context), FALSE); if (!nm_dbus_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_dbus_manager_get_unix_user(NMDBusManager *self, const char *sender, gulong *out_uid) { NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE(self); const CallerInfo * caller_info; PrivateServer * s; 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 */ caller_info = _get_caller_info_ensure(self, sender, TRUE, FALSE); *out_uid = caller_info->uid; if (!caller_info->uid_valid) { _LOGW("failed to get unix user for dbus sender '%s'", sender); return FALSE; } return TRUE; } /*****************************************************************************/ static const NMDBusInterfaceInfoExtended * _reg_data_get_interface_info(RegistrationData *reg_data) { nm_assert(reg_data); return reg_data->klass->interface_infos[reg_data->info_idx]; } /*****************************************************************************/ static void dbus_vtable_method_call(GDBusConnection * connection, const char * sender, const char * object_path, const char * interface_name, const char * method_name, GVariant * parameters, GDBusMethodInvocation *invocation, gpointer user_data) { NMDBusManager * self; NMDBusManagerPrivate * priv; RegistrationData * reg_data = user_data; NMDBusObject * obj = reg_data->obj; const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info(reg_data); const NMDBusMethodInfoExtended * method_info = NULL; gboolean on_same_interface; on_same_interface = nm_streq(interface_info->parent.name, interface_name); /* handle property setter first... */ if (!on_same_interface && nm_streq(interface_name, DBUS_INTERFACE_PROPERTIES) && nm_streq(method_name, "Set")) { const NMDBusPropertyInfoExtended *property_info = NULL; const char * property_interface; const char * property_name; gs_unref_variant GVariant *value = NULL; self = nm_dbus_object_get_manager(obj); priv = NM_DBUS_MANAGER_GET_PRIVATE(self); g_variant_get(parameters, "(&s&sv)", &property_interface, &property_name, &value); nm_assert(nm_streq(property_interface, interface_info->parent.name)); property_info = (const NMDBusPropertyInfoExtended *) nm_dbus_utils_interface_info_lookup_property( &interface_info->parent, property_name, NULL); if (!property_info || !NM_FLAGS_HAS(property_info->parent.flags, G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE)) g_return_if_reached(); if (!priv->set_property_handler) { g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_AUTH_FAILED, "Cannot authenticate setting property %s", property_name); return; } priv->set_property_handler(obj, interface_info, property_info, connection, sender, invocation, value, priv->set_property_handler_data); return; } if (on_same_interface) { method_info = (const NMDBusMethodInfoExtended *) nm_dbus_utils_interface_info_lookup_method( &interface_info->parent, method_name); } if (!method_info) { g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD, "Unknown method %s", method_name); return; } self = nm_dbus_object_get_manager(obj); priv = NM_DBUS_MANAGER_GET_PRIVATE(self); if (priv->shutting_down && !method_info->allow_during_shutdown) { g_dbus_method_invocation_return_error_literal(invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "NetworkManager is exiting"); return; } method_info->handle(reg_data->obj, interface_info, method_info, connection, sender, invocation, parameters); } static GVariant * _obj_get_property(RegistrationData *reg_data, guint property_idx, gboolean refetch) { const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info(reg_data); const NMDBusPropertyInfoExtended * property_info; GVariant * value; property_info = (const NMDBusPropertyInfoExtended *) (interface_info->parent.properties[property_idx]); if (refetch) nm_clear_g_variant(®_data->property_cache[property_idx].value); else { value = reg_data->property_cache[property_idx].value; if (value) goto out; } value = nm_dbus_utils_get_property(G_OBJECT(reg_data->obj), property_info->parent.signature, property_info->property_name); reg_data->property_cache[property_idx].value = value; out: return g_variant_ref(value); } static GVariant * dbus_vtable_get_property(GDBusConnection *connection, const char * sender, const char * object_path, const char * interface_name, const char * property_name, GError ** error, gpointer user_data) { RegistrationData * reg_data = user_data; const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info(reg_data); guint property_idx; if (!nm_dbus_utils_interface_info_lookup_property(&interface_info->parent, property_name, &property_idx)) g_return_val_if_reached(NULL); return _obj_get_property(reg_data, property_idx, FALSE); } static const GDBusInterfaceVTable dbus_vtable = { .method_call = dbus_vtable_method_call, .get_property = dbus_vtable_get_property, /* set_property is handled via method_call as well. We need to authenticate * which requires an asynchronous handler. */ .set_property = NULL, }; static void _obj_register(NMDBusManager *self, NMDBusObject *obj) { NMDBusManagerPrivate * priv = NM_DBUS_MANAGER_GET_PRIVATE(self); guint i, k; guint n_klasses; GType gtype; NMDBusObjectClass * klasses[10]; const NMDBusInterfaceInfoExtended *const *prev_interface_infos = NULL; GVariantBuilder builder; nm_assert(c_list_is_empty(&obj->internal.registration_lst_head)); nm_assert(priv->main_dbus_connection); nm_assert(priv->objmgr_registration_id != 0); nm_assert(priv->started); n_klasses = 0; gtype = G_OBJECT_TYPE(obj); while (gtype != NM_TYPE_DBUS_OBJECT) { nm_assert(n_klasses < G_N_ELEMENTS(klasses)); klasses[n_klasses++] = g_type_class_ref(gtype); gtype = g_type_parent(gtype); } for (k = n_klasses; k > 0;) { NMDBusObjectClass *klass = NM_DBUS_OBJECT_CLASS(klasses[--k]); if (!klass->interface_infos) continue; if (prev_interface_infos == klass->interface_infos) { /* derived classes inherrit the interface-infos from the parent class. * For convenience, we allow the subclass to leave interface-infos untouched, * but it means we must ignore the parent's interface, because we already * handled it. * * Note that the loop goes from the parent classes to child classes */ continue; } prev_interface_infos = klass->interface_infos; for (i = 0; klass->interface_infos[i]; i++) { const NMDBusInterfaceInfoExtended *interface_info = klass->interface_infos[i]; RegistrationData * reg_data; gs_free_error GError *error = NULL; guint registration_id; guint prop_len = NM_PTRARRAY_LEN(interface_info->parent.properties); reg_data = g_malloc0(sizeof(RegistrationData) + (sizeof(PropertyCacheData) * prop_len)); registration_id = g_dbus_connection_register_object( priv->main_dbus_connection, obj->internal.path, NM_UNCONST_PTR(GDBusInterfaceInfo, &interface_info->parent), &dbus_vtable, reg_data, NULL, &error); if (!registration_id) { _LOGE("failure to register object %s: %s", obj->internal.path, error->message); g_free(reg_data); continue; } reg_data->obj = obj; reg_data->klass = g_type_class_ref(G_TYPE_FROM_CLASS(klass)); reg_data->info_idx = i; reg_data->registration_id = registration_id; c_list_link_tail(&obj->internal.registration_lst_head, ®_data->registration_lst); } } for (k = 0; k < n_klasses; k++) g_type_class_unref(klasses[k]); nm_assert(!c_list_is_empty(&obj->internal.registration_lst_head)); /* Currently, the interfaces of an object do not changed and strictly depend on the object glib type. * We don't need more flexibility, and it simplifies the code. Hence, now emit interface-added * signal for the new object. * * Warning: note that if @obj's notify signal is currently blocked via g_object_freeze_notify(), * we might emit properties with an inconsistent (internal) state. There is no easy solution, * because we have to emit the signal now, and we don't know what the correct desired state * of the properties is. * Another problem is, upon unfreezing the signals, we immediately send PropertiesChanged * notifications out. Which is a bit odd, as we just export the object. * * In general, it's ok to export an object with frozen signals. But you better make sure * that all properties are in a self-consistent state when exporting the object. */ g_dbus_connection_emit_signal(priv->main_dbus_connection, NULL, OBJECT_MANAGER_SERVER_BASE_PATH, interface_info_objmgr.name, signal_info_objmgr_interfaces_added.name, g_variant_new("(oa{sa{sv}})", obj->internal.path, _obj_collect_properties_all(obj, &builder)), NULL); } static void _obj_unregister(NMDBusManager *self, NMDBusObject *obj) { NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE(self); RegistrationData * reg_data; GVariantBuilder builder; nm_assert(NM_IS_DBUS_OBJECT(obj)); nm_assert(priv->main_dbus_connection); nm_assert(priv->objmgr_registration_id != 0); nm_assert(priv->started); nm_assert(!c_list_is_empty(&obj->internal.registration_lst_head)); g_variant_builder_init(&builder, G_VARIANT_TYPE("as")); while ((reg_data = c_list_last_entry(&obj->internal.registration_lst_head, RegistrationData, registration_lst))) { const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info(reg_data); guint i; g_variant_builder_add(&builder, "s", interface_info->parent.name); c_list_unlink_stale(®_data->registration_lst); if (!g_dbus_connection_unregister_object(priv->main_dbus_connection, reg_data->registration_id)) nm_assert_not_reached(); if (interface_info->parent.properties) { for (i = 0; interface_info->parent.properties[i]; i++) nm_clear_g_variant(®_data->property_cache[i].value); } g_type_class_unref(reg_data->klass); g_free(reg_data); } g_dbus_connection_emit_signal(priv->main_dbus_connection, NULL, OBJECT_MANAGER_SERVER_BASE_PATH, interface_info_objmgr.name, signal_info_objmgr_interfaces_removed.name, g_variant_new("(oas)", obj->internal.path, &builder), NULL); } gpointer nm_dbus_manager_lookup_object(NMDBusManager *self, const char *path) { NMDBusManagerPrivate *priv; gpointer ptr; NMDBusObject * obj; g_return_val_if_fail(NM_IS_DBUS_MANAGER(self), NULL); g_return_val_if_fail(path, NULL); priv = NM_DBUS_MANAGER_GET_PRIVATE(self); ptr = g_hash_table_lookup(priv->objects_by_path, &path); if (!ptr) return NULL; obj = (NMDBusObject *) (((char *) ptr) - G_STRUCT_OFFSET(NMDBusObject, internal)); nm_assert(NM_IS_DBUS_OBJECT(obj)); return obj; } void _nm_dbus_manager_obj_export(NMDBusObject *obj) { NMDBusManager * self; NMDBusManagerPrivate *priv; g_return_if_fail(NM_IS_DBUS_OBJECT(obj)); g_return_if_fail(obj->internal.path); g_return_if_fail(NM_IS_DBUS_MANAGER(obj->internal.bus_manager)); g_return_if_fail(c_list_is_empty(&obj->internal.objects_lst)); nm_assert(c_list_is_empty(&obj->internal.registration_lst_head)); self = obj->internal.bus_manager; priv = NM_DBUS_MANAGER_GET_PRIVATE(self); if (!g_hash_table_add(priv->objects_by_path, &obj->internal)) nm_assert_not_reached(); c_list_link_tail(&priv->objects_lst_head, &obj->internal.objects_lst); if (priv->started) _obj_register(self, obj); } void _nm_dbus_manager_obj_unexport(NMDBusObject *obj) { NMDBusManager * self; NMDBusManagerPrivate *priv; g_return_if_fail(NM_IS_DBUS_OBJECT(obj)); g_return_if_fail(obj->internal.path); g_return_if_fail(NM_IS_DBUS_MANAGER(obj->internal.bus_manager)); g_return_if_fail(!c_list_is_empty(&obj->internal.objects_lst)); self = obj->internal.bus_manager; priv = NM_DBUS_MANAGER_GET_PRIVATE(self); nm_assert(&obj->internal == g_hash_table_lookup(priv->objects_by_path, &obj->internal)); nm_assert(c_list_contains(&priv->objects_lst_head, &obj->internal.objects_lst)); if (priv->started) _obj_unregister(self, obj); else nm_assert(c_list_is_empty(&obj->internal.registration_lst_head)); if (!g_hash_table_remove(priv->objects_by_path, &obj->internal)) nm_assert_not_reached(); c_list_unlink(&obj->internal.objects_lst); } void _nm_dbus_manager_obj_notify(NMDBusObject *obj, guint n_pspecs, const GParamSpec *const *pspecs) { NMDBusManager * self; NMDBusManagerPrivate *priv; RegistrationData * reg_data; guint i, p; gboolean any_legacy_signals = FALSE; gboolean any_legacy_properties = FALSE; GVariantBuilder legacy_builder; GVariant * device_statistics_args = NULL; nm_assert(NM_IS_DBUS_OBJECT(obj)); nm_assert(obj->internal.path); nm_assert(NM_IS_DBUS_MANAGER(obj->internal.bus_manager)); nm_assert(!c_list_is_empty(&obj->internal.objects_lst)); self = obj->internal.bus_manager; priv = NM_DBUS_MANAGER_GET_PRIVATE(self); nm_assert(!priv->started || priv->objmgr_registration_id != 0); nm_assert(priv->objmgr_registration_id == 0 || priv->main_dbus_connection); nm_assert(c_list_is_empty(&obj->internal.registration_lst_head) != priv->started); if (G_UNLIKELY(!priv->started)) return; c_list_for_each_entry (reg_data, &obj->internal.registration_lst_head, registration_lst) { if (_reg_data_get_interface_info(reg_data)->legacy_property_changed) { any_legacy_signals = TRUE; break; } } /* do a naive search for the matching NMDBusPropertyInfoExtended infos. Since the number of * (interfaces x properties) is static and possibly small, this naive search is effectively * O(1). We might wanna introduce some index to lookup the properties in question faster. * * The nice part of this implementation is however, that the order in which properties * are added to the GVariant is strictly defined to be the order in which the D-Bus property-info * is declared. Getting a defined ordering with some smart lookup would be hard. */ c_list_for_each_entry (reg_data, &obj->internal.registration_lst_head, registration_lst) { const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info(reg_data); gboolean has_properties = FALSE; GVariantBuilder builder; GVariantBuilder invalidated_builder; GVariant * args; if (!interface_info->parent.properties) continue; for (i = 0; interface_info->parent.properties[i]; i++) { const NMDBusPropertyInfoExtended *property_info = (const NMDBusPropertyInfoExtended *) interface_info->parent.properties[i]; for (p = 0; p < n_pspecs; p++) { const GParamSpec *pspec = pspecs[p]; gs_unref_variant GVariant *value = NULL; if (!nm_streq(property_info->property_name, pspec->name)) continue; value = _obj_get_property(reg_data, i, TRUE); if (property_info->include_in_legacy_property_changed && any_legacy_signals) { /* also track the value in the legacy_builder to emit legacy signals below. */ if (!any_legacy_properties) { any_legacy_properties = TRUE; g_variant_builder_init(&legacy_builder, G_VARIANT_TYPE("a{sv}")); } g_variant_builder_add(&legacy_builder, "{sv}", property_info->parent.name, value); } if (!has_properties) { has_properties = TRUE; g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); } g_variant_builder_add(&builder, "{sv}", property_info->parent.name, value); } } if (!has_properties) continue; args = g_variant_builder_end(&builder); if (G_UNLIKELY(interface_info == &nm_interface_info_device_statistics)) { /* we treat the Device.Statistics signal special, because we need to * emit a signal also for it (below). */ nm_assert(!device_statistics_args); device_statistics_args = g_variant_ref_sink(args); } g_variant_builder_init(&invalidated_builder, G_VARIANT_TYPE("as")); g_dbus_connection_emit_signal( priv->main_dbus_connection, NULL, obj->internal.path, "org.freedesktop.DBus.Properties", "PropertiesChanged", g_variant_new("(s@a{sv}as)", interface_info->parent.name, args, &invalidated_builder), NULL); } if (G_UNLIKELY(device_statistics_args)) { /* this is a special interface: it has a legacy PropertiesChanged signal, * however, contrary to other interfaces with ~regular~ legacy signals, * we only notify about properties that actually belong to this interface. */ g_dbus_connection_emit_signal(priv->main_dbus_connection, NULL, obj->internal.path, nm_interface_info_device_statistics.parent.name, "PropertiesChanged", g_variant_new("(@a{sv})", device_statistics_args), NULL); g_variant_unref(device_statistics_args); } if (any_legacy_properties) { gs_unref_variant GVariant *args = NULL; /* The legacy PropertyChanged signal on the NetworkManager D-Bus interface is * deprecated for the standard signal on org.freedesktop.DBus.Properties. However, * for backward compatibility, we still need to emit it. * * Due to a bug in dbus-glib in NetworkManager <= 1.0, the signal would * not only notify about properties that were actually on the corresponding * D-Bus interface. Instead, it would notify about all relevant properties * on all interfaces that had such a signal. * * For example, "HwAddress" gets emitted both on "fdo.NM.Device.Ethernet" * and "fdo.NM.Device.Veth" for veth interfaces, although only the former * actually has such a property. * Also note that "fdo.NM.Device" interface has no legacy signal. All notifications * about its properties are instead emitted on the interfaces of the subtypes. * * See bgo#770629 and commit bef26a2e69f51259095fa080221db73de09fd38d. */ args = g_variant_ref_sink(g_variant_new("(a{sv})", &legacy_builder)); c_list_for_each_entry (reg_data, &obj->internal.registration_lst_head, registration_lst) { const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info(reg_data); if (interface_info->legacy_property_changed) { g_dbus_connection_emit_signal(priv->main_dbus_connection, NULL, obj->internal.path, interface_info->parent.name, "PropertiesChanged", args, NULL); } } } } void _nm_dbus_manager_obj_emit_signal(NMDBusObject * obj, const NMDBusInterfaceInfoExtended *interface_info, const GDBusSignalInfo * signal_info, GVariant * args) { NMDBusManager * self; NMDBusManagerPrivate *priv; g_return_if_fail(NM_IS_DBUS_OBJECT(obj)); g_return_if_fail(obj->internal.path); g_return_if_fail(NM_IS_DBUS_MANAGER(obj->internal.bus_manager)); g_return_if_fail(!c_list_is_empty(&obj->internal.objects_lst)); self = obj->internal.bus_manager; priv = NM_DBUS_MANAGER_GET_PRIVATE(self); if (!priv->started) { nm_g_variant_unref_floating(args); return; } g_dbus_connection_emit_signal(priv->main_dbus_connection, NULL, obj->internal.path, interface_info->parent.name, signal_info->name, args, NULL); } /*****************************************************************************/ static GVariantBuilder * _obj_collect_properties_per_interface(NMDBusObject * obj, RegistrationData *reg_data, GVariantBuilder * builder) { const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info(reg_data); guint i; g_variant_builder_init(builder, G_VARIANT_TYPE("a{sv}")); if (interface_info->parent.properties) { for (i = 0; interface_info->parent.properties[i]; i++) { const NMDBusPropertyInfoExtended *property_info = (const NMDBusPropertyInfoExtended *) interface_info->parent.properties[i]; gs_unref_variant GVariant *variant = NULL; variant = _obj_get_property(reg_data, i, FALSE); g_variant_builder_add(builder, "{sv}", property_info->parent.name, variant); } } return builder; } static GVariantBuilder * _obj_collect_properties_all(NMDBusObject *obj, GVariantBuilder *builder) { RegistrationData *reg_data; g_variant_builder_init(builder, G_VARIANT_TYPE("a{sa{sv}}")); c_list_for_each_entry (reg_data, &obj->internal.registration_lst_head, registration_lst) { GVariantBuilder properties_builder; g_variant_builder_add( builder, "{sa{sv}}", _reg_data_get_interface_info(reg_data)->parent.name, _obj_collect_properties_per_interface(obj, reg_data, &properties_builder)); } return builder; } static void dbus_vtable_objmgr_method_call(GDBusConnection * connection, const char * sender, const char * object_path, const char * interface_name, const char * method_name, GVariant * parameters, GDBusMethodInvocation *invocation, gpointer user_data) { NMDBusManager * self = user_data; NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE(self); GVariantBuilder array_builder; NMDBusObject * obj; nm_assert(nm_streq0(object_path, OBJECT_MANAGER_SERVER_BASE_PATH)); if (!nm_streq(method_name, "GetManagedObjects") || !nm_streq(interface_name, interface_info_objmgr.name)) { g_dbus_method_invocation_return_error( invocation, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD, "Unknown method %s - only GetManagedObjects() is supported", method_name); return; } g_variant_builder_init(&array_builder, G_VARIANT_TYPE("a{oa{sa{sv}}}")); c_list_for_each_entry (obj, &priv->objects_lst_head, internal.objects_lst) { GVariantBuilder interfaces_builder; /* note that we are called on an idle handler. Hence, all properties are * supposed to be in a consistent state. That is true, if you always * g_object_thaw_notify() before returning to the mainloop. Keeping * signals frozen between while returning from the current call stack * is anyway a very fragile thing, easy to get wrong. Don't do that. */ g_variant_builder_add(&array_builder, "{oa{sa{sv}}}", obj->internal.path, _obj_collect_properties_all(obj, &interfaces_builder)); } g_dbus_method_invocation_return_value(invocation, g_variant_new("(a{oa{sa{sv}}})", &array_builder)); } static const GDBusInterfaceVTable dbus_vtable_objmgr = {.method_call = dbus_vtable_objmgr_method_call}; static const GDBusSignalInfo signal_info_objmgr_interfaces_added = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT( "InterfacesAdded", .args = NM_DEFINE_GDBUS_ARG_INFOS( NM_DEFINE_GDBUS_ARG_INFO("object_path", "o"), NM_DEFINE_GDBUS_ARG_INFO("interfaces_and_properties", "a{sa{sv}}"), ), ); static const GDBusSignalInfo signal_info_objmgr_interfaces_removed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT( "InterfacesRemoved", .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("object_path", "o"), NM_DEFINE_GDBUS_ARG_INFO("interfaces", "as"), ), ); static const GDBusInterfaceInfo interface_info_objmgr = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( DBUS_INTERFACE_OBJECT_MANAGER, .methods = NM_DEFINE_GDBUS_METHOD_INFOS( NM_DEFINE_GDBUS_METHOD_INFO( "GetManagedObjects", .out_args = NM_DEFINE_GDBUS_ARG_INFOS( NM_DEFINE_GDBUS_ARG_INFO("object_paths_interfaces_and_properties", "a{oa{sa{sv}}}"), ), ), ), .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&signal_info_objmgr_interfaces_added, &signal_info_objmgr_interfaces_removed, ), ); /*****************************************************************************/ GDBusConnection * nm_dbus_manager_get_dbus_connection(NMDBusManager *self) { g_return_val_if_fail(NM_IS_DBUS_MANAGER(self), NULL); return NM_DBUS_MANAGER_GET_PRIVATE(self)->main_dbus_connection; } void nm_dbus_manager_start(NMDBusManager * self, NMDBusManagerSetPropertyHandler set_property_handler, gpointer set_property_handler_data) { NMDBusManagerPrivate *priv; NMDBusObject * obj; g_return_if_fail(NM_IS_DBUS_MANAGER(self)); priv = NM_DBUS_MANAGER_GET_PRIVATE(self); nm_assert(!priv->started); if (priv->objmgr_registration_id == 0) { /* Do nothing. We're presumably in the configure-and-quit mode. */ return; } priv->set_property_handler = set_property_handler; priv->set_property_handler_data = set_property_handler_data; priv->started = TRUE; c_list_for_each_entry (obj, &priv->objects_lst_head, internal.objects_lst) _obj_register(self, obj); } gboolean nm_dbus_manager_acquire_bus(NMDBusManager *self, gboolean request_name) { NMDBusManagerPrivate *priv; gs_free_error GError *error = NULL; gs_unref_variant GVariant *ret = NULL; guint32 result; guint registration_id; g_return_val_if_fail(NM_IS_DBUS_MANAGER(self), FALSE); priv = NM_DBUS_MANAGER_GET_PRIVATE(self); /* Create the D-Bus connection and registering the name synchronously. * That is necessary because we need to exit right away if we can't * acquire the name despite connecting to the bus successfully. * It means that something is gravely broken -- such as another NetworkManager * instance running. */ priv->main_dbus_connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); if (!priv->main_dbus_connection) { _LOGE("cannot connect to D-Bus: %s", error->message); return FALSE; } g_dbus_connection_set_exit_on_close(priv->main_dbus_connection, FALSE); if (!request_name) { _LOGD("D-Bus connection created"); return TRUE; } registration_id = g_dbus_connection_register_object( priv->main_dbus_connection, OBJECT_MANAGER_SERVER_BASE_PATH, NM_UNCONST_PTR(GDBusInterfaceInfo, &interface_info_objmgr), &dbus_vtable_objmgr, self, NULL, &error); if (!registration_id) { _LOGE("failure to register object manager: %s", error->message); return FALSE; } ret = g_dbus_connection_call_sync( priv->main_dbus_connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "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, &error); if (!ret) { _LOGE("fatal failure to acquire D-Bus service \"%s" ": %s", NM_DBUS_SERVICE, error->message); g_dbus_connection_unregister_object(priv->main_dbus_connection, registration_id); return FALSE; } g_variant_get(ret, "(u)", &result); if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { _LOGE("fatal failure to acquire D-Bus service \"%s\" (%u). Service already taken", NM_DBUS_SERVICE, (guint) result); g_dbus_connection_unregister_object(priv->main_dbus_connection, registration_id); return FALSE; } priv->objmgr_registration_id = registration_id; _LOGI("acquired D-Bus service \"%s\"", NM_DBUS_SERVICE); return TRUE; } void nm_dbus_manager_stop(NMDBusManager *self) { NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE(self); priv->shutting_down = TRUE; /* during shutdown we also clear the set-property-handler. It's no longer * possible to set a property, because doing so would require authorization, * which is async, which is just complicated to get right. No more property * setting from now on. */ priv->set_property_handler = NULL; priv->set_property_handler_data = NULL; } gboolean nm_dbus_manager_is_stopping(NMDBusManager *self) { return NM_DBUS_MANAGER_GET_PRIVATE(self)->shutting_down; } /*****************************************************************************/ static void nm_dbus_manager_init(NMDBusManager *self) { NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE(self); c_list_init(&priv->private_servers_lst_head); c_list_init(&priv->objects_lst_head); priv->objects_by_path = g_hash_table_new((GHashFunc) _objects_by_path_hash, (GEqualFunc) _objects_by_path_equal); c_list_init(&priv->caller_info_lst_head); } static void dispose(GObject *object) { NMDBusManager * self = NM_DBUS_MANAGER(object); NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE(self); PrivateServer * s, *s_safe; CallerInfo * caller_info; /* All exported NMDBusObject instances keep the manager alive, so we don't * expect any remaining objects. */ nm_assert(!priv->objects_by_path || g_hash_table_size(priv->objects_by_path) == 0); nm_assert(c_list_is_empty(&priv->objects_lst_head)); nm_clear_pointer(&priv->objects_by_path, g_hash_table_destroy); c_list_for_each_entry_safe (s, s_safe, &priv->private_servers_lst_head, private_servers_lst) private_server_free(s); if (priv->objmgr_registration_id) { g_dbus_connection_unregister_object(priv->main_dbus_connection, nm_steal_int(&priv->objmgr_registration_id)); } g_clear_object(&priv->main_dbus_connection); G_OBJECT_CLASS(nm_dbus_manager_parent_class)->dispose(object); while ((caller_info = c_list_first_entry(&priv->caller_info_lst_head, CallerInfo, caller_info_lst))) _caller_info_free(caller_info); } static void nm_dbus_manager_class_init(NMDBusManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->dispose = dispose; signals[PRIVATE_CONNECTION_NEW] = g_signal_new(NM_DBUS_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_DBUS_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); } static NMAuthSubject * _new_unix_process(GDBusMethodInvocation *context, GDBusConnection * connection, GDBusMessage * message) { NMAuthSubject *self; const char * dbus_sender = NULL; gulong uid = 0; gulong pid = 0; gboolean success; g_return_val_if_fail(context || (connection && message), NULL); if (context) { success = nm_dbus_manager_get_caller_info(nm_dbus_manager_get(), context, &dbus_sender, &uid, &pid); } else { nm_assert(message); success = nm_dbus_manager_get_caller_info_from_message(nm_dbus_manager_get(), connection, message, &dbus_sender, &uid, &pid); } if (!success) return NULL; g_return_val_if_fail(dbus_sender && *dbus_sender, NULL); /* polkit glib library stores uid and pid as int. There might be some * pitfalls if the id ever happens to be larger then that. Just assert against * it here. */ g_return_val_if_fail(uid <= MIN(G_MAXINT, G_MAXINT32), NULL); g_return_val_if_fail(pid > 0 && pid <= MIN(G_MAXINT, G_MAXINT32), NULL); self = nm_auth_subject_new_unix_process(dbus_sender, pid, uid); if (nm_auth_subject_get_subject_type(self) != NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS) { /* this most likely happened because the process is gone (start_time==0). * Either that is not assert-worthy, or constructed() already asserted. * Just return NULL. */ g_clear_object(&self); } return self; } NMAuthSubject * nm_dbus_manager_new_auth_subject_from_context(GDBusMethodInvocation *context) { return _new_unix_process(context, NULL, NULL); } NMAuthSubject * nm_dbus_manager_new_auth_subject_from_message(GDBusConnection *connection, GDBusMessage *message) { return _new_unix_process(NULL, connection, message); }