diff options
author | Thomas Haller <thaller@redhat.com> | 2019-01-30 12:36:13 +0100 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2019-02-22 11:00:10 +0100 |
commit | e148ec07d5d3c1e5d570913006cddc6ca060e3d9 (patch) | |
tree | cf81b81caa2e94694ad2ed86f0dc40d743b3065e /libnm-core | |
parent | b521f426ab228b003e1e87348e116291d23dc88b (diff) | |
download | NetworkManager-e148ec07d5d3c1e5d570913006cddc6ca060e3d9.tar.gz |
libnm: add NMWireGuardPeer and libnm support for peers
Diffstat (limited to 'libnm-core')
-rw-r--r-- | libnm-core/nm-core-internal.h | 12 | ||||
-rw-r--r-- | libnm-core/nm-keyfile-utils.h | 3 | ||||
-rw-r--r-- | libnm-core/nm-keyfile.c | 228 | ||||
-rw-r--r-- | libnm-core/nm-setting-wireguard.c | 1964 | ||||
-rw-r--r-- | libnm-core/nm-setting-wireguard.h | 121 | ||||
-rw-r--r-- | libnm-core/nm-setting.c | 2 |
6 files changed, 2321 insertions, 9 deletions
diff --git a/libnm-core/nm-core-internal.h b/libnm-core/nm-core-internal.h index 54d852a7fd..4a5796bd6e 100644 --- a/libnm-core/nm-core-internal.h +++ b/libnm-core/nm-core-internal.h @@ -634,6 +634,15 @@ NM_AUTO_DEFINE_FCN_VOID0 (NMSockAddrEndpoint *, _nm_auto_unref_sockaddrendpoint, /*****************************************************************************/ +NMSockAddrEndpoint *_nm_wireguard_peer_get_endpoint (const NMWireGuardPeer *self); +void _nm_wireguard_peer_set_endpoint (NMWireGuardPeer *self, + NMSockAddrEndpoint *endpoint); + +void _nm_wireguard_peer_set_public_key_bin (NMWireGuardPeer *self, + const guint8 public_key[static NM_WIREGUARD_PUBLIC_KEY_LEN]); + +/*****************************************************************************/ + typedef struct _NMSettInfoSetting NMSettInfoSetting; typedef struct _NMSettInfoProperty NMSettInfoProperty; @@ -769,6 +778,9 @@ gboolean _nm_connection_find_secret (NMConnection *self, /*****************************************************************************/ +#define nm_auto_unref_wgpeer nm_auto(_nm_auto_unref_wgpeer) +NM_AUTO_DEFINE_FCN_VOID0 (NMWireGuardPeer *, _nm_auto_unref_wgpeer, nm_wireguard_peer_unref) + gboolean _nm_utils_wireguard_decode_key (const char *base64_key, gsize required_key_len, guint8 *out_key); diff --git a/libnm-core/nm-keyfile-utils.h b/libnm-core/nm-keyfile-utils.h index 0467230e49..9403dfa3a8 100644 --- a/libnm-core/nm-keyfile-utils.h +++ b/libnm-core/nm-keyfile-utils.h @@ -25,7 +25,8 @@ #error Cannot use this header. #endif -#define NM_KEYFILE_GROUP_VPN_SECRETS "vpn-secrets" +#define NM_KEYFILE_GROUP_VPN_SECRETS "vpn-secrets" +#define NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER "wireguard-peer." const char *nm_keyfile_plugin_get_alias_for_setting_name (const char *setting_name); diff --git a/libnm-core/nm-keyfile.c b/libnm-core/nm-keyfile.c index b81020a3bc..d756a17733 100644 --- a/libnm-core/nm-keyfile.c +++ b/libnm-core/nm-keyfile.c @@ -32,6 +32,7 @@ #include <linux/pkt_sched.h> #include "nm-utils/nm-secret-utils.h" +#include "systemd/nm-sd-utils-shared.h" #include "nm-common-macros.h" #include "nm-core-internal.h" #include "nm-keyfile-utils.h" @@ -2902,6 +2903,137 @@ out: } static void +_read_setting_wireguard_peer (KeyfileReaderInfo *info) +{ + gs_unref_object NMSettingWireGuard *s_wg_new = NULL; + nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL; + gs_free_error GError *error = NULL; + NMSettingWireGuard *s_wg; + gs_free char *str = NULL; + const char *cstr = NULL; + const char *key; + gint64 i64; + gs_strfreev char **sa = NULL; + gsize n_sa; + + peer = nm_wireguard_peer_new (); + + nm_assert (g_str_has_prefix (info->group, NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER)); + cstr = &info->group[NM_STRLEN (NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER)]; + if ( !_nm_utils_wireguard_normalize_key (cstr, NM_WIREGUARD_PUBLIC_KEY_LEN, &str) + || !nm_streq0 (str, cstr)) { + /* the group name must be identical to the normalized(!) key, so that it + * is uniquely identified. */ + handle_warn (info, NULL, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid peer public key in section '%s'"), + info->group); + return; + } + nm_wireguard_peer_set_public_key (peer, cstr); + nm_clear_g_free (&str); + + key = NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY; + str = nm_keyfile_plugin_kf_get_string (info->keyfile, info->group, key, NULL); + if (str) { + if (!_nm_utils_wireguard_decode_key (str, NM_WIREGUARD_SYMMETRIC_KEY_LEN, NULL)) { + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' is not not a valid 256 bit key in base64 encoding"), + info->group, key)) + return; + } else + nm_wireguard_peer_set_preshared_key (peer, str); + nm_clear_g_free (&str); + } + + key = NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS; + i64 = nm_keyfile_plugin_kf_get_int64 (info->keyfile, info->group, key, 0, 0, NM_SETTING_SECRET_FLAG_ALL, -1, NULL); + if (errno != ENODATA) { + if ( i64 == -1 + || !_nm_setting_secret_flags_valid (i64)) { + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' is not not a valid secret flag"), + info->group, key)) + return; + } else + nm_wireguard_peer_set_preshared_key_flags (peer, i64); + } + + key = NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE; + i64 = nm_keyfile_plugin_kf_get_int64 (info->keyfile, info->group, key, 0, 0, G_MAXUINT32, -1, NULL); + if (errno != ENODATA) { + if (i64 == -1) { + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' is not not a integer in range 0 to 2^32"), + info->group, key)) + return; + } else + nm_wireguard_peer_set_persistent_keepalive (peer, i64); + } + + key = NM_WIREGUARD_PEER_ATTR_ENDPOINT; + str = nm_keyfile_plugin_kf_get_string (info->keyfile, info->group, key, NULL); + if (str && str[0]) { + nm_auto_unref_sockaddrendpoint NMSockAddrEndpoint *ep = NULL; + + ep = nm_sock_addr_endpoint_new (str); + if (!nm_sock_addr_endpoint_get_host (ep)) { + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' is not not a valid endpoint"), + info->group, key)) + return; + } else + _nm_wireguard_peer_set_endpoint (peer, ep); + } + nm_clear_g_free (&str); + + key = NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS; + sa = nm_keyfile_plugin_kf_get_string_list (info->keyfile, info->group, key, &n_sa, NULL); + if (n_sa > 0) { + gboolean has_error = FALSE; + gsize i; + + for (i = 0; i < n_sa; i++) { + if (!nm_utils_parse_inaddr_prefix_bin (AF_UNSPEC, sa[i], NULL, NULL, NULL)) { + has_error = TRUE; + continue; + } + nm_wireguard_peer_append_allowed_ip (peer, sa[i], TRUE); + } + if (has_error) { + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' has invalid allowed-ips"), + info->group, key)) + return; + } + } + nm_clear_pointer (&sa, g_strfreev); + + if (info->error) + return; + + if (!nm_wireguard_peer_is_valid (peer, TRUE, TRUE, &error)) { + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("peer '%s' is invalid: %s"), + info->group, error->message)) + return; + return; + } + + s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (info->connection, NM_TYPE_SETTING_WIREGUARD)); + if (!s_wg) { + s_wg_new = NM_SETTING_WIREGUARD (nm_setting_wireguard_new ()); + s_wg = s_wg_new; + } + + nm_setting_wireguard_append_peer (s_wg, peer); + + if (s_wg_new) { + nm_connection_add_setting (info->connection, + NM_SETTING (g_steal_pointer (&s_wg_new))); + } +} + +static void _read_setting_vpn_secrets (KeyfileReaderInfo *info) { gs_strfreev char **keys = NULL; @@ -3021,7 +3153,9 @@ nm_keyfile_read (GKeyFile *keyfile, if (nm_streq (groups[i], NM_KEYFILE_GROUP_VPN_SECRETS)) { /* Only read out secrets when needed */ vpn_secrets = TRUE; - } else + } else if (NM_STR_HAS_PREFIX (groups[i], NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER)) + _read_setting_wireguard_peer (&info); + else _read_setting (&info); info.group = NULL; @@ -3198,6 +3332,92 @@ out_unset_value: g_value_unset (&value); } +static void +_write_setting_wireguard (NMSetting *setting, KeyfileWriterInfo *info) +{ + NMSettingWireGuard *s_wg; + guint i_peer, n_peers; + + s_wg = NM_SETTING_WIREGUARD (setting); + + n_peers = nm_setting_wireguard_get_peers_len (s_wg); + for (i_peer = 0; i_peer < n_peers; i_peer++) { + NMWireGuardPeer *peer = nm_setting_wireguard_get_peer (s_wg, i_peer); + const char *public_key; + char group[NM_STRLEN (NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER) + 200]; + NMSettingSecretFlags secret_flags; + gboolean any_key = FALSE; + guint i_aip, n_aip; + const char *cstr; + guint32 u32; + + public_key = nm_wireguard_peer_get_public_key (peer); + if ( !public_key + || !public_key[0] + || !NM_STRCHAR_ALL (public_key, ch, nm_sd_utils_unbase64char (ch, TRUE) >= 0)) { + /* invalid peer. Skip it */ + continue; + } + + if (g_snprintf (group, + sizeof (group), + "%s%s", + NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER, + nm_wireguard_peer_get_public_key (peer)) >= sizeof (group)) { + /* Too long. Not a valid public key. Skip the peer. */ + continue; + } + + cstr = nm_wireguard_peer_get_endpoint (peer); + if (cstr) { + g_key_file_set_string (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ENDPOINT, cstr); + any_key = TRUE; + } + + secret_flags = nm_wireguard_peer_get_preshared_key_flags (peer); + if (_secret_flags_persist_secret (secret_flags)) { + cstr = nm_wireguard_peer_get_preshared_key (peer); + if (cstr) { + g_key_file_set_string (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, cstr); + any_key = TRUE; + } + } + + /* usually, we don't persist the secret-flags 0 (because they are the default). + * For WireGuard peers, the default secret-flags for preshared-key are 4 (not-required). + * So, in this case behave differently: a missing preshared-key-flag setting means + * "not-required". */ + if (secret_flags != NM_SETTING_SECRET_FLAG_NOT_REQUIRED) { + g_key_file_set_int64 (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS, secret_flags); + any_key = TRUE; + } + + u32 = nm_wireguard_peer_get_persistent_keepalive (peer); + if (u32) { + g_key_file_set_uint64 (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE, u32); + any_key = TRUE; + } + + n_aip = nm_wireguard_peer_get_allowed_ips_len (peer); + if (n_aip > 0) { + gs_free const char **strv = NULL; + + strv = g_new (const char *, ((gsize) n_aip) + 1); + for (i_aip = 0; i_aip < n_aip; i_aip++) + strv[i_aip] = nm_wireguard_peer_get_allowed_ip (peer, i_aip, NULL); + strv[n_aip] = NULL; + g_key_file_set_string_list (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS, + strv, n_aip); + any_key = TRUE; + } + + if (!any_key) { + /* we cannot omit all keys. At an empty endpoint. */ + g_key_file_set_string (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ENDPOINT, ""); + } + } +} + GKeyFile * nm_keyfile_write (NMConnection *connection, NMKeyfileWriteHandler handler, @@ -3275,6 +3495,12 @@ nm_keyfile_write (NMConnection *connection, goto out_with_info_error; } + if (NM_IS_SETTING_WIREGUARD (setting)) { + _write_setting_wireguard (setting, &info); + if (info.error) + goto out_with_info_error; + } + nm_assert (!info.error); } diff --git a/libnm-core/nm-setting-wireguard.c b/libnm-core/nm-setting-wireguard.c index 724dda55d1..19b418547d 100644 --- a/libnm-core/nm-setting-wireguard.c +++ b/libnm-core/nm-setting-wireguard.c @@ -38,6 +38,817 @@ /*****************************************************************************/ +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 expception, 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 accociate 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. + * + * 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. + * + * Since: 1.16 + */ +void +nm_wireguard_peer_set_public_key (NMWireGuardPeer *self, + const char *public_key) +{ + char *public_key_normalized = NULL; + + g_return_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE)); + + if (!public_key) { + nm_clear_g_free (&self->public_key); + return; + } + + self->public_key_valid = _nm_utils_wireguard_normalize_key (public_key, + NM_WIREGUARD_PUBLIC_KEY_LEN, + &public_key_normalized); + nm_assert (self->public_key_valid == (public_key_normalized != NULL)); + + g_free (self->public_key); + self->public_key = public_key_normalized ?: g_strdup (public_key); +} + +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. + * + * 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. + * + * Since: 1.16 + */ +void +nm_wireguard_peer_set_preshared_key (NMWireGuardPeer *self, + const char *preshared_key) +{ + char *preshared_key_normalized = NULL; + + g_return_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE)); + + if (!preshared_key) { + nm_clear_pointer (&self->preshared_key, nm_free_secret); + return; + } + + self->preshared_key_valid = _nm_utils_wireguard_normalize_key (preshared_key, + NM_WIREGUARD_SYMMETRIC_KEY_LEN, + &preshared_key_normalized); + nm_assert (self->preshared_key_valid == (preshared_key_normalized != NULL)); + + nm_free_secret (self->preshared_key); + self->preshared_key = preshared_key_normalized ?: g_strdup (preshared_key); +} + +/** + * 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. + * + * Sets or clears the endpoint of @self. + * + * It is a bug trying to modify a sealed #NMWireGuardPeer instance. + * + * Since: 1.16 + */ +void +nm_wireguard_peer_set_endpoint (NMWireGuardPeer *self, + const char *endpoint) +{ + NMSockAddrEndpoint *old; + + g_return_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE)); + + old = self->endpoint; + self->endpoint = endpoint + ? nm_sock_addr_endpoint_new (endpoint) + : NULL; + nm_sock_addr_endpoint_unref (old); +} + +/** + * 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. + * + * 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; + + g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), NULL); + + if ( !self->allowed_ips + || idx >= self->allowed_ips->len) { + NM_SET_OUT (out_is_valid, FALSE); + 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; + + 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); + } 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 TRUE; +} + +/** + * 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_PRIVATE_KEY, PROP_PRIVATE_KEY_FLAGS, @@ -47,6 +858,8 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE ( typedef struct { char *private_key; + GPtrArray *peers_arr; + GHashTable *peers_hash; NMSettingSecretFlags private_key_flags; guint32 fwmark; guint16 listen_port; @@ -75,6 +888,32 @@ G_DEFINE_TYPE (NMSettingWireGuard, nm_setting_wireguard, NM_TYPE_SETTING) /*****************************************************************************/ +#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 @@ -141,10 +980,597 @@ nm_setting_wireguard_get_listen_port (NMSettingWireGuard *self) /*****************************************************************************/ +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_wireguard_normalize_key (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) +{ + 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_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_ONLY_SECRETS) + && 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_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_NO_SECRETS) + && peer->preshared_key) + g_variant_builder_add (&builder, "{sv}", NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, g_variant_new_string (peer->preshared_key)); + + if ( !NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_ONLY_SECRETS) + && 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_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_ONLY_SECRETS) + && 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_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_ONLY_SECRETS) + && 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++) { + if (strv[i][0] != ALLOWED_IP_INVALID_X) + 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 (); + nm_wireguard_peer_set_public_key (peer, cstr); + if (!peer->public_key_valid) { + 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); + + 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; @@ -156,6 +1582,19 @@ verify (NMSetting *setting, NMConnection *connection, GError **error) 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; @@ -201,6 +1640,7 @@ 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) { @@ -210,6 +1650,19 @@ verify_secrets (NMSetting *setting, NMConnection *connection, GError **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, FALSE, TRUE, error)) { + g_prefix_error (error, + "%s.%s[%u]: ", + NM_SETTING_WIREGUARD_SETTING_NAME, + NM_SETTING_WIREGUARD_PEERS, + i); + return FALSE; + } + } + return TRUE; } @@ -218,16 +1671,485 @@ 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, NULL); - g_ptr_array_add (secrets, NM_SETTING_WIREGUARD_PRIVATE_KEY); + 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; + + /* only stack-allocate (alloca) a few times. */ + if (j++ < 5) + name = peers_psk_get_secret_name_a (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; + PeerData *pd; + NMWireGuardPeer *peer; + 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); + + 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, + NMSetting *setting, + NMSetting *other, + 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 (!other) + return TRUE; + + a_priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting); + b_priv = NM_SETTING_WIREGUARD_GET_PRIVATE (other); + + 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, + setting, + other, + 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 @@ -298,6 +2220,10 @@ set_property (GObject *object, guint prop_id, 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); } /** @@ -322,6 +2248,10 @@ finalize (GObject *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); } @@ -330,14 +2260,24 @@ 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->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: @@ -398,7 +2338,19 @@ nm_setting_wireguard_class_init (NMSettingWireGuardClass *klass) | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + /* ---dbus--- + * property: peers + * format: array of 'a{sv}' + * description: Array of dictionaries for the WireGuard peers. + * ---end--- + */ + _properties_override_add_dbus_only (properties_override, + NM_SETTING_WIREGUARD_PEERS, + G_VARIANT_TYPE ("aa{sv}"), + _peers_dbus_only_synth, + _peers_dbus_only_set); + g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties); - _nm_setting_class_commit (setting_class, NM_META_SETTING_TYPE_WIREGUARD); + _nm_setting_class_commit_full (setting_class, NM_META_SETTING_TYPE_WIREGUARD, NULL, properties_override); } diff --git a/libnm-core/nm-setting-wireguard.h b/libnm-core/nm-setting-wireguard.h index 1127b11382..3810aa3048 100644 --- a/libnm-core/nm-setting-wireguard.h +++ b/libnm-core/nm-setting-wireguard.h @@ -25,6 +25,7 @@ #endif #include "nm-setting.h" +#include "nm-utils.h" G_BEGIN_DECLS @@ -35,6 +36,87 @@ G_BEGIN_DECLS /*****************************************************************************/ +typedef struct _NMWireGuardPeer NMWireGuardPeer; + +NM_AVAILABLE_IN_1_16 +GType nm_wireguard_peer_get_type (void); + +NM_AVAILABLE_IN_1_16 +NMWireGuardPeer *nm_wireguard_peer_new (void); + +NM_AVAILABLE_IN_1_16 +NMWireGuardPeer *nm_wireguard_peer_new_clone (const NMWireGuardPeer *self, + gboolean with_secrets); + +NM_AVAILABLE_IN_1_16 +NMWireGuardPeer *nm_wireguard_peer_ref (NMWireGuardPeer *self); +NM_AVAILABLE_IN_1_16 +void nm_wireguard_peer_unref (NMWireGuardPeer *self); + +NM_AVAILABLE_IN_1_16 +void nm_wireguard_peer_seal (NMWireGuardPeer *self); +NM_AVAILABLE_IN_1_16 +gboolean nm_wireguard_peer_is_sealed (const NMWireGuardPeer *self); + +NM_AVAILABLE_IN_1_16 +const char *nm_wireguard_peer_get_public_key (const NMWireGuardPeer *self); +NM_AVAILABLE_IN_1_16 +void nm_wireguard_peer_set_public_key (NMWireGuardPeer *self, + const char *public_key); + +NM_AVAILABLE_IN_1_16 +const char *nm_wireguard_peer_get_preshared_key (const NMWireGuardPeer *self); +NM_AVAILABLE_IN_1_16 +void nm_wireguard_peer_set_preshared_key (NMWireGuardPeer *self, + const char *preshared_key); + +NM_AVAILABLE_IN_1_16 +NMSettingSecretFlags nm_wireguard_peer_get_preshared_key_flags (const NMWireGuardPeer *self); +NM_AVAILABLE_IN_1_16 +void nm_wireguard_peer_set_preshared_key_flags (NMWireGuardPeer *self, + NMSettingSecretFlags preshared_key_flags); + +NM_AVAILABLE_IN_1_16 +guint16 nm_wireguard_peer_get_persistent_keepalive (const NMWireGuardPeer *self); +NM_AVAILABLE_IN_1_16 +void nm_wireguard_peer_set_persistent_keepalive (NMWireGuardPeer *self, + guint16 persistent_keepalive); + +NM_AVAILABLE_IN_1_16 +const char *nm_wireguard_peer_get_endpoint (const NMWireGuardPeer *self); +NM_AVAILABLE_IN_1_16 +void nm_wireguard_peer_set_endpoint (NMWireGuardPeer *self, + const char *endpoint); + +NM_AVAILABLE_IN_1_16 +guint nm_wireguard_peer_get_allowed_ips_len (const NMWireGuardPeer *self); +NM_AVAILABLE_IN_1_16 +const char *nm_wireguard_peer_get_allowed_ip (const NMWireGuardPeer *self, + guint idx, + gboolean *out_is_valid); +NM_AVAILABLE_IN_1_16 +void nm_wireguard_peer_clear_allowed_ips (NMWireGuardPeer *self); +NM_AVAILABLE_IN_1_16 +gboolean nm_wireguard_peer_append_allowed_ip (NMWireGuardPeer *self, + const char *allowed_ip, + gboolean accept_invalid); +NM_AVAILABLE_IN_1_16 +gboolean nm_wireguard_peer_remove_allowed_ip (NMWireGuardPeer *self, + guint idx); + +NM_AVAILABLE_IN_1_16 +gboolean nm_wireguard_peer_is_valid (const NMWireGuardPeer *self, + gboolean check_non_secrets, + gboolean check_secrets, + GError **error); + +NM_AVAILABLE_IN_1_16 +int nm_wireguard_peer_cmp (const NMWireGuardPeer *a, + const NMWireGuardPeer *b, + NMSettingCompareFlags compare_flags); + +/*****************************************************************************/ + #define NM_TYPE_SETTING_WIREGUARD (nm_setting_wireguard_get_type ()) #define NM_SETTING_WIREGUARD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_SETTING_WIREGUARD, NMSettingWireGuard)) #define NM_SETTING_WIREGUARD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_SETTING_WIREGUARD, NMSettingWireGuardClass)) @@ -49,6 +131,15 @@ G_BEGIN_DECLS #define NM_SETTING_WIREGUARD_LISTEN_PORT "listen-port" #define NM_SETTING_WIREGUARD_FWMARK "fwmark" +#define NM_SETTING_WIREGUARD_PEERS "peers" + +#define NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY "public-key" +#define NM_WIREGUARD_PEER_ATTR_ENDPOINT "endpoint" +#define NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY "preshared-key" +#define NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS "preshared-key-flags" +#define NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS "allowed-ips" +#define NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE "persistent-keepalive" + /*****************************************************************************/ typedef struct _NMSettingWireGuardClass NMSettingWireGuardClass; @@ -75,6 +166,36 @@ guint32 nm_setting_wireguard_get_fwmark (NMSettingWireGuard *self); /*****************************************************************************/ +NM_AVAILABLE_IN_1_16 +guint nm_setting_wireguard_get_peers_len (NMSettingWireGuard *self); + +NM_AVAILABLE_IN_1_16 +NMWireGuardPeer *nm_setting_wireguard_get_peer (NMSettingWireGuard *self, + guint idx); + +NM_AVAILABLE_IN_1_16 +NMWireGuardPeer *nm_setting_wireguard_get_peer_by_public_key (NMSettingWireGuard *self, + const char *public_key, + guint *out_idx); + +NM_AVAILABLE_IN_1_16 +void nm_setting_wireguard_set_peer (NMSettingWireGuard *self, + NMWireGuardPeer *peer, + guint idx); + +NM_AVAILABLE_IN_1_16 +void nm_setting_wireguard_append_peer (NMSettingWireGuard *self, + NMWireGuardPeer *peer); + +NM_AVAILABLE_IN_1_16 +gboolean nm_setting_wireguard_remove_peer (NMSettingWireGuard *self, + guint idx); + +NM_AVAILABLE_IN_1_16 +guint nm_setting_wireguard_clear_peers (NMSettingWireGuard *self); + +/*****************************************************************************/ + G_END_DECLS #endif /* __NM_SETTING_WIREGUARD_H__ */ diff --git a/libnm-core/nm-setting.c b/libnm-core/nm-setting.c index 269f83175c..065aad2b3d 100644 --- a/libnm-core/nm-setting.c +++ b/libnm-core/nm-setting.c @@ -2067,7 +2067,7 @@ _nm_setting_update_secrets (NMSetting *setting, GVariant *secrets, GError **erro int success; success = NM_SETTING_GET_CLASS (setting)->update_one_secret (setting, secret_key, secret_value, &tmp_error); - g_assert (!((success == NM_SETTING_UPDATE_SECRET_ERROR) ^ (!!tmp_error))); + nm_assert (!((success == NM_SETTING_UPDATE_SECRET_ERROR) ^ (!!tmp_error))); g_variant_unref (secret_value); |