Blob Blame History Raw
// 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 (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);
}