settings: ensure an agent is authorized before overwriting system-owned secrets

If the agent returns system-owned secrets, like when activating a new
connection which was created with no secrets, make sure the agent is
authorized to modify network settings before saving or using the
new secrets.
This commit is contained in:
Dan Williams 2011-02-02 12:17:58 -06:00
parent 4ff0b5f0b7
commit 76aabe4b72
3 changed files with 325 additions and 22 deletions

View file

@ -322,6 +322,7 @@ done:
typedef void (*RequestCompleteFunc) (Request *req,
GHashTable *secrets,
const char *agent_dbus_owner,
GError *error,
gpointer user_data);
typedef void (*RequestNextFunc) (Request *req);
@ -341,9 +342,7 @@ struct _Request {
NMSecretAgent *current;
gconstpointer current_call_id;
/* Stores the sorted list of NMSecretAgents which will be
* asked for secrets.
*/
/* Stores the sorted list of NMSecretAgents which will be asked for secrets */
GSList *pending;
/* Stores the list of NMSecretAgent hashes that we've already
@ -584,7 +583,7 @@ next_generic (Request *req, const char *detail)
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, error, req->complete_callback_data);
req->complete_callback (req, NULL, NULL, error, req->complete_callback_data);
g_error_free (error);
} else {
/* Send a secrets request to the next agent */
@ -622,6 +621,7 @@ get_done_cb (NMSecretAgent *agent,
{
Request *req = user_data;
GHashTable *setting_secrets;
const char *agent_dbus_owner;
g_return_if_fail (call_id == req->current_call_id);
@ -656,7 +656,8 @@ get_done_cb (NMSecretAgent *agent,
nm_secret_agent_get_description (agent),
req, req->setting_name);
req->complete_callback (req, secrets, NULL, req->complete_callback_data);
agent_dbus_owner = nm_secret_agent_get_dbus_owner (agent);
req->complete_callback (req, secrets, agent_dbus_owner, NULL, req->complete_callback_data);
}
static void
@ -705,7 +706,7 @@ get_start (gpointer user_data)
g_assert (tmp);
if (!nm_connection_update_secrets (tmp, req->setting_name, req->existing_secrets, &error)) {
req->complete_callback (req, NULL, error, req->complete_callback_data);
req->complete_callback (req, NULL, NULL, error, req->complete_callback_data);
g_clear_error (&error);
} else {
/* Do we have everything we need? */
@ -715,7 +716,7 @@ get_start (gpointer user_data)
req, req->setting_name);
/* Got everything, we're done */
req->complete_callback (req, req->existing_secrets, NULL, req->complete_callback_data);
req->complete_callback (req, req->existing_secrets, NULL, NULL, req->complete_callback_data);
} else {
nm_log_dbg (LOGD_AGENTS, "(%p/%s) system settings secrets insufficient, asking agents",
req, req->setting_name);
@ -739,6 +740,7 @@ get_start (gpointer user_data)
static void
get_complete_cb (Request *req,
GHashTable *secrets,
const char *agent_dbus_owner,
GError *error,
gpointer user_data)
{
@ -748,7 +750,9 @@ get_complete_cb (Request *req,
/* Send secrets back to the requesting object */
req->callback (self,
req->reqid,
agent_dbus_owner,
req->setting_name,
req->flags,
error ? NULL : secrets,
error,
req->callback_data,
@ -837,6 +841,7 @@ save_done_cb (NMSecretAgent *agent,
gpointer user_data)
{
Request *req = user_data;
const char *agent_dbus_owner;
g_return_if_fail (call_id == req->current_call_id);
@ -859,7 +864,8 @@ save_done_cb (NMSecretAgent *agent,
nm_secret_agent_get_description (agent),
req, req->setting_name);
req->complete_callback (req, NULL, NULL, req->complete_callback_data);
agent_dbus_owner = nm_secret_agent_get_dbus_owner (agent);
req->complete_callback (req, NULL, agent_dbus_owner, NULL, req->complete_callback_data);
}
static void
@ -883,6 +889,7 @@ save_next_cb (Request *req)
static void
save_complete_cb (Request *req,
GHashTable *secrets,
const char *agent_dbus_owner,
GError *error,
gpointer user_data)
{
@ -977,6 +984,7 @@ delete_next_cb (Request *req)
static void
delete_complete_cb (Request *req,
GHashTable *secrets,
const char *agent_dbus_owner,
GError *error,
gpointer user_data)
{

View file

@ -44,9 +44,12 @@ GType nm_agent_manager_get_type (void);
NMAgentManager *nm_agent_manager_get (void);
/* If no agent fulfilled the secrets request, agent_dbus_owner will be NULL */
typedef void (*NMAgentSecretsResultFunc) (NMAgentManager *manager,
guint32 call_id,
const char *agent_dbus_owner,
const char *setting_name,
guint32 flags,
GHashTable *secrets,
GError *error,
gpointer user_data,

View file

@ -352,10 +352,216 @@ supports_secrets (NMSettingsConnection *connection, const char *setting_name)
return TRUE;
}
/* Return TRUE to continue, FALSE to stop */
typedef gboolean (*ForEachSecretFunc) (GHashTableIter *iter,
NMSettingSecretFlags flags,
gpointer user_data);
static gboolean
clear_system_owned_secrets (GHashTableIter *iter,
NMSettingSecretFlags flags,
gpointer user_data)
{
if (flags == NM_SETTING_SECRET_FLAG_SYSTEM_OWNED)
g_hash_table_iter_remove (iter);
return TRUE;
}
static gboolean
has_system_owned_secrets (GHashTableIter *iter,
NMSettingSecretFlags flags,
gpointer user_data)
{
gboolean *has_system_owned = user_data;
if (flags == NM_SETTING_SECRET_FLAG_SYSTEM_OWNED) {
*has_system_owned = TRUE;
return FALSE;
}
return TRUE;
}
static void
for_each_secret (NMConnection *connection,
GHashTable *secrets,
ForEachSecretFunc callback,
gpointer callback_data)
{
GHashTableIter iter;
const char *setting_name;
GHashTable *setting_hash;
/* Walk through the list of setting hashes */
g_hash_table_iter_init (&iter, secrets);
while (g_hash_table_iter_next (&iter,
(gpointer *) &setting_name,
(gpointer *) &setting_hash)) {
GHashTableIter setting_iter;
const char *secret_name;
/* Walk through the list of keys in each setting hash */
g_hash_table_iter_init (&setting_iter, setting_hash);
while (g_hash_table_iter_next (&setting_iter, (gpointer *) &secret_name, NULL)) {
NMSetting *setting;
NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_SYSTEM_OWNED;
/* Get the actual NMSetting from the connection so we can get secret flags */
setting = nm_connection_get_setting_by_name (connection, setting_name);
if (setting && nm_setting_get_secret_flags (setting, secret_name, &flags, NULL)) {
if (callback (&setting_iter, flags, callback_data) == FALSE)
return;
}
}
}
}
static void
new_secrets_commit_cb (NMSettingsConnection *connection,
GError *error,
gpointer user_data)
{
if (error) {
nm_log_warn (LOGD_SETTINGS, "Error saving new secrets to backing storage: (%d) %s",
error->code, error->message ? error->message : "(unknown)");
}
}
static void
get_secrets_done (NMSettingsConnection *self,
guint32 call_id,
const char *setting_name,
GHashTable *secrets,
GError *error,
NMSettingsConnectionSecretsFunc callback,
gpointer callback_data)
{
NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
NMSettingConnection *s_con;
GError *local = NULL;
GHashTable *hash = NULL;
s_con = (NMSettingConnection *) nm_connection_get_setting (NM_CONNECTION (self), NM_TYPE_SETTING_CONNECTION);
g_assert (s_con);
if (error) {
nm_log_dbg (LOGD_SETTINGS, "(%s/%s:%u) secrets request completed; error: (%d) %s",
nm_setting_connection_get_uuid (s_con),
setting_name,
call_id,
error->code,
error->message ? error->message : "(unknown)");
callback (self, call_id, setting_name, error, callback_data);
return;
}
nm_log_dbg (LOGD_SETTINGS, "(%s/%s:%u) secrets request completed successfully",
nm_setting_connection_get_uuid (s_con),
setting_name,
call_id);
/* Update the connection with our existing secrets from backing storage */
nm_connection_clear_secrets (NM_CONNECTION (self));
hash = nm_connection_to_hash (priv->secrets, NM_SETTING_HASH_FLAG_ONLY_SECRETS);
if (nm_connection_update_secrets (NM_CONNECTION (self), setting_name, hash, &local)) {
/* Update the connection with the agent's secrets; by this point if any
* system-owned secrets exist in 'secrets' the agent that provided them
* will have been authenticated, so those secrets can replace the existing
* system secrets.
*/
if (nm_connection_update_secrets (NM_CONNECTION (self), setting_name, secrets, &local)) {
/* Now that all secrets are updated, copy and cache new secrets,
* then save them to backing storage.
*/
if (priv->secrets)
g_object_unref (priv->secrets);
priv->secrets = nm_connection_duplicate (NM_CONNECTION (self));
nm_log_dbg (LOGD_SETTINGS, "(%s/%s:%u) saving new secrets to backing storage",
nm_setting_connection_get_uuid (s_con),
setting_name,
call_id);
nm_settings_connection_commit_changes (self, new_secrets_commit_cb, NULL);
} else {
nm_log_dbg (LOGD_SETTINGS, "(%s/%s:%u) failed to update with agent secrets: (%d) %s",
nm_setting_connection_get_uuid (s_con),
setting_name,
call_id,
local->code,
local->message ? local->message : "(unknown)");
}
} else {
nm_log_dbg (LOGD_SETTINGS, "(%s/%s:%u) failed to update with existing secrets: (%d) %s",
nm_setting_connection_get_uuid (s_con),
setting_name,
call_id,
local->code,
local->message ? local->message : "(unknown)");
}
callback (self, call_id, setting_name, local, callback_data);
g_clear_error (&local);
if (hash)
g_hash_table_destroy (hash);
}
static void
agent_secrets_modify_auth_cb (NMAuthChain *chain,
GError *error,
DBusGMethodInvocation *context,
gpointer user_data)
{
NMSettingsConnection *self = NM_SETTINGS_CONNECTION (user_data);
NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
NMSettingConnection *s_con;
NMAuthCallResult result;
GHashTable *secrets = nm_auth_chain_get_data (chain, "secrets");
const char *setting_name = nm_auth_chain_get_data (chain, "setting-name");
guint32 call_id = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "call-id"));
s_con = (NMSettingConnection *) nm_connection_get_setting (NM_CONNECTION (self), NM_TYPE_SETTING_CONNECTION);
g_assert (s_con);
priv->pending_auths = g_slist_remove (priv->pending_auths, chain);
result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_SETTINGS_CONNECTION_MODIFY);
nm_log_dbg (LOGD_SETTINGS, "(%s/%s:%u) agent MODIFY check result %d",
nm_setting_connection_get_uuid (s_con),
setting_name,
call_id,
result);
if (result == NM_AUTH_CALL_RESULT_YES) {
/* Agent can modify system connections; system-owned secrets it returned
* replace any secrets in backing storage.
*/
} else {
/* Agent didn't successfully authenticate; clear system-owned secrets
* from the secrets the agent returned.
*/
nm_log_dbg (LOGD_SETTINGS, "(%s/%s:%u) agent failed to authenticate after providing system secrets",
nm_setting_connection_get_uuid (s_con),
setting_name,
call_id);
for_each_secret (NM_CONNECTION (self), secrets, clear_system_owned_secrets, NULL);
}
get_secrets_done (self,
call_id,
setting_name,
secrets,
error,
nm_auth_chain_get_data (chain, "callback"),
nm_auth_chain_get_data (chain, "callback_data"));
}
static void
agent_secrets_done_cb (NMAgentManager *manager,
guint32 call_id,
const char *agent_dbus_owner,
const char *setting_name,
guint32 flags,
GHashTable *secrets,
GError *error,
gpointer user_data,
@ -366,29 +572,100 @@ agent_secrets_done_cb (NMAgentManager *manager,
NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
NMSettingsConnectionSecretsFunc callback = other_data2;
gpointer callback_data = other_data3;
NMSettingConnection *s_con;
GError *local = NULL;
GHashTable *hash;
s_con = (NMSettingConnection *) nm_connection_get_setting (NM_CONNECTION (self), NM_TYPE_SETTING_CONNECTION);
g_assert (s_con);
if (error) {
nm_log_dbg (LOGD_SETTINGS, "(%s/%s:%u) secrets request error: (%d) %s",
nm_setting_connection_get_uuid (s_con),
setting_name,
call_id,
error->code,
error->message ? error->message : "(unknown)");
callback (self, call_id, setting_name, error, callback_data);
return;
}
/* Update the connection with the agent's secrets */
nm_connection_clear_secrets (NM_CONNECTION (self));
if (nm_connection_update_secrets (NM_CONNECTION (self), setting_name, secrets, &local)) {
/* FIXME: if the agent's owner has "modify" permission, then agent
* supplied secrets should overwrite existing secrets, and those agent
* supplied secrets should get written back out to persistent storage.
*/
hash = nm_connection_to_hash (priv->secrets, NM_SETTING_HASH_FLAG_ONLY_SECRETS);
nm_connection_update_secrets (NM_CONNECTION (self), setting_name, hash, &local);
g_hash_table_destroy (hash);
if (!nm_connection_get_setting_by_name (NM_CONNECTION (self), setting_name)) {
local = g_error_new (NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_SETTING,
"%s.%d - Connection didn't have requested setting '%s'.",
__FILE__, __LINE__, setting_name);
callback (self, call_id, setting_name, local, callback_data);
g_clear_error (&local);
return;
}
callback (self, call_id, setting_name, local, callback_data);
g_clear_error (&local);
g_assert (secrets);
if (agent_dbus_owner) {
gboolean has_system = FALSE;
nm_log_dbg (LOGD_SETTINGS, "(%s/%s:%u) secrets returned from agent %s",
nm_setting_connection_get_uuid (s_con),
setting_name,
call_id,
agent_dbus_owner);
/* If the agent returned any system-owned secrets (initial connect and no
* secrets given when the connection was created, or something like that)
* make sure the agent's UID has the 'modify' permission before we use or
* save those system-owned secrets. If not, discard them and use the
* existing secrets, or fail the connection.
*/
for_each_secret (NM_CONNECTION (self), secrets, has_system_owned_secrets, &has_system);
if (has_system) {
if (flags == 0) { /* ie SECRETS_FLAG_NONE */
/* No user interaction was allowed when requesting secrets; the
* agent is being bad. Remove system-owned secrets.
*/
for_each_secret (NM_CONNECTION (self), secrets, clear_system_owned_secrets, NULL);
nm_log_dbg (LOGD_SETTINGS, "(%s/%s:%u) interaction forbidden but agent %s returned system secrets",
nm_setting_connection_get_uuid (s_con),
setting_name,
call_id,
agent_dbus_owner);
} else {
NMAuthChain *chain;
nm_log_dbg (LOGD_SETTINGS, "(%s/%s:%u) agent %s returned system secrets; checking for MODIFY",
nm_setting_connection_get_uuid (s_con),
setting_name,
call_id,
agent_dbus_owner);
/* User interaction was allowed, check whether the agent's UID
* has the 'modify' privilege before using the system-owned
* secrets supplied by the agent.
*/
chain = nm_auth_chain_new_dbus_sender (priv->authority,
agent_dbus_owner,
agent_secrets_modify_auth_cb,
self);
g_assert (chain);
nm_auth_chain_set_data (chain, "call-id", GUINT_TO_POINTER (call_id), NULL);
nm_auth_chain_set_data (chain, "setting-name", g_strdup (setting_name), g_free);
nm_auth_chain_set_data (chain, "secrets", g_hash_table_ref (secrets), (GDestroyNotify) g_hash_table_unref);
nm_auth_chain_set_data (chain, "callback", callback, NULL);
nm_auth_chain_set_data (chain, "callback-data", callback_data, NULL);
nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_SETTINGS_CONNECTION_MODIFY, TRUE);
priv->pending_auths = g_slist_append (priv->pending_auths, chain);
return;
}
}
} else {
nm_log_dbg (LOGD_SETTINGS, "(%s/%s:%u) existing secrets returned",
nm_setting_connection_get_uuid (s_con),
setting_name,
call_id);
}
get_secrets_done (self, call_id, setting_name, secrets, error, callback, callback_data);
}
/**
@ -421,6 +698,7 @@ nm_settings_connection_get_secrets (NMSettingsConnection *self,
GError **error)
{
NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
NMSettingConnection *s_con;
GHashTable *existing_secrets;
guint32 call_id = 0;
@ -460,6 +738,14 @@ nm_settings_connection_get_secrets (NMSettingsConnection *self,
callback_data);
g_hash_table_unref (existing_secrets);
s_con = (NMSettingConnection *) nm_connection_get_setting (NM_CONNECTION (self), NM_TYPE_SETTING_CONNECTION);
nm_log_dbg (LOGD_SETTINGS, "(%s/%s:%u) secrets requested flags 0x%X hint '%s'",
nm_setting_connection_get_uuid (s_con),
setting_name,
call_id,
flags,
hint);
return call_id;
}
@ -468,6 +754,12 @@ nm_settings_connection_cancel_secrets (NMSettingsConnection *self,
guint32 call_id)
{
NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
NMSettingConnection *s_con;
s_con = (NMSettingConnection *) nm_connection_get_setting (NM_CONNECTION (self), NM_TYPE_SETTING_CONNECTION);
nm_log_dbg (LOGD_SETTINGS, "(%s:%u) secrets canceled",
nm_setting_connection_get_uuid (s_con),
call_id);
priv->reqs = g_slist_remove (priv->reqs, GUINT_TO_POINTER (call_id));
nm_agent_manager_cancel_secrets (priv->agent_mgr, call_id);