diff options
Diffstat (limited to 'src/libnm-core-impl/nm-setting-wireguard.c')
| -rw-r--r-- | src/libnm-core-impl/nm-setting-wireguard.c | 2608 |
1 files changed, 2608 insertions, 0 deletions
diff --git a/src/libnm-core-impl/nm-setting-wireguard.c b/src/libnm-core-impl/nm-setting-wireguard.c new file mode 100644 index 00000000..3290ff56 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-wireguard.c @@ -0,0 +1,2608 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 - 2019 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-wireguard.h" + +#include "nm-setting-private.h" +#include "nm-utils-private.h" +#include "nm-connection-private.h" +#include "libnm-glib-aux/nm-secret-utils.h" + +/*****************************************************************************/ + +/** + * SECTION:nm-setting-wireguard + * @short_description: Describes connection properties for wireguard related options + * + * The #NMSettingWireGuard object is a #NMSetting subclass that contains settings + * for configuring WireGuard. + **/ + +/*****************************************************************************/ + +static NMWireGuardPeer *_wireguard_peer_dup(const NMWireGuardPeer *self); + +G_DEFINE_BOXED_TYPE(NMWireGuardPeer, + nm_wireguard_peer, + _wireguard_peer_dup, + nm_wireguard_peer_unref) + +/* NMWireGuardPeer can also track invalid allowed-ip settings, and only reject + * them later during is_valid(). Such values are marked by a leading 'X' character + * in the @allowed_ips. It is expected, that such values are the exception, and + * commonly not present. */ +#define ALLOWED_IP_INVALID_X 'X' +#define ALLOWED_IP_INVALID_X_STR "X" + +/** + * NMWireGuardPeer: + * + * The settings of one WireGuard peer. + * + * Since: 1.16 + */ +struct _NMWireGuardPeer { + NMSockAddrEndpoint * endpoint; + char * public_key; + char * preshared_key; + GPtrArray * allowed_ips; + guint refcount; + NMSettingSecretFlags preshared_key_flags; + guint16 persistent_keepalive; + bool public_key_valid : 1; + bool preshared_key_valid : 1; + bool sealed : 1; +}; + +static gboolean +NM_IS_WIREGUARD_PEER(const NMWireGuardPeer *self, gboolean also_sealed) +{ + return self && self->refcount > 0 && (also_sealed || !self->sealed); +} + +/** + * nm_wireguard_peer_new: + * + * Returns: (transfer full): a new, default, unsealed #NMWireGuardPeer instance. + * + * Since: 1.16 + */ +NMWireGuardPeer * +nm_wireguard_peer_new(void) +{ + NMWireGuardPeer *self; + + self = g_slice_new(NMWireGuardPeer); + *self = (NMWireGuardPeer){ + .refcount = 1, + .preshared_key_flags = NM_SETTING_SECRET_FLAG_NOT_REQUIRED, + }; + return self; +} + +/** + * nm_wireguard_peer_new_clone: + * @self: the #NMWireGuardPeer instance to copy. + * @with_secrets: if %TRUE, the preshared-key secrets are copied + * as well. Otherwise, they will be removed. + * + * Returns: (transfer full): a clone of @self. This instance + * is always unsealed. + * + * Since: 1.16 + */ +NMWireGuardPeer * +nm_wireguard_peer_new_clone(const NMWireGuardPeer *self, gboolean with_secrets) +{ + NMWireGuardPeer *new; + guint i; + + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL); + + new = g_slice_new(NMWireGuardPeer); + *new = (NMWireGuardPeer){ + .refcount = 1, + .public_key = g_strdup(self->public_key), + .public_key_valid = self->public_key_valid, + .preshared_key = with_secrets ? g_strdup(self->preshared_key) : NULL, + .preshared_key_valid = self->preshared_key_valid, + .preshared_key_flags = self->preshared_key_flags, + .endpoint = nm_sock_addr_endpoint_ref(self->endpoint), + .persistent_keepalive = self->persistent_keepalive, + }; + if (self->allowed_ips && self->allowed_ips->len > 0) { + new->allowed_ips = g_ptr_array_new_full(self->allowed_ips->len, g_free); + for (i = 0; i < self->allowed_ips->len; i++) { + g_ptr_array_add(new->allowed_ips, g_strdup(self->allowed_ips->pdata[i])); + } + } + return new; +} + +/** + * nm_wireguard_peer_ref: + * @self: (allow-none): the #NMWireGuardPeer instance + * + * This is not thread-safe. + * + * Returns: returns the input argument @self after incrementing + * the reference count. + * + * Since: 1.16 + */ +NMWireGuardPeer * +nm_wireguard_peer_ref(NMWireGuardPeer *self) +{ + if (!self) + return NULL; + + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL); + + nm_assert(self->refcount < G_MAXUINT); + + self->refcount++; + return self; +} + +/** + * nm_wireguard_peer_unref: + * @self: (allow-none): the #NMWireGuardPeer instance + * + * Drop a reference to @self. If the last reference is dropped, + * the instance is freed and all associate data released. + * + * This is not thread-safe. + * + * Since: 1.16 + */ +void +nm_wireguard_peer_unref(NMWireGuardPeer *self) +{ + if (!self) + return; + + g_return_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE)); + + if (--self->refcount > 0) + return; + + nm_sock_addr_endpoint_unref(self->endpoint); + if (self->allowed_ips) + g_ptr_array_unref(self->allowed_ips); + g_free(self->public_key); + nm_free_secret(self->preshared_key); + g_slice_free(NMWireGuardPeer, self); +} + +/** + * _wireguard_peer_dup: + * @self: the #NMWireGuardPeer instance + * + * Duplicates the #NMWireGuardPeer instance. Note that if @self + * is already sealed, this increments the reference count and + * returns it. If the instance is still unsealed, it is copied. + * + * Returns: (transfer full): a duplicate of @self, or (if the + * instance is sealed and thus immutable) a reference to @self. + * As such, the instance will be sealed if and only if @self is + * sealed. + */ +static NMWireGuardPeer * +_wireguard_peer_dup(const NMWireGuardPeer *self) +{ + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL); + + if (self->sealed) + return nm_wireguard_peer_ref((NMWireGuardPeer *) self); + return nm_wireguard_peer_new_clone(self, TRUE); +} + +/** + * nm_wireguard_peer_seal: + * @self: the #NMWireGuardPeer instance + * + * Seal the #NMWireGuardPeer instance. Afterwards, it is a bug + * to call all functions that modify the instance (except ref/unref). + * A sealed instance cannot be unsealed again, but you can create + * an unsealed copy with nm_wireguard_peer_new_clone(). + * + * Since: 1.16 + */ +void +nm_wireguard_peer_seal(NMWireGuardPeer *self) +{ + g_return_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE)); + + self->sealed = TRUE; + + if (self->allowed_ips) { + if (self->allowed_ips->len == 0) + nm_clear_pointer(&self->allowed_ips, g_ptr_array_unref); + } +} + +/** + * nm_wireguard_peer_is_sealed: + * @self: the #NMWireGuardPeer instance + * + * Returns: whether @self is sealed or not. + * + * Since: 1.16 + */ +gboolean +nm_wireguard_peer_is_sealed(const NMWireGuardPeer *self) +{ + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), FALSE); + + return self->sealed; +} + +/** + * nm_wireguard_peer_get_public_key: + * @self: the #NMWireGuardPeer instance + * + * Returns: (transfer none): the public key or %NULL if unset. + * + * Since: 1.16 + */ +const char * +nm_wireguard_peer_get_public_key(const NMWireGuardPeer *self) +{ + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL); + + return self->public_key; +} + +/** + * nm_wireguard_peer_set_public_key: + * @self: the unsealed #NMWireGuardPeer instance + * @public_key: (allow-none) (transfer none): the new public + * key or %NULL to clear the public key. + * @accept_invalid: if %TRUE and @public_key is not %NULL and + * invalid, then do not modify the instance. + * + * Reset the public key. Note that if the public key is valid, it + * will be normalized (which may or may not modify the set value). + * + * It is a bug trying to modify a sealed #NMWireGuardPeer instance. + * + * Returns: %TRUE if the key was valid or %NULL. Returns + * %FALSE for invalid keys. Depending on @accept_invalid + * will an invalid key be set or not. + * + * Since: 1.16 + */ +gboolean +nm_wireguard_peer_set_public_key(NMWireGuardPeer *self, + const char * public_key, + gboolean accept_invalid) +{ + char * public_key_normalized = NULL; + gboolean is_valid; + + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE), FALSE); + + if (!public_key) { + nm_clear_g_free(&self->public_key); + return TRUE; + } + + is_valid = nm_utils_base64secret_normalize(public_key, + NM_WIREGUARD_PUBLIC_KEY_LEN, + &public_key_normalized); + nm_assert(is_valid == (public_key_normalized != NULL)); + + if (!is_valid && !accept_invalid) + return FALSE; + + self->public_key_valid = is_valid; + g_free(self->public_key); + self->public_key = public_key_normalized ?: g_strdup(public_key); + return is_valid; +} + +void +_nm_wireguard_peer_set_public_key_bin(NMWireGuardPeer *self, + const guint8 public_key[static NM_WIREGUARD_PUBLIC_KEY_LEN]) +{ + g_return_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE)); + + nm_clear_g_free(&self->public_key); + + if (!public_key) + return; + + self->public_key = g_base64_encode(public_key, NM_WIREGUARD_PUBLIC_KEY_LEN); + self->public_key_valid = TRUE; +} + +/** + * nm_wireguard_peer_get_preshared_key: + * @self: the #NMWireGuardPeer instance + * + * Returns: (transfer none): the preshared key or %NULL if unset. + * + * Since: 1.16 + */ +const char * +nm_wireguard_peer_get_preshared_key(const NMWireGuardPeer *self) +{ + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL); + + return self->preshared_key; +} + +/** + * nm_wireguard_peer_set_preshared_key: + * @self: the unsealed #NMWireGuardPeer instance + * @preshared_key: (allow-none) (transfer none): the new preshared + * key or %NULL to clear the preshared key. + * @accept_invalid: whether to allow setting the key to an invalid + * value. If %FALSE, @self is unchanged if the key is invalid + * and if %FALSE is returned. + * + * Reset the preshared key. Note that if the preshared key is valid, it + * will be normalized (which may or may not modify the set value). + * + * Note that the preshared-key is a secret and consequently has corresponding + * preshared-key-flags property. This is so that secrets can be optional + * and requested on demand from a secret-agent. Also, an invalid preshared-key + * may optionally cause nm_wireguard_peer_is_valid() to fail or it may + * be accepted. + * + * It is a bug trying to modify a sealed #NMWireGuardPeer instance. + * + * Returns: %TRUE if the preshared-key is valid, otherwise %FALSE. + * %NULL is considered a valid value. + * If the key is invalid, it depends on @accept_invalid whether the + * previous value was reset. + * + * Since: 1.16 + */ +gboolean +nm_wireguard_peer_set_preshared_key(NMWireGuardPeer *self, + const char * preshared_key, + gboolean accept_invalid) +{ + char * preshared_key_normalized = NULL; + gboolean is_valid; + + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE), FALSE); + + if (!preshared_key) { + nm_clear_pointer(&self->preshared_key, nm_free_secret); + return TRUE; + } + + is_valid = nm_utils_base64secret_normalize(preshared_key, + NM_WIREGUARD_SYMMETRIC_KEY_LEN, + &preshared_key_normalized); + nm_assert(is_valid == (preshared_key_normalized != NULL)); + + if (!is_valid && !accept_invalid) + return FALSE; + + self->preshared_key_valid = is_valid; + nm_free_secret(self->preshared_key); + self->preshared_key = preshared_key_normalized ?: g_strdup(preshared_key); + return is_valid; +} + +/** + * nm_wireguard_peer_get_preshared_key_flags: + * @self: the #NMWireGuardPeer instance + * + * Returns: get the secret flags for the preshared-key. + * + * Since: 1.16 + */ +NMSettingSecretFlags +nm_wireguard_peer_get_preshared_key_flags(const NMWireGuardPeer *self) +{ + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), 0); + + return self->preshared_key_flags; +} + +/** + * nm_wireguard_peer_set_preshared_key_flags: + * @self: the unsealed #NMWireGuardPeer instance + * @preshared_key_flags: the secret flags to set. + * + * It is a bug trying to modify a sealed #NMWireGuardPeer instance. + * + * Since: 1.16 + */ +void +nm_wireguard_peer_set_preshared_key_flags(NMWireGuardPeer * self, + NMSettingSecretFlags preshared_key_flags) +{ + g_return_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE)); + + self->preshared_key_flags = preshared_key_flags; +} + +/** + * nm_wireguard_peer_get_persistent_keepalive: + * @self: the #NMWireGuardPeer instance + * + * Returns: get the persistent-keepalive setting in seconds. Set to zero to disable + * keep-alive. + * + * Since: 1.16 + */ +guint16 +nm_wireguard_peer_get_persistent_keepalive(const NMWireGuardPeer *self) +{ + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), 0); + + return self->persistent_keepalive; +} + +/** + * nm_wireguard_peer_set_persistent_keepalive: + * @self: the unsealed #NMWireGuardPeer instance + * @persistent_keepalive: the keep-alive value to set. + * + * It is a bug trying to modify a sealed #NMWireGuardPeer instance. + * + * Since: 1.16 + */ +void +nm_wireguard_peer_set_persistent_keepalive(NMWireGuardPeer *self, guint16 persistent_keepalive) +{ + g_return_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE)); + + self->persistent_keepalive = persistent_keepalive; +} + +NMSockAddrEndpoint * +_nm_wireguard_peer_get_endpoint(const NMWireGuardPeer *self) +{ + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL); + + return self->endpoint; +} + +/** + * nm_wireguard_peer_get_endpoint: + * @self: the #NMWireGuardPeer instance + * + * Returns: (transfer none): the endpoint or %NULL if none was set. + * + * Since: 1.16 + */ +const char * +nm_wireguard_peer_get_endpoint(const NMWireGuardPeer *self) +{ + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL); + + return self->endpoint ? nm_sock_addr_endpoint_get_endpoint(self->endpoint) : NULL; +} + +void +_nm_wireguard_peer_set_endpoint(NMWireGuardPeer *self, NMSockAddrEndpoint *endpoint) +{ + NMSockAddrEndpoint *old; + + nm_assert(NM_IS_WIREGUARD_PEER(self, FALSE)); + + old = self->endpoint; + self->endpoint = nm_sock_addr_endpoint_ref(endpoint); + nm_sock_addr_endpoint_unref(old); +} + +/** + * nm_wireguard_peer_set_endpoint: + * @self: the unsealed #NMWireGuardPeer instance + * @endpoint: the socket address endpoint to set or %NULL. + * @allow_invalid: if %TRUE, also invalid values are set. + * If %FALSE, the function does nothing for invalid @endpoint + * arguments. + * + * Sets or clears the endpoint of @self. + * + * It is a bug trying to modify a sealed #NMWireGuardPeer instance. + * + * Returns: %TRUE if the endpoint is %NULL or valid. For an + * invalid @endpoint argument, %FALSE is returned. Depending + * on @allow_invalid, the instance will be modified. + * + * Since: 1.16 + */ +gboolean +nm_wireguard_peer_set_endpoint(NMWireGuardPeer *self, const char *endpoint, gboolean allow_invalid) +{ + NMSockAddrEndpoint *old; + NMSockAddrEndpoint *new; + gboolean is_valid; + + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE), FALSE); + + if (!endpoint) { + nm_clear_pointer(&self->endpoint, nm_sock_addr_endpoint_unref); + return TRUE; + } + + new = nm_sock_addr_endpoint_new(endpoint); + + is_valid = (nm_sock_addr_endpoint_get_host(new) != NULL); + + if (!allow_invalid && !is_valid) { + nm_sock_addr_endpoint_unref(new); + return FALSE; + } + + old = self->endpoint; + self->endpoint = new; + nm_sock_addr_endpoint_unref(old); + return is_valid; +} + +/** + * nm_wireguard_peer_get_allowed_ips_len: + * @self: the #NMWireGuardPeer instance + * + * Returns: the number of allowed-ips entries. + * + * Since: 1.16 + */ +guint +nm_wireguard_peer_get_allowed_ips_len(const NMWireGuardPeer *self) +{ + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), 0); + + return self->allowed_ips ? self->allowed_ips->len : 0u; +} + +/** + * nm_wireguard_peer_get_allowed_ip: + * @self: the #NMWireGuardPeer instance + * @idx: the index from zero to (allowed-ips-len - 1) to + * retrieve. + * @out_is_valid: (allow-none): %TRUE if the returned value is a valid allowed-ip + * setting. + * This parameter is wrongly not marked as (out) argument, it is + * thus not accessible via introspection. This cannot be fixed without + * breaking API for introspection users. + * + * Returns: (transfer none): the allowed-ip setting at index @idx. + * If @idx is out of range, %NULL will be returned. + * + * Since: 1.16 + */ +const char * +nm_wireguard_peer_get_allowed_ip(const NMWireGuardPeer *self, guint idx, gboolean *out_is_valid) +{ + const char *s; + + /* With LTO, the compiler might warn about the g_return_val_if_fail() + * code path not initializing the output argument. Workaround that by + * always setting the out argument. */ + NM_SET_OUT(out_is_valid, FALSE); + + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL); + + if (!self->allowed_ips || idx >= self->allowed_ips->len) + return NULL; + + s = self->allowed_ips->pdata[idx]; + NM_SET_OUT(out_is_valid, s[0] != ALLOWED_IP_INVALID_X); + return s[0] == ALLOWED_IP_INVALID_X ? &s[1] : s; +} + +/** + * nm_wireguard_peer_clear_allowed_ips: + * @self: the unsealed #NMWireGuardPeer instance + * + * Removes all allowed-ip entries. + * + * It is a bug trying to modify a sealed #NMWireGuardPeer instance. + * + * Since: 1.16 + */ +void +nm_wireguard_peer_clear_allowed_ips(NMWireGuardPeer *self) +{ + g_return_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE)); + + if (self->allowed_ips) + g_ptr_array_set_size(self->allowed_ips, 0); +} + +static gboolean +_peer_append_allowed_ip(NMWireGuardPeer *self, const char *allowed_ip, gboolean accept_invalid) +{ + int addr_family; + int prefix; + NMIPAddr addrbin; + char * str; + gboolean is_valid = TRUE; + + nm_assert(NM_IS_WIREGUARD_PEER(self, FALSE)); + nm_assert(allowed_ip); + + /* normalize the address (if it is valid. Otherwise, take it + * as-is (it will render the instance invalid). */ + if (!nm_utils_parse_inaddr_prefix_bin(AF_UNSPEC, allowed_ip, &addr_family, &addrbin, &prefix)) { + if (!accept_invalid) + return FALSE; + /* mark the entry as invalid by having a "X" prefix. */ + str = g_strconcat(ALLOWED_IP_INVALID_X_STR, allowed_ip, NULL); + is_valid = FALSE; + } else { + char addrstr[NM_UTILS_INET_ADDRSTRLEN]; + + nm_assert_addr_family(addr_family); + + nm_utils_inet_ntop(addr_family, &addrbin, addrstr); + if (prefix >= 0) + str = g_strdup_printf("%s/%d", addrstr, prefix); + else + str = g_strdup(addrstr); + nm_assert(str[0] != ALLOWED_IP_INVALID_X); + } + + if (!self->allowed_ips) + self->allowed_ips = g_ptr_array_new_with_free_func(g_free); + + g_ptr_array_add(self->allowed_ips, str); + return is_valid; +} + +/** + * nm_wireguard_peer_append_allowed_ip: + * @self: the unsealed #NMWireGuardPeer instance + * @allowed_ip: the allowed-ip entry to set. + * @accept_invalid: if %TRUE, also invalid @allowed_ip value + * will be appended. Otherwise, the function does nothing + * in face of invalid values and returns %FALSE. + * + * Appends @allowed_ip setting to the list. This does not check + * for duplicates and always appends @allowed_ip to the end of the + * list. If @allowed_ip is valid, it will be normalized and a modified + * for might be appended. If @allowed_ip is invalid, it will still be + * appended, but later verification will fail. + * + * It is a bug trying to modify a sealed #NMWireGuardPeer instance. + * + * Returns: %TRUE if the value is a valid allowed-ips value, %FALSE otherwise. + * Depending on @accept_invalid, also invalid values are added. + * + * Since: 1.16 + */ +gboolean +nm_wireguard_peer_append_allowed_ip(NMWireGuardPeer *self, + const char * allowed_ip, + gboolean accept_invalid) +{ + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE), FALSE); + g_return_val_if_fail(allowed_ip, FALSE); + + return _peer_append_allowed_ip(self, allowed_ip, accept_invalid); +} + +/** + * nm_wireguard_peer_remove_allowed_ip: + * @self: the unsealed #NMWireGuardPeer instance + * @idx: the index from zero to (allowed-ips-len - 1) to + * retrieve. If the index is out of range, %FALSE is returned + * and nothing is done. + * + * Removes the allowed-ip at the given @idx. This shifts all + * following entries one index down. + * + * It is a bug trying to modify a sealed #NMWireGuardPeer instance. + * + * Returns: %TRUE if @idx was valid and the allowed-ip was removed. + * %FALSE otherwise, and the peer will not be changed. + * + * Since: 1.16 + */ +gboolean +nm_wireguard_peer_remove_allowed_ip(NMWireGuardPeer *self, guint idx) +{ + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE), FALSE); + + if (!self->allowed_ips || idx >= self->allowed_ips->len) + return FALSE; + + g_ptr_array_remove_index(self->allowed_ips, idx); + return TRUE; +} + +/** + * nm_wireguard_peer_is_valid: + * @self: the #NMWireGuardPeer instance + * @check_secrets: if %TRUE, non-secret properties are validated. + * Otherwise, they are ignored for this purpose. + * @check_non_secrets: if %TRUE, secret properties are validated. + * Otherwise, they are ignored for this purpose. + * @error: the #GError location for returning the failure reason. + * + * Returns: %TRUE if the peer is valid or fails with an error + * reason. + * + * Since: 1.16 + */ +gboolean +nm_wireguard_peer_is_valid(const NMWireGuardPeer *self, + gboolean check_non_secrets, + gboolean check_secrets, + GError ** error) +{ + guint i; + + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + if (check_non_secrets) { + if (!self->public_key) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("missing public-key for peer")); + return FALSE; + } else if (!self->public_key_valid) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid public-key for peer")); + return FALSE; + } + } + + if (check_secrets) { + if (self->preshared_key && !self->preshared_key_valid) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid preshared-key for peer")); + return FALSE; + } + } + + if (check_non_secrets) { + if (!_nm_utils_secret_flags_validate(self->preshared_key_flags, + NULL, + NULL, + NM_SETTING_SECRET_FLAG_NONE, + error)) + return FALSE; + } + + if (check_non_secrets) { + if (self->endpoint && !nm_sock_addr_endpoint_get_host(self->endpoint)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid endpoint for peer")); + return FALSE; + } + + if (self->allowed_ips) { + for (i = 0; i < self->allowed_ips->len; i++) { + const char *s = self->allowed_ips->pdata[i]; + + if (s[0] == ALLOWED_IP_INVALID_X) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid IP address \"%s\" for allowed-ip of peer"), + &s[1]); + return FALSE; + } + } + } + + if (!_nm_setting_secret_flags_valid(self->preshared_key_flags)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid preshared-key-flags for peer")); + return FALSE; + } + } + + return TRUE; +} + +/** + * nm_wireguard_peer_cmp: + * @a: (allow-none): the #NMWireGuardPeer to compare. + * @b: (allow-none): the other #NMWireGuardPeer to compare. + * @compare_flags: #NMSettingCompareFlags to affect the comparison. + * + * Returns: zero of the two instances are equivalent or + * a non-zero integer otherwise. This defines a total ordering + * over the peers. Whether a peer is sealed or not, does not + * affect the comparison. + * + * Since: 1.16 + */ +int +nm_wireguard_peer_cmp(const NMWireGuardPeer *a, + const NMWireGuardPeer *b, + NMSettingCompareFlags compare_flags) +{ + guint i, n; + + NM_CMP_SELF(a, b); + + /* regardless of the @compare_flags, the public-key is the ID of the peer. It must + * always be compared. */ + NM_CMP_FIELD_BOOL(a, b, public_key_valid); + NM_CMP_FIELD_STR0(a, b, public_key); + + if (NM_FLAGS_ANY(compare_flags, + NM_SETTING_COMPARE_FLAG_INFERRABLE | NM_SETTING_COMPARE_FLAG_FUZZY)) + return 0; + + NM_CMP_FIELD_BOOL(a, b, endpoint); + if (a->endpoint) { + NM_CMP_DIRECT_STRCMP0(nm_sock_addr_endpoint_get_endpoint(a->endpoint), + nm_sock_addr_endpoint_get_endpoint(b->endpoint)); + } + + NM_CMP_FIELD(a, b, persistent_keepalive); + + NM_CMP_DIRECT((n = (a->allowed_ips ? a->allowed_ips->len : 0u)), + (b->allowed_ips ? b->allowed_ips->len : 0u)); + for (i = 0; i < n; i++) + NM_CMP_DIRECT_STRCMP0(a->allowed_ips->pdata[i], b->allowed_ips->pdata[i]); + + NM_CMP_FIELD(a, b, preshared_key_flags); + + if (!NM_FLAGS_HAS(compare_flags, NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS)) { + if (NM_FLAGS_HAS(compare_flags, NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS) + && NM_FLAGS_HAS(a->preshared_key_flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED)) { + /* pass */ + } else if (NM_FLAGS_HAS(compare_flags, NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS) + && NM_FLAGS_HAS(a->preshared_key_flags, NM_SETTING_SECRET_FLAG_NOT_SAVED)) { + /* pass */ + } else { + NM_CMP_FIELD_BOOL(a, b, preshared_key_valid); + NM_CMP_FIELD_STR0(a, b, preshared_key); + } + } + + return 0; +} + +/*****************************************************************************/ + +typedef struct { + const char * public_key; + NMWireGuardPeer *peer; + guint idx; +} PeerData; + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_FWMARK, + PROP_IP4_AUTO_DEFAULT_ROUTE, + PROP_IP6_AUTO_DEFAULT_ROUTE, + PROP_LISTEN_PORT, + PROP_MTU, + PROP_PEER_ROUTES, + PROP_PRIVATE_KEY, + PROP_PRIVATE_KEY_FLAGS, ); + +typedef struct { + char * private_key; + GPtrArray * peers_arr; + GHashTable * peers_hash; + NMSettingSecretFlags private_key_flags; + NMTernary ip4_auto_default_route; + NMTernary ip6_auto_default_route; + guint32 fwmark; + guint32 mtu; + guint16 listen_port; + bool private_key_valid : 1; + bool peer_routes : 1; +} NMSettingWireGuardPrivate; + +/** + * NMSettingWireGuard: + * + * WireGuard Settings + * + * Since: 1.16 + */ +struct _NMSettingWireGuard { + NMSetting parent; + NMSettingWireGuardPrivate _priv; +}; + +struct _NMSettingWireGuardClass { + NMSettingClass parent; +}; + +G_DEFINE_TYPE(NMSettingWireGuard, nm_setting_wireguard, NM_TYPE_SETTING) + +#define NM_SETTING_WIREGUARD_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMSettingWireGuard, NM_IS_SETTING_WIREGUARD, NMSetting) + +/*****************************************************************************/ + +#define peers_psk_get_secret_name_a(public_key, to_free) \ + nm_construct_name_a(NM_SETTING_WIREGUARD_PEERS ".%s." NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, \ + (public_key), \ + (to_free)) + +#define peers_psk_get_secret_name_dup(public_key) \ + g_strdup_printf(NM_SETTING_WIREGUARD_PEERS ".%s." NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, \ + (public_key)) + +#define peers_psk_get_secret_parse_a(secret_public_key, public_key_free) \ + ({ \ + const char *_secret_public_key = (secret_public_key); \ + char ** _public_key_free = (public_key_free); \ + const char *_public_key = NULL; \ + \ + nm_assert(_public_key_free && !*_public_key_free); \ + \ + if (NM_STR_HAS_PREFIX(_secret_public_key, NM_SETTING_WIREGUARD_PEERS ".")) { \ + _secret_public_key += NM_STRLEN(NM_SETTING_WIREGUARD_PEERS "."); \ + if (NM_STR_HAS_SUFFIX(_secret_public_key, "." NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY)) { \ + _public_key = \ + nm_strndup_a(300, \ + _secret_public_key, \ + strlen(_secret_public_key) \ + - NM_STRLEN("." NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY), \ + _public_key_free); \ + } \ + } \ + \ + _public_key; \ + }) + +/*****************************************************************************/ + +/** + * nm_setting_wireguard_get_private_key: + * @self: the #NMSettingWireGuard instance + * + * Returns: (transfer none): the set private-key or %NULL. + * + * Since: 1.16 + */ +const char * +nm_setting_wireguard_get_private_key(NMSettingWireGuard *self) +{ + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), NULL); + + return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->private_key; +} + +/** + * nm_setting_wireguard_get_private_key_flags: + * @self: the #NMSettingWireGuard instance + * + * Returns: the secret-flags for #NMSettingWireGuard:private-key. + * + * Since: 1.16 + */ +NMSettingSecretFlags +nm_setting_wireguard_get_private_key_flags(NMSettingWireGuard *self) +{ + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), 0); + + return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->private_key_flags; +} + +/** + * nm_setting_wireguard_get_fwmark: + * @self: the #NMSettingWireGuard instance + * + * Returns: the set firewall mark. + * + * Since: 1.16 + */ +guint32 +nm_setting_wireguard_get_fwmark(NMSettingWireGuard *self) +{ + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), 0); + + return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->fwmark; +} + +/** + * nm_setting_wireguard_get_listen_port: + * @self: the #NMSettingWireGuard instance + * + * Returns: the set UDP listen port. + * + * Since: 1.16 + */ +guint16 +nm_setting_wireguard_get_listen_port(NMSettingWireGuard *self) +{ + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), 0); + + return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->listen_port; +} + +/** + * nm_setting_wireguard_get_peer_routes: + * @self: the #NMSettingWireGuard instance + * + * Returns: whether automatically add peer routes. + * + * Since: 1.16 + */ +gboolean +nm_setting_wireguard_get_peer_routes(NMSettingWireGuard *self) +{ + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), TRUE); + + return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->peer_routes; +} + +/** + * nm_setting_wireguard_get_mtu: + * @self: the #NMSettingWireGuard instance + * + * Returns: the MTU of the setting. + * + * Since: 1.16 + */ +guint32 +nm_setting_wireguard_get_mtu(NMSettingWireGuard *self) +{ + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), 0); + + return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->mtu; +} + +/** + * nm_setting_wireguard_get_ip4_auto_default_route: + * @self: the #NMSettingWireGuard setting. + * + * Returns: the "ip4-auto-default-route" property of the setting. + * + * Since: 1.20 + */ +NMTernary +nm_setting_wireguard_get_ip4_auto_default_route(NMSettingWireGuard *self) +{ + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), NM_TERNARY_DEFAULT); + + return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->ip4_auto_default_route; +} + +/** + * nm_setting_wireguard_get_ip6_auto_default_route: + * @self: the #NMSettingWireGuard setting. + * + * Returns: the "ip6-auto-default-route" property of the setting. + * + * Since: 1.20 + */ +NMTernary +nm_setting_wireguard_get_ip6_auto_default_route(NMSettingWireGuard *self) +{ + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), NM_TERNARY_DEFAULT); + + return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->ip6_auto_default_route; +} + +/*****************************************************************************/ + +static void +_peer_free(PeerData *pd) +{ + nm_assert(pd); + + nm_wireguard_peer_unref(pd->peer); + g_slice_free(PeerData, pd); +} + +/*****************************************************************************/ + +static void +_peers_notify(gpointer self) +{ + _nm_setting_emit_property_changed(self); +} + +static PeerData * +_peers_get(NMSettingWireGuardPrivate *priv, guint idx) +{ + PeerData *pd; + + nm_assert(priv); + nm_assert(idx < priv->peers_arr->len); + + pd = priv->peers_arr->pdata[idx]; + + nm_assert(pd); + nm_assert(pd->idx == idx); + nm_assert(NM_IS_WIREGUARD_PEER(pd->peer, TRUE)); + nm_assert(nm_wireguard_peer_is_sealed(pd->peer)); + nm_assert(pd->public_key == nm_wireguard_peer_get_public_key(pd->peer)); + nm_assert(g_hash_table_lookup(priv->peers_hash, pd) == pd); + + return pd; +} + +static PeerData * +_peers_get_by_public_key(NMSettingWireGuardPrivate *priv, + const char * public_key, + gboolean try_with_normalized_key) +{ + gs_free char *public_key_normalized = NULL; + PeerData * pd; + +again: + nm_assert(priv); + nm_assert(public_key); + + pd = g_hash_table_lookup(priv->peers_hash, &public_key); + if (pd) { + nm_assert(_peers_get(priv, pd->idx) == pd); + return pd; + } + if (try_with_normalized_key + && nm_utils_base64secret_normalize(public_key, + NM_WIREGUARD_PUBLIC_KEY_LEN, + &public_key_normalized)) { + public_key = public_key_normalized; + try_with_normalized_key = FALSE; + goto again; + } + return NULL; +} + +static void +_peers_remove(NMSettingWireGuardPrivate *priv, PeerData *pd, gboolean do_free) +{ + guint i; + + nm_assert(pd); + nm_assert(_peers_get(priv, pd->idx) == pd); + + for (i = pd->idx + 1; i < priv->peers_arr->len; i++) + _peers_get(priv, i)->idx--; + + g_ptr_array_remove_index(priv->peers_arr, pd->idx); + if (!g_hash_table_remove(priv->peers_hash, pd)) + nm_assert_not_reached(); + if (do_free) + _peer_free(pd); +} + +/** + * nm_setting_wireguard_get_peers_len: + * @self: the #NMSettingWireGuard instance + * + * Returns: the number of registered peers. + * + * Since: 1.16 + */ +guint +nm_setting_wireguard_get_peers_len(NMSettingWireGuard *self) +{ + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), 0); + + return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->peers_arr->len; +} + +/** + * nm_setting_wireguard_get_peer: + * @self: the #NMSettingWireGuard instance + * @idx: the index to lookup. + * + * Returns: (transfer none): the #NMWireGuardPeer entry at + * index @idx. If the index is out of range, %NULL is returned. + * + * Since: 1.16 + */ +NMWireGuardPeer * +nm_setting_wireguard_get_peer(NMSettingWireGuard *self, guint idx) +{ + NMSettingWireGuardPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), NULL); + + priv = NM_SETTING_WIREGUARD_GET_PRIVATE(self); + + if (idx >= priv->peers_arr->len) + return NULL; + + return _peers_get(priv, idx)->peer; +} + +/** + * nm_setting_wireguard_get_peer_by_public_key: + * @self: the #NMSettingWireGuard instance + * @public_key: the public key for looking up the + * peer. + * @out_idx: (out) (allow-none): optional output argument + * for the index of the found peer. If no index is found, + * this is set to the nm_setting_wireguard_get_peers_len(). + * + * Returns: (transfer none): the #NMWireGuardPeer instance with a + * matching public key. If no such peer exists, %NULL is returned. + * + * Since: 1.16 + */ +NMWireGuardPeer * +nm_setting_wireguard_get_peer_by_public_key(NMSettingWireGuard *self, + const char * public_key, + guint * out_idx) +{ + NMSettingWireGuardPrivate *priv; + PeerData * pd; + + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), NULL); + g_return_val_if_fail(public_key, NULL); + + priv = NM_SETTING_WIREGUARD_GET_PRIVATE(self); + + pd = _peers_get_by_public_key(priv, public_key, TRUE); + if (!pd) { + NM_SET_OUT(out_idx, priv->peers_arr->len); + return NULL; + } + NM_SET_OUT(out_idx, pd->idx); + return pd->peer; +} + +static gboolean +_peers_set(NMSettingWireGuardPrivate *priv, + NMWireGuardPeer * peer, + guint idx, + gboolean check_same_key) +{ + PeerData * pd_same_key = NULL; + PeerData * pd_idx = NULL; + const char *public_key; + + nm_assert(idx <= priv->peers_arr->len); + + public_key = nm_wireguard_peer_get_public_key(peer); + + if (idx < priv->peers_arr->len) { + pd_idx = _peers_get(priv, idx); + + if (pd_idx->peer == peer) + return FALSE; + + if (check_same_key && nm_streq(public_key, nm_wireguard_peer_get_public_key(pd_idx->peer))) + check_same_key = FALSE; + } + + nm_wireguard_peer_seal(peer); + nm_wireguard_peer_ref(peer); + + if (check_same_key) { + pd_same_key = _peers_get_by_public_key(priv, public_key, FALSE); + if (pd_same_key) { + if (pd_idx) { + nm_assert(pd_same_key != pd_idx); + _peers_remove(priv, pd_same_key, TRUE); + pd_same_key = NULL; + } else { + if (pd_same_key->peer == peer && pd_same_key->idx == priv->peers_arr->len - 1) { + nm_wireguard_peer_unref(peer); + return FALSE; + } + _peers_remove(priv, pd_same_key, FALSE); + nm_wireguard_peer_unref(pd_same_key->peer); + } + } + } else + nm_assert(_peers_get_by_public_key(priv, public_key, FALSE) == pd_idx); + + if (pd_idx) { + g_hash_table_remove(priv->peers_hash, pd_idx); + nm_wireguard_peer_unref(pd_idx->peer); + pd_idx->public_key = public_key; + pd_idx->peer = peer; + g_hash_table_add(priv->peers_hash, pd_idx); + return TRUE; + } + + if (!pd_same_key) + pd_same_key = g_slice_new(PeerData); + + *pd_same_key = (PeerData){ + .peer = peer, + .public_key = public_key, + .idx = priv->peers_arr->len, + }; + + g_ptr_array_add(priv->peers_arr, pd_same_key); + if (!nm_g_hash_table_add(priv->peers_hash, pd_same_key)) + nm_assert_not_reached(); + + nm_assert(_peers_get(priv, pd_same_key->idx) == pd_same_key); + + return TRUE; +} + +static gboolean +_peers_append(NMSettingWireGuardPrivate *priv, NMWireGuardPeer *peer, gboolean check_same_key) +{ + return _peers_set(priv, peer, priv->peers_arr->len, check_same_key); +} + +/** + * nm_setting_wireguard_set_peer: + * @self: the #NMSettingWireGuard instance + * @peer: the #NMWireGuardPeer instance to set. + * This seals @peer and keeps a reference on the + * instance. + * @idx: the index, in the range of 0 to the number of + * peers (including). That means, if @idx is one past + * the end of the number of peers, this is the same as + * nm_setting_wireguard_append_peer(). Otherwise, the + * peer at this index is replaced. + * + * If @idx is one past the last peer, the behavior is the same + * as nm_setting_wireguard_append_peer(). + * Otherwise, the peer will be at @idx and replace the peer + * instance at that index. Note that if a peer with the same + * public-key exists on another index, then that peer will also + * be replaced. In that case, the number of peers will shrink + * by one (because the one at @idx got replace and then one + * with the same public-key got removed). This also means, + * that the resulting index afterwards may be one less than + * @idx (if another peer with a lower index was dropped). + * + * Since: 1.16 + */ +void +nm_setting_wireguard_set_peer(NMSettingWireGuard *self, NMWireGuardPeer *peer, guint idx) +{ + NMSettingWireGuardPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_WIREGUARD(self)); + g_return_if_fail(NM_IS_WIREGUARD_PEER(peer, TRUE)); + + priv = NM_SETTING_WIREGUARD_GET_PRIVATE(self); + + g_return_if_fail(idx <= priv->peers_arr->len); + + if (_peers_set(priv, peer, idx, TRUE)) + _peers_notify(self); +} + +/** + * nm_setting_wireguard_append_peer: + * @self: the #NMSettingWireGuard instance + * @peer: the #NMWireGuardPeer instance to append. + * This seals @peer and keeps a reference on the + * instance. + * + * If a peer with the same public-key already exists, that + * one is replaced by @peer. The new @peer is always appended + * (or moved to) the end, so in case a peer is replaced, the + * indexes are shifted and the number of peers stays unchanged. + * + * Since: 1.16 + */ +void +nm_setting_wireguard_append_peer(NMSettingWireGuard *self, NMWireGuardPeer *peer) +{ + g_return_if_fail(NM_IS_SETTING_WIREGUARD(self)); + g_return_if_fail(NM_IS_WIREGUARD_PEER(peer, TRUE)); + + if (_peers_append(NM_SETTING_WIREGUARD_GET_PRIVATE(self), peer, TRUE)) + _peers_notify(self); +} + +/** + * nm_setting_wireguard_remove_peer + * @self: the #NMSettingWireGuard instance + * @idx: the index to remove. + * + * Returns: %TRUE if @idx was in range and a peer + * was removed. Otherwise, @self is unchanged. + * + * Since: 1.16 + */ +gboolean +nm_setting_wireguard_remove_peer(NMSettingWireGuard *self, guint idx) +{ + NMSettingWireGuardPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), FALSE); + + priv = NM_SETTING_WIREGUARD_GET_PRIVATE(self); + + if (idx >= priv->peers_arr->len) + return FALSE; + + _peers_remove(priv, _peers_get(priv, idx), TRUE); + _peers_notify(self); + return TRUE; +} + +static guint +_peers_clear(NMSettingWireGuardPrivate *priv) +{ + guint l; + + l = priv->peers_arr->len; + while (priv->peers_arr->len > 0) { + _peers_remove(priv, _peers_get(priv, priv->peers_arr->len - 1), TRUE); + } + return l; +} + +/** + * nm_setting_wireguard_: + * @self: the #NMSettingWireGuard instance + * + * Returns: the number of cleared peers. + * + * Since: 1.16 + */ +guint +nm_setting_wireguard_clear_peers(NMSettingWireGuard *self) +{ + guint l; + + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), 0); + + l = _peers_clear(NM_SETTING_WIREGUARD_GET_PRIVATE(self)); + if (l > 0) + _peers_notify(self); + return l; +} + +/*****************************************************************************/ + +static GVariant * +_peers_dbus_only_synth(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + NMSettingWireGuard * self = NM_SETTING_WIREGUARD(setting); + NMSettingWireGuardPrivate *priv; + gboolean any_peers = FALSE; + GVariantBuilder peers_builder; + guint i_peer, n_peers; + guint i; + + n_peers = nm_setting_wireguard_get_peers_len(self); + if (n_peers == 0) + return NULL; + + priv = NM_SETTING_WIREGUARD_GET_PRIVATE(self); + + for (i_peer = 0; i_peer < n_peers; i_peer++) { + const NMWireGuardPeer *peer = _peers_get(priv, i_peer)->peer; + GVariantBuilder builder; + + if (!peer->public_key) + continue; + + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); + + g_variant_builder_add(&builder, + "{sv}", + NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY, + g_variant_new_string(peer->public_key)); + + if (_nm_connection_serialize_non_secret(flags) && peer->endpoint) + g_variant_builder_add( + &builder, + "{sv}", + NM_WIREGUARD_PEER_ATTR_ENDPOINT, + g_variant_new_string(nm_sock_addr_endpoint_get_endpoint(peer->endpoint))); + + if (_nm_connection_serialize_secrets(flags, peer->preshared_key_flags) + && peer->preshared_key) + g_variant_builder_add(&builder, + "{sv}", + NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, + g_variant_new_string(peer->preshared_key)); + + if (_nm_connection_serialize_non_secret(flags) + && peer->preshared_key_flags != NM_SETTING_SECRET_FLAG_NOT_REQUIRED) + g_variant_builder_add(&builder, + "{sv}", + NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS, + g_variant_new_uint32(peer->preshared_key_flags)); + + if (_nm_connection_serialize_non_secret(flags) && peer->persistent_keepalive != 0) + g_variant_builder_add(&builder, + "{sv}", + NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE, + g_variant_new_uint32(peer->persistent_keepalive)); + + if (_nm_connection_serialize_non_secret(flags) && peer->allowed_ips + && peer->allowed_ips->len > 0) { + const char *const * strv = (const char *const *) peer->allowed_ips->pdata; + gs_free const char **strv_fixed = NULL; + + for (i = 0; i < peer->allowed_ips->len; i++) { + /* This is a bit odd. The original peer->allowed_ips is a strv array + * of all allowed-ips, but some of them may have an ALLOWED_IP_INVALID_X prefix + * (as first character). + * + * When we serialize the array to GVariant, we need to get rid of this + * character, by pointing to the second character. + * + * In that case, first clone the strv array to strv_fixed. Once we cloned + * it, adjust the pointer to drop the first character. */ + if (strv[i][0] != ALLOWED_IP_INVALID_X) { + /* this string is valid, no need for adjustment. */ + continue; + } + if (!strv_fixed) { + strv_fixed = nm_memdup(strv, sizeof(strv[0]) * peer->allowed_ips->len); + strv = strv_fixed; + } + ((const char **) strv)[i]++; + } + g_variant_builder_add(&builder, + "{sv}", + NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS, + g_variant_new_strv(strv, peer->allowed_ips->len)); + } + + if (!any_peers) { + g_variant_builder_init(&peers_builder, G_VARIANT_TYPE("aa{sv}")); + any_peers = TRUE; + } + g_variant_builder_add(&peers_builder, "a{sv}", &builder); + } + + return any_peers ? g_variant_builder_end(&peers_builder) : NULL; +} + +static gboolean +_peers_dbus_only_set(NMSetting * setting, + GVariant * connection_dict, + const char * property, + GVariant * value, + NMSettingParseFlags parse_flags, + GError ** error) +{ + GVariantIter iter_peers; + GVariant * peer_var; + guint i_peer; + gboolean success = FALSE; + gboolean peers_changed = FALSE; + + nm_assert(g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}"))); + + g_variant_iter_init(&iter_peers, value); + + i_peer = 0; + while (g_variant_iter_next(&iter_peers, "@a{sv}", &peer_var)) { + _nm_unused gs_unref_variant GVariant *peer_var_unref = peer_var; + nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL; + const char * cstr; + guint32 u32; + GVariant * var; + + i_peer++; + + if (!g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY, "&s", &cstr)) { + if (NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("peer #%u has no public-key"), + i_peer); + goto out; + } + continue; + } + + peer = nm_wireguard_peer_new(); + if (!nm_wireguard_peer_set_public_key(peer, cstr, TRUE)) { + if (NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("peer #%u has invalid public-key"), + i_peer); + goto out; + } + continue; + } + + if (g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_ENDPOINT, "&s", &cstr)) { + nm_auto_unref_sockaddrendpoint NMSockAddrEndpoint *ep = NULL; + + ep = nm_sock_addr_endpoint_new(cstr); + if (!nm_sock_addr_endpoint_get_host(ep)) { + if (NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("peer #%u has invalid endpoint"), + i_peer); + goto out; + } + } else + _nm_wireguard_peer_set_endpoint(peer, ep); + } + + if (g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, "&s", &cstr)) + nm_wireguard_peer_set_preshared_key(peer, cstr, TRUE); + + if (g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS, "u", &u32)) + nm_wireguard_peer_set_preshared_key_flags(peer, u32); + + if (g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE, "u", &u32)) + nm_wireguard_peer_set_persistent_keepalive(peer, u32); + + if (g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS, "@as", &var)) { + _nm_unused gs_unref_variant GVariant *var_free = var; + gs_free const char ** allowed_ips = NULL; + gsize i, l; + + allowed_ips = g_variant_get_strv(var, &l); + if (allowed_ips) { + for (i = 0; i < l; i++) { + if (_peer_append_allowed_ip(peer, allowed_ips[i], FALSE)) + continue; + if (!NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) + continue; + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("peer #%u has invalid allowed-ips setting"), + i_peer); + goto out; + } + } + } + + if (NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) { + gs_free_error GError *local = NULL; + + if (!nm_wireguard_peer_is_valid(peer, TRUE, FALSE, &local)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("peer #%u is invalid: %s"), + i_peer, + local->message); + goto out; + } + } + + /* we could easily reject duplicate peers (by public-key) or duplicate GVariant attributes. + * However, don't do that. In case of duplicate values, the latter peer overwrite the earlier + * and GVariant attributes are ignored by g_variant_lookup() above. */ + if (_peers_append(NM_SETTING_WIREGUARD_GET_PRIVATE(setting), peer, TRUE)) + peers_changed = TRUE; + } + + success = TRUE; + +out: + if (peers_changed) + _peers_notify(setting); + return success; +} + +/*****************************************************************************/ + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingWireGuard * s_wg = NM_SETTING_WIREGUARD(setting); + NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(setting); + guint i; + + if (!_nm_connection_verify_required_interface_name(connection, error)) + return FALSE; + + if (!_nm_utils_secret_flags_validate(nm_setting_wireguard_get_private_key_flags(s_wg), + NM_SETTING_WIREGUARD_SETTING_NAME, + NM_SETTING_WIREGUARD_PRIVATE_KEY_FLAGS, + NM_SETTING_SECRET_FLAG_NOT_REQUIRED, + error)) + return FALSE; + + for (i = 0; i < priv->peers_arr->len; i++) { + NMWireGuardPeer *peer = _peers_get(priv, i)->peer; + + if (!nm_wireguard_peer_is_valid(peer, TRUE, FALSE, error)) { + g_prefix_error(error, + "%s.%s[%u]: ", + NM_SETTING_WIREGUARD_SETTING_NAME, + NM_SETTING_WIREGUARD_PEERS, + i); + return FALSE; + } + } + + if (connection) { + NMSettingIPConfig *s_ip4; + NMSettingIPConfig *s_ip6; + const char * method; + + /* WireGuard is Layer 3 only. For the moment, we only support a restricted set of + * IP methods. We may relax that later, once we fix the implementations so they + * actually work. */ + + if ((s_ip4 = nm_connection_get_setting_ip4_config(connection)) + && (method = nm_setting_ip_config_get_method(s_ip4)) + && !NM_IN_STRSET(method, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("method \"%s\" is not supported for WireGuard"), + method); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_METHOD); + return FALSE; + } + + if ((s_ip6 = nm_connection_get_setting_ip6_config(connection)) + && (method = nm_setting_ip_config_get_method(s_ip6)) + && !NM_IN_STRSET(method, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("method \"%s\" is not supported for WireGuard"), + method); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_METHOD); + return FALSE; + } + } + + /* private-key is a secret, hence we cannot verify it like a regular property. */ + return TRUE; +} + +static gboolean +verify_secrets(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(setting); + guint i; + + if (priv->private_key && !priv->private_key_valid) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("key must be 32 bytes base64 encoded")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIREGUARD_SETTING_NAME, + NM_SETTING_WIREGUARD_PRIVATE_KEY); + return FALSE; + } + + for (i = 0; i < priv->peers_arr->len; i++) { + NMWireGuardPeer *peer = _peers_get(priv, i)->peer; + + if (!nm_wireguard_peer_is_valid(peer, FALSE, TRUE, error)) { + g_prefix_error(error, + "%s.%s[%u]: ", + NM_SETTING_WIREGUARD_SETTING_NAME, + NM_SETTING_WIREGUARD_PEERS, + i); + return FALSE; + } + } + + return TRUE; +} + +static GPtrArray * +need_secrets(NMSetting *setting) +{ + NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(setting); + GPtrArray * secrets = NULL; + guint i; + + if (!priv->private_key || !priv->private_key_valid) { + secrets = g_ptr_array_new_full(1, g_free); + g_ptr_array_add(secrets, g_strdup(NM_SETTING_WIREGUARD_PRIVATE_KEY)); + } + + for (i = 0; i < priv->peers_arr->len; i++) { + NMWireGuardPeer *peer = _peers_get(priv, i)->peer; + + if (NM_FLAGS_HAS(peer->preshared_key_flags, NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) + continue; + + if (peer->preshared_key_valid) + continue; + + if (!peer->public_key_valid) + continue; + + if (!secrets) + secrets = g_ptr_array_new_full(1, g_free); + g_ptr_array_add(secrets, peers_psk_get_secret_name_dup(peer->public_key)); + } + + return secrets; +} + +static gboolean +clear_secrets(const NMSettInfoSetting * sett_info, + guint property_idx, + NMSetting * setting, + NMSettingClearSecretsWithFlagsFn func, + gpointer user_data) +{ + if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_WIREGUARD_PEERS)) { + NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(setting); + gboolean peers_changed = FALSE; + guint i, j; + + j = 0; + for (i = 0; i < priv->peers_arr->len; i++) { + NMWireGuardPeer *peer = _peers_get(priv, i)->peer; + + if (!peer->preshared_key) + continue; + + if (func) { + gs_free char *name_free = NULL; + const char * name; + + if (j++ < 5) { + /* we use alloca() inside a loop here, but it is guarded to happen at most + * a few times. */ + name = peers_psk_get_secret_name_a(/* lgtm [cpp/alloca-in-loop] */ + peer->public_key, + &name_free); + } else { + name_free = peers_psk_get_secret_name_dup(peer->public_key); + name = name_free; + } + + if (!func(setting, name, peer->preshared_key_flags, user_data)) + continue; + } + + { + nm_auto_unref_wgpeer NMWireGuardPeer *peer2 = NULL; + + peer2 = nm_wireguard_peer_new_clone(peer, FALSE); + + if (_peers_set(priv, peer2, i, FALSE)) + peers_changed = TRUE; + } + } + + if (peers_changed) + _peers_notify(setting); + return peers_changed; + } + + return NM_SETTING_CLASS(nm_setting_wireguard_parent_class) + ->clear_secrets(sett_info, property_idx, setting, func, user_data); +} + +static int +update_one_secret(NMSetting *setting, const char *key, GVariant *value, GError **error) +{ + NMSettingWireGuard * self = NM_SETTING_WIREGUARD(setting); + NMSettingWireGuardPrivate *priv; + gboolean has_changes = FALSE; + gboolean has_error = FALSE; + GVariantIter iter_peers; + GVariant * peer_var; + guint i_peer; + + if (!nm_streq(key, NM_SETTING_WIREGUARD_PEERS)) { + return NM_SETTING_CLASS(nm_setting_wireguard_parent_class) + ->update_one_secret(setting, key, value, error); + } + + if (!g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}"))) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET, + _("invalid peer secrets")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIREGUARD_SETTING_NAME, + NM_SETTING_WIREGUARD_PEERS); + return NM_SETTING_UPDATE_SECRET_ERROR; + } + + priv = NM_SETTING_WIREGUARD_GET_PRIVATE(self); + + g_variant_iter_init(&iter_peers, value); + + i_peer = 0; + while (g_variant_iter_next(&iter_peers, "@a{sv}", &peer_var)) { + _nm_unused gs_unref_variant GVariant *peer_var_unref = peer_var; + nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL; + PeerData * pd; + const char * cstr; + + i_peer++; + + if (!g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY, "&s", &cstr)) { + if (!has_error) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET, + _("peer #%u lacks public-key"), + i_peer - 1); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIREGUARD_SETTING_NAME, + NM_SETTING_WIREGUARD_PEERS); + has_error = TRUE; + } + continue; + } + + pd = _peers_get_by_public_key(priv, cstr, TRUE); + if (!pd) { + if (!has_error) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET, + _("non-existing peer '%s'"), + cstr); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIREGUARD_SETTING_NAME, + NM_SETTING_WIREGUARD_PEERS); + has_error = TRUE; + } + continue; + } + + if (!g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, "&s", &cstr)) { + /* no preshared-key. Ignore the rest. + * + * In particular, we don't reject all unknown fields. */ + continue; + } + + if (nm_streq0(cstr, nm_wireguard_peer_get_preshared_key(pd->peer))) + continue; + + peer = nm_wireguard_peer_new_clone(pd->peer, FALSE); + nm_wireguard_peer_set_preshared_key(peer, cstr, TRUE); + + if (!_peers_set(priv, peer, pd->idx, FALSE)) + nm_assert_not_reached(); + has_changes = TRUE; + } + + if (has_error) + return NM_SETTING_UPDATE_SECRET_ERROR; + if (has_changes) + return NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED; + return NM_SETTING_UPDATE_SECRET_SUCCESS_UNCHANGED; +} + +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) +{ + NMSettingWireGuardPrivate *a_priv; + NMSettingWireGuardPrivate *b_priv; + guint i; + + if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_WIREGUARD_PEERS)) { + if (NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)) + return NM_TERNARY_DEFAULT; + + if (!set_b) + return TRUE; + + a_priv = NM_SETTING_WIREGUARD_GET_PRIVATE(set_a); + b_priv = NM_SETTING_WIREGUARD_GET_PRIVATE(set_b); + + if (a_priv->peers_arr->len != b_priv->peers_arr->len) + return FALSE; + for (i = 0; i < a_priv->peers_arr->len; i++) { + NMWireGuardPeer *a_peer = _peers_get(a_priv, i)->peer; + NMWireGuardPeer *b_peer = _peers_get(b_priv, i)->peer; + + if (nm_wireguard_peer_cmp(a_peer, b_peer, flags) != 0) + return FALSE; + } + + return TRUE; + } + + return NM_SETTING_CLASS(nm_setting_wireguard_parent_class) + ->compare_property(sett_info, property_idx, con_a, set_a, con_b, set_b, flags); +} + +static void +duplicate_copy_properties(const NMSettInfoSetting *sett_info, NMSetting *src, NMSetting *dst) +{ + NMSettingWireGuardPrivate *priv_src = NM_SETTING_WIREGUARD_GET_PRIVATE(src); + NMSettingWireGuardPrivate *priv_dst = NM_SETTING_WIREGUARD_GET_PRIVATE(dst); + guint i; + gboolean peers_changed = FALSE; + + NM_SETTING_CLASS(nm_setting_wireguard_parent_class) + ->duplicate_copy_properties(sett_info, src, dst); + + /* We don't bother comparing the existing peers with what we are about to set. + * Always reset all. */ + if (_peers_clear(priv_dst) > 0) + peers_changed = TRUE; + for (i = 0; i < priv_src->peers_arr->len; i++) { + if (_peers_append(priv_dst, _peers_get(priv_src, i)->peer, FALSE)) + peers_changed = TRUE; + } + if (peers_changed) + _peers_notify(dst); +} + +static void +enumerate_values(const NMSettInfoProperty *property_info, + NMSetting * setting, + NMSettingValueIterFn func, + gpointer user_data) +{ + if (nm_streq(property_info->name, NM_SETTING_WIREGUARD_PEERS)) { + NMSettingWireGuardPrivate * priv = NM_SETTING_WIREGUARD_GET_PRIVATE(setting); + nm_auto_unset_gvalue GValue value = G_VALUE_INIT; + GPtrArray * ptr = NULL; + guint i; + + if (priv->peers_arr && priv->peers_arr->len > 0) { + ptr = g_ptr_array_new_with_free_func((GDestroyNotify) nm_wireguard_peer_unref); + for (i = 0; i < priv->peers_arr->len; i++) + g_ptr_array_add(ptr, nm_wireguard_peer_ref(_peers_get(priv, i)->peer)); + } + g_value_init(&value, G_TYPE_PTR_ARRAY); + g_value_take_boxed(&value, ptr); + func(setting, property_info->name, &value, 0, user_data); + return; + } + + NM_SETTING_CLASS(nm_setting_wireguard_parent_class) + ->enumerate_values(property_info, setting, func, user_data); +} + +static gboolean +aggregate(NMSetting *setting, int type_i, gpointer arg) +{ + NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(setting); + NMConnectionAggregateType type = type_i; + NMSettingSecretFlags secret_flags; + guint i; + + nm_assert(NM_IN_SET(type, + NM_CONNECTION_AGGREGATE_ANY_SECRETS, + NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS)); + + switch (type) { + case NM_CONNECTION_AGGREGATE_ANY_SECRETS: + if (priv->private_key) + goto out_done; + for (i = 0; i < priv->peers_arr->len; i++) { + if (nm_wireguard_peer_get_preshared_key(_peers_get(priv, i)->peer)) + goto out_done; + } + break; + + case NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS: +#if NM_MORE_ASSERTS + if (!nm_setting_get_secret_flags(setting, + NM_SETTING_WIREGUARD_PRIVATE_KEY, + &secret_flags, + NULL)) + nm_assert_not_reached(); + nm_assert(secret_flags == priv->private_key_flags); +#endif + if (priv->private_key_flags == NM_SETTING_SECRET_FLAG_NONE) + goto out_done; + for (i = 0; i < priv->peers_arr->len; i++) { + secret_flags = nm_wireguard_peer_get_preshared_key_flags(_peers_get(priv, i)->peer); + if (secret_flags == NM_SETTING_SECRET_FLAG_NONE) + goto out_done; + } + break; + } + + return FALSE; + +out_done: + *((gboolean *) arg) = TRUE; + return TRUE; +} + +static gboolean +get_secret_flags(NMSetting * setting, + const char * secret_name, + NMSettingSecretFlags *out_flags, + GError ** error) +{ + if (NM_STR_HAS_PREFIX(secret_name, NM_SETTING_WIREGUARD_PEERS ".")) { + NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(setting); + gs_free char * public_key_free = NULL; + const char * public_key; + PeerData * pd; + + public_key = peers_psk_get_secret_parse_a(secret_name, &public_key_free); + if (public_key && (pd = _peers_get_by_public_key(priv, public_key, FALSE))) { + NM_SET_OUT(out_flags, nm_wireguard_peer_get_preshared_key_flags(pd->peer)); + return TRUE; + } + } + + return NM_SETTING_CLASS(nm_setting_wireguard_parent_class) + ->get_secret_flags(setting, secret_name, out_flags, error); +} + +static gboolean +set_secret_flags(NMSetting * setting, + const char * secret_name, + NMSettingSecretFlags flags, + GError ** error) +{ + if (NM_STR_HAS_PREFIX(secret_name, NM_SETTING_WIREGUARD_PEERS ".")) { + NMSettingWireGuard * self = NM_SETTING_WIREGUARD(setting); + NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(self); + gs_free char * public_key_free = NULL; + const char * public_key; + PeerData * pd; + + public_key = peers_psk_get_secret_parse_a(secret_name, &public_key_free); + if (public_key && (pd = _peers_get_by_public_key(priv, public_key, FALSE))) { + if (nm_wireguard_peer_get_preshared_key_flags(pd->peer) != flags) { + nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL; + + peer = nm_wireguard_peer_new_clone(pd->peer, TRUE); + peer->preshared_key_flags = flags; + if (_peers_set(priv, peer, pd->idx, FALSE)) + _peers_notify(self); + } + + return TRUE; + } + } + + return NM_SETTING_CLASS(nm_setting_wireguard_parent_class) + ->set_secret_flags(setting, secret_name, flags, error); +} + +static void +for_each_secret(NMSetting * setting, + const char * data_key, + GVariant * data_val, + gboolean remove_non_secrets, + _NMConnectionForEachSecretFunc callback, + gpointer callback_data, + GVariantBuilder * setting_builder) +{ + NMSettingWireGuard * s_wg; + NMSettingWireGuardPrivate *priv; + GVariantBuilder peers_builder; + GVariantIter * peer_iter; + GVariantIter data_iter; + const char * key; + + if (!nm_streq(data_key, NM_SETTING_WIREGUARD_PEERS)) { + NM_SETTING_CLASS(nm_setting_wireguard_parent_class) + ->for_each_secret(setting, + data_key, + data_val, + remove_non_secrets, + callback, + callback_data, + setting_builder); + return; + } + + if (!g_variant_is_of_type(data_val, G_VARIANT_TYPE("aa{sv}"))) { + /* invalid type. Silently ignore content as we cannot find secret-keys + * here. */ + return; + } + + s_wg = NM_SETTING_WIREGUARD(setting); + priv = NM_SETTING_WIREGUARD_GET_PRIVATE(s_wg); + + g_variant_builder_init(&peers_builder, G_VARIANT_TYPE("aa{sv}")); + g_variant_iter_init(&data_iter, data_val); + while (g_variant_iter_next(&data_iter, "a{sv}", &peer_iter)) { + _nm_unused nm_auto_free_variant_iter GVariantIter *peer_iter_free = peer_iter; + gs_unref_variant GVariant *preshared_key = NULL; + PeerData * pd = NULL; + NMSettingSecretFlags secret_flags; + GVariant * val; + GVariantBuilder peer_builder; + + g_variant_builder_init(&peer_builder, G_VARIANT_TYPE("a{sv}")); + + while (g_variant_iter_next(peer_iter, "{&sv}", &key, &val)) { + _nm_unused gs_unref_variant GVariant *val_free = val; + + if (nm_streq(key, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY)) { + if (!preshared_key && g_variant_is_of_type(val, G_VARIANT_TYPE_STRING)) + preshared_key = g_variant_ref(val); + continue; + } + + if (nm_streq(key, NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY)) { + if (!pd && g_variant_is_of_type(val, G_VARIANT_TYPE_STRING)) + pd = _peers_get_by_public_key(priv, g_variant_get_string(val, NULL), TRUE); + } else if (remove_non_secrets) + continue; + + g_variant_builder_add(&peer_builder, "{sv}", key, val); + } + + if (pd && preshared_key) { + /* without specifying a public-key of an existing peer, the secret is + * ignored. */ + secret_flags = nm_wireguard_peer_get_preshared_key_flags(pd->peer); + if (callback(secret_flags, callback_data)) + g_variant_builder_add(&peer_builder, + "{sv}", + NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, + preshared_key); + } + + g_variant_builder_add(&peers_builder, "a{sv}", &peer_builder); + } + + g_variant_builder_add(setting_builder, + "{sv}", + NM_SETTING_WIREGUARD_PEERS, + g_variant_builder_end(&peers_builder)); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingWireGuard * setting = NM_SETTING_WIREGUARD(object); + NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_FWMARK: + g_value_set_uint(value, priv->fwmark); + break; + case PROP_IP4_AUTO_DEFAULT_ROUTE: + g_value_set_enum(value, priv->ip4_auto_default_route); + break; + case PROP_IP6_AUTO_DEFAULT_ROUTE: + g_value_set_enum(value, priv->ip6_auto_default_route); + break; + case PROP_LISTEN_PORT: + g_value_set_uint(value, priv->listen_port); + break; + case PROP_MTU: + g_value_set_uint(value, priv->mtu); + break; + case PROP_PEER_ROUTES: + g_value_set_boolean(value, priv->peer_routes); + break; + case PROP_PRIVATE_KEY: + g_value_set_string(value, priv->private_key); + break; + case PROP_PRIVATE_KEY_FLAGS: + g_value_set_flags(value, priv->private_key_flags); + 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) +{ + NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(object); + const char * str; + + switch (prop_id) { + case PROP_FWMARK: + priv->fwmark = g_value_get_uint(value); + break; + case PROP_IP4_AUTO_DEFAULT_ROUTE: + priv->ip4_auto_default_route = g_value_get_enum(value); + break; + case PROP_IP6_AUTO_DEFAULT_ROUTE: + priv->ip6_auto_default_route = g_value_get_enum(value); + break; + case PROP_LISTEN_PORT: + priv->listen_port = g_value_get_uint(value); + break; + case PROP_MTU: + priv->mtu = g_value_get_uint(value); + break; + case PROP_PEER_ROUTES: + priv->peer_routes = g_value_get_boolean(value); + break; + case PROP_PRIVATE_KEY: + nm_clear_pointer(&priv->private_key, nm_free_secret); + str = g_value_get_string(value); + if (str) { + if (nm_utils_base64secret_normalize(str, + NM_WIREGUARD_PUBLIC_KEY_LEN, + &priv->private_key)) + priv->private_key_valid = TRUE; + else { + priv->private_key = g_strdup(str); + priv->private_key_valid = FALSE; + } + } + break; + case PROP_PRIVATE_KEY_FLAGS: + priv->private_key_flags = g_value_get_flags(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_wireguard_init(NMSettingWireGuard *setting) +{ + NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(setting); + + priv->peers_arr = g_ptr_array_new(); + priv->peers_hash = g_hash_table_new(nm_pstr_hash, nm_pstr_equal); + priv->peer_routes = TRUE; + priv->ip4_auto_default_route = NM_TERNARY_DEFAULT; + priv->ip6_auto_default_route = NM_TERNARY_DEFAULT; +} + +/** + * nm_setting_wireguard_new: + * + * Creates a new #NMSettingWireGuard object with default values. + * + * Returns: (transfer full): the new empty #NMSettingWireGuard object + * + * Since: 1.16 + **/ +NMSetting * +nm_setting_wireguard_new(void) +{ + return g_object_new(NM_TYPE_SETTING_WIREGUARD, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(object); + + nm_free_secret(priv->private_key); + + _peers_clear(priv); + g_ptr_array_unref(priv->peers_arr); + g_hash_table_unref(priv->peers_hash); + + G_OBJECT_CLASS(nm_setting_wireguard_parent_class)->finalize(object); +} + +static void +nm_setting_wireguard_class_init(NMSettingWireGuardClass *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(); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + setting_class->verify_secrets = verify_secrets; + setting_class->need_secrets = need_secrets; + setting_class->clear_secrets = clear_secrets; + setting_class->update_one_secret = update_one_secret; + setting_class->compare_property = compare_property; + setting_class->duplicate_copy_properties = duplicate_copy_properties; + setting_class->enumerate_values = enumerate_values; + setting_class->aggregate = aggregate; + setting_class->get_secret_flags = get_secret_flags; + setting_class->set_secret_flags = set_secret_flags; + setting_class->for_each_secret = for_each_secret; + + /** + * NMSettingWireGuard:private-key: + * + * The 256 bit private-key in base64 encoding. + * + * Since: 1.16 + **/ + obj_properties[PROP_PRIVATE_KEY] = + g_param_spec_string(NM_SETTING_WIREGUARD_PRIVATE_KEY, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWireGuard:private-key-flags: + * + * Flags indicating how to handle the #NMSettingWirelessSecurity:private-key + * property. + * + * Since: 1.16 + **/ + obj_properties[PROP_PRIVATE_KEY_FLAGS] = + g_param_spec_flags(NM_SETTING_WIREGUARD_PRIVATE_KEY_FLAGS, + "", + "", + NM_TYPE_SETTING_SECRET_FLAGS, + NM_SETTING_SECRET_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWireGuard:fwmark: + * + * The use of fwmark is optional and is by default off. Setting it to 0 + * disables it. Otherwise, it is a 32-bit fwmark for outgoing packets. + * + * Note that "ip4-auto-default-route" or "ip6-auto-default-route" enabled, + * implies to automatically choose a fwmark. + * + * Since: 1.16 + **/ + obj_properties[PROP_FWMARK] = + g_param_spec_uint(NM_SETTING_WIREGUARD_FWMARK, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWireGuard:listen-port: + * + * The listen-port. If listen-port is not specified, the port will be chosen + * randomly when the interface comes up. + * + * Since: 1.16 + **/ + obj_properties[PROP_LISTEN_PORT] = + g_param_spec_uint(NM_SETTING_WIREGUARD_LISTEN_PORT, + "", + "", + 0, + 65535, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWireGuard:peer-routes: + * + * Whether to automatically add routes for the AllowedIPs ranges + * of the peers. If %TRUE (the default), NetworkManager will automatically + * add routes in the routing tables according to ipv4.route-table and + * ipv6.route-table. Usually you want this automatism enabled. + * If %FALSE, no such routes are added automatically. In this case, the + * user may want to configure static routes in ipv4.routes and ipv6.routes, + * respectively. + * + * Note that if the peer's AllowedIPs is "0.0.0.0/0" or "::/0" and the profile's + * ipv4.never-default or ipv6.never-default setting is enabled, the peer route for + * this peer won't be added automatically. + * + * Since: 1.16 + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_WIREGUARD_PEER_ROUTES, + PROP_PEER_ROUTES, + TRUE, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_wireguard_get_peer_routes); + + /** + * NMSettingWireGuard:mtu: + * + * If non-zero, only transmit packets of the specified size or smaller, + * breaking larger packets up into multiple fragments. + * + * If zero a default MTU is used. Note that contrary to wg-quick's MTU + * setting, this does not take into account the current routes at the + * time of activation. + * + * Since: 1.16 + **/ + obj_properties[PROP_MTU] = + g_param_spec_uint(NM_SETTING_WIREGUARD_MTU, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWireGuard:ip4-auto-default-route: + * + * Whether to enable special handling of the IPv4 default route. + * If enabled, the IPv4 default route from wireguard.peer-routes + * will be placed to a dedicated routing-table and two policy routing rules + * will be added. The fwmark number is also used as routing-table for the default-route, + * and if fwmark is zero, an unused fwmark/table is chosen automatically. + * This corresponds to what wg-quick does with Table=auto and what WireGuard + * calls "Improved Rule-based Routing". + * + * Note that for this automatism to work, you usually don't want to set + * ipv4.gateway, because that will result in a conflicting default route. + * + * Leaving this at the default will enable this option automatically + * if ipv4.never-default is not set and there are any peers that use + * a default-route as allowed-ips. + * + * Since: 1.20 + **/ + obj_properties[PROP_IP4_AUTO_DEFAULT_ROUTE] = g_param_spec_enum( + NM_SETTING_WIREGUARD_IP4_AUTO_DEFAULT_ROUTE, + "", + "", + NM_TYPE_TERNARY, + NM_TERNARY_DEFAULT, + NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWireGuard:ip6-auto-default-route: + * + * Like ip4-auto-default-route, but for the IPv6 default route. + * + * Since: 1.20 + **/ + obj_properties[PROP_IP6_AUTO_DEFAULT_ROUTE] = g_param_spec_enum( + NM_SETTING_WIREGUARD_IP6_AUTO_DEFAULT_ROUTE, + "", + "", + NM_TYPE_TERNARY, + NM_TERNARY_DEFAULT, + NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /* ---dbus--- + * property: peers + * format: array of 'a{sv}' + * description: Array of dictionaries for the WireGuard peers. + * ---end--- + */ + _nm_properties_override_dbus( + properties_override, + NM_SETTING_WIREGUARD_PEERS, + NM_SETT_INFO_PROPERT_TYPE_DBUS(NM_G_VARIANT_TYPE("aa{sv}"), + .to_dbus_fcn = _peers_dbus_only_synth, + .from_dbus_fcn = _peers_dbus_only_set, )); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_WIREGUARD, + NULL, + properties_override); +} |