// SPDX-License-Identifier: LGPL-2.1+ /* * Copyright (C) 2007 - 2013 Red Hat, Inc. * Copyright (C) 2007 - 2008 Novell, Inc. */ #include "nm-default.h" #include "nm-setting-vpn.h" #include #include "nm-glib-aux/nm-secret-utils.h" #include "nm-utils.h" #include "nm-utils-private.h" #include "nm-setting-private.h" /** * SECTION:nm-setting-vpn * @short_description: Describes connection properties for Virtual Private Networks * * The #NMSettingVpn object is a #NMSetting subclass that describes properties * necessary for connection to Virtual Private Networks. NetworkManager uses * a plugin architecture to allow easier use of new VPN types, and this * setting abstracts the configuration for those plugins. Since the configuration * options are only known to the VPN plugins themselves, the VPN configuration * options are stored as key/value pairs of strings rather than GObject * properties. **/ /*****************************************************************************/ NM_GOBJECT_PROPERTIES_DEFINE (NMSettingVpn, PROP_SERVICE_TYPE, PROP_USER_NAME, PROP_PERSISTENT, PROP_DATA, PROP_SECRETS, PROP_TIMEOUT, ); typedef struct { char *service_type; /* username of the user requesting this connection, thus * it's really only valid for user connections, and it also * should never be saved out to persistent config. */ char *user_name; /* Whether the VPN stays up across link changes, until the user * explicitly disconnects it. */ gboolean persistent; /* The hash table is created at setting object * init time and should not be replaced. It is * a char * -> char * mapping, and both the key * and value are owned by the hash table, and should * be allocated with functions whose value can be * freed with g_free(). Should not contain secrets. */ GHashTable *data; /* The hash table is created at setting object * init time and should not be replaced. It is * a char * -> char * mapping, and both the key * and value are owned by the hash table, and should * be allocated with functions whose value can be * freed with g_free(). Should contain secrets only. */ GHashTable *secrets; /* Timeout for the VPN service to establish the connection */ guint32 timeout; } NMSettingVpnPrivate; G_DEFINE_TYPE (NMSettingVpn, nm_setting_vpn, NM_TYPE_SETTING) #define NM_SETTING_VPN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_SETTING_VPN, NMSettingVpnPrivate)) /*****************************************************************************/ static GHashTable * _ensure_strdict (GHashTable **p_hash, gboolean for_secrets) { if (!*p_hash) { *p_hash = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, for_secrets ? (GDestroyNotify) nm_free_secret : g_free); } return *p_hash; } /*****************************************************************************/ /** * nm_setting_vpn_get_service_type: * @setting: the #NMSettingVpn * * Returns the service name of the VPN, which identifies the specific VPN * plugin that should be used to connect to this VPN. * * Returns: the VPN plugin's service name **/ const char * nm_setting_vpn_get_service_type (NMSettingVpn *setting) { g_return_val_if_fail (NM_IS_SETTING_VPN (setting), NULL); return NM_SETTING_VPN_GET_PRIVATE (setting)->service_type; } /** * nm_setting_vpn_get_user_name: * @setting: the #NMSettingVpn * * Returns: the #NMSettingVpn:user-name property of the setting **/ const char * nm_setting_vpn_get_user_name (NMSettingVpn *setting) { g_return_val_if_fail (NM_IS_SETTING_VPN (setting), NULL); return NM_SETTING_VPN_GET_PRIVATE (setting)->user_name; } /** * nm_setting_vpn_get_persistent: * @setting: the #NMSettingVpn * * Returns: the #NMSettingVpn:persistent property of the setting **/ gboolean nm_setting_vpn_get_persistent (NMSettingVpn *setting) { g_return_val_if_fail (NM_IS_SETTING_VPN (setting), FALSE); return NM_SETTING_VPN_GET_PRIVATE (setting)->persistent; } /** * nm_setting_vpn_get_num_data_items: * @setting: the #NMSettingVpn * * Gets number of key/value pairs of VPN configuration data. * * Returns: the number of VPN plugin specific configuration data items **/ guint32 nm_setting_vpn_get_num_data_items (NMSettingVpn *setting) { g_return_val_if_fail (NM_IS_SETTING_VPN (setting), 0); return nm_g_hash_table_size (NM_SETTING_VPN_GET_PRIVATE (setting)->data); } /** * nm_setting_vpn_add_data_item: * @setting: the #NMSettingVpn * @key: a name that uniquely identifies the given value @item * @item: (allow-none): the value to be referenced by @key * * Establishes a relationship between @key and @item internally in the * setting which may be retrieved later. Should not be used to store passwords * or other secrets, which is what nm_setting_vpn_add_secret() is for. * * Before 1.24, @item must not be %NULL and not an empty string. Since 1.24, * @item can be set to an empty string. It can also be set to %NULL to unset * the key. In that case, the behavior is as if calling nm_setting_vpn_remove_data_item(). **/ void nm_setting_vpn_add_data_item (NMSettingVpn *setting, const char *key, const char *item) { if (!item) { nm_setting_vpn_remove_data_item (setting, key); return; } g_return_if_fail (NM_IS_SETTING_VPN (setting)); g_return_if_fail (key && key[0]); g_hash_table_insert (_ensure_strdict (&NM_SETTING_VPN_GET_PRIVATE (setting)->data, FALSE), g_strdup (key), g_strdup (item)); _notify (setting, PROP_DATA); } /** * nm_setting_vpn_get_data_item: * @setting: the #NMSettingVpn * @key: the name of the data item to retrieve * * Retrieves the data item of a key/value relationship previously established * by nm_setting_vpn_add_data_item(). * * Returns: the data item, if any **/ const char * nm_setting_vpn_get_data_item (NMSettingVpn *setting, const char *key) { g_return_val_if_fail (NM_IS_SETTING_VPN (setting), NULL); g_return_val_if_fail (key && key[0], NULL); return nm_g_hash_table_lookup (NM_SETTING_VPN_GET_PRIVATE (setting)->data, key); } /** * nm_setting_vpn_get_data_keys: * @setting: the #NMSettingVpn * @out_length: (allow-none) (out): the length of the returned array * * Retrieves every data key inside @setting, as an array. * * Returns: (array length=out_length) (transfer container): a * %NULL-terminated array containing each data key or %NULL if * there are no data items. * * Since: 1.12 */ const char ** nm_setting_vpn_get_data_keys (NMSettingVpn *setting, guint *out_length) { g_return_val_if_fail (NM_IS_SETTING_VPN (setting), NULL); return nm_utils_strdict_get_keys (NM_SETTING_VPN_GET_PRIVATE (setting)->data, TRUE, out_length); } /** * nm_setting_vpn_remove_data_item: * @setting: the #NMSettingVpn * @key: the name of the data item to remove * * Deletes a key/value relationship previously established by * nm_setting_vpn_add_data_item(). * * Returns: %TRUE if the data item was found and removed from the internal list, * %FALSE if it was not. **/ gboolean nm_setting_vpn_remove_data_item (NMSettingVpn *setting, const char *key) { g_return_val_if_fail (NM_IS_SETTING_VPN (setting), FALSE); g_return_val_if_fail (key && key[0], FALSE); if (nm_g_hash_table_remove (NM_SETTING_VPN_GET_PRIVATE (setting)->data, key)) { _notify (setting, PROP_DATA); return TRUE; } return FALSE; } static void foreach_item_helper (NMSettingVpn *self, GHashTable **p_hash, NMVpnIterFunc func, gpointer user_data) { gs_unref_object NMSettingVpn *self_keep_alive = NULL; gs_strfreev char **keys = NULL; guint i, len; nm_assert (NM_IS_SETTING_VPN (self)); nm_assert (func); keys = nm_utils_strv_make_deep_copied (nm_utils_strdict_get_keys (*p_hash, TRUE, &len)); if (len == 0u) { nm_assert (!keys); return; } if (len > 1u) self_keep_alive = g_object_ref (self); for (i = 0; i < len; i++) { /* NOTE: note that we call the function with a clone of @key, * not with the actual key from the dictionary. * * The @value on the other hand, is not cloned but retrieved before * invoking @func(). That means, if @func() modifies the setting while * being called, the values are as they currently are, but the * keys (and their order) were pre-determined before starting to * invoke the callbacks. * * The idea is to give some sensible, stable behavior in case the user * modifies the settings. Whether this particular behavior is optimal * is unclear. It's probably a bad idea to modify the settings while * iterating the values. But at least, it's a safe thing to do and we * do something sensible. */ func (keys[i], nm_g_hash_table_lookup (*p_hash, keys[i]), user_data); } } /** * nm_setting_vpn_foreach_data_item: * @setting: a #NMSettingVpn * @func: (scope call): an user provided function * @user_data: data to be passed to @func * * Iterates all data items stored in this setting. It is safe to add, remove, * and modify data items inside @func, though any additions or removals made * during iteration will not be part of the iteration. */ void nm_setting_vpn_foreach_data_item (NMSettingVpn *setting, NMVpnIterFunc func, gpointer user_data) { g_return_if_fail (NM_IS_SETTING_VPN (setting)); g_return_if_fail (func); foreach_item_helper (setting, &NM_SETTING_VPN_GET_PRIVATE (setting)->data, func, user_data); } /** * nm_setting_vpn_get_num_secrets: * @setting: the #NMSettingVpn * * Gets number of VPN plugin specific secrets in the setting. * * Returns: the number of VPN plugin specific secrets **/ guint32 nm_setting_vpn_get_num_secrets (NMSettingVpn *setting) { g_return_val_if_fail (NM_IS_SETTING_VPN (setting), 0); return nm_g_hash_table_size (NM_SETTING_VPN_GET_PRIVATE (setting)->secrets); } /** * nm_setting_vpn_add_secret: * @setting: the #NMSettingVpn * @key: a name that uniquely identifies the given secret @secret * @secret: (allow-none): the secret to be referenced by @key * * Establishes a relationship between @key and @secret internally in the * setting which may be retrieved later. * * Before 1.24, @secret must not be %NULL and not an empty string. Since 1.24, * @secret can be set to an empty string. It can also be set to %NULL to unset * the key. In that case, the behavior is as if calling nm_setting_vpn_remove_secret(). **/ void nm_setting_vpn_add_secret (NMSettingVpn *setting, const char *key, const char *secret) { if (!secret) { nm_setting_vpn_remove_secret (setting, key); return; } g_return_if_fail (NM_IS_SETTING_VPN (setting)); g_return_if_fail (key && key[0]); g_hash_table_insert (_ensure_strdict (&NM_SETTING_VPN_GET_PRIVATE (setting)->secrets, TRUE), g_strdup (key), g_strdup (secret)); _notify (setting, PROP_SECRETS); } /** * nm_setting_vpn_get_secret: * @setting: the #NMSettingVpn * @key: the name of the secret to retrieve * * Retrieves the secret of a key/value relationship previously established * by nm_setting_vpn_add_secret(). * * Returns: the secret, if any **/ const char * nm_setting_vpn_get_secret (NMSettingVpn *setting, const char *key) { g_return_val_if_fail (NM_IS_SETTING_VPN (setting), NULL); g_return_val_if_fail (key && key[0], NULL); return nm_g_hash_table_lookup (NM_SETTING_VPN_GET_PRIVATE (setting)->secrets, key); } /** * nm_setting_vpn_get_secret_keys: * @setting: the #NMSettingVpn * @out_length: (allow-none) (out): the length of the returned array * * Retrieves every secret key inside @setting, as an array. * * Returns: (array length=out_length) (transfer container): a * %NULL-terminated array containing each secret key or %NULL if * there are no secrets. * * Since: 1.12 */ const char ** nm_setting_vpn_get_secret_keys (NMSettingVpn *setting, guint *out_length) { g_return_val_if_fail (NM_IS_SETTING_VPN (setting), NULL); return nm_utils_strdict_get_keys (NM_SETTING_VPN_GET_PRIVATE (setting)->secrets, TRUE, out_length); } /** * nm_setting_vpn_remove_secret: * @setting: the #NMSettingVpn * @key: the name of the secret to remove * * Deletes a key/value relationship previously established by * nm_setting_vpn_add_secret(). * * Returns: %TRUE if the secret was found and removed from the internal list, * %FALSE if it was not. **/ gboolean nm_setting_vpn_remove_secret (NMSettingVpn *setting, const char *key) { g_return_val_if_fail (NM_IS_SETTING_VPN (setting), FALSE); g_return_val_if_fail (key && key[0], FALSE); if (nm_g_hash_table_remove (NM_SETTING_VPN_GET_PRIVATE (setting)->secrets, key)) { _notify (setting, PROP_SECRETS); return TRUE; } return FALSE; } /** * nm_setting_vpn_foreach_secret: * @setting: a #NMSettingVpn * @func: (scope call): an user provided function * @user_data: data to be passed to @func * * Iterates all secrets stored in this setting. It is safe to add, remove, * and modify secrets inside @func, though any additions or removals made during * iteration will not be part of the iteration. */ void nm_setting_vpn_foreach_secret (NMSettingVpn *setting, NMVpnIterFunc func, gpointer user_data) { g_return_if_fail (NM_IS_SETTING_VPN (setting)); g_return_if_fail (func); foreach_item_helper (setting, &NM_SETTING_VPN_GET_PRIVATE (setting)->secrets, func, user_data); } static gboolean aggregate (NMSetting *setting, int type_i, gpointer arg) { NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE (setting); NMConnectionAggregateType type = type_i; NMSettingSecretFlags secret_flags; const char *key_name; GHashTableIter iter; switch (type) { case NM_CONNECTION_AGGREGATE_ANY_SECRETS: if (nm_g_hash_table_size (priv->secrets) > 0u) { *((gboolean *) arg) = TRUE; return TRUE; } return FALSE; case NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS: if (priv->secrets) { g_hash_table_iter_init (&iter, priv->secrets); while (g_hash_table_iter_next (&iter, (gpointer *) &key_name, NULL)) { if (!nm_setting_get_secret_flags (NM_SETTING (setting), key_name, &secret_flags, NULL)) nm_assert_not_reached (); if (secret_flags == NM_SETTING_SECRET_FLAG_NONE) { *((gboolean *) arg) = TRUE; return TRUE; } } } /* OK, we have no secrets with system-secret flags. * But do we have any secret-flags (without secrets) that indicate system secrets? */ if (priv->data) { g_hash_table_iter_init (&iter, priv->data); while (g_hash_table_iter_next (&iter, (gpointer *) &key_name, NULL)) { gs_free char *secret_name = NULL; if (!NM_STR_HAS_SUFFIX (key_name, "-flags")) continue; secret_name = g_strndup (key_name, strlen (key_name) - NM_STRLEN ("-flags")); if (secret_name[0] == '\0') continue; if (!nm_setting_get_secret_flags (NM_SETTING (setting), secret_name, &secret_flags, NULL)) nm_assert_not_reached (); if (secret_flags == NM_SETTING_SECRET_FLAG_NONE) { *((gboolean *) arg) = TRUE; return TRUE; } } } return FALSE; } g_return_val_if_reached (FALSE); } /** * nm_setting_vpn_get_timeout: * @setting: the #NMSettingVpn * * Returns: the #NMSettingVpn:timeout property of the setting * * Since: 1.2 **/ guint32 nm_setting_vpn_get_timeout (NMSettingVpn *setting) { g_return_val_if_fail (NM_IS_SETTING_VPN (setting), 0); return NM_SETTING_VPN_GET_PRIVATE (setting)->timeout; } static gboolean verify (NMSetting *setting, NMConnection *connection, GError **error) { NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE (setting); NMSettingConnection *s_con; if (!priv->service_type) { g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_PROPERTY, _("property is missing")); g_prefix_error (error, "%s.%s: ", NM_SETTING_VPN_SETTING_NAME, NM_SETTING_VPN_SERVICE_TYPE); return FALSE; } if (!priv->service_type[0]) { g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("property is empty")); g_prefix_error (error, "%s.%s: ", NM_SETTING_VPN_SETTING_NAME, NM_SETTING_VPN_SERVICE_TYPE); return FALSE; } /* default username can be NULL, but can't be zero-length */ if ( priv->user_name && !priv->user_name[0]) { g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("property is empty")); g_prefix_error (error, "%s.%s: ", NM_SETTING_VPN_SETTING_NAME, NM_SETTING_VPN_USER_NAME); return FALSE; } if ( connection && (s_con = nm_connection_get_setting_connection (connection)) && nm_setting_connection_get_multi_connect (s_con) != NM_CONNECTION_MULTI_CONNECT_DEFAULT) { g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("cannot set connection.multi-connect for VPN setting")); return FALSE; } return TRUE; } static NMSettingUpdateSecretResult update_secret_string (NMSetting *setting, const char *key, const char *value, GError **error) { NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE (setting); g_return_val_if_fail (key && key[0], NM_SETTING_UPDATE_SECRET_ERROR); g_return_val_if_fail (value, NM_SETTING_UPDATE_SECRET_ERROR); if (nm_streq0 (nm_g_hash_table_lookup (priv->secrets, key), value)) return NM_SETTING_UPDATE_SECRET_SUCCESS_UNCHANGED; g_hash_table_insert (_ensure_strdict (&priv->secrets, TRUE), g_strdup (key), g_strdup (value)); return NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED; } static NMSettingUpdateSecretResult update_secret_dict (NMSetting *setting, GVariant *secrets, GError **error) { NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE (setting); GVariantIter iter; const char *name, *value; NMSettingUpdateSecretResult result = NM_SETTING_UPDATE_SECRET_SUCCESS_UNCHANGED; g_return_val_if_fail (secrets != NULL, NM_SETTING_UPDATE_SECRET_ERROR); /* Make sure the items are valid */ g_variant_iter_init (&iter, secrets); while (g_variant_iter_next (&iter, "{&s&s}", &name, &value)) { if (!name[0]) { g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING, _("setting contained a secret with an empty name")); g_prefix_error (error, "%s: ", NM_SETTING_VPN_SETTING_NAME); return NM_SETTING_UPDATE_SECRET_ERROR; } } /* Now add the items to the settings' secrets list */ g_variant_iter_init (&iter, secrets); while (g_variant_iter_next (&iter, "{&s&s}", &name, &value)) { if (nm_streq0 (nm_g_hash_table_lookup (priv->secrets, name), value)) continue; g_hash_table_insert (_ensure_strdict (&priv->secrets, TRUE), g_strdup (name), g_strdup (value)); result = NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED; } return result; } static int update_one_secret (NMSetting *setting, const char *key, GVariant *value, GError **error) { NMSettingUpdateSecretResult success = NM_SETTING_UPDATE_SECRET_ERROR; g_return_val_if_fail (key != NULL, NM_SETTING_UPDATE_SECRET_ERROR); g_return_val_if_fail (value != NULL, NM_SETTING_UPDATE_SECRET_ERROR); if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) { /* Passing the string properties individually isn't correct, and won't * produce the correct result, but for some reason that's how it used * to be done. So even though it's not correct, keep the code around * for compatibility's sake. */ success = update_secret_string (setting, key, g_variant_get_string (value, NULL), error); } else if (g_variant_is_of_type (value, G_VARIANT_TYPE ("a{ss}"))) { if (!nm_streq (key, NM_SETTING_VPN_SECRETS)) { g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET, _("not a secret property")); g_prefix_error (error, "%s.%s ", NM_SETTING_VPN_SETTING_NAME, key); } else success = update_secret_dict (setting, value, error); } else { g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("secret is not of correct type")); g_prefix_error (error, "%s.%s: ", NM_SETTING_VPN_SETTING_NAME, key); } if (success == NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED) _notify (NM_SETTING_VPN (setting), PROP_SECRETS); return success; } static void for_each_secret (NMSetting *setting, const char *secret_name, GVariant *val, gboolean remove_non_secrets, _NMConnectionForEachSecretFunc callback, gpointer callback_data, GVariantBuilder *setting_builder) { GVariantBuilder vpn_secrets_builder; GVariantIter vpn_secrets_iter; const char *vpn_secret_name; const char *secret; if (!nm_streq (secret_name, NM_SETTING_VPN_SECRETS)) { NM_SETTING_CLASS (nm_setting_vpn_parent_class)->for_each_secret (setting, secret_name, val, remove_non_secrets, callback, callback_data, setting_builder); return; } if (!g_variant_is_of_type (val, G_VARIANT_TYPE ("a{ss}"))) { /* invalid type. Silently ignore the secrets as we cannot find out the * secret-flags. */ return; } /* Iterate through each secret from the VPN dict in the overall secrets dict */ g_variant_builder_init (&vpn_secrets_builder, G_VARIANT_TYPE ("a{ss}")); g_variant_iter_init (&vpn_secrets_iter, val); while (g_variant_iter_next (&vpn_secrets_iter, "{&s&s}", &vpn_secret_name, &secret)) { NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE; /* we ignore the return value of get_secret_flags. The function may determine * that this is not a secret, based on having not secret-flags and no secrets. * But we have the secret at hand. We know it would be a valid secret, if we * only add it to the VPN settings. */ nm_setting_get_secret_flags (setting, vpn_secret_name, &secret_flags, NULL); if (callback (secret_flags, callback_data)) g_variant_builder_add (&vpn_secrets_builder, "{ss}", vpn_secret_name, secret); } g_variant_builder_add (setting_builder, "{sv}", secret_name, g_variant_builder_end (&vpn_secrets_builder)); } static gboolean get_secret_flags (NMSetting *setting, const char *secret_name, NMSettingSecretFlags *out_flags, GError **error) { NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE (setting); gs_free char *flags_key_free = NULL; const char *flags_key; const char *flags_val; gint64 i64; nm_assert (secret_name); if (!secret_name[0]) { g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET, _("secret name cannot be empty")); return FALSE; } flags_key = nm_construct_name_a ("%s-flags", secret_name, &flags_key_free); if ( !priv->data || !g_hash_table_lookup_extended (priv->data, flags_key, NULL, (gpointer *) &flags_val)) { NM_SET_OUT (out_flags, NM_SETTING_SECRET_FLAG_NONE); /* having no secret flag for the secret is fine, as long as there * is the secret itself... */ if (!nm_g_hash_table_lookup (priv->secrets, secret_name)) { g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET, _("secret flags property not found")); g_prefix_error (error, "%s.%s: ", NM_SETTING_VPN_SETTING_NAME, flags_key); return FALSE; } return TRUE; } i64 = _nm_utils_ascii_str_to_int64 (flags_val, 10, 0, NM_SETTING_SECRET_FLAG_ALL, -1); if ( i64 == -1 || !_nm_setting_secret_flags_valid (i64)) { /* The flags keys is set to an unexpected value. That is a configuration * error. Note that keys named "*-flags" are reserved for secrets. The user * must not use this for anything but secret flags. Hence, we cannot fail * to read the secret, we pretend that the secret flag is set to the default * NM_SETTING_SECRET_FLAG_NONE. */ NM_SET_OUT (out_flags, NM_SETTING_SECRET_FLAG_NONE); return TRUE; } NM_SET_OUT (out_flags, (NMSettingSecretFlags) i64); return TRUE; } static gboolean set_secret_flags (NMSetting *setting, const char *secret_name, NMSettingSecretFlags flags, GError **error) { nm_assert (secret_name); if (!secret_name[0]) { g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET, _("secret name cannot be empty")); return FALSE; } g_hash_table_insert (_ensure_strdict (&NM_SETTING_VPN_GET_PRIVATE (setting)->data, FALSE), g_strdup_printf ("%s-flags", secret_name), g_strdup_printf ("%u", flags)); _notify (NM_SETTING_VPN (setting), PROP_SECRETS); return TRUE; } static GPtrArray * need_secrets (NMSetting *setting) { /* Assume that VPN connections need secrets since they almost always will */ return g_ptr_array_sized_new (1); } static NMTernary compare_property_secrets (NMSettingVpn *a, NMSettingVpn *b, NMSettingCompareFlags flags) { GHashTableIter iter; const char *key, *val; int run; if (NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_FUZZY)) return NM_TERNARY_DEFAULT; if (NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS)) return NM_TERNARY_DEFAULT; if (!b) return TRUE; for (run = 0; run < 2; run++) { NMSettingVpn *current_a = (run == 0) ? a : b; NMSettingVpn *current_b = (run == 0) ? b : a; NMSettingVpnPrivate *priv_a = NM_SETTING_VPN_GET_PRIVATE (current_a); if (!priv_a->secrets) continue; g_hash_table_iter_init (&iter, priv_a->secrets); while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &val)) { if (nm_streq0 (val, nm_setting_vpn_get_secret (current_b, key))) continue; if (!_nm_setting_should_compare_secret_property (NM_SETTING (current_a), NM_SETTING (current_b), key, flags)) continue; return FALSE; } } return TRUE; } static NMTernary compare_property (const NMSettInfoSetting *sett_info, guint property_idx, NMConnection *con_a, NMSetting *set_a, NMConnection *con_b, NMSetting *set_b, NMSettingCompareFlags flags) { if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_VPN_SECRETS)) { if (NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)) return NM_TERNARY_DEFAULT; return compare_property_secrets (NM_SETTING_VPN (set_a), NM_SETTING_VPN (set_b), flags); } return NM_SETTING_CLASS (nm_setting_vpn_parent_class)->compare_property (sett_info, property_idx, con_a, set_a, con_b, set_b, flags); } static gboolean clear_secrets (const NMSettInfoSetting *sett_info, guint property_idx, NMSetting *setting, NMSettingClearSecretsWithFlagsFn func, gpointer user_data) { NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE (setting); GParamSpec *prop_spec = sett_info->property_infos[property_idx].param_spec; GHashTableIter iter; const char *secret; gboolean changed = TRUE; if ( !prop_spec || !NM_FLAGS_HAS (prop_spec->flags, NM_SETTING_PARAM_SECRET)) return FALSE; nm_assert (nm_streq (prop_spec->name, NM_SETTING_VPN_SECRETS)); if (!priv->secrets) return FALSE; g_hash_table_iter_init (&iter, priv->secrets); while (g_hash_table_iter_next (&iter, (gpointer) &secret, NULL)) { if (func) { NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE; if (!nm_setting_get_secret_flags (setting, secret, &flags, NULL)) nm_assert_not_reached (); if (!func (setting, secret, flags, user_data)) continue; } else nm_assert (nm_setting_get_secret_flags (setting, secret, NULL, NULL)); g_hash_table_iter_remove (&iter); changed = TRUE; } if (changed) _notify (NM_SETTING_VPN (setting), PROP_SECRETS); return changed; } static gboolean vpn_secrets_from_dbus (NMSetting *setting, GVariant *connection_dict, const char *property, GVariant *value, NMSettingParseFlags parse_flags, GError **error) { NMSettingVpn *self = NM_SETTING_VPN (setting); NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE (self); gs_unref_hashtable GHashTable *hash_free = NULL; GVariantIter iter; const char *key; const char *val; hash_free = g_steal_pointer (&priv->secrets); g_variant_iter_init (&iter, value); while (g_variant_iter_next (&iter, "{&s&s}", &key, &val)) { if (!key[0]) continue; g_hash_table_insert (_ensure_strdict (&priv->secrets, TRUE), g_strdup (key), g_strdup (val)); } _notify (self, PROP_SECRETS); return TRUE; } static GVariant * vpn_secrets_to_dbus (const NMSettInfoSetting *sett_info, guint property_idx, NMConnection *connection, NMSetting *setting, NMConnectionSerializationFlags flags, const NMConnectionSerializationOptions *options) { NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE (setting); GVariantBuilder builder; gs_free const char **keys = NULL; guint i, len; if (NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_NO_SECRETS)) return NULL; g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}")); keys = nm_utils_strdict_get_keys (priv->secrets, TRUE, &len); for (i = 0; i < len; i++) { const char *key = keys[i]; NMSettingSecretFlags secret_flags; if (NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED)) { if ( !nm_setting_get_secret_flags (setting, key, &secret_flags, NULL) || !NM_FLAGS_HAS (secret_flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED)) continue; } g_variant_builder_add (&builder, "{ss}", key, g_hash_table_lookup (priv->secrets, key)); } return g_variant_builder_end (&builder); } /*****************************************************************************/ static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { NMSettingVpn *setting = NM_SETTING_VPN (object); NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE (setting); switch (prop_id) { case PROP_SERVICE_TYPE: g_value_set_string (value, nm_setting_vpn_get_service_type (setting)); break; case PROP_USER_NAME: g_value_set_string (value, nm_setting_vpn_get_user_name (setting)); break; case PROP_PERSISTENT: g_value_set_boolean (value, priv->persistent); break; case PROP_DATA: g_value_take_boxed (value, _nm_utils_copy_strdict (priv->data)); break; case PROP_SECRETS: g_value_take_boxed (value, _nm_utils_copy_strdict (priv->secrets)); break; case PROP_TIMEOUT: g_value_set_uint (value, nm_setting_vpn_get_timeout (setting)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE (object); switch (prop_id) { case PROP_SERVICE_TYPE: g_free (priv->service_type); priv->service_type = g_value_dup_string (value); break; case PROP_USER_NAME: g_free (priv->user_name); priv->user_name = g_value_dup_string (value); break; case PROP_PERSISTENT: priv->persistent = g_value_get_boolean (value); break; case PROP_DATA: case PROP_SECRETS: { gs_unref_hashtable GHashTable *hash_free = NULL; GHashTable *src_hash = g_value_get_boxed (value); GHashTable **p_hash; const gboolean is_secrets = (prop_id == PROP_SECRETS); if (is_secrets) p_hash = &priv->secrets; else p_hash = &priv->data; hash_free = g_steal_pointer (p_hash); if ( src_hash && g_hash_table_size (src_hash) > 0) { GHashTableIter iter; const char *key; const char *val; g_hash_table_iter_init (&iter, src_hash); while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &val)) { if ( !key || !key[0] || !val) { /* NULL keys/values and empty key are not allowed. Usually, we would reject them in verify(), but * then our nm_setting_vpn_remove_data_item() also doesn't allow empty keys. So, if we failed * it in verify(), it would be only fixable by setting PROP_DATA again. Instead, * silently ignore them. */ continue; } g_hash_table_insert (_ensure_strdict (p_hash, is_secrets), g_strdup (key), g_strdup (val)); } } } break; case PROP_TIMEOUT: priv->timeout = g_value_get_uint (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } /*****************************************************************************/ static void nm_setting_vpn_init (NMSettingVpn *setting) { } /** * nm_setting_vpn_new: * * Creates a new #NMSettingVpn object with default values. * * Returns: (transfer full): the new empty #NMSettingVpn object **/ NMSetting * nm_setting_vpn_new (void) { return (NMSetting *) g_object_new (NM_TYPE_SETTING_VPN, NULL); } static void finalize (GObject *object) { NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE (object); g_free (priv->service_type); g_free (priv->user_name); if (priv->data) g_hash_table_unref (priv->data); if (priv->secrets) g_hash_table_unref (priv->secrets); G_OBJECT_CLASS (nm_setting_vpn_parent_class)->finalize (object); } static void nm_setting_vpn_class_init (NMSettingVpnClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); NMSettingClass *setting_class = NM_SETTING_CLASS (klass); GArray *properties_override = _nm_sett_info_property_override_create_array (); g_type_class_add_private (klass, sizeof (NMSettingVpnPrivate)); object_class->get_property = get_property; object_class->set_property = set_property; object_class->finalize = finalize; setting_class->verify = verify; setting_class->update_one_secret = update_one_secret; setting_class->for_each_secret = for_each_secret; setting_class->get_secret_flags = get_secret_flags; setting_class->set_secret_flags = set_secret_flags; setting_class->need_secrets = need_secrets; setting_class->compare_property = compare_property; setting_class->clear_secrets = clear_secrets; setting_class->aggregate = aggregate; /** * NMSettingVpn:service-type: * * D-Bus service name of the VPN plugin that this setting uses to connect to * its network. i.e. org.freedesktop.NetworkManager.vpnc for the vpnc * plugin. **/ obj_properties[PROP_SERVICE_TYPE] = g_param_spec_string (NM_SETTING_VPN_SERVICE_TYPE, "", "", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * NMSettingVpn:user-name: * * If the VPN connection requires a user name for authentication, that name * should be provided here. If the connection is available to more than one * user, and the VPN requires each user to supply a different name, then * leave this property empty. If this property is empty, NetworkManager * will automatically supply the username of the user which requested the * VPN connection. **/ obj_properties[PROP_USER_NAME] = g_param_spec_string (NM_SETTING_VPN_USER_NAME, "", "", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * NMSettingVpn:persistent: * * If the VPN service supports persistence, and this property is %TRUE, * the VPN will attempt to stay connected across link changes and outages, * until explicitly disconnected. **/ obj_properties[PROP_PERSISTENT] = g_param_spec_boolean (NM_SETTING_VPN_PERSISTENT, "", "", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * NMSettingVpn:data: (type GHashTable(utf8,utf8)): * * Dictionary of key/value pairs of VPN plugin specific data. Both keys and * values must be strings. **/ /* ---keyfile--- * property: data * variable: separate variables named after keys of the dictionary * description: The keys of the data dictionary are used as variable names directly * under [vpn] section. * example: remote=ovpn.corp.com cipher=AES-256-CBC username=joe * ---end--- */ obj_properties[PROP_DATA] = g_param_spec_boxed (NM_SETTING_VPN_DATA, "", "", G_TYPE_HASH_TABLE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); _nm_properties_override_gobj (properties_override, obj_properties[PROP_DATA], &nm_sett_info_propert_type_strdict); /** * NMSettingVpn:secrets: (type GHashTable(utf8,utf8)): * * Dictionary of key/value pairs of VPN plugin specific secrets like * passwords or private keys. Both keys and values must be strings. **/ /* ---keyfile--- * property: secrets * variable: separate variables named after keys of the dictionary * description: The keys of the secrets dictionary are used as variable names directly * under [vpn-secrets] section. * example: password=Popocatepetl * ---end--- */ obj_properties[PROP_SECRETS] = g_param_spec_boxed (NM_SETTING_VPN_SECRETS, "", "", G_TYPE_HASH_TABLE, G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | NM_SETTING_PARAM_TO_DBUS_IGNORE_FLAGS | G_PARAM_STATIC_STRINGS); _nm_properties_override_gobj (properties_override, obj_properties[PROP_SECRETS], NM_SETT_INFO_PROPERT_TYPE ( .dbus_type = NM_G_VARIANT_TYPE ("a{ss}"), .to_dbus_fcn = vpn_secrets_to_dbus, .from_dbus_fcn = vpn_secrets_from_dbus, )); /** * NMSettingVpn:timeout: * * Timeout for the VPN service to establish the connection. Some services * may take quite a long time to connect. * Value of 0 means a default timeout, which is 60 seconds (unless overridden * by vpn.timeout in configuration file). Values greater than zero mean * timeout in seconds. * * Since: 1.2 **/ obj_properties[PROP_TIMEOUT] = g_param_spec_uint (NM_SETTING_VPN_TIMEOUT, "", "", 0, G_MAXUINT32, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties); _nm_setting_class_commit_full (setting_class, NM_META_SETTING_TYPE_VPN, NULL, properties_override); }