mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-01-06 03:50:17 +01:00
We only needed proper glib enum types for having properties and signal arguments. These got all converted to plain int, so no longer generate such an enum type.
1663 lines
52 KiB
C
1663 lines
52 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
|
|
/* NetworkManager -- Network link manager
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Copyright (C) 2010 - 2013 Red Hat, Inc.
|
|
*/
|
|
|
|
#include "nm-default.h"
|
|
|
|
#include "nm-agent-manager.h"
|
|
|
|
#include <string.h>
|
|
#include <pwd.h>
|
|
|
|
#include "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-bus-manager.h"
|
|
#include "nm-session-monitor.h"
|
|
#include "nm-simple-connection.h"
|
|
#include "NetworkManagerUtils.h"
|
|
#include "nm-core-internal.h"
|
|
|
|
#include "nmdbus-agent-manager.h"
|
|
|
|
/*****************************************************************************/
|
|
|
|
enum {
|
|
AGENT_REGISTERED,
|
|
LAST_SIGNAL,
|
|
};
|
|
|
|
static guint signals[LAST_SIGNAL] = { 0 };
|
|
|
|
typedef struct {
|
|
NMAuthManager *auth_mgr;
|
|
|
|
/* Auth chains for checking agent permissions */
|
|
GSList *chains;
|
|
|
|
/* Hashed by owner name, not identifier, since two agents in different
|
|
* sessions can use the same identifier.
|
|
*/
|
|
GHashTable *agents;
|
|
|
|
GHashTable *requests;
|
|
} NMAgentManagerPrivate;
|
|
|
|
struct _NMAgentManager {
|
|
NMExportedObject parent;
|
|
NMAgentManagerPrivate _priv;
|
|
};
|
|
|
|
struct _NMAgentManagerClass {
|
|
NMExportedObjectClass parent;
|
|
};
|
|
|
|
G_DEFINE_TYPE (NMAgentManager, nm_agent_manager, NM_TYPE_EXPORTED_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[%p]", ""_NMLOG_PREFIX_NAME"", (self)); \
|
|
else \
|
|
g_strlcpy (__prefix1, _NMLOG_PREFIX_NAME, sizeof (__prefix1)); \
|
|
if (__agent) { \
|
|
g_snprintf (__prefix2, sizeof (__prefix2), \
|
|
": req[%p, %s]", \
|
|
__agent, \
|
|
nm_secret_agent_get_description (__agent)); \
|
|
} else \
|
|
__prefix2[0] = '\0'; \
|
|
_nm_log ((level), (_NMLOG_DOMAIN), 0, \
|
|
"%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
|
|
__prefix1, __prefix2 _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
|
|
} \
|
|
} G_STMT_END
|
|
|
|
#define LOG_REQ_FMT "[%p/%s%s%s%s%s%s]"
|
|
#define LOG_REQ_ARG(req) \
|
|
(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, GSList **pending_reqs);
|
|
|
|
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);
|
|
|
|
/*****************************************************************************/
|
|
|
|
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 "??";
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static gboolean
|
|
remove_agent (NMAgentManager *self, const char *owner)
|
|
{
|
|
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
|
|
NMSecretAgent *agent;
|
|
GHashTableIter iter;
|
|
gpointer data;
|
|
GSList *pending_reqs = NULL;
|
|
|
|
g_return_val_if_fail (owner != NULL, FALSE);
|
|
|
|
/* Make sure this agent has already registered */
|
|
agent = g_hash_table_lookup (priv->agents, owner);
|
|
if (!agent)
|
|
return FALSE;
|
|
|
|
_LOGD (agent, "agent unregistered or disappeared");
|
|
|
|
/* Remove this agent from any in-progress secrets requests */
|
|
g_hash_table_iter_init (&iter, priv->requests);
|
|
while (g_hash_table_iter_next (&iter, &data, NULL))
|
|
request_remove_agent ((Request *) data, agent, &pending_reqs);
|
|
|
|
/* We cannot call request_next_agent() from from within hash iterating loop,
|
|
* because it may remove the request from the hash table, which invalidates
|
|
* the iterator. So, only remove the agent from requests. And store the requests
|
|
* that should be sent to other agent to a temporary list to proceed afterwards.
|
|
*/
|
|
g_slist_free_full (pending_reqs, (GDestroyNotify) request_next_agent);
|
|
|
|
/* And dispose of the agent */
|
|
g_hash_table_remove (priv->agents, owner);
|
|
return TRUE;
|
|
}
|
|
|
|
/* Call this *after* calling request_next_agent() */
|
|
static void
|
|
maybe_remove_agent_on_error (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))
|
|
remove_agent (nm_agent_manager_get (), nm_secret_agent_get_dbus_owner (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_register_permissions_done (NMAuthChain *chain,
|
|
GError *error,
|
|
GDBusMethodInvocation *context,
|
|
gpointer user_data)
|
|
{
|
|
NMAgentManager *self = NM_AGENT_MANAGER (user_data);
|
|
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
|
|
NMSecretAgent *agent;
|
|
const char *sender;
|
|
GError *local = NULL;
|
|
NMAuthCallResult result;
|
|
GHashTableIter iter;
|
|
Request *req;
|
|
|
|
g_assert (context);
|
|
|
|
priv->chains = g_slist_remove (priv->chains, chain);
|
|
|
|
if (error) {
|
|
local = g_error_new (NM_AGENT_MANAGER_ERROR,
|
|
NM_AGENT_MANAGER_ERROR_PERMISSION_DENIED,
|
|
"Failed to request agent permissions: %s",
|
|
error->message);
|
|
g_dbus_method_invocation_take_error (context, local);
|
|
} else {
|
|
agent = nm_auth_chain_steal_data (chain, "agent");
|
|
g_assert (agent);
|
|
|
|
result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED);
|
|
if (result == NM_AUTH_CALL_RESULT_YES)
|
|
nm_secret_agent_add_permission (agent, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED, TRUE);
|
|
|
|
result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN);
|
|
if (result == NM_AUTH_CALL_RESULT_YES)
|
|
nm_secret_agent_add_permission (agent, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN, TRUE);
|
|
|
|
sender = nm_secret_agent_get_dbus_owner (agent);
|
|
g_hash_table_insert (priv->agents, g_strdup (sender), agent);
|
|
_LOGD (agent, "agent registered");
|
|
g_dbus_method_invocation_return_value (context, NULL);
|
|
|
|
/* Signal an agent was registered */
|
|
g_signal_emit (self, signals[AGENT_REGISTERED], 0, agent);
|
|
|
|
/* Add this agent to any in-progress secrets requests */
|
|
g_hash_table_iter_init (&iter, priv->requests);
|
|
while (g_hash_table_iter_next (&iter, (gpointer) &req, NULL))
|
|
request_add_agent (req, agent);
|
|
}
|
|
|
|
nm_auth_chain_unref (chain);
|
|
}
|
|
|
|
static NMSecretAgent *
|
|
find_agent_by_identifier_and_uid (NMAgentManager *self,
|
|
const char *identifier,
|
|
gulong sender_uid)
|
|
{
|
|
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
|
|
GHashTableIter iter;
|
|
NMSecretAgent *agent;
|
|
|
|
g_hash_table_iter_init (&iter, priv->agents);
|
|
while (g_hash_table_iter_next (&iter, NULL, (gpointer) &agent)) {
|
|
if ( g_strcmp0 (nm_secret_agent_get_identifier (agent), identifier) == 0
|
|
&& nm_secret_agent_get_owner_uid (agent) == sender_uid)
|
|
return agent;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
agent_disconnected_cb (NMSecretAgent *agent, gpointer user_data)
|
|
{
|
|
/* The agent quit, so remove it and let interested clients know */
|
|
remove_agent (NM_AGENT_MANAGER (user_data),
|
|
nm_secret_agent_get_dbus_owner (agent));
|
|
}
|
|
|
|
static void
|
|
impl_agent_manager_register_with_capabilities (NMAgentManager *self,
|
|
GDBusMethodInvocation *context,
|
|
const char *identifier,
|
|
guint32 capabilities)
|
|
{
|
|
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
|
|
NMAuthSubject *subject;
|
|
gulong sender_uid = G_MAXULONG;
|
|
GError *error = NULL;
|
|
NMSecretAgent *agent;
|
|
NMAuthChain *chain;
|
|
|
|
subject = nm_auth_subject_new_unix_process_from_context (context);
|
|
if (!subject) {
|
|
error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
|
|
NM_AGENT_MANAGER_ERROR_PERMISSION_DENIED,
|
|
"Unable to determine request sender and UID.");
|
|
goto done;
|
|
}
|
|
sender_uid = nm_auth_subject_get_unix_process_uid (subject);
|
|
|
|
/* Validate the identifier */
|
|
if (!validate_identifier (identifier, &error))
|
|
goto done;
|
|
|
|
/* Only one agent for each identifier is allowed per user */
|
|
if (find_agent_by_identifier_and_uid (self, 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.");
|
|
goto done;
|
|
}
|
|
|
|
/* Success, add the new agent */
|
|
agent = nm_secret_agent_new (context, subject, identifier, capabilities);
|
|
if (!agent) {
|
|
error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
|
|
NM_AGENT_MANAGER_ERROR_FAILED,
|
|
"Failed to initialize the agent");
|
|
goto done;
|
|
}
|
|
g_signal_connect (agent, NM_SECRET_AGENT_DISCONNECTED,
|
|
G_CALLBACK (agent_disconnected_cb), self);
|
|
|
|
_LOGD (agent, "requesting permissions");
|
|
|
|
/* Kick off permissions requests for this agent */
|
|
chain = nm_auth_chain_new_subject (subject, context, agent_register_permissions_done, self);
|
|
if (chain) {
|
|
nm_auth_chain_set_data (chain, "agent", agent, g_object_unref);
|
|
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);
|
|
|
|
priv->chains = g_slist_append (priv->chains, chain);
|
|
} else {
|
|
g_object_unref (agent);
|
|
error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
|
|
NM_AGENT_MANAGER_ERROR_FAILED,
|
|
"Unable to start agent authentication.");
|
|
}
|
|
|
|
done:
|
|
if (error)
|
|
g_dbus_method_invocation_take_error (context, error);
|
|
g_clear_object (&subject);
|
|
}
|
|
|
|
static void
|
|
impl_agent_manager_register (NMAgentManager *self,
|
|
GDBusMethodInvocation *context,
|
|
const char *identifier)
|
|
{
|
|
impl_agent_manager_register_with_capabilities (self, context, identifier, 0);
|
|
}
|
|
|
|
static void
|
|
impl_agent_manager_unregister (NMAgentManager *self,
|
|
GDBusMethodInvocation *context)
|
|
{
|
|
GError *error = NULL;
|
|
char *sender = NULL;
|
|
|
|
if (!nm_bus_manager_get_caller_info (nm_bus_manager_get (),
|
|
context,
|
|
&sender,
|
|
NULL,
|
|
NULL)) {
|
|
error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
|
|
NM_AGENT_MANAGER_ERROR_PERMISSION_DENIED,
|
|
"Unable to determine request sender.");
|
|
goto done;
|
|
}
|
|
|
|
/* Found the agent, unregister and remove it */
|
|
if (!remove_agent (self, sender)) {
|
|
error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
|
|
NM_AGENT_MANAGER_ERROR_NOT_REGISTERED,
|
|
"Caller is not registered as an Agent");
|
|
goto done;
|
|
}
|
|
|
|
g_dbus_method_invocation_return_value (context, NULL);
|
|
|
|
done:
|
|
if (error)
|
|
g_dbus_method_invocation_take_error (context, error);
|
|
g_free (sender);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
struct _NMAgentManagerCallId {
|
|
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 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);
|
|
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);
|
|
if (req->con.chain)
|
|
nm_auth_chain_unref (req->con.chain);
|
|
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);
|
|
|
|
if (req->current && req->current_call_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_secrets (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 (!g_hash_table_contains (req->self->_priv.requests, req));
|
|
|
|
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;
|
|
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
|
|
|
|
if (!g_hash_table_remove (priv->requests, req))
|
|
g_return_if_reached ();
|
|
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 gint
|
|
agent_compare_func (gconstpointer aa, gconstpointer bb, gpointer user_data)
|
|
{
|
|
NMSecretAgent *a = (NMSecretAgent *)aa;
|
|
NMSecretAgent *b = (NMSecretAgent *)bb;
|
|
Request *req = user_data;
|
|
gboolean a_active, b_active;
|
|
gulong a_pid, b_pid, requester;
|
|
|
|
/* Prefer agents in the process the request came from */
|
|
if (nm_auth_subject_is_unix_process (req->subject)) {
|
|
requester = nm_auth_subject_get_unix_process_pid (req->subject);
|
|
a_pid = nm_secret_agent_get_pid (a);
|
|
b_pid = nm_secret_agent_get_pid (b);
|
|
|
|
if (a_pid != b_pid) {
|
|
if (a_pid == requester)
|
|
return -1;
|
|
else if (b_pid == requester)
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* Prefer agents in active sessions */
|
|
a_active = nm_session_monitor_session_exists (nm_session_monitor_get (), nm_secret_agent_get_owner_uid (a), TRUE);
|
|
b_active = nm_session_monitor_session_exists (nm_session_monitor_get (), nm_secret_agent_get_owner_uid (b), TRUE);
|
|
if (a_active && !b_active)
|
|
return -1;
|
|
else if (a_active == b_active)
|
|
return 0;
|
|
else if (!a_active && b_active)
|
|
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_is_unix_process (req->subject)) {
|
|
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);
|
|
GHashTableIter iter;
|
|
gpointer data;
|
|
|
|
g_hash_table_iter_init (&iter, priv->agents);
|
|
while (g_hash_table_iter_next (&iter, NULL, &data))
|
|
request_add_agent (req, NM_SECRET_AGENT (data));
|
|
}
|
|
|
|
static void
|
|
request_next_agent (Request *req)
|
|
{
|
|
NMAgentManager *self;
|
|
GError *error = NULL;
|
|
|
|
self = req->self;
|
|
|
|
if (req->current) {
|
|
if (req->current_call_id)
|
|
nm_secret_agent_cancel_secrets (req->current, req->current_call_id);
|
|
g_clear_object (&req->current);
|
|
}
|
|
g_warn_if_fail (!req->current_call_id);
|
|
|
|
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, GSList **pending_reqs)
|
|
{
|
|
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:
|
|
if (req->con.chain) {
|
|
/* This cancels the pending authorization requests. */
|
|
nm_auth_chain_unref (req->con.chain);
|
|
req->con.chain = NULL;
|
|
}
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
*pending_reqs = g_slist_prepend (*pending_reqs, 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 {
|
|
if (req->current_call_id) {
|
|
/* Tell the failed agent we're no longer interested. */
|
|
nm_secret_agent_cancel_secrets (req->current, req->current_call_id);
|
|
}
|
|
|
|
/* Try the next agent */
|
|
request_next_agent (req);
|
|
maybe_remove_agent_on_error (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,
|
|
GError *error,
|
|
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 (error) {
|
|
_LOGD (req->current, "agent "LOG_REQ_FMT" MODIFY check error: %s",
|
|
LOG_REQ_ARG (req),
|
|
error->message);
|
|
/* Try the next agent */
|
|
request_next_agent (req);
|
|
} else {
|
|
/* 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);
|
|
}
|
|
|
|
nm_auth_chain_unref (chain);
|
|
}
|
|
|
|
static void
|
|
has_system_secrets_check (NMSetting *setting,
|
|
const char *key,
|
|
const GValue *value,
|
|
GParamFlags flags,
|
|
gpointer user_data)
|
|
{
|
|
NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
|
|
gboolean *has_system = user_data;
|
|
|
|
if (!(flags & NM_SETTING_PARAM_SECRET))
|
|
return;
|
|
|
|
/* Clear out system-owned or always-ask secrets */
|
|
if (NM_IS_SETTING_VPN (setting) && !strcmp (key, NM_SETTING_VPN_SECRETS)) {
|
|
GHashTableIter iter;
|
|
const char *secret_name = NULL;
|
|
|
|
/* VPNs are special; need to handle each secret separately */
|
|
g_hash_table_iter_init (&iter, (GHashTable *) g_value_get_boxed (value));
|
|
while (g_hash_table_iter_next (&iter, (gpointer *) &secret_name, NULL)) {
|
|
secret_flags = NM_SETTING_SECRET_FLAG_NONE;
|
|
nm_setting_get_secret_flags (setting, secret_name, &secret_flags, NULL);
|
|
if (secret_flags == NM_SETTING_SECRET_FLAG_NONE)
|
|
*has_system = TRUE;
|
|
}
|
|
} else {
|
|
if (!nm_setting_get_secret_flags (setting, key, &secret_flags, NULL))
|
|
g_return_if_reached ();
|
|
if (secret_flags == NM_SETTING_SECRET_FLAG_NONE)
|
|
*has_system = TRUE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
has_system_secrets (NMConnection *connection)
|
|
{
|
|
gboolean has_system = FALSE;
|
|
|
|
nm_connection_for_each_setting_value (connection, has_system_secrets_check, &has_system);
|
|
return has_system;
|
|
}
|
|
|
|
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 || has_system_secrets (req->con.connection))) {
|
|
_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);
|
|
g_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 (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 **hints,
|
|
NMAgentSecretsResultFunc callback,
|
|
gpointer callback_data)
|
|
{
|
|
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
|
|
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 (!nm_g_hash_table_add (priv->requests, req))
|
|
g_assert_not_reached ();
|
|
|
|
/* Kick off the request */
|
|
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);
|
|
|
|
if (!g_hash_table_remove (NM_AGENT_MANAGER_GET_PRIVATE (self)->requests,
|
|
request_id))
|
|
g_return_if_reached ();
|
|
|
|
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 (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)
|
|
{
|
|
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
|
|
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);
|
|
if (!nm_g_hash_table_add (priv->requests, req))
|
|
g_assert_not_reached ();
|
|
|
|
/* Kick off the request */
|
|
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 (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)
|
|
{
|
|
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
|
|
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);
|
|
if (!nm_g_hash_table_add (priv->requests, req))
|
|
g_assert_not_reached ();
|
|
|
|
/* Kick off the request */
|
|
request_add_agents (self, req);
|
|
req->idle_id = g_idle_add (request_start, req);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
NMSecretAgent *
|
|
nm_agent_manager_get_agent_by_user (NMAgentManager *self, const char *username)
|
|
{
|
|
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
|
|
GHashTableIter iter;
|
|
NMSecretAgent *agent;
|
|
|
|
g_hash_table_iter_init (&iter, priv->agents);
|
|
while (g_hash_table_iter_next (&iter, NULL, (gpointer) &agent)) {
|
|
if (g_strcmp0 (nm_secret_agent_get_owner_username (agent), username) == 0)
|
|
return agent;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
gboolean
|
|
nm_agent_manager_all_agents_have_capability (NMAgentManager *manager,
|
|
NMAuthSubject *subject,
|
|
NMSecretAgentCapabilities capability)
|
|
{
|
|
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (manager);
|
|
GHashTableIter iter;
|
|
NMSecretAgent *agent;
|
|
gboolean subject_is_unix_process = nm_auth_subject_is_unix_process (subject);
|
|
gulong subject_uid = subject_is_unix_process ? nm_auth_subject_get_unix_process_uid (subject) : 0;
|
|
|
|
g_hash_table_iter_init (&iter, priv->agents);
|
|
while (g_hash_table_iter_next (&iter, NULL, (gpointer) &agent)) {
|
|
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
|
|
agent_permissions_changed_done (NMAuthChain *chain,
|
|
GError *error,
|
|
GDBusMethodInvocation *context,
|
|
gpointer user_data)
|
|
{
|
|
NMAgentManager *self = NM_AGENT_MANAGER (user_data);
|
|
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
|
|
NMSecretAgent *agent;
|
|
gboolean share_protected = FALSE, share_open = FALSE;
|
|
|
|
priv->chains = g_slist_remove (priv->chains, chain);
|
|
|
|
agent = nm_auth_chain_get_data (chain, "agent");
|
|
g_assert (agent);
|
|
|
|
if (error)
|
|
_LOGD (agent, "failed to request updated agent permissions");
|
|
else {
|
|
_LOGD (agent, "updated agent permissions");
|
|
|
|
if (nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED) == NM_AUTH_CALL_RESULT_YES)
|
|
share_protected = TRUE;
|
|
if (nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN) == NM_AUTH_CALL_RESULT_YES)
|
|
share_open = TRUE;
|
|
}
|
|
|
|
nm_secret_agent_add_permission (agent, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED, share_protected);
|
|
nm_secret_agent_add_permission (agent, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN, share_open);
|
|
|
|
nm_auth_chain_unref (chain);
|
|
}
|
|
|
|
static void
|
|
authority_changed_cb (NMAuthManager *auth_manager, NMAgentManager *self)
|
|
{
|
|
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
|
|
GHashTableIter iter;
|
|
NMSecretAgent *agent;
|
|
|
|
/* Recheck the permissions of all secret agents */
|
|
g_hash_table_iter_init (&iter, priv->agents);
|
|
while (g_hash_table_iter_next (&iter, NULL, (gpointer) &agent)) {
|
|
NMAuthChain *chain;
|
|
|
|
/* Kick off permissions requests for this agent */
|
|
chain = nm_auth_chain_new_subject (nm_secret_agent_get_subject (agent),
|
|
NULL,
|
|
agent_permissions_changed_done,
|
|
self);
|
|
g_assert (chain);
|
|
priv->chains = g_slist_append (priv->chains, chain);
|
|
|
|
/* Make sure if the agent quits while the permissions call is in progress
|
|
* that the object sticks around until our callback.
|
|
*/
|
|
nm_auth_chain_set_data (chain, "agent", g_object_ref (agent), g_object_unref);
|
|
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);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
nm_agent_manager_init (NMAgentManager *self)
|
|
{
|
|
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
|
|
|
|
priv->agents = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
|
|
priv->requests = g_hash_table_new (g_direct_hash, g_direct_equal);
|
|
}
|
|
|
|
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 ());
|
|
|
|
nm_exported_object_export (NM_EXPORTED_OBJECT (object));
|
|
|
|
g_signal_connect (priv->auth_mgr,
|
|
NM_AUTH_MANAGER_SIGNAL_CHANGED,
|
|
G_CALLBACK (authority_changed_cb),
|
|
object);
|
|
|
|
NM_UTILS_KEEP_ALIVE (object, nm_session_monitor_get (), "NMAgentManager-depends-on-NMSessionMonitor");
|
|
}
|
|
|
|
static void
|
|
dispose (GObject *object)
|
|
{
|
|
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE ((NMAgentManager *) object);
|
|
|
|
if (priv->requests) {
|
|
GHashTableIter iter;
|
|
Request *req;
|
|
|
|
cancel_more:
|
|
g_hash_table_iter_init (&iter, priv->requests);
|
|
if (g_hash_table_iter_next (&iter, (gpointer *) &req, NULL)) {
|
|
g_hash_table_iter_remove (&iter);
|
|
req_complete_cancel (req, TRUE);
|
|
goto cancel_more;
|
|
}
|
|
g_hash_table_unref (priv->requests);
|
|
priv->requests = NULL;
|
|
}
|
|
|
|
g_slist_free_full (priv->chains, (GDestroyNotify) nm_auth_chain_unref);
|
|
priv->chains = NULL;
|
|
|
|
if (priv->agents) {
|
|
g_hash_table_destroy (priv->agents);
|
|
priv->agents = NULL;
|
|
}
|
|
|
|
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_exported_object_unexport (NM_EXPORTED_OBJECT (object));
|
|
|
|
G_OBJECT_CLASS (nm_agent_manager_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
nm_agent_manager_class_init (NMAgentManagerClass *agent_manager_class)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (agent_manager_class);
|
|
NMExportedObjectClass *exported_object_class = NM_EXPORTED_OBJECT_CLASS (agent_manager_class);
|
|
|
|
exported_object_class->export_path = NM_DBUS_PATH_AGENT_MANAGER;
|
|
|
|
object_class->constructed = constructed;
|
|
object_class->dispose = dispose;
|
|
|
|
signals[AGENT_REGISTERED] =
|
|
g_signal_new ("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);
|
|
|
|
nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (agent_manager_class),
|
|
NMDBUS_TYPE_AGENT_MANAGER_SKELETON,
|
|
"Register", impl_agent_manager_register,
|
|
"RegisterWithCapabilities", impl_agent_manager_register_with_capabilities,
|
|
"Unregister", impl_agent_manager_unregister,
|
|
NULL);
|
|
}
|