/* -*- 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 #include #include #include #include #include #include #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, const char *agent_username, 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 void req_complete_success (Request *req, GHashTable *secrets, const char *agent_dbus_owner, const char *agent_uname, gboolean agent_has_modify) { req->complete_callback (req, secrets, agent_dbus_owner, agent_uname, agent_has_modify, NULL, req->complete_callback_data); } static void req_complete_error (Request *req, GError *error) { req->complete_callback (req, NULL, NULL, NULL, FALSE, error, req->complete_callback_data); } 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_error (req, error); 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; struct passwd *pw; char *agent_uname = NULL; 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); /* 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_success (req, secrets, agent_dbus_owner, agent_uname, agent_has_modify); g_free (agent_uname); } 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_error (req, error); 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_success (req, req->existing_secrets, NULL, NULL, FALSE); } 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, const char *agent_username, 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_username, 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_success (req, NULL, NULL, agent_dbus_owner, FALSE); } 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, const char *agent_username, 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, const char *agent_username, 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); }