/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* 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 "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);
}