NetworkManager/src/settings/nm-agent-manager.c
Thomas Haller bf25081dfe agent-manager: fix races registering secret agent and track auth-chain per agent
We don't need a separate "GSList *chains" to track the NMAuthChain
requests for the agents. Every agent should only have one auth-chain in
fly at any time. We can attach that NMAuthChain to the secret-agent.

Also, fix a race where:

  1) A secret agent registers. We would start an auth-chain check, but not
    yet track the secret agent.
  2) Then the secret agent unregisters. The unregistration request will fail,
    because the secret agent is not yet in the list of fully registered agents.
    The same happens if the secret agent disconnects at this point.
    agent_disconnect_cb() would not find the secret agent to remove.
  3) afterwards, authentication completes and we register the
    secret-agent, although we should not.

There is also another race: if we get authority_changed_cb() we would
not restart the authentication for the secret-agent that is still
registering. Hence, we don't know whether the result once it completes
would already contain the latest state.
2019-12-31 02:13:45 +01:00

1595 lines
50 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2010 - 2013 Red Hat, Inc.
*/
#include "nm-default.h"
#include "nm-agent-manager.h"
#include <pwd.h>
#include "nm-libnm-core-intern/nm-common-macros.h"
#include "nm-dbus-interface.h"
#include "nm-secret-agent.h"
#include "nm-auth-utils.h"
#include "nm-setting-vpn.h"
#include "nm-auth-manager.h"
#include "nm-dbus-manager.h"
#include "nm-session-monitor.h"
#include "nm-simple-connection.h"
#include "NetworkManagerUtils.h"
#include "nm-core-internal.h"
#include "c-list/src/c-list.h"
/*****************************************************************************/
enum {
AGENT_REGISTERED,
LAST_SIGNAL,
};
static guint signals[LAST_SIGNAL] = { 0 };
typedef struct {
NMAuthManager *auth_mgr;
NMSessionMonitor *session_monitor;
CList agent_lst_head;
CList request_lst_head;
guint64 agent_version_id;
} NMAgentManagerPrivate;
struct _NMAgentManager {
NMDBusObject parent;
NMAgentManagerPrivate _priv;
};
struct _NMAgentManagerClass {
NMDBusObjectClass parent;
};
G_DEFINE_TYPE (NMAgentManager, nm_agent_manager, NM_TYPE_DBUS_OBJECT)
#define NM_AGENT_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMAgentManager, NM_IS_AGENT_MANAGER)
/*****************************************************************************/
NM_DEFINE_SINGLETON_GETTER (NMAgentManager, nm_agent_manager_get, NM_TYPE_AGENT_MANAGER);
/*****************************************************************************/
#define _NMLOG_PREFIX_NAME "agent-manager"
#define _NMLOG_DOMAIN LOGD_AGENTS
#define _NMLOG(level, agent, ...) \
G_STMT_START { \
if (nm_logging_enabled ((level), (_NMLOG_DOMAIN))) { \
char __prefix1[32]; \
char __prefix2[128]; \
NMSecretAgent *__agent = (agent); \
\
if (!(self)) \
g_snprintf (__prefix1, sizeof (__prefix1), "%s%s", ""_NMLOG_PREFIX_NAME"", "[]"); \
else if ((self) != singleton_instance) \
g_snprintf (__prefix1, sizeof (__prefix1), "%s["NM_HASH_OBFUSCATE_PTR_FMT"]", ""_NMLOG_PREFIX_NAME"", NM_HASH_OBFUSCATE_PTR (self)); \
else \
g_strlcpy (__prefix1, _NMLOG_PREFIX_NAME, sizeof (__prefix1)); \
if (__agent) { \
g_snprintf (__prefix2, sizeof (__prefix2), \
": agent["NM_HASH_OBFUSCATE_PTR_FMT",%s]", \
NM_HASH_OBFUSCATE_PTR (__agent), \
nm_secret_agent_get_description (__agent)); \
} else \
__prefix2[0] = '\0'; \
_nm_log ((level), (_NMLOG_DOMAIN), 0, NULL, NULL, \
"%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
__prefix1, __prefix2 _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
} \
} G_STMT_END
#define LOG_REQ_FMT "["NM_HASH_OBFUSCATE_PTR_FMT"/%s%s%s%s%s%s]"
#define LOG_REQ_ARG(req) \
NM_HASH_OBFUSCATE_PTR (req), \
NM_PRINT_FMT_QUOTE_STRING ((req)->detail), \
NM_PRINT_FMT_QUOTED (((req)->request_type == REQUEST_TYPE_CON_GET) && (req)->con.get.setting_name, \
"/\"", (req)->con.get.setting_name, "\"", \
((req)->request_type == REQUEST_TYPE_CON_GET ? "/(none)" : _request_type_to_string ((req)->request_type, FALSE)))
/*****************************************************************************/
typedef struct _NMAgentManagerCallId Request;
static void request_add_agent (Request *req, NMSecretAgent *agent);
static void request_remove_agent (Request *req, NMSecretAgent *agent);
static void request_next_agent (Request *req);
static void _con_get_request_start (Request *req);
static void _con_save_request_start (Request *req);
static void _con_del_request_start (Request *req);
static gboolean _con_get_try_complete_early (Request *req);
static void agent_disconnected_cb (NMSecretAgent *agent, gpointer user_data);
/*****************************************************************************/
guint64
nm_agent_manager_get_agent_version_id (NMAgentManager *self)
{
g_return_val_if_fail (NM_IS_AGENT_MANAGER (self), 0);
return NM_AGENT_MANAGER_GET_PRIVATE (self)->agent_version_id;
}
/*****************************************************************************/
typedef enum {
REQUEST_TYPE_INVALID,
REQUEST_TYPE_CON_GET,
REQUEST_TYPE_CON_SAVE,
REQUEST_TYPE_CON_DEL,
} RequestType;
static const char *
_request_type_to_string (RequestType request_type, gboolean verbose)
{
switch (request_type) {
case REQUEST_TYPE_CON_GET: return verbose ? "getting" : "get";
case REQUEST_TYPE_CON_SAVE: return verbose ? "saving" : "sav";
case REQUEST_TYPE_CON_DEL: return verbose ? "deleting" : "del";
default: return "??";
}
}
/*****************************************************************************/
struct _NMAgentManagerCallId {
CList request_lst;
NMAgentManager *self;
RequestType request_type;
char *detail;
NMAuthSubject *subject;
/* Current agent being asked for secrets */
NMSecretAgent *current;
NMSecretAgentCallId *current_call_id;
/* Stores the sorted list of NMSecretAgents which will be asked for secrets */
GSList *pending;
guint idle_id;
union {
struct {
char *path;
NMConnection *connection;
NMAuthChain *chain;
/* Whether the agent currently being asked for secrets
* has the system.modify privilege.
*/
gboolean current_has_modify;
union {
struct {
NMSecretAgentGetSecretsFlags flags;
char *setting_name;
char **hints;
GVariant *existing_secrets;
NMAgentSecretsResultFunc callback;
gpointer callback_data;
} get;
};
} con;
};
};
/*****************************************************************************/
static NMSecretAgent *
_agent_find_by_owner (NMAgentManagerPrivate *priv,
const char *owner)
{
NMSecretAgent *agent;
c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) {
if (nm_streq (nm_secret_agent_get_dbus_owner (agent), owner))
return agent;
}
return NULL;
}
static NMSecretAgent *
_agent_find_by_identifier_and_uid (NMAgentManagerPrivate *priv,
const char *identifier,
gulong sender_uid)
{
NMSecretAgent *agent;
c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) {
if ( nm_streq0 (nm_secret_agent_get_identifier (agent), identifier)
&& sender_uid == nm_secret_agent_get_owner_uid (agent))
return agent;
}
return NULL;
}
/*****************************************************************************/
static void
_agent_remove (NMAgentManager *self, NMSecretAgent *agent)
{
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
CList *iter, *safe;
nm_assert (NM_IS_SECRET_AGENT (agent));
nm_assert (c_list_contains (&priv->agent_lst_head, &agent->agent_lst));
_LOGD (agent, "agent unregistered or disappeared");
nm_clear_pointer (&agent->auth_chain, nm_auth_chain_destroy);
c_list_unlink (&agent->agent_lst);
g_signal_handlers_disconnect_by_func (agent, G_CALLBACK (agent_disconnected_cb), self);
/* Remove this agent from any in-progress secrets requests */
c_list_for_each_safe (iter, safe, &priv->request_lst_head)
request_remove_agent (c_list_entry (iter, Request, request_lst), agent);
g_object_unref (agent);
}
/* Call this *after* calling request_next_agent() */
static void
maybe_remove_agent_on_error (NMAgentManager *self,
NMSecretAgent *agent,
GError *error)
{
if ( !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED)
&& !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_DISCONNECTED)
&& !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER))
return;
if (!c_list_is_empty (&agent->agent_lst))
_agent_remove (self, agent);
}
/*****************************************************************************/
static gboolean
validate_identifier (const char *identifier, GError **error)
{
const char *p = identifier;
size_t id_len;
if (!identifier) {
g_set_error_literal (error,
NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_INVALID_IDENTIFIER,
"No identifier was given");
return FALSE;
}
/* Length between 3 and 255 characters inclusive */
id_len = strlen (identifier);
if (id_len < 3 || id_len > 255) {
g_set_error_literal (error,
NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_INVALID_IDENTIFIER,
"Identifier length not between 3 and 255 characters (inclusive)");
return FALSE;
}
if ((identifier[0] == '.') || (identifier[id_len - 1] == '.')) {
g_set_error_literal (error,
NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_INVALID_IDENTIFIER,
"Identifier must not start or end with '.'");
return FALSE;
}
/* FIXME: do complete validation here */
while (p && *p) {
if (!g_ascii_isalnum (*p) && (*p != '_') && (*p != '-') && (*p != '.')) {
g_set_error (error,
NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_INVALID_IDENTIFIER,
"Identifier contains invalid character '%c'", *p);
return FALSE;
}
if ((*p == '.') && (*(p + 1) == '.')) {
g_set_error_literal (error,
NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_INVALID_IDENTIFIER,
"Identifier contains two '.' characters in sequence");
return FALSE;
}
p++;
}
return TRUE;
}
static void
_agent_permissions_check_done (NMAuthChain *chain,
GDBusMethodInvocation *context,
gpointer user_data)
{
NMAgentManager *self = NM_AGENT_MANAGER (user_data);
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
NMSecretAgent *agent;
Request *request;
nm_assert (!context || G_IS_DBUS_METHOD_INVOCATION (context));
agent = nm_auth_chain_steal_data (chain, "agent");
nm_assert (NM_IS_SECRET_AGENT (agent));
nm_assert (agent->auth_chain == chain);
nm_assert (agent->fully_registered == (!context));
nm_assert (c_list_contains (&priv->agent_lst_head, &agent->agent_lst));
agent->auth_chain = NULL;
nm_secret_agent_add_permission (agent,
NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED,
(nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED) == NM_AUTH_CALL_RESULT_YES));
nm_secret_agent_add_permission (agent,
NM_AUTH_PERMISSION_WIFI_SHARE_OPEN,
(nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN) == NM_AUTH_CALL_RESULT_YES));
if (agent->fully_registered) {
_LOGD (agent, "updated agent permissions");
return;
}
_LOGI (agent, "agent registered");
agent->fully_registered = TRUE;
priv->agent_version_id += 1;
g_dbus_method_invocation_return_value (context, NULL);
c_list_for_each_entry (request, &priv->request_lst_head, request_lst)
request_add_agent (request, agent);
g_signal_emit (self, signals[AGENT_REGISTERED], 0, agent);
}
static NMAuthChain *
_agent_create_auth_chain (NMAgentManager *self,
NMSecretAgent *agent,
GDBusMethodInvocation *context)
{
NMAuthChain *chain;
_LOGD (agent, "requesting permissions");
nm_assert ( !agent->auth_chain
|| (agent->fully_registered == (!nm_auth_chain_get_context (agent->auth_chain))));
if ( agent->auth_chain
&& !context
&& !agent->fully_registered) {
/* we restart the authorization check (without a @context), but the currently
* pending auth-chain carries a context. We need to pass it on as we replace
* the auth-chain. */
context = nm_auth_chain_get_context (agent->auth_chain);
nm_assert (context);
}
chain = nm_auth_chain_new_subject (nm_secret_agent_get_subject (agent),
context,
_agent_permissions_check_done,
self);
nm_auth_chain_set_data (chain, "agent", agent, NULL);
nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED, FALSE);
nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN, FALSE);
nm_clear_pointer (&agent->auth_chain, nm_auth_chain_destroy);
agent->auth_chain = chain;
return chain;
}
static void
agent_disconnected_cb (NMSecretAgent *agent, gpointer user_data)
{
_agent_remove (NM_AGENT_MANAGER (user_data), agent);
}
static void
agent_manager_register_with_capabilities (NMAgentManager *self,
GDBusMethodInvocation *context,
const char *identifier,
guint32 capabilities)
{
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
gs_unref_object NMAuthSubject *subject = NULL;
gulong sender_uid = G_MAXULONG;
GError *error = NULL;
NMSecretAgent *agent;
subject = nm_dbus_manager_new_auth_subject_from_context (context);
if (!subject) {
error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_PERMISSION_DENIED,
NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN);
g_dbus_method_invocation_take_error (context, error);
return;
}
sender_uid = nm_auth_subject_get_unix_process_uid (subject);
/* Validate the identifier */
if (!validate_identifier (identifier, &error)) {
g_dbus_method_invocation_take_error (context, error);
return;
}
/* Only one agent for each identifier is allowed per user */
if (_agent_find_by_identifier_and_uid (priv, identifier, sender_uid)) {
error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_PERMISSION_DENIED,
"An agent with this ID is already registered for this user.");
g_dbus_method_invocation_take_error (context, error);
return;
}
agent = nm_secret_agent_new (context, subject, identifier, capabilities);
g_signal_connect (agent, NM_SECRET_AGENT_DISCONNECTED,
G_CALLBACK (agent_disconnected_cb), self);
c_list_link_tail (&priv->agent_lst_head, &agent->agent_lst);
_agent_create_auth_chain (self, agent, context);
}
static void
impl_agent_manager_register (NMDBusObject *obj,
const NMDBusInterfaceInfoExtended *interface_info,
const NMDBusMethodInfoExtended *method_info,
GDBusConnection *connection,
const char *sender,
GDBusMethodInvocation *invocation,
GVariant *parameters)
{
const char *identifier;
g_variant_get (parameters, "(&s)", &identifier);
agent_manager_register_with_capabilities (NM_AGENT_MANAGER (obj), invocation, identifier, 0);
}
static void
impl_agent_manager_register_with_capabilities (NMDBusObject *obj,
const NMDBusInterfaceInfoExtended *interface_info,
const NMDBusMethodInfoExtended *method_info,
GDBusConnection *connection,
const char *sender,
GDBusMethodInvocation *invocation,
GVariant *parameters)
{
const char *identifier;
guint32 capabilities;
g_variant_get (parameters, "(&su)", &identifier, &capabilities);
agent_manager_register_with_capabilities (NM_AGENT_MANAGER (obj), invocation, identifier, capabilities);
}
static void
impl_agent_manager_unregister (NMDBusObject *obj,
const NMDBusInterfaceInfoExtended *interface_info,
const NMDBusMethodInfoExtended *method_info,
GDBusConnection *connection,
const char *sender,
GDBusMethodInvocation *invocation,
GVariant *parameters)
{
NMAgentManager *self = NM_AGENT_MANAGER (obj);
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
NMSecretAgent *agent;
agent = _agent_find_by_owner (priv, sender);
if (!agent) {
g_dbus_method_invocation_return_error_literal (invocation,
NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_NOT_REGISTERED,
"Caller is not registered as an Agent");
return;
}
_agent_remove (self, agent);
g_dbus_method_invocation_return_value (invocation, NULL);
}
/*****************************************************************************/
static Request *
request_new (NMAgentManager *self,
RequestType request_type,
const char *detail,
NMAuthSubject *subject)
{
Request *req;
req = g_slice_new0 (Request);
req->self = g_object_ref (self);
req->request_type = request_type;
req->detail = g_strdup (detail);
req->subject = g_object_ref (subject);
c_list_link_tail (&NM_AGENT_MANAGER_GET_PRIVATE (self)->request_lst_head, &req->request_lst);
return req;
}
static void
request_free (Request *req)
{
switch (req->request_type) {
case REQUEST_TYPE_CON_GET:
case REQUEST_TYPE_CON_SAVE:
case REQUEST_TYPE_CON_DEL:
g_object_unref (req->con.connection);
g_free (req->con.path);
nm_clear_pointer (&req->con.chain, nm_auth_chain_destroy);
if (req->request_type == REQUEST_TYPE_CON_GET) {
g_free (req->con.get.setting_name);
g_strfreev (req->con.get.hints);
if (req->con.get.existing_secrets)
g_variant_unref (req->con.get.existing_secrets);
}
break;
default:
g_assert_not_reached ();
}
if (req->idle_id)
g_source_remove (req->idle_id);
/* cancel-secrets invokes the done-callback synchronously -- in which case
* the handler just return.
* Hence, we can proceed to free @req... */
nm_secret_agent_cancel_call (req->current, req->current_call_id);
g_object_unref (req->subject);
g_free (req->detail);
g_slist_free_full (req->pending, g_object_unref);
g_object_unref (req->self);
if (req->current)
g_object_unref (req->current);
memset (req, 0, sizeof (Request));
g_slice_free (Request, req);
}
static void
req_complete_release (Request *req,
GVariant *secrets,
const char *agent_dbus_owner,
const char *agent_username,
GError *error)
{
NMAgentManager *self = req->self;
switch (req->request_type) {
case REQUEST_TYPE_CON_GET:
req->con.get.callback (self,
req,
agent_dbus_owner,
agent_username,
req->con.current_has_modify,
req->con.get.setting_name,
req->con.get.flags,
error ? NULL : secrets,
error,
req->con.get.callback_data);
break;
case REQUEST_TYPE_CON_SAVE:
case REQUEST_TYPE_CON_DEL:
break;
default:
g_return_if_reached ();
}
request_free (req);
}
static void
req_complete_cancel (Request *req, gboolean is_disposing)
{
gs_free_error GError *error = NULL;
nm_assert (req && req->self);
nm_assert (!c_list_contains (&NM_AGENT_MANAGER_GET_PRIVATE (req->self)->request_lst_head, &req->request_lst));
nm_utils_error_set_cancelled (&error, is_disposing, "NMAgentManager");
req_complete_release (req, NULL, NULL, NULL, error);
}
static void
req_complete (Request *req,
GVariant *secrets,
const char *agent_dbus_owner,
const char *agent_username,
GError *error)
{
NMAgentManager *self = req->self;
nm_assert (c_list_contains (&NM_AGENT_MANAGER_GET_PRIVATE (self)->request_lst_head, &req->request_lst));
c_list_unlink (&req->request_lst);
req_complete_release (req, secrets, agent_dbus_owner, agent_username, error);
}
static void
req_complete_error (Request *req, GError *error)
{
req_complete (req, NULL, NULL, NULL, error);
}
static int
agent_compare_func (gconstpointer aa, gconstpointer bb, gpointer user_data)
{
NMSecretAgent *a = (NMSecretAgent *)aa;
NMSecretAgent *b = (NMSecretAgent *)bb;
Request *req = user_data;
NMSessionMonitor *sm;
gboolean a_active, b_active;
gulong a_pid, b_pid, requester;
guint64 a_start, b_start;
a_pid = nm_secret_agent_get_pid (a);
b_pid = nm_secret_agent_get_pid (b);
/* Prefer agents in the process the request came from */
if (nm_auth_subject_get_subject_type (req->subject) == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS) {
requester = nm_auth_subject_get_unix_process_pid (req->subject);
if (a_pid != b_pid) {
if (a_pid == requester)
return -1;
else if (b_pid == requester)
return 1;
}
}
/* Prefer agents in active sessions */
sm = NM_AGENT_MANAGER_GET_PRIVATE (req->self)->session_monitor;
a_active = nm_session_monitor_session_exists (sm, nm_secret_agent_get_owner_uid (a), TRUE);
b_active = nm_session_monitor_session_exists (sm, nm_secret_agent_get_owner_uid (b), TRUE);
if (a_active && !b_active)
return -1;
else if (!a_active && b_active)
return 1;
/* Prefer agents launched later (this is essentially to ease agent debugging) */
a_start = nm_utils_get_start_time_for_pid (a_pid, NULL, NULL);
b_start = nm_utils_get_start_time_for_pid (b_pid, NULL, NULL);
if (a_start > b_start)
return -1;
else if (a_start < b_start)
return 1;
return 0;
}
static void
request_add_agent (Request *req, NMSecretAgent *agent)
{
NMAgentManager *self;
g_return_if_fail (req != NULL);
g_return_if_fail (agent != NULL);
self = req->self;
if (req->request_type == REQUEST_TYPE_CON_GET) {
NMAuthSubject *subject = nm_secret_agent_get_subject (agent);
/* Ensure the caller's username exists in the connection's permissions,
* or that the permissions is empty (ie, visible by everyone).
*/
if (!nm_auth_is_subject_in_acl (req->con.connection, subject, NULL)) {
_LOGD (agent, "agent ignored for secrets request "LOG_REQ_FMT" (not in ACL)",
LOG_REQ_ARG (req));
/* Connection not visible to this agent's user */
return;
}
}
/* If the request should filter agents by UID, do that now */
if (nm_auth_subject_get_subject_type (req->subject) == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS) {
uid_t agent_uid, subject_uid;
agent_uid = nm_secret_agent_get_owner_uid (agent);
subject_uid = nm_auth_subject_get_unix_process_uid (req->subject);
if (agent_uid != subject_uid) {
_LOGD (agent, "agent ignored for secrets request "LOG_REQ_FMT" "
"(uid %ld not required %ld)",
LOG_REQ_ARG (req),
(long) agent_uid, (long) subject_uid);
return;
}
}
_LOGD (agent, "agent allowed for secrets request "LOG_REQ_FMT,
LOG_REQ_ARG (req));
/* Add this agent to the list, sorted appropriately */
req->pending = g_slist_insert_sorted_with_data (req->pending,
g_object_ref (agent),
agent_compare_func,
req);
}
static void
request_add_agents (NMAgentManager *self, Request *req)
{
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
NMSecretAgent *agent;
c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) {
if (agent->fully_registered)
request_add_agent (req, agent);
}
}
static void
request_next_agent (Request *req)
{
NMAgentManager *self;
GError *error = NULL;
self = req->self;
nm_secret_agent_cancel_call (req->current, req->current_call_id);
nm_assert (!req->current_call_id);
g_clear_object (&req->current);
if (req->pending) {
/* Send the request to the next agent */
req->current = req->pending->data;
req->pending = g_slist_remove (req->pending, req->current);
_LOGD (req->current, "agent %s secrets for request "LOG_REQ_FMT,
_request_type_to_string (req->request_type, TRUE),
LOG_REQ_ARG (req));
switch (req->request_type) {
case REQUEST_TYPE_CON_GET:
_con_get_request_start (req);
break;
case REQUEST_TYPE_CON_SAVE:
_con_save_request_start (req);
break;
case REQUEST_TYPE_CON_DEL:
_con_del_request_start (req);
break;
default:
g_assert_not_reached ();
}
} else {
/* No more secret agents are available to fulfill this secrets request */
error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_NO_SECRETS,
"No agents were available for this request.");
req_complete_error (req, error);
g_error_free (error);
}
}
static void
request_remove_agent (Request *req, NMSecretAgent *agent)
{
NMAgentManager *self;
g_return_if_fail (req != NULL);
g_return_if_fail (agent != NULL);
self = req->self;
if (agent == req->current) {
nm_assert (!g_slist_find (req->pending, agent));
_LOGD (agent, "current agent removed from secrets request "LOG_REQ_FMT,
LOG_REQ_ARG (req));
switch (req->request_type) {
case REQUEST_TYPE_CON_GET:
case REQUEST_TYPE_CON_SAVE:
case REQUEST_TYPE_CON_DEL:
/* This cancels the pending authorization requests. */
nm_clear_pointer (&req->con.chain, nm_auth_chain_destroy);
break;
default:
g_assert_not_reached ();
}
request_next_agent (req);
} else if (g_slist_find (req->pending, agent)) {
req->pending = g_slist_remove (req->pending, agent);
_LOGD (agent, "agent removed from secrets request "LOG_REQ_FMT,
LOG_REQ_ARG (req));
g_object_unref (agent);
}
}
static gboolean
request_start (gpointer user_data)
{
Request *req = user_data;
req->idle_id = 0;
switch (req->request_type) {
case REQUEST_TYPE_CON_GET:
if (_con_get_try_complete_early (req))
goto out;
break;
default:
break;
}
request_next_agent (req);
out:
return FALSE;
}
/*****************************************************************************/
static void
_con_get_request_done (NMSecretAgent *agent,
NMSecretAgentCallId *call_id,
GVariant *secrets,
GError *error,
gpointer user_data)
{
NMAgentManager *self;
Request *req = user_data;
GVariant *setting_secrets;
const char *agent_dbus_owner;
struct passwd *pw;
char *agent_uname = NULL;
g_return_if_fail (call_id == req->current_call_id);
g_return_if_fail (agent == req->current);
g_return_if_fail (req->request_type == REQUEST_TYPE_CON_GET);
self = req->self;
req->current_call_id = NULL;
if (error) {
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
_LOGD (agent, "get secrets request cancelled: "LOG_REQ_FMT,
LOG_REQ_ARG (req));
return;
}
_LOGD (agent, "agent failed secrets request "LOG_REQ_FMT": %s",
LOG_REQ_ARG (req),
error->message);
if (g_error_matches (error, NM_SECRET_AGENT_ERROR, NM_SECRET_AGENT_ERROR_USER_CANCELED)) {
error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_USER_CANCELED,
"User canceled the secrets request.");
req_complete_error (req, error);
g_error_free (error);
} else {
/* Tell the failed agent we're no longer interested. */
nm_secret_agent_cancel_call (req->current, req->current_call_id);
/* Try the next agent */
request_next_agent (req);
maybe_remove_agent_on_error (self, agent, error);
}
return;
}
/* Ensure the setting we wanted secrets for got returned and has something in it */
setting_secrets = g_variant_lookup_value (secrets, req->con.get.setting_name, NM_VARIANT_TYPE_SETTING);
if (!setting_secrets || !g_variant_n_children (setting_secrets)) {
_LOGD (agent, "agent returned no secrets for request "LOG_REQ_FMT,
LOG_REQ_ARG (req));
/* Try the next agent */
request_next_agent (req);
return;
}
_LOGD (agent, "agent returned secrets for request "LOG_REQ_FMT,
LOG_REQ_ARG (req));
/* Get the agent's username */
pw = getpwuid (nm_secret_agent_get_owner_uid (agent));
if (pw && strlen (pw->pw_name)) {
/* Needs to be UTF-8 valid since it may be pushed through D-Bus */
if (g_utf8_validate (pw->pw_name, -1, NULL))
agent_uname = g_strdup (pw->pw_name);
}
agent_dbus_owner = nm_secret_agent_get_dbus_owner (agent);
req_complete (req, secrets, agent_dbus_owner, agent_uname, NULL);
g_free (agent_uname);
}
static void
set_secrets_not_required (NMConnection *connection, GVariant *dict)
{
GVariantIter iter, setting_iter;
const char *setting_name = NULL;
GVariant *setting_dict = NULL;
/* Iterate through the settings dicts */
g_variant_iter_init (&iter, dict);
while (g_variant_iter_next (&iter, "{&s@a{sv}}", &setting_name, &setting_dict)) {
const char *key_name = NULL;
NMSetting *setting;
GVariant *val;
setting = nm_connection_get_setting_by_name (connection, setting_name);
if (setting) {
/* Now through each secret in the setting and mark it as not required */
g_variant_iter_init (&setting_iter, setting_dict);
while (g_variant_iter_next (&setting_iter, "{&sv}", &key_name, &val)) {
/* For each secret, set the flag that it's not required; VPN
* secrets need slightly different treatment here since the
* "secrets" property is actually a dictionary of secrets.
*/
if ( strcmp (setting_name, NM_SETTING_VPN_SETTING_NAME) == 0
&& strcmp (key_name, NM_SETTING_VPN_SECRETS) == 0
&& g_variant_is_of_type (val, G_VARIANT_TYPE ("a{ss}"))) {
GVariantIter vpn_secret_iter;
const char *secret_name, *secret;
g_variant_iter_init (&vpn_secret_iter, val);
while (g_variant_iter_next (&vpn_secret_iter, "{&s&s}", &secret_name, &secret))
nm_setting_set_secret_flags (setting, secret_name, NM_SETTING_SECRET_FLAG_NOT_REQUIRED, NULL);
} else
nm_setting_set_secret_flags (setting, key_name, NM_SETTING_SECRET_FLAG_NOT_REQUIRED, NULL);
g_variant_unref (val);
}
}
g_variant_unref (setting_dict);
}
}
static void
_con_get_request_start_proceed (Request *req, gboolean include_system_secrets)
{
NMConnection *tmp;
g_return_if_fail (req->request_type == REQUEST_TYPE_CON_GET);
tmp = nm_simple_connection_new_clone (req->con.connection);
nm_connection_clear_secrets (tmp);
if (include_system_secrets) {
if (req->con.get.existing_secrets)
(void) nm_connection_update_secrets (tmp, req->con.get.setting_name, req->con.get.existing_secrets, NULL);
} else {
/* Update secret flags in the temporary connection to indicate that
* the system secrets we're not sending to the agent aren't required,
* so the agent can properly validate UI controls and such.
*/
if (req->con.get.existing_secrets)
set_secrets_not_required (tmp, req->con.get.existing_secrets);
}
req->current_call_id = nm_secret_agent_get_secrets (req->current,
req->con.path,
tmp,
req->con.get.setting_name,
(const char **) req->con.get.hints,
req->con.get.flags,
_con_get_request_done,
req);
if (!req->current_call_id) {
g_warn_if_reached ();
request_next_agent (req);
}
g_object_unref (tmp);
}
static void
_con_get_request_start_validated (NMAuthChain *chain,
GDBusMethodInvocation *context,
gpointer user_data)
{
NMAgentManager *self;
Request *req = user_data;
const char *perm;
g_return_if_fail (req->request_type == REQUEST_TYPE_CON_GET);
self = req->self;
req->con.chain = NULL;
/* If the agent obtained the 'modify' permission, we send all system secrets
* to it. If it didn't, we still ask it for secrets, but we don't send
* any system secrets.
*/
perm = nm_auth_chain_get_data (chain, "perm");
g_assert (perm);
if (nm_auth_chain_get_result (chain, perm) == NM_AUTH_CALL_RESULT_YES)
req->con.current_has_modify = TRUE;
_LOGD (req->current, "agent "LOG_REQ_FMT" MODIFY check result %s",
LOG_REQ_ARG (req),
req->con.current_has_modify ? "YES" : "NO");
_con_get_request_start_proceed (req, req->con.current_has_modify);
}
static void
_con_get_request_start (Request *req)
{
NMAgentManager *self;
NMSettingConnection *s_con;
const char *agent_dbus_owner, *perm;
self = req->self;
req->con.current_has_modify = FALSE;
agent_dbus_owner = nm_secret_agent_get_dbus_owner (req->current);
/* If the request flags allow user interaction, and there are existing
* system secrets (or blank secrets that are supposed to be system-owned),
* check whether the agent has the 'modify' permission before sending those
* secrets to the agent. We shouldn't leak system-owned secrets to
* unprivileged users.
*/
if ( (req->con.get.flags != NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE)
&& ( req->con.get.existing_secrets
|| _nm_connection_aggregate (req->con.connection, NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS, NULL))) {
_LOGD (NULL, "("LOG_REQ_FMT") request has system secrets; checking agent %s for MODIFY",
LOG_REQ_ARG (req), agent_dbus_owner);
req->con.chain = nm_auth_chain_new_subject (nm_secret_agent_get_subject (req->current),
NULL,
_con_get_request_start_validated,
req);
nm_assert (req->con.chain);
/* If the caller is the only user in the connection's permissions, then
* we use the 'modify.own' permission instead of 'modify.system'. If the
* request affects more than just the caller, require 'modify.system'.
*/
s_con = nm_connection_get_setting_connection (req->con.connection);
g_assert (s_con);
if (nm_setting_connection_get_num_permissions (s_con) == 1)
perm = NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN;
else
perm = NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM;
nm_auth_chain_set_data (req->con.chain, "perm", (gpointer) perm, NULL);
nm_auth_chain_add_call_unsafe (req->con.chain, perm, TRUE);
} else {
_LOGD (NULL, "("LOG_REQ_FMT") requesting user-owned secrets from agent %s",
LOG_REQ_ARG (req), agent_dbus_owner);
_con_get_request_start_proceed (req, FALSE);
}
}
static gboolean
_con_get_try_complete_early (Request *req)
{
NMAgentManager *self;
gs_unref_variant GVariant *setting_secrets = NULL;
gs_unref_object NMConnection *tmp = NULL;
GError *error = NULL;
self = req->self;
/* Check if there are any existing secrets */
if (req->con.get.existing_secrets)
setting_secrets = g_variant_lookup_value (req->con.get.existing_secrets, req->con.get.setting_name, NM_VARIANT_TYPE_SETTING);
if (!setting_secrets || !g_variant_n_children (setting_secrets))
return FALSE;
/* The connection already had secrets; check if any more are required.
* If no more are required, we're done. If secrets are still needed,
* ask a secret agent for more. This allows admins to provide generic
* secrets but allow additional user-specific ones as well.
*/
tmp = nm_simple_connection_new_clone (req->con.connection);
g_assert (tmp);
if (!nm_connection_update_secrets (tmp, req->con.get.setting_name, req->con.get.existing_secrets, &error)) {
req_complete_error (req, error);
g_clear_error (&error);
return TRUE;
}
/* Do we have everything we need? */
if ( NM_FLAGS_HAS (req->con.get.flags, NM_SECRET_AGENT_GET_SECRETS_FLAG_ONLY_SYSTEM)
|| ( (nm_connection_need_secrets (tmp, NULL) == NULL)
&& !NM_FLAGS_HAS(req->con.get.flags, NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW))) {
_LOGD (NULL, "("LOG_REQ_FMT") system settings secrets sufficient",
LOG_REQ_ARG (req));
/* Got everything, we're done */
req_complete (req, req->con.get.existing_secrets, NULL, NULL, NULL);
return TRUE;
}
_LOGD (NULL, "("LOG_REQ_FMT") system settings secrets insufficient, asking agents",
LOG_REQ_ARG (req));
/* We don't, so ask some agents for additional secrets */
if ( req->con.get.flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_NO_ERRORS
&& !req->pending) {
/* The request initiated from GetSecrets() via DBus,
* don't error out if any secrets are missing. */
req_complete (req, req->con.get.existing_secrets, NULL, NULL, NULL);
return TRUE;
}
/* Couldn't get secrets from system settings, so now we ask the
* agents for secrets. Let the Agent Manager handle which agents
* we'll ask and in which order.
*/
return FALSE;
}
/**
* nm_agent_manager_get_secrets:
* @self:
* @path:
* @connection:
* @subject:
* @existing_secrets:
* @flags:
* @hints:
* @callback:
* @callback_data:
*
* Requests secrets for a connection.
*
* This function cannot fail. The callback will be invoked
* asynchrnously, but it will always be invoked exactly once.
* Even for cancellation and disposing of @self. In those latter
* cases, the callback is invoked synchrnously during the cancellation/
* disposal.
*
* Returns: a call-id to cancel the call.
*/
NMAgentManagerCallId
nm_agent_manager_get_secrets (NMAgentManager *self,
const char *path,
NMConnection *connection,
NMAuthSubject *subject,
GVariant *existing_secrets,
const char *setting_name,
NMSecretAgentGetSecretsFlags flags,
const char *const*hints,
NMAgentSecretsResultFunc callback,
gpointer callback_data)
{
Request *req;
g_return_val_if_fail (self != NULL, NULL);
g_return_val_if_fail (path && *path, NULL);
g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
g_return_val_if_fail (callback != NULL, NULL);
nm_log_dbg (LOGD_SETTINGS,
"Secrets requested for connection %s (%s/%s)",
path,
nm_connection_get_id (connection),
setting_name);
/* NOTE: a few things in the Request handling depend on existing_secrets
* being NULL if there aren't any system-owned secrets for this connection.
* This in turn depends on nm_connection_to_dbus() and nm_setting_to_hash()
* both returning NULL if they didn't hash anything.
*/
req = request_new (self,
REQUEST_TYPE_CON_GET,
nm_connection_get_id (connection),
subject);
req->con.path = g_strdup (path);
req->con.connection = g_object_ref (connection);
if (existing_secrets)
req->con.get.existing_secrets = g_variant_ref (existing_secrets);
req->con.get.setting_name = g_strdup (setting_name);
req->con.get.hints = g_strdupv ((char **) hints);
req->con.get.flags = flags;
req->con.get.callback = callback;
req->con.get.callback_data = callback_data;
if (!(req->con.get.flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_ONLY_SYSTEM))
request_add_agents (self, req);
req->idle_id = g_idle_add (request_start, req);
return req;
}
void
nm_agent_manager_cancel_secrets (NMAgentManager *self,
NMAgentManagerCallId request_id)
{
g_return_if_fail (self != NULL);
g_return_if_fail (request_id);
g_return_if_fail (request_id->request_type == REQUEST_TYPE_CON_GET);
nm_assert (c_list_contains (&NM_AGENT_MANAGER_GET_PRIVATE (self)->request_lst_head, &request_id->request_lst));
c_list_unlink (&request_id->request_lst);
req_complete_cancel (request_id, FALSE);
}
/*****************************************************************************/
static void
_con_save_request_done (NMSecretAgent *agent,
NMSecretAgentCallId *call_id,
GVariant *secrets,
GError *error,
gpointer user_data)
{
NMAgentManager *self;
Request *req = user_data;
const char *agent_dbus_owner;
g_return_if_fail (call_id == req->current_call_id);
g_return_if_fail (agent == req->current);
g_return_if_fail (req->request_type == REQUEST_TYPE_CON_SAVE);
self = req->self;
req->current_call_id = NULL;
if (error) {
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
_LOGD (agent, "save secrets request cancelled: "LOG_REQ_FMT,
LOG_REQ_ARG (req));
return;
}
_LOGD (agent, "agent failed save secrets request "LOG_REQ_FMT": %s",
LOG_REQ_ARG (req), error->message);
/* Try the next agent */
request_next_agent (req);
maybe_remove_agent_on_error (self, agent, error);
return;
}
_LOGD (agent, "agent saved secrets for request "LOG_REQ_FMT,
LOG_REQ_ARG (req));
agent_dbus_owner = nm_secret_agent_get_dbus_owner (agent);
req_complete (req, NULL, NULL, agent_dbus_owner, NULL);
}
static void
_con_save_request_start (Request *req)
{
req->current_call_id = nm_secret_agent_save_secrets (req->current,
req->con.path,
req->con.connection,
_con_save_request_done,
req);
if (!req->current_call_id) {
g_warn_if_reached ();
request_next_agent (req);
}
}
void
nm_agent_manager_save_secrets (NMAgentManager *self,
const char *path,
NMConnection *connection,
NMAuthSubject *subject)
{
Request *req;
g_return_if_fail (self);
g_return_if_fail (path && *path);
g_return_if_fail (NM_IS_CONNECTION (connection));
nm_log_dbg (LOGD_SETTINGS,
"Saving secrets for connection %s (%s)",
path,
nm_connection_get_id (connection));
req = request_new (self,
REQUEST_TYPE_CON_SAVE,
nm_connection_get_id (connection),
subject);
req->con.path = g_strdup (path);
req->con.connection = g_object_ref (connection);
request_add_agents (self, req);
req->idle_id = g_idle_add (request_start, req);
}
/*****************************************************************************/
static void
_con_del_request_done (NMSecretAgent *agent,
NMSecretAgentCallId *call_id,
GVariant *secrets,
GError *error,
gpointer user_data)
{
NMAgentManager *self;
Request *req = user_data;
g_return_if_fail (call_id == req->current_call_id);
g_return_if_fail (agent == req->current);
g_return_if_fail (req->request_type == REQUEST_TYPE_CON_DEL);
self = req->self;
req->current_call_id = NULL;
if (error) {
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
_LOGD (agent, "delete secrets request cancelled: "LOG_REQ_FMT,
LOG_REQ_ARG (req));
return;
}
_LOGD (agent, "agent failed delete secrets request "LOG_REQ_FMT": %s",
LOG_REQ_ARG (req), error->message);
} else {
_LOGD (agent, "agent deleted secrets for request "LOG_REQ_FMT,
LOG_REQ_ARG (req));
}
/* Tell the next agent to delete secrets */
request_next_agent (req);
if (error)
maybe_remove_agent_on_error (self, agent, error);
}
static void
_con_del_request_start (Request *req)
{
req->current_call_id = nm_secret_agent_delete_secrets (req->current,
req->con.path,
req->con.connection,
_con_del_request_done,
req);
if (!req->current_call_id) {
g_warn_if_reached ();
request_next_agent (req);
}
}
void
nm_agent_manager_delete_secrets (NMAgentManager *self,
const char *path,
NMConnection *connection)
{
NMAuthSubject *subject;
Request *req;
g_return_if_fail (self != NULL);
g_return_if_fail (path && *path);
g_return_if_fail (NM_IS_CONNECTION (connection));
nm_log_dbg (LOGD_SETTINGS,
"Deleting secrets for connection %s (%s)",
path,
nm_connection_get_id (connection));
subject = nm_auth_subject_new_internal ();
req = request_new (self,
REQUEST_TYPE_CON_DEL,
nm_connection_get_id (connection),
subject);
req->con.path = g_strdup (path);
req->con.connection = g_object_ref (connection);
g_object_unref (subject);
request_add_agents (self, req);
req->idle_id = g_idle_add (request_start, req);
}
/*****************************************************************************/
gboolean
nm_agent_manager_has_agent_with_permission (NMAgentManager *self,
const char *username,
const char *permission)
{
NMAgentManagerPrivate *priv;
NMSecretAgent *agent;
g_return_val_if_fail (NM_IS_AGENT_MANAGER (self), FALSE);
g_return_val_if_fail (username, FALSE);
g_return_val_if_fail (permission, FALSE);
priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) {
if (!agent->fully_registered)
continue;
if (!nm_streq0 (nm_secret_agent_get_owner_username (agent), username))
continue;
if (nm_secret_agent_has_permission (agent, permission))
return TRUE;
}
return FALSE;
}
/*****************************************************************************/
gboolean
nm_agent_manager_all_agents_have_capability (NMAgentManager *manager,
NMAuthSubject *subject,
NMSecretAgentCapabilities capability)
{
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (manager);
NMSecretAgent *agent;
gboolean subject_is_unix_process = (nm_auth_subject_get_subject_type (subject) == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS);
gulong subject_uid = subject_is_unix_process ? nm_auth_subject_get_unix_process_uid (subject) : 0u;
c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) {
if (!agent->fully_registered)
continue;
if ( subject_is_unix_process
&& nm_secret_agent_get_owner_uid (agent) != subject_uid)
continue;
if (!(nm_secret_agent_get_capabilities (agent) & capability))
return FALSE;
}
return TRUE;
}
/*****************************************************************************/
static void
authority_changed_cb (NMAuthManager *auth_manager, NMAgentManager *self)
{
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
NMSecretAgent *agent;
c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst)
_agent_create_auth_chain (self, agent, NULL);
}
/*****************************************************************************/
static void
nm_agent_manager_init (NMAgentManager *self)
{
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
priv->agent_version_id = 1;
c_list_init (&priv->agent_lst_head);
c_list_init (&priv->request_lst_head);
}
static void
constructed (GObject *object)
{
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE ((NMAgentManager *) object);
G_OBJECT_CLASS (nm_agent_manager_parent_class)->constructed (object);
priv->auth_mgr = g_object_ref (nm_auth_manager_get ());
priv->session_monitor = g_object_ref (nm_session_monitor_get ());
nm_dbus_object_export (NM_DBUS_OBJECT (object));
g_signal_connect (priv->auth_mgr,
NM_AUTH_MANAGER_SIGNAL_CHANGED,
G_CALLBACK (authority_changed_cb),
object);
}
static void
dispose (GObject *object)
{
NMAgentManager *self = NM_AGENT_MANAGER (object);
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
Request *request;
NMSecretAgent *agent;
while ((request = c_list_first_entry (&priv->request_lst_head, Request, request_lst))) {
c_list_unlink (&request->request_lst);
req_complete_cancel (request, TRUE);
}
while ((agent = c_list_first_entry (&priv->agent_lst_head, NMSecretAgent, agent_lst)))
_agent_remove (self, agent);
if (priv->auth_mgr) {
g_signal_handlers_disconnect_by_func (priv->auth_mgr,
G_CALLBACK (authority_changed_cb),
object);
g_clear_object (&priv->auth_mgr);
}
nm_dbus_object_unexport (NM_DBUS_OBJECT (object));
g_clear_object (&priv->session_monitor);
G_OBJECT_CLASS (nm_agent_manager_parent_class)->dispose (object);
}
static const NMDBusInterfaceInfoExtended interface_info_agent_manager = {
.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
NM_DBUS_INTERFACE_AGENT_MANAGER,
.methods = NM_DEFINE_GDBUS_METHOD_INFOS (
NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
NM_DEFINE_GDBUS_METHOD_INFO_INIT (
"Register",
.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
NM_DEFINE_GDBUS_ARG_INFO ("identifier", "s"),
),
),
.handle = impl_agent_manager_register,
),
NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
NM_DEFINE_GDBUS_METHOD_INFO_INIT (
"RegisterWithCapabilities",
.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
NM_DEFINE_GDBUS_ARG_INFO ("identifier", "s"),
NM_DEFINE_GDBUS_ARG_INFO ("capabilities", "u"),
),
),
.handle = impl_agent_manager_register_with_capabilities,
),
NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
NM_DEFINE_GDBUS_METHOD_INFO_INIT (
"Unregister",
),
.handle = impl_agent_manager_unregister,
),
),
),
};
static void
nm_agent_manager_class_init (NMAgentManagerClass *agent_manager_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (agent_manager_class);
NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (agent_manager_class);
dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_STATIC (NM_DBUS_PATH_AGENT_MANAGER);
dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_agent_manager);
object_class->constructed = constructed;
object_class->dispose = dispose;
signals[AGENT_REGISTERED] =
g_signal_new (NM_AGENT_MANAGER_AGENT_REGISTERED,
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1,
G_TYPE_OBJECT);
}