NetworkManager/libnm-core/nm-setting-vpn.c
Thomas Haller 88071abb43
all: unify comment style for SPDX-License-Identifier tag
Our coding style recommends C style comments (/* */) instead of C++
(//). Also, systemd (which we partly fork) uses C style comments for
the SPDX-License-Identifier.

Unify the style.

  $ sed -i '1 s#// SPDX-License-Identifier: \([^ ]\+\)$#/* SPDX-License-Identifier: \1 */#' -- $(git ls-files -- '*.[hc]' '*.[hc]pp')
2020-09-29 16:50:53 +02:00

1234 lines
43 KiB
C

/* 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 <stdlib.h>
#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);
}