// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2006 - 2013 Red Hat, Inc.
* Copyright (C) 2006 - 2008 Novell, Inc.
*/
#include "nm-default.h"
#include "nm-dbus-manager.h"
#include <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 flixibility, 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);
}