NetworkManager/src/settings/nm-agent-manager.c
Dan Williams bc9803f92d settings: fix requesting new secrets when the old ones don't work
If the connection had system secrets, previously the settings core
would consider those sufficient even if the device code had requested
new secrets because the old ones didn't work.
2011-03-29 22:56:08 -05:00

1351 lines
43 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 - 2011 Red Hat, Inc.
*/
#include <config.h>
#include <string.h>
#include <ctype.h>
#include <glib.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-lowlevel.h>
#include "NetworkManager.h"
#include "nm-logging.h"
#include "nm-agent-manager.h"
#include "nm-secret-agent.h"
#include "nm-manager-auth.h"
#include "nm-dbus-glib-types.h"
#include "nm-polkit-helpers.h"
#include "nm-manager-auth.h"
#include "nm-setting-vpn.h"
#include "nm-setting-connection.h"
G_DEFINE_TYPE (NMAgentManager, nm_agent_manager, G_TYPE_OBJECT)
#define NM_AGENT_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
NM_TYPE_AGENT_MANAGER, \
NMAgentManagerPrivate))
typedef struct {
gboolean disposed;
NMDBusManager *dbus_mgr;
NMSessionMonitor *session_monitor;
PolkitAuthority *authority;
/* Hashed by owner name, not identifier, since two agents in different
* sessions can use the same identifier.
*/
GHashTable *agents;
GHashTable *requests;
} NMAgentManagerPrivate;
typedef struct _Request Request;
static void request_add_agent (Request *req,
NMSecretAgent *agent,
NMSessionMonitor *session_monitor);
static void request_remove_agent (Request *req, NMSecretAgent *agent);
static void impl_agent_manager_register (NMAgentManager *self,
const char *identifier,
DBusGMethodInvocation *context);
static void impl_agent_manager_unregister (NMAgentManager *self,
DBusGMethodInvocation *context);
#include "nm-agent-manager-glue.h"
/********************************************************************/
#define NM_AGENT_MANAGER_ERROR (nm_agent_manager_error_quark ())
#define NM_TYPE_AGENT_MANAGER_ERROR (nm_agent_manager_error_get_type ())
typedef enum {
NM_AGENT_MANAGER_ERROR_SENDER_UNKNOWN = 0,
NM_AGENT_MANAGER_ERROR_PERMISSION_DENIED,
NM_AGENT_MANAGER_ERROR_SESSION_NOT_FOUND,
NM_AGENT_MANAGER_ERROR_INVALID_IDENTIFIER,
NM_AGENT_MANAGER_ERROR_NOT_REGISTERED,
NM_AGENT_MANAGER_ERROR_INTERNAL_ERROR,
NM_AGENT_MANAGER_ERROR_NO_SECRETS
} NMAgentManagerError;
static GQuark
nm_agent_manager_error_quark (void)
{
static GQuark ret = 0;
if (G_UNLIKELY (ret == 0))
ret = g_quark_from_static_string ("nm-agent-manager-error");
return ret;
}
#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
static GType
nm_agent_manager_error_get_type (void)
{
static GType etype = 0;
if (etype == 0) {
static const GEnumValue values[] = {
/* Unable to determine caller's sender or UID */
ENUM_ENTRY (NM_AGENT_MANAGER_ERROR_SENDER_UNKNOWN, "SenderUnknown"),
/* Permission for some operation was denied */
ENUM_ENTRY (NM_AGENT_MANAGER_ERROR_PERMISSION_DENIED, "PermissionDenied"),
/* The caller's session could not be determined */
ENUM_ENTRY (NM_AGENT_MANAGER_ERROR_SESSION_NOT_FOUND, "SessionNotFound"),
/* The identifier was invalid */
ENUM_ENTRY (NM_AGENT_MANAGER_ERROR_INVALID_IDENTIFIER, "InvalidIdentifier"),
/* Request was not from a registered agent */
ENUM_ENTRY (NM_AGENT_MANAGER_ERROR_NOT_REGISTERED, "NotRegistered"),
/* Some internal error occurred */
ENUM_ENTRY (NM_AGENT_MANAGER_ERROR_INTERNAL_ERROR, "InternalError"),
/* No secrets were available */
ENUM_ENTRY (NM_AGENT_MANAGER_ERROR_NO_SECRETS, "NoSecrets"),
{ 0, 0, 0 }
};
etype = g_enum_register_static ("NMAgentManagerError", values);
}
return etype;
}
/*************************************************************/
static gboolean
remove_agent (NMAgentManager *self, const char *owner)
{
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
NMSecretAgent *agent;
GHashTableIter iter;
gpointer data;
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;
nm_log_dbg (LOGD_AGENTS, "(%s) agent unregistered",
nm_secret_agent_get_description (agent));
/* Remove this agent to any in-progress secrets requests */
g_hash_table_iter_init (&iter, priv->requests);
while (g_hash_table_iter_next (&iter, NULL, &data))
request_remove_agent ((Request *) data, agent);
/* And dispose of the agent */
g_hash_table_remove (priv->agents, owner);
return TRUE;
}
/*************************************************************/
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 (!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
impl_agent_manager_register (NMAgentManager *self,
const char *identifier,
DBusGMethodInvocation *context)
{
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
char *error_desc = NULL, *sender = NULL;
gulong sender_uid = G_MAXULONG;
GError *error = NULL, *local = NULL;
NMSecretAgent *agent;
GHashTableIter iter;
gpointer data;
if (!nm_auth_get_caller_uid (context,
priv->dbus_mgr,
&sender_uid,
&error_desc)) {
error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_SENDER_UNKNOWN,
error_desc);
g_free (error_desc);
goto done;
}
if (!nm_session_monitor_uid_has_session (priv->session_monitor,
sender_uid,
NULL,
&local)) {
error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_SESSION_NOT_FOUND,
local && local->message ? local->message : "Session not found");
goto done;
}
sender = dbus_g_method_get_sender (context);
if (!sender) {
error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_SENDER_UNKNOWN,
"Failed to get D-Bus request sender");
goto done;
}
/* Validate the identifier */
if (!validate_identifier (identifier, &error))
goto done;
/* Success, add the new agent */
agent = nm_secret_agent_new (priv->dbus_mgr, sender, identifier, sender_uid);
if (!agent) {
error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_INTERNAL_ERROR,
"Failed to initialize the agent");
goto done;
}
g_hash_table_insert (priv->agents, g_strdup (sender), agent);
nm_log_dbg (LOGD_AGENTS, "(%s) agent registered",
nm_secret_agent_get_description (agent));
dbus_g_method_return (context);
/* Add this agent to any in-progress secrets requests */
g_hash_table_iter_init (&iter, priv->requests);
while (g_hash_table_iter_next (&iter, NULL, &data))
request_add_agent ((Request *) data, agent, priv->session_monitor);
done:
if (error)
dbus_g_method_return_error (context, error);
g_clear_error (&error);
g_clear_error (&local);
g_free (sender);
}
static void
impl_agent_manager_unregister (NMAgentManager *self,
DBusGMethodInvocation *context)
{
GError *error = NULL;
char *sender = NULL;
sender = dbus_g_method_get_sender (context);
if (!sender) {
error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_SENDER_UNKNOWN,
"Failed to get D-Bus 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;
}
dbus_g_method_return (context);
done:
if (error)
dbus_g_method_return_error (context, error);
g_clear_error (&error);
g_free (sender);
}
/*************************************************************/
typedef void (*RequestCompleteFunc) (Request *req,
GHashTable *secrets,
const char *agent_dbus_owner,
gboolean agent_has_modify,
GError *error,
gpointer user_data);
typedef void (*RequestNextFunc) (Request *req);
typedef void (*RequestCancelFunc) (Request *req);
struct _Request {
guint32 reqid;
PolkitAuthority *authority;
NMAuthChain *chain;
NMConnection *connection;
gboolean filter_by_uid;
gulong uid_filter;
char *setting_name;
NMSettingsGetSecretsFlags flags;
char *hint;
/* Current agent being asked for secrets */
NMSecretAgent *current;
gconstpointer current_call_id;
gboolean current_has_modify;
/* Stores the sorted list of NMSecretAgents which will be asked for secrets */
GSList *pending;
/* Stores the list of NMSecretAgent hashes that we've already
* asked for secrets, so that we don't ask the same agent twice
* if it quits and re-registers during this secrets request.
*/
GSList *asked;
guint32 idle_id;
GHashTable *existing_secrets;
NMAgentSecretsResultFunc callback;
gpointer callback_data;
gpointer other_data2;
gpointer other_data3;
RequestCancelFunc cancel_callback;
RequestNextFunc next_callback;
RequestCompleteFunc complete_callback;
gpointer complete_callback_data;
};
static guint32 next_req_id = 1;
static Request *
request_new_get (NMConnection *connection,
PolkitAuthority *authority,
gboolean filter_by_uid,
gulong uid_filter,
GHashTable *existing_secrets,
const char *setting_name,
NMSettingsGetSecretsFlags flags,
const char *hint,
NMAgentSecretsResultFunc callback,
gpointer callback_data,
gpointer other_data2,
gpointer other_data3,
RequestCompleteFunc complete_callback,
gpointer complete_callback_data,
RequestNextFunc next_callback,
RequestCancelFunc cancel_callback)
{
Request *req;
req = g_malloc0 (sizeof (Request));
req->reqid = next_req_id++;
req->connection = g_object_ref (connection);
req->authority = g_object_ref (authority);
req->filter_by_uid = filter_by_uid;
req->uid_filter = uid_filter;
if (existing_secrets)
req->existing_secrets = g_hash_table_ref (existing_secrets);
req->setting_name = g_strdup (setting_name);
req->flags = flags;
req->hint = g_strdup (hint);
req->callback = callback;
req->callback_data = callback_data;
req->other_data2 = other_data2;
req->other_data3 = other_data3;
req->complete_callback = complete_callback;
req->complete_callback_data = complete_callback_data;
req->next_callback = next_callback;
req->cancel_callback = cancel_callback;
return req;
}
static Request *
request_new_other (NMConnection *connection,
gboolean filter_by_uid,
gulong uid_filter,
RequestCompleteFunc complete_callback,
gpointer complete_callback_data,
RequestNextFunc next_callback)
{
Request *req;
req = g_malloc0 (sizeof (Request));
req->reqid = next_req_id++;
req->connection = g_object_ref (connection);
req->filter_by_uid = filter_by_uid;
req->uid_filter = uid_filter;
req->complete_callback = complete_callback;
req->complete_callback_data = complete_callback_data;
req->next_callback = next_callback;
return req;
}
static void
request_free (Request *req)
{
if (req->idle_id)
g_source_remove (req->idle_id);
if (req->cancel_callback)
req->cancel_callback (req);
g_slist_free (req->pending);
g_slist_free (req->asked);
g_object_unref (req->connection);
g_free (req->setting_name);
g_free (req->hint);
if (req->existing_secrets)
g_hash_table_unref (req->existing_secrets);
if (req->chain)
nm_auth_chain_unref (req->chain);
if (req->authority)
g_object_unref (req->authority);
memset (req, 0, sizeof (Request));
g_free (req);
}
static gint
agent_compare_func (NMSecretAgent *a, NMSecretAgent *b, gpointer user_data)
{
NMSessionMonitor *session_monitor = NM_SESSION_MONITOR (user_data);
gboolean a_active, b_active;
if (a && !b)
return -1;
else if (a == b)
return 0;
else if (!a && b)
return 1;
/* Prefer agents in active sessions */
a_active = nm_session_monitor_uid_active (session_monitor,
nm_secret_agent_get_owner_uid (a),
NULL);
b_active = nm_session_monitor_uid_active (session_monitor,
nm_secret_agent_get_owner_uid (b),
NULL);
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,
NMSessionMonitor *session_monitor)
{
uid_t agent_uid;
g_return_if_fail (req != NULL);
g_return_if_fail (agent != NULL);
if (g_slist_find (req->asked, GUINT_TO_POINTER (nm_secret_agent_get_hash (agent))))
return;
/* Ensure the caller's username exists in the connection's permissions,
* or that the permissions is empty (ie, visible by everyone).
*/
agent_uid = nm_secret_agent_get_owner_uid (agent);
if (0 != agent_uid) {
if (!nm_auth_uid_in_acl (req->connection, session_monitor, agent_uid, NULL)) {
nm_log_dbg (LOGD_AGENTS, "(%s) agent ignored for secrets request %p/%s (not in ACL)",
nm_secret_agent_get_description (agent),
req, req->setting_name);
/* Connection not visible to this agent's user */
return;
}
/* Caller is allowed to manipulate this connection */
}
/* If the request should filter agents by UID, do that now */
if (req->filter_by_uid && (agent_uid != req->uid_filter)) {
nm_log_dbg (LOGD_AGENTS, "(%s) agent ignored for secrets request %p/%s "
"(uid %d not required %ld)",
nm_secret_agent_get_description (agent),
req, req->setting_name, agent_uid, req->uid_filter);
return;
}
nm_log_dbg (LOGD_AGENTS, "(%s) agent allowed for secrets request %p/%s",
nm_secret_agent_get_description (agent),
req, req->setting_name);
/* Add this agent to the list, preferring active sessions */
req->pending = g_slist_insert_sorted_with_data (req->pending,
agent,
(GCompareDataFunc) agent_compare_func,
session_monitor);
}
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), priv->session_monitor);
}
static void
request_remove_agent (Request *req, NMSecretAgent *agent)
{
gboolean try_next = FALSE;
const char *detail = "";
g_return_if_fail (req != NULL);
g_return_if_fail (agent != NULL);
/* If this agent is being asked right now, cancel the request */
if (agent == req->current) {
req->cancel_callback (req);
req->current_has_modify = FALSE;
req->current = NULL;
req->current_call_id = NULL;
try_next = TRUE;
detail = " current";
}
nm_log_dbg (LOGD_AGENTS, "(%s)%s agent removed from secrets request %p/%s",
nm_secret_agent_get_description (agent),
detail, req, req->setting_name);
req->pending = g_slist_remove (req->pending, agent);
if (try_next) {
/* If the agent serving the in-progress secrets request went away then
* we need to send the request to the next agent.
*/
req->next_callback (req);
}
}
static gboolean
next_generic (Request *req, const char *detail)
{
GError *error = NULL;
gboolean success = FALSE;
if (req->pending == NULL) {
/* 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_callback (req, NULL, NULL, FALSE, error, req->complete_callback_data);
g_error_free (error);
} else {
/* Send a secrets request to the next agent */
req->current_has_modify = FALSE;
req->current = req->pending->data;
req->pending = g_slist_remove (req->pending, req->current);
nm_log_dbg (LOGD_AGENTS, "(%s) agent %s secrets for request %p/%s",
nm_secret_agent_get_description (req->current),
detail, req, req->setting_name);
success = TRUE;
}
return success;
}
static gboolean
start_generic (gpointer user_data)
{
Request *req = user_data;
req->idle_id = 0;
req->next_callback (req);
return FALSE;
}
/*************************************************************/
static void
get_done_cb (NMSecretAgent *agent,
gconstpointer call_id,
GHashTable *secrets,
GError *error,
gpointer user_data)
{
Request *req = user_data;
GHashTable *setting_secrets;
const char *agent_dbus_owner;
gboolean agent_has_modify;
g_return_if_fail (call_id == req->current_call_id);
agent_has_modify = req->current_has_modify;
req->current_has_modify = FALSE;
req->current = NULL;
req->current_call_id = NULL;
if (error) {
nm_log_dbg (LOGD_AGENTS, "(%s) agent failed secrets request %p/%s: (%d) %s",
nm_secret_agent_get_description (agent),
req, req->setting_name,
error ? error->code : -1,
(error && error->message) ? error->message : "(unknown)");
/* Try the next agent */
req->next_callback (req);
return;
}
/* Ensure the setting we wanted secrets for got returned and has something in it */
setting_secrets = g_hash_table_lookup (secrets, req->setting_name);
if (!setting_secrets || !g_hash_table_size (setting_secrets)) {
nm_log_dbg (LOGD_AGENTS, "(%s) agent returned no secrets for request %p/%s",
nm_secret_agent_get_description (agent),
req, req->setting_name);
/* Try the next agent */
req->next_callback (req);
return;
}
nm_log_dbg (LOGD_AGENTS, "(%s) agent returned secrets for request %p/%s",
nm_secret_agent_get_description (agent),
req, req->setting_name);
agent_dbus_owner = nm_secret_agent_get_dbus_owner (agent);
req->complete_callback (req, secrets, agent_dbus_owner, agent_has_modify, NULL, req->complete_callback_data);
}
static void
set_secrets_not_required (NMConnection *connection, GHashTable *hash)
{
GHashTableIter iter, setting_iter;
const char *setting_name = NULL;
GHashTable *setting_hash = NULL;
/* Iterate through the settings hashes */
g_hash_table_iter_init (&iter, hash);
while (g_hash_table_iter_next (&iter,
(gpointer *) &setting_name,
(gpointer *) &setting_hash)) {
const char *key_name = NULL;
NMSetting *setting;
GValue *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_hash_table_iter_init (&setting_iter, setting_hash);
while (g_hash_table_iter_next (&setting_iter, (gpointer *) &key_name, (gpointer *) &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 hash table of secrets.
*/
if ( strcmp (setting_name, NM_SETTING_VPN_SETTING_NAME) == 0
&& strcmp (key_name, NM_SETTING_VPN_SECRETS) == 0
&& G_VALUE_HOLDS (val, DBUS_TYPE_G_MAP_OF_STRING)) {
GHashTableIter vpn_secret_iter;
const char *secret_name;
g_hash_table_iter_init (&vpn_secret_iter, g_value_get_boxed (val));
while (g_hash_table_iter_next (&vpn_secret_iter, (gpointer *) &secret_name, NULL))
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);
}
}
}
}
static void
get_agent_request_secrets (Request *req, gboolean include_system_secrets)
{
NMConnection *tmp;
tmp = nm_connection_duplicate (req->connection);
nm_connection_clear_secrets (tmp);
if (include_system_secrets) {
if (req->existing_secrets)
nm_connection_update_secrets (tmp, req->setting_name, req->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->existing_secrets)
set_secrets_not_required (tmp, req->existing_secrets);
}
req->current_call_id = nm_secret_agent_get_secrets (NM_SECRET_AGENT (req->current),
tmp,
req->setting_name,
req->hint,
req->flags,
get_done_cb,
req);
if (req->current_call_id == NULL) {
/* Shouldn't hit this, but handle it anyway */
g_warn_if_fail (req->current_call_id != NULL);
req->current_has_modify = FALSE;
req->current = NULL;
req->next_callback (req);
}
g_object_unref (tmp);
}
static void
get_agent_modify_auth_cb (NMAuthChain *chain,
GError *error,
DBusGMethodInvocation *context,
gpointer user_data)
{
Request *req = user_data;
NMAuthCallResult result;
const char *perm;
req->chain = NULL;
if (error) {
nm_log_dbg (LOGD_AGENTS, "(%p/%s) agent MODIFY check error: (%d) %s",
req, req->setting_name,
error->code, error->message ? error->message : "(unknown)");
/* Try the next agent */
req->next_callback (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);
result = nm_auth_chain_get_result (chain, perm);
if (result == NM_AUTH_CALL_RESULT_YES)
req->current_has_modify = TRUE;
nm_log_dbg (LOGD_AGENTS, "(%p/%s) agent MODIFY check result %d",
req, req->setting_name, result);
get_agent_request_secrets (req, req->current_has_modify);
}
nm_auth_chain_unref (chain);
}
static void
check_system_secrets_cb (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)) {
if (nm_setting_get_secret_flags (setting, secret_name, &secret_flags, NULL)) {
if (secret_flags == NM_SETTING_SECRET_FLAG_NONE)
*has_system = TRUE;
}
}
} else {
nm_setting_get_secret_flags (setting, key, &secret_flags, NULL);
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, check_system_secrets_cb, &has_system);
return has_system;
}
static void
get_next_cb (Request *req)
{
NMSettingConnection *s_con;
const char *agent_dbus_owner, *perm;
if (!next_generic (req, "getting"))
return;
agent_dbus_owner = nm_secret_agent_get_dbus_owner (NM_SECRET_AGENT (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->flags != NM_SETTINGS_GET_SECRETS_FLAG_NONE)
&& (req->existing_secrets || has_system_secrets (req->connection))) {
nm_log_dbg (LOGD_AGENTS, "(%p/%s) request has system secrets; checking agent %s for MODIFY",
req, req->setting_name, agent_dbus_owner);
req->chain = nm_auth_chain_new_dbus_sender (req->authority,
agent_dbus_owner,
get_agent_modify_auth_cb,
req);
g_assert (req->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 = (NMSettingConnection *) nm_connection_get_setting (req->connection, NM_TYPE_SETTING_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->chain, "perm", (gpointer) perm, NULL);
nm_auth_chain_add_call (req->chain, perm, TRUE);
} else {
nm_log_dbg (LOGD_AGENTS, "(%p/%s) requesting user-owned secrets from agent %s",
req, req->setting_name, agent_dbus_owner);
get_agent_request_secrets (req, FALSE);
}
}
static gboolean
get_start (gpointer user_data)
{
Request *req = user_data;
GHashTable *setting_secrets = NULL;
req->idle_id = 0;
/* Check if there are any existing secrets */
if (req->existing_secrets)
setting_secrets = g_hash_table_lookup (req->existing_secrets, req->setting_name);
if (setting_secrets && g_hash_table_size (setting_secrets)) {
NMConnection *tmp;
GError *error = NULL;
gboolean request_new = (req->flags & NM_SETTINGS_GET_SECRETS_FLAG_REQUEST_NEW);
/* 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_connection_duplicate (req->connection);
g_assert (tmp);
if (!nm_connection_update_secrets (tmp, req->setting_name, req->existing_secrets, &error)) {
req->complete_callback (req, NULL, NULL, FALSE, error, req->complete_callback_data);
g_clear_error (&error);
} else {
/* Do we have everything we need? */
/* FIXME: handle second check for VPN connections */
if ((nm_connection_need_secrets (tmp, NULL) == NULL) && (request_new == FALSE)) {
nm_log_dbg (LOGD_AGENTS, "(%p/%s) system settings secrets sufficient",
req, req->setting_name);
/* Got everything, we're done */
req->complete_callback (req, req->existing_secrets, NULL, FALSE, NULL, req->complete_callback_data);
} else {
nm_log_dbg (LOGD_AGENTS, "(%p/%s) system settings secrets insufficient, asking agents",
req, req->setting_name);
/* We don't, so ask some agents for additional secrets */
req->next_callback (req);
}
}
g_object_unref (tmp);
} else {
/* 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.
*/
req->next_callback (req);
}
return FALSE;
}
static void
get_complete_cb (Request *req,
GHashTable *secrets,
const char *agent_dbus_owner,
gboolean agent_has_modify,
GError *error,
gpointer user_data)
{
NMAgentManager *self = NM_AGENT_MANAGER (user_data);
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
/* Send secrets back to the requesting object */
req->callback (self,
req->reqid,
agent_dbus_owner,
agent_has_modify,
req->setting_name,
req->flags,
error ? NULL : secrets,
error,
req->callback_data,
req->other_data2,
req->other_data3);
g_hash_table_remove (priv->requests, GUINT_TO_POINTER (req->reqid));
}
static void
get_cancel_cb (Request *req)
{
if (req->current && req->current_call_id)
nm_secret_agent_cancel_secrets (req->current, req->current_call_id);
}
guint32
nm_agent_manager_get_secrets (NMAgentManager *self,
NMConnection *connection,
gboolean filter_by_uid,
gulong uid_filter,
GHashTable *existing_secrets,
const char *setting_name,
NMSettingsGetSecretsFlags flags,
const char *hint,
NMAgentSecretsResultFunc callback,
gpointer callback_data,
gpointer other_data2,
gpointer other_data3)
{
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
Request *req;
g_return_val_if_fail (self != NULL, 0);
g_return_val_if_fail (connection != NULL, 0);
g_return_val_if_fail (NM_IS_CONNECTION (connection), 0);
g_return_val_if_fail (callback != NULL, 0);
nm_log_dbg (LOGD_SETTINGS,
"Secrets requested for connection %s (%s)",
nm_connection_get_path (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_hash() and nm_setting_to_hash()
* both returning NULL if they didn't hash anything.
*/
req = request_new_get (connection,
priv->authority,
filter_by_uid,
uid_filter,
existing_secrets,
setting_name,
flags,
hint,
callback,
callback_data,
other_data2,
other_data3,
get_complete_cb,
self,
get_next_cb,
get_cancel_cb);
g_hash_table_insert (priv->requests, GUINT_TO_POINTER (req->reqid), req);
/* Kick off the request */
request_add_agents (self, req);
req->idle_id = g_idle_add (get_start, req);
return req->reqid;
}
void
nm_agent_manager_cancel_secrets (NMAgentManager *self,
guint32 request_id)
{
g_return_if_fail (self != NULL);
g_return_if_fail (request_id > 0);
g_hash_table_remove (NM_AGENT_MANAGER_GET_PRIVATE (self)->requests,
GUINT_TO_POINTER (request_id));
}
/*************************************************************/
static void
save_done_cb (NMSecretAgent *agent,
gconstpointer call_id,
GHashTable *secrets,
GError *error,
gpointer user_data)
{
Request *req = user_data;
const char *agent_dbus_owner;
g_return_if_fail (call_id == req->current_call_id);
req->current = NULL;
req->current_call_id = NULL;
if (error) {
nm_log_dbg (LOGD_AGENTS, "(%s) agent failed save secrets request %p/%s: (%d) %s",
nm_secret_agent_get_description (agent),
req, req->setting_name,
error ? error->code : -1,
(error && error->message) ? error->message : "(unknown)");
/* Try the next agent */
req->next_callback (req);
return;
}
nm_log_dbg (LOGD_AGENTS, "(%s) agent saved secrets for request %p/%s",
nm_secret_agent_get_description (agent),
req, req->setting_name);
agent_dbus_owner = nm_secret_agent_get_dbus_owner (agent);
req->complete_callback (req, NULL, agent_dbus_owner, FALSE, NULL, req->complete_callback_data);
}
static void
save_next_cb (Request *req)
{
if (!next_generic (req, "saving"))
return;
req->current_call_id = nm_secret_agent_save_secrets (NM_SECRET_AGENT (req->current),
req->connection,
save_done_cb,
req);
if (req->current_call_id == NULL) {
/* Shouldn't hit this, but handle it anyway */
g_warn_if_fail (req->current_call_id != NULL);
req->current = NULL;
req->next_callback (req);
}
}
static void
save_complete_cb (Request *req,
GHashTable *secrets,
const char *agent_dbus_owner,
gboolean agent_has_modify,
GError *error,
gpointer user_data)
{
NMAgentManager *self = NM_AGENT_MANAGER (user_data);
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
g_hash_table_remove (priv->requests, GUINT_TO_POINTER (req->reqid));
}
guint32
nm_agent_manager_save_secrets (NMAgentManager *self,
NMConnection *connection,
gboolean filter_by_uid,
gulong uid_filter)
{
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
Request *req;
g_return_val_if_fail (self != NULL, 0);
g_return_val_if_fail (connection != NULL, 0);
g_return_val_if_fail (NM_IS_CONNECTION (connection), 0);
nm_log_dbg (LOGD_SETTINGS,
"Saving secrets for connection %s",
nm_connection_get_path (connection));
req = request_new_other (connection,
filter_by_uid,
uid_filter,
save_complete_cb,
self,
save_next_cb);
g_hash_table_insert (priv->requests, GUINT_TO_POINTER (req->reqid), req);
/* Kick off the request */
request_add_agents (self, req);
req->idle_id = g_idle_add (start_generic, req);
return req->reqid;
}
/*************************************************************/
static void
delete_done_cb (NMSecretAgent *agent,
gconstpointer call_id,
GHashTable *secrets,
GError *error,
gpointer user_data)
{
Request *req = user_data;
g_return_if_fail (call_id == req->current_call_id);
req->current = NULL;
req->current_call_id = NULL;
if (error) {
nm_log_dbg (LOGD_AGENTS, "(%s) agent failed delete secrets request %p/%s: (%d) %s",
nm_secret_agent_get_description (agent),
req, req->setting_name,
error ? error->code : -1,
(error && error->message) ? error->message : "(unknown)");
} else {
nm_log_dbg (LOGD_AGENTS, "(%s) agent deleted secrets for request %p/%s",
nm_secret_agent_get_description (agent),
req, req->setting_name);
}
/* Tell the next agent to delete secrets */
req->next_callback (req);
}
static void
delete_next_cb (Request *req)
{
if (!next_generic (req, "deleting"))
return;
req->current_call_id = nm_secret_agent_delete_secrets (NM_SECRET_AGENT (req->current),
req->connection,
delete_done_cb,
req);
if (req->current_call_id == NULL) {
/* Shouldn't hit this, but handle it anyway */
g_warn_if_fail (req->current_call_id != NULL);
req->current = NULL;
req->next_callback (req);
}
}
static void
delete_complete_cb (Request *req,
GHashTable *secrets,
const char *agent_dbus_owner,
gboolean agent_has_modify,
GError *error,
gpointer user_data)
{
NMAgentManager *self = NM_AGENT_MANAGER (user_data);
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
g_hash_table_remove (priv->requests, GUINT_TO_POINTER (req->reqid));
}
guint32
nm_agent_manager_delete_secrets (NMAgentManager *self,
NMConnection *connection,
gboolean filter_by_uid,
gulong uid_filter)
{
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
Request *req;
g_return_val_if_fail (self != NULL, 0);
g_return_val_if_fail (connection != NULL, 0);
g_return_val_if_fail (NM_IS_CONNECTION (connection), 0);
nm_log_dbg (LOGD_SETTINGS,
"Deleting secrets for connection %s",
nm_connection_get_path (connection));
req = request_new_other (connection,
filter_by_uid,
uid_filter,
delete_complete_cb,
self,
delete_next_cb);
g_hash_table_insert (priv->requests, GUINT_TO_POINTER (req->reqid), req);
/* Kick off the request */
request_add_agents (self, req);
req->idle_id = g_idle_add (start_generic, req);
return req->reqid;
}
/*************************************************************/
static void
name_owner_changed_cb (NMDBusManager *dbus_mgr,
const char *name,
const char *old_owner,
const char *new_owner,
gpointer user_data)
{
if (old_owner) {
/* The agent quit, so remove it and let interested clients know */
remove_agent (NM_AGENT_MANAGER (user_data), old_owner);
}
}
/*************************************************************/
NMAgentManager *
nm_agent_manager_get (void)
{
static NMAgentManager *singleton = NULL;
NMAgentManagerPrivate *priv;
DBusGConnection *connection;
if (singleton)
return g_object_ref (singleton);
singleton = (NMAgentManager *) g_object_new (NM_TYPE_AGENT_MANAGER, NULL);
g_assert (singleton);
priv = NM_AGENT_MANAGER_GET_PRIVATE (singleton);
priv->session_monitor = nm_session_monitor_get ();
priv->dbus_mgr = nm_dbus_manager_get ();
connection = nm_dbus_manager_get_connection (priv->dbus_mgr);
dbus_g_connection_register_g_object (connection,
NM_DBUS_PATH_AGENT_MANAGER,
G_OBJECT (singleton));
g_signal_connect (priv->dbus_mgr,
NM_DBUS_MANAGER_NAME_OWNER_CHANGED,
G_CALLBACK (name_owner_changed_cb),
singleton);
return singleton;
}
static void
nm_agent_manager_init (NMAgentManager *self)
{
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
GError *error = NULL;
priv->authority = polkit_authority_get_sync (NULL, &error);
if (!priv->authority) {
nm_log_warn (LOGD_SETTINGS, "failed to create PolicyKit authority: (%d) %s",
error ? error->code : -1,
error && error->message ? error->message : "(unknown)");
g_clear_error (&error);
}
priv->agents = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
priv->requests = g_hash_table_new_full (g_direct_hash,
g_direct_equal,
NULL,
(GDestroyNotify) request_free);
}
static void
dispose (GObject *object)
{
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (object);
if (!priv->disposed) {
priv->disposed = TRUE;
g_hash_table_destroy (priv->agents);
g_hash_table_destroy (priv->requests);
g_object_unref (priv->session_monitor);
g_object_unref (priv->dbus_mgr);
g_object_unref (priv->authority);
}
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);
g_type_class_add_private (agent_manager_class, sizeof (NMAgentManagerPrivate));
/* virtual methods */
object_class->dispose = dispose;
dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (agent_manager_class),
&dbus_glib_nm_agent_manager_object_info);
dbus_g_error_domain_register (NM_AGENT_MANAGER_ERROR,
NM_DBUS_INTERFACE_AGENT_MANAGER,
NM_TYPE_AGENT_MANAGER_ERROR);
}