diff --git a/libnm-util/nm-connection.c b/libnm-util/nm-connection.c index 6e9f92b59c..91fc3afac4 100644 --- a/libnm-util/nm-connection.c +++ b/libnm-util/nm-connection.c @@ -99,6 +99,7 @@ nm_connection_error_get_type (void) ENUM_ENTRY (NM_CONNECTION_ERROR_UNKNOWN, "UnknownError"), ENUM_ENTRY (NM_CONNECTION_ERROR_CONNECTION_SETTING_NOT_FOUND, "ConnectionSettingNotFound"), ENUM_ENTRY (NM_CONNECTION_ERROR_CONNECTION_TYPE_INVALID, "ConnectionTypeInvalid"), + ENUM_ENTRY (NM_CONNECTION_ERROR_SETTING_NOT_FOUND, "SettingNotFound"), { 0, 0, 0 } }; etype = g_enum_register_static ("NMConnectionError", values); @@ -790,60 +791,76 @@ nm_connection_verify (NMConnection *connection, GError **error) * nm_connection_update_secrets: * @connection: the #NMConnection * @setting_name: the setting object name to which the secrets apply - * @setting_secrets: (element-type utf8 GObject.Value): a #GHashTable mapping + * @secrets: (element-type utf8 GObject.Value): a #GHashTable mapping * string:#GValue of setting property names and secrets of the given @setting_name * @error: location to store error, or %NULL * * Update the specified setting's secrets, given a hash table of secrets * intended for that setting (deserialized from D-Bus for example). Will also * extract the given setting's secrets hash if given a hash of hashes, as would - * be returned from nm_connection_to_hash(). + * be returned from nm_connection_to_hash(). If @setting_name is %NULL, expects + * a fully serialized #NMConnection as returned by nm_connection_to_hash() and + * will update all secrets from all settings contained in @secrets. * - * Returns: %TRUE if the secrets were successfully updated and the connection - * is valid, %FALSE on failure or if the setting was never added to the connection + * Returns: %TRUE if the secrets were successfully updated, %FALSE if the update + * failed (tried to update secrets for a setting that doesn't exist, etc) **/ gboolean nm_connection_update_secrets (NMConnection *connection, const char *setting_name, - GHashTable *setting_secrets, + GHashTable *secrets, GError **error) { NMSetting *setting; gboolean success; GHashTable *tmp; - GType setting_type; g_return_val_if_fail (connection != NULL, FALSE); g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE); - g_return_val_if_fail (setting_name != NULL, FALSE); - g_return_val_if_fail (setting_secrets != NULL, FALSE); + g_return_val_if_fail (secrets != NULL, FALSE); if (error) g_return_val_if_fail (*error == NULL, FALSE); - setting_type = nm_connection_lookup_setting_type (setting_name); - if (!setting_type) { - g_set_error_literal (error, - NM_CONNECTION_ERROR, - NM_CONNECTION_ERROR_CONNECTION_SETTING_NOT_FOUND, - setting_name); - return FALSE; + if (setting_name) { + /* Update just one setting */ + setting = nm_connection_get_setting_by_name (connection, setting_name); + if (!setting) { + g_set_error_literal (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_SETTING_NOT_FOUND, + setting_name); + return FALSE; + } + + /* Check if this is a hash of hashes, ie a full deserialized connection, + * not just a single hashed setting. + */ + tmp = g_hash_table_lookup (secrets, setting_name); + success = nm_setting_update_secrets (setting, tmp ? tmp : secrets, error); + } else { + GHashTableIter iter; + const char *name; + + success = TRUE; /* Just in case 'secrets' has no elements */ + + /* Try as a serialized connection (GHashTable of GHashTables) */ + g_hash_table_iter_init (&iter, secrets); + while (g_hash_table_iter_next (&iter, (gpointer) &name, (gpointer) &tmp)) { + setting = nm_connection_get_setting_by_name (connection, name); + if (!setting) { + g_set_error_literal (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_SETTING_NOT_FOUND, + name); + return FALSE; + } + + /* Update the secrets for this setting */ + success = nm_setting_update_secrets (setting, tmp, error); + if (success == FALSE) + break; + } } - - setting = nm_connection_get_setting (connection, setting_type); - if (!setting) { - g_set_error_literal (error, - NM_CONNECTION_ERROR, - NM_CONNECTION_ERROR_CONNECTION_SETTING_NOT_FOUND, - setting_name); - return FALSE; - } - - /* Check if this is a hash of hashes, ie a full deserialized connection, - * not just a single hashed setting. - */ - tmp = g_hash_table_lookup (setting_secrets, setting_name); - - success = nm_setting_update_secrets (setting, tmp ? tmp : setting_secrets, error); if (success) g_signal_emit (connection, signals[SECRETS_UPDATED], 0, setting_name); return success; diff --git a/libnm-util/nm-connection.h b/libnm-util/nm-connection.h index 87b053c101..a7e0721cc1 100644 --- a/libnm-util/nm-connection.h +++ b/libnm-util/nm-connection.h @@ -66,6 +66,8 @@ G_BEGIN_DECLS * 'connection' setting did not point to a valid connection base type; ie * it was not a hardware-related setting like #NMSettingWired or * #NMSettingWireless. + * @NM_CONNECTION_ERROR_SETTING_NOT_FOUND: the #NMConnection object + * did not contain the specified #NMSetting object * * Describes errors that may result from operations involving a #NMConnection. * @@ -74,7 +76,8 @@ typedef enum { NM_CONNECTION_ERROR_UNKNOWN = 0, NM_CONNECTION_ERROR_CONNECTION_SETTING_NOT_FOUND, - NM_CONNECTION_ERROR_CONNECTION_TYPE_INVALID + NM_CONNECTION_ERROR_CONNECTION_TYPE_INVALID, + NM_CONNECTION_ERROR_SETTING_NOT_FOUND } NMConnectionError; #define NM_TYPE_CONNECTION_ERROR (nm_connection_error_get_type ()) diff --git a/libnm-util/tests/test-secrets.c b/libnm-util/tests/test-secrets.c index f1d105fcd2..1fe3c43823 100644 --- a/libnm-util/tests/test-secrets.c +++ b/libnm-util/tests/test-secrets.c @@ -591,7 +591,84 @@ test_update_secrets_wifi_bad_setting_name (void) "asdfasdfasdfasf", secrets, &error); - g_assert_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_CONNECTION_SETTING_NOT_FOUND); + g_assert_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_SETTING_NOT_FOUND); + g_assert (success == FALSE); + + g_object_unref (connection); +} + +static void +test_update_secrets_whole_connection (void) +{ + NMConnection *connection; + NMSettingWirelessSecurity *s_wsec; + GHashTable *secrets, *wsec_hash; + GError *error = NULL; + gboolean success; + const char *wepkey = "11111111111111111111111111"; + + connection = wifi_connection_new (); + + /* Build up the secrets hash */ + secrets = nm_connection_to_hash (connection, NM_SETTING_HASH_FLAG_ALL); + wsec_hash = g_hash_table_lookup (secrets, NM_SETTING_WIRELESS_SECURITY_SETTING_NAME); + g_assert (wsec_hash); + g_hash_table_insert (wsec_hash, NM_SETTING_WIRELESS_SECURITY_WEP_KEY0, string_to_gvalue (wepkey)); + + success = nm_connection_update_secrets (connection, NULL, secrets, &error); + g_assert_no_error (error); + g_assert (success == TRUE); + + s_wsec = nm_connection_get_setting_wireless_security (connection); + g_assert (s_wsec); + g_assert_cmpstr (nm_setting_wireless_security_get_wep_key (s_wsec, 0), ==, wepkey); + + g_object_unref (connection); +} + +static void +test_update_secrets_whole_connection_empty_hash (void) +{ + NMConnection *connection; + GHashTable *secrets; + GError *error = NULL; + gboolean success; + + connection = wifi_connection_new (); + secrets = g_hash_table_new (g_str_hash, g_str_equal); + success = nm_connection_update_secrets (connection, NULL, secrets, &error); + g_assert_no_error (error); + g_assert (success == TRUE); + g_object_unref (connection); +} + +static void +test_update_secrets_whole_connection_bad_setting (void) +{ + NMConnection *connection; + GHashTable *secrets, *wsec_hash; + GError *error = NULL; + gboolean success; + const char *wepkey = "11111111111111111111111111"; + + connection = wifi_connection_new (); + + /* Build up the secrets hash */ + secrets = nm_connection_to_hash (connection, NM_SETTING_HASH_FLAG_ALL); + wsec_hash = g_hash_table_lookup (secrets, NM_SETTING_WIRELESS_SECURITY_SETTING_NAME); + g_assert (wsec_hash); + g_hash_table_insert (wsec_hash, NM_SETTING_WIRELESS_SECURITY_WEP_KEY0, string_to_gvalue (wepkey)); + + /* Steal the wsec setting hash so it's not deallocated, and stuff it back + * in with a different name so we ensure libnm-util is returning the right + * error when it finds an entry in the connection hash that doesn't match + * any setting in the connection. + */ + g_hash_table_steal (secrets, NM_SETTING_WIRELESS_SECURITY_SETTING_NAME); + g_hash_table_insert (secrets, "asdfasdfasdfasdf", wsec_hash); + + success = nm_connection_update_secrets (connection, NULL, secrets, &error); + g_assert_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_SETTING_NOT_FOUND); g_assert (success == FALSE); g_object_unref (connection); @@ -617,6 +694,10 @@ int main (int argc, char **argv) test_update_secrets_wifi_full_hash (); test_update_secrets_wifi_bad_setting_name (); + test_update_secrets_whole_connection (); + test_update_secrets_whole_connection_empty_hash (); + test_update_secrets_whole_connection_bad_setting (); + base = g_path_get_basename (argv[0]); fprintf (stdout, "%s: SUCCESS\n", base); g_free (base); diff --git a/src/settings/nm-settings-connection.c b/src/settings/nm-settings-connection.c index f0f9c1a6d7..4b3a56cd6d 100644 --- a/src/settings/nm-settings-connection.c +++ b/src/settings/nm-settings-connection.c @@ -382,25 +382,14 @@ nm_settings_connection_replace_settings (NMSettingsConnection *self, new_settings = nm_connection_to_hash (new, NM_SETTING_HASH_FLAG_ALL); g_assert (new_settings); if (nm_connection_replace_settings (NM_CONNECTION (self), new_settings, error)) { - GHashTableIter iter; - NMSetting *setting; - const char *setting_name; - GHashTable *setting_hash; - /* Copy the connection to keep its secrets around even if NM * calls nm_connection_clear_secrets(). */ update_secrets_cache (self); /* And add the transient secrets back */ - if (transient_secrets) { - g_hash_table_iter_init (&iter, transient_secrets); - while (g_hash_table_iter_next (&iter, (gpointer) &setting_name, (gpointer) &setting_hash)) { - setting = nm_connection_get_setting_by_name (NM_CONNECTION (self), setting_name); - if (setting) - nm_setting_update_secrets (setting, setting_hash, NULL); - } - } + if (transient_secrets) + nm_connection_update_secrets (NM_CONNECTION (self), NULL, transient_secrets, NULL); nm_settings_connection_recheck_visibility (self); success = TRUE; @@ -1109,31 +1098,30 @@ con_update_cb (NMSettingsConnection *connection, } static void -only_agent_secrets_cb (NMSetting *setting, - const char *key, - const GValue *value, - GParamFlags flags, - gpointer user_data) +secrets_filter_cb (NMSetting *setting, + const char *key, + const GValue *value, + GParamFlags flags, + gpointer user_data) { + NMSettingSecretFlags filter_flags = GPOINTER_TO_UINT (user_data); + NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE; + const char *secret_name = NULL; + GHashTableIter iter; + if (flags & NM_SETTING_PARAM_SECRET) { - NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE; - - /* 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)) { + 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_AGENT_OWNED) + if (!(secret_flags & filter_flags)) nm_setting_vpn_remove_secret (NM_SETTING_VPN (setting), secret_name); } } else { nm_setting_get_secret_flags (setting, key, &secret_flags, NULL); - if (secret_flags != NM_SETTING_SECRET_FLAG_AGENT_OWNED) + if (!(secret_flags & filter_flags)) g_object_set (G_OBJECT (setting), key, NULL, NULL); } } @@ -1148,23 +1136,52 @@ update_auth_cb (NMSettingsConnection *self, { NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self); NMConnection *new_settings = data; - NMConnection *for_agent; + NMConnection *for_agent, *dup; + NMSettingSecretFlags filter_flags; + GHashTable *hash; + GError *local = NULL; if (error) dbus_g_method_return_error (context, error); else { + /* Cache the new secrets since they may get overwritten by the replace + * when transient secrets are copied back. + */ + dup = nm_connection_duplicate (new_settings); + /* Update and commit our settings. */ nm_settings_connection_replace_and_commit (self, new_settings, con_update_cb, context); + /* Copy new agent secrets back to the connection */ + filter_flags = NM_SETTING_SECRET_FLAG_AGENT_OWNED | NM_SETTING_SECRET_FLAG_NOT_SAVED; + nm_connection_for_each_setting_value (dup, + secrets_filter_cb, + GUINT_TO_POINTER (filter_flags)); + hash = nm_connection_to_hash (dup, NM_SETTING_HASH_FLAG_ONLY_SECRETS); + g_object_unref (dup); + + if (hash) { + if (!nm_connection_update_secrets (NM_CONNECTION (self), NULL, hash, &local)) { + nm_log_warn (LOGD_SETTINGS, "Failed to update connection secrets: (%d) %s", + local ? local->code : -1, + local && local->message ? local->message : "(unknown)"); + g_clear_error (&local); + } + g_hash_table_destroy (hash); + } + /* Dupe the connection and clear out non-agent-owned secrets so we can * send the agent-owned ones to agents to be saved. Only send them to * agents of the same UID as the Update() request sender. */ for_agent = nm_connection_duplicate (NM_CONNECTION (self)); - nm_connection_for_each_setting_value (for_agent, only_agent_secrets_cb, NULL); + filter_flags = NM_SETTING_SECRET_FLAG_AGENT_OWNED; + nm_connection_for_each_setting_value (for_agent, + secrets_filter_cb, + GUINT_TO_POINTER (filter_flags)); nm_agent_manager_save_secrets (priv->agent_mgr, for_agent, TRUE, sender_uid); g_object_unref (for_agent); }