mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2025-12-25 06:00:08 +01:00
This makes the non-obvious fact clearer, that when you look up an object by an untrusted, user-provided path, it might not be the object type you are looking for. In basically all cases, you need to check that the result is of the expected type. This helper makes that clearer.
1654 lines
59 KiB
C
1654 lines
59 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
/*
|
|
* Copyright (C) 2006 - 2013 Red Hat, Inc.
|
|
* Copyright (C) 2006 - 2008 Novell, Inc.
|
|
*/
|
|
|
|
#include "src/core/nm-default-daemon.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 "libnm-glib-aux/nm-c-list.h"
|
|
#include "nm-dbus-interface.h"
|
|
#include "libnm-core-intern/nm-core-internal.h"
|
|
#include "libnm-std-aux/nm-dbus-compat.h"
|
|
#include "nm-dbus-object.h"
|
|
#include "NetworkManagerUtils.h"
|
|
#include "libnm-core-aux-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 */
|
|
nm_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;
|
|
}
|
|
|
|
gpointer
|
|
nm_dbus_manager_lookup_object_with_type(NMDBusManager *self, GType gtype, const char *path)
|
|
{
|
|
gpointer ptr;
|
|
|
|
nm_assert(g_type_is_a(gtype, NM_TYPE_DBUS_OBJECT));
|
|
nm_assert(gtype != NM_TYPE_DBUS_OBJECT);
|
|
|
|
ptr = nm_dbus_manager_lookup_object(self, path);
|
|
if (!ptr || !G_TYPE_CHECK_INSTANCE_TYPE(ptr, gtype))
|
|
return NULL;
|
|
|
|
return ptr;
|
|
}
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
/* 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 (!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);
|
|
|
|
g_variant_builder_init(&invalidated_builder, G_VARIANT_TYPE("as"));
|
|
g_dbus_connection_emit_signal(
|
|
priv->main_dbus_connection,
|
|
NULL,
|
|
obj->internal.path,
|
|
DBUS_INTERFACE_PROPERTIES,
|
|
"PropertiesChanged",
|
|
g_variant_new("(s@a{sv}as)", interface_info->parent.name, args, &invalidated_builder),
|
|
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);
|
|
}
|