about summary refs log tree commit diff
path: root/libnm-core/nm-utils.c
diff options
context:
space:
mode:
authorSebastien Bacher <seb128@ubuntu.com>2019-03-12 15:13:33 +0100
committerSebastien Bacher <seb128@ubuntu.com>2019-03-12 15:13:33 +0100
commitdd428301eb6f02542015121d7b08d9997f137e50 (patch)
tree5530189f63510287d65268fc36025bdbc9414c00 /libnm-core/nm-utils.c
parentbbae86d3d2997a853ca0365e8eb7a3ca7489ee09 (diff)
New upstream version 1.15.91
Diffstat (limited to 'libnm-core/nm-utils.c')
-rw-r--r--libnm-core/nm-utils.c677
1 files changed, 458 insertions, 219 deletions
diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c
index 47099712..d276cfe6 100644
--- a/libnm-core/nm-utils.c
+++ b/libnm-core/nm-utils.c
@@ -23,8 +23,6 @@
 
 #include "nm-utils.h"
 
-#include <string.h>
-#include <errno.h>
 #include <stdlib.h>
 #include <netinet/ether.h>
 #include <arpa/inet.h>
@@ -40,6 +38,8 @@
 #endif
 
 #include "nm-utils/nm-enum-utils.h"
+#include "nm-utils/nm-secret-utils.h"
+#include "systemd/nm-sd-utils-shared.h"
 #include "nm-common-macros.h"
 #include "nm-utils-private.h"
 #include "nm-setting-private.h"
@@ -61,6 +61,277 @@
  * access points and devices, among other things.
  */
 
+/*****************************************************************************/
+
+struct _NMSockAddrEndpoint {
+	const char *host;
+	guint16 port;
+	guint refcount;
+	char endpoint[];
+};
+
+static gboolean
+NM_IS_SOCK_ADDR_ENDPOINT (const NMSockAddrEndpoint *self)
+{
+	return self && self->refcount > 0;
+}
+
+static const char *
+_parse_endpoint (char *str,
+                 guint16 *out_port)
+{
+	char *s;
+	const char *s_port;
+	gint16 port;
+
+	/* Like
+	 * - https://git.zx2c4.com/WireGuard/tree/src/tools/config.c?id=5e99a6d43fe2351adf36c786f5ea2086a8fe7ab8#n192
+	 * - https://github.com/systemd/systemd/blob/911649fdd43f3a9158b847947724a772a5a45c34/src/network/netdev/wireguard.c#L614
+	 */
+
+	g_strstrip (str);
+
+	if (!str[0])
+		return NULL;
+
+	if (str[0] == '[') {
+		str++;
+		s = strchr (str, ']');
+		if (!s)
+			return NULL;
+		if (s == str)
+			return NULL;
+		if (s[1] != ':')
+			return NULL;
+		if (!s[2])
+			return NULL;
+		*s = '\0';
+		s_port = &s[2];
+	} else {
+		s = strrchr (str, ':');
+		if (!s)
+			return NULL;
+		if (s == str)
+			return NULL;
+		if (!s[1])
+			return NULL;
+		*s = '\0';
+		s_port = &s[1];
+	}
+
+	if (!NM_STRCHAR_ALL (s_port, ch, (ch >= '0' && ch <= '9')))
+		return NULL;
+
+	port = _nm_utils_ascii_str_to_int64 (s_port, 10, 1, G_MAXUINT16, 0);
+	if (port == 0)
+		return NULL;
+
+	*out_port = port;
+	return str;
+}
+
+/**
+ * nm_sock_addr_endpoint_new:
+ * @endpoint: the endpoint string.
+ *
+ * This function cannot fail, even if the @endpoint is invalid.
+ * The reason is to allow NMSockAddrEndpoint also to be used
+ * for tracking invalid endpoints. Use nm_sock_addr_endpoint_get_host()
+ * to determine whether the endpoint is valid.
+ *
+ * Returns: (transfer full): the new #NMSockAddrEndpoint endpoint.
+ */
+NMSockAddrEndpoint *
+nm_sock_addr_endpoint_new (const char *endpoint)
+{
+	NMSockAddrEndpoint *ep;
+	gsize l_endpoint;
+	gsize l_host = 0;
+	gsize i;
+	gs_free char *host_clone = NULL;
+	const char *host;
+	guint16 port;
+
+	g_return_val_if_fail (endpoint, NULL);
+
+	l_endpoint = strlen (endpoint) + 1;
+
+	host = _parse_endpoint (nm_strndup_a (200, endpoint, l_endpoint - 1, &host_clone),
+	                        &port);
+
+	if (host)
+		l_host = strlen (host) + 1;
+
+	ep = g_malloc (sizeof (NMSockAddrEndpoint) + l_endpoint + l_host);
+	ep->refcount = 1;
+	memcpy (ep->endpoint, endpoint, l_endpoint);
+	if (host) {
+		i = l_endpoint;
+		memcpy (&ep->endpoint[i], host, l_host);
+		ep->host = &ep->endpoint[i];
+		ep->port = port;
+	} else {
+		ep->host = NULL;
+		ep->port = 0;
+	}
+	return ep;
+}
+
+/**
+ * nm_sock_addr_endpoint_ref:
+ * @self: (allow-none): the #NMSockAddrEndpoint
+ */
+NMSockAddrEndpoint *
+nm_sock_addr_endpoint_ref (NMSockAddrEndpoint *self)
+{
+	if (!self)
+		return NULL;
+
+	g_return_val_if_fail (NM_IS_SOCK_ADDR_ENDPOINT (self), NULL);
+
+	nm_assert (self->refcount < G_MAXUINT);
+
+	self->refcount++;
+	return self;
+}
+
+/**
+ * nm_sock_addr_endpoint_unref:
+ * @self: (allow-none): the #NMSockAddrEndpoint
+ */
+void
+nm_sock_addr_endpoint_unref (NMSockAddrEndpoint *self)
+{
+	if (!self)
+		return;
+
+	g_return_if_fail (NM_IS_SOCK_ADDR_ENDPOINT (self));
+
+	if (--self->refcount == 0)
+		g_free (self);
+}
+
+/**
+ * nm_sock_addr_endpoint_get_endpoint:
+ * @self: the #NMSockAddrEndpoint
+ *
+ * Gives the endpoint string. Since #NMSockAddrEndpoint's only
+ * information is the endpoint string, this can be used for comparing
+ * to instances for equality and order them lexically.
+ *
+ * Returns: (transfer none): the endpoint.
+ */
+const char *
+nm_sock_addr_endpoint_get_endpoint (NMSockAddrEndpoint *self)
+{
+	g_return_val_if_fail (NM_IS_SOCK_ADDR_ENDPOINT (self), NULL);
+
+	return self->endpoint;
+}
+
+/**
+ * nm_sock_addr_endpoint_get_host:
+ * @self: the #NMSockAddrEndpoint
+ *
+ * Returns: (transfer none): the parsed host part of the endpoint.
+ *   If the endpoint is invalid, %NULL will be returned.
+ */
+const char *
+nm_sock_addr_endpoint_get_host (NMSockAddrEndpoint *self)
+{
+	g_return_val_if_fail (NM_IS_SOCK_ADDR_ENDPOINT (self), NULL);
+
+	return self->host;
+}
+
+/**
+ * nm_sock_addr_endpoint_get_port:
+ * @self: the #NMSockAddrEndpoint
+ *
+ * Returns: the parsed port part of the endpoint (the service).
+ *   If the endpoint is invalid, -1 will be returned.
+ */
+gint32
+nm_sock_addr_endpoint_get_port (NMSockAddrEndpoint *self)
+{
+	g_return_val_if_fail (NM_IS_SOCK_ADDR_ENDPOINT (self), -1);
+
+	return self->host ? (int) self->port : -1;
+}
+
+gboolean
+nm_sock_addr_endpoint_get_fixed_sockaddr (NMSockAddrEndpoint *self,
+                                          gpointer sockaddr)
+{
+	int addr_family;
+	NMIPAddr addrbin;
+	const char *s;
+	guint scope_id = 0;
+
+	g_return_val_if_fail (NM_IS_SOCK_ADDR_ENDPOINT (self), FALSE);
+	g_return_val_if_fail (sockaddr, FALSE);
+
+	if (!self->host)
+		return FALSE;
+
+	if (nm_utils_parse_inaddr_bin (AF_UNSPEC, self->host, &addr_family, &addrbin))
+		goto good;
+
+	/* See if there is an IPv6 scope-id...
+	 *
+	 * Note that it does not make sense to persist connection profiles to disk,
+	 * that refenrence a scope-id (because the interface's ifindex changes on
+	 * reboot). However, we also support runtime only changes like `nmcli device modify`
+	 * where nothing is persisted to disk. At least in that case, passing a scope-id
+	 * might be reasonable. So, parse that too. */
+	s = strchr (self->host, '%');
+	if (!s)
+		return FALSE;
+
+	if (   s[1] == '\0'
+	    || !NM_STRCHAR_ALL (&s[1], ch, (ch >= '0' && ch <= '9')))
+		return FALSE;
+
+	scope_id = _nm_utils_ascii_str_to_int64 (&s[1], 10, 0, G_MAXINT32, G_MAXUINT);
+	if (scope_id == G_MAXUINT && errno)
+		return FALSE;
+
+	{
+		gs_free char *tmp_str = NULL;
+		const char *host_part;
+
+		host_part = nm_strndup_a (200, self->host, s - self->host, &tmp_str);
+		if (nm_utils_parse_inaddr_bin (AF_INET6, host_part, &addr_family, &addrbin))
+			goto good;
+	}
+
+	return FALSE;
+
+good:
+	switch (addr_family) {
+	case AF_INET:
+		*((struct sockaddr_in *) sockaddr) = (struct sockaddr_in) {
+			.sin_family = AF_INET,
+			.sin_addr   = addrbin.addr4_struct,
+			.sin_port   = htons (self->port),
+		};
+		return TRUE;
+	case AF_INET6:
+		*((struct sockaddr_in6 *) sockaddr) = (struct sockaddr_in6) {
+			.sin6_family   = AF_INET6,
+			.sin6_addr     = addrbin.addr6,
+			.sin6_port     = htons (self->port),
+			.sin6_scope_id = scope_id,
+			.sin6_flowinfo = 0,
+		};
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+/*****************************************************************************/
+
 struct IsoLangToEncodings
 {
 	const char *lang;
@@ -2892,7 +3163,7 @@ nm_utils_uuid_generate_from_string_bin (NMUuid *uuid, const char *s, gssize slen
 		break;
 	case NM_UTILS_UUID_TYPE_VERSION3:
 	case NM_UTILS_UUID_TYPE_VERSION5: {
-		NMUuid ns_uuid = { 0 };
+		NMUuid ns_uuid = { };
 
 		if (type_args) {
 			/* type_args can be a name space UUID. Interpret it as (char *) */
@@ -3107,7 +3378,7 @@ _nm_utils_check_file (const char *filename,
 		g_set_error (error,
 		             NM_VPN_PLUGIN_ERROR,
 		             NM_VPN_PLUGIN_ERROR_FAILED,
-		             _("failed stat file %s: %s"), filename, strerror (errsv));
+		             _("failed stat file %s: %s"), filename, nm_strerror_native (errsv));
 		return FALSE;
 	}
 
@@ -3226,7 +3497,7 @@ _nm_utils_check_module_file (const char *name,
  * @predicate: (scope call): if given, pass the file name to this function
  *   for additional checks. This check is performed after the check for
  *   @file_test_flags. You cannot omit both @file_test_flags and @predicate.
- * @user_data: (closure): (allow-none): user data for @predicate function.
+ * @user_data: (closure) (allow-none): user data for @predicate function.
  * @error: (allow-none): on failure, set a "not found" error %G_IO_ERROR %G_IO_ERROR_NOT_FOUND.
  *
  * Searches for a @progname file in a list of search @paths.
@@ -3603,147 +3874,6 @@ nm_utils_hwaddr_len (int type)
 	g_return_val_if_reached (0);
 }
 
-guint8 *
-_nm_utils_hexstr2bin_full (const char *hexstr,
-                           gboolean allow_0x_prefix,
-                           gboolean delimiter_required,
-                           const char *delimiter_candidates,
-                           gsize required_len,
-                           guint8 *buffer,
-                           gsize buffer_len,
-                           gsize *out_len)
-{
-	const char *in = hexstr;
-	guint8 *out = buffer;
-	gboolean delimiter_has = TRUE;
-	guint8 delimiter = '\0';
-	gsize len;
-
-	nm_assert (hexstr);
-	nm_assert (buffer);
-	nm_assert (required_len > 0 || out_len);
-
-	if (   allow_0x_prefix
-	    && in[0] == '0'
-	    && in[1] == 'x')
-		in += 2;
-
-	while (TRUE) {
-		const guint8 d1 = in[0];
-		guint8 d2;
-		int i1, i2;
-
-		i1 = nm_utils_hexchar_to_int (d1);
-		if (i1 < 0)
-			goto fail;
-
-		/* If there's no leading zero (ie "aa:b:cc") then fake it */
-		d2 = in[1];
-		if (   d2
-		    && (i2 = nm_utils_hexchar_to_int (d2)) >= 0) {
-			*out++ = (i1 << 4) + i2;
-			d2 = in[2];
-			if (!d2)
-				break;
-			in += 2;
-		} else {
-			/* Fake leading zero */
-			*out++ = i1;
-			if (!d2) {
-				if (!delimiter_has) {
-					/* when using no delimiter, there must be pairs of hex chars */
-					goto fail;
-				}
-				break;
-			}
-			in += 1;
-		}
-
-		if (--buffer_len == 0)
-			goto fail;
-
-		if (delimiter_has) {
-			if (d2 != delimiter) {
-				if (delimiter)
-					goto fail;
-				if (delimiter_candidates) {
-					while (delimiter_candidates[0]) {
-						if (delimiter_candidates++[0] == d2)
-							delimiter = d2;
-					}
-				}
-				if (!delimiter) {
-					if (delimiter_required)
-						goto fail;
-					delimiter_has = FALSE;
-					continue;
-				}
-			}
-			in++;
-		}
-	}
-
-	len = out - buffer;
-	if (   required_len == 0
-	    || len == required_len) {
-		NM_SET_OUT (out_len, len);
-		return buffer;
-	}
-
-fail:
-	NM_SET_OUT (out_len, 0);
-	return NULL;
-}
-
-guint8 *
-_nm_utils_hexstr2bin_alloc (const char *hexstr,
-                            gboolean allow_0x_prefix,
-                            gboolean delimiter_required,
-                            const char *delimiter_candidates,
-                            gsize required_len,
-                            gsize *out_len)
-{
-	guint8 *buffer;
-	gsize buffer_len, len;
-
-	g_return_val_if_fail (hexstr, NULL);
-
-	nm_assert (required_len > 0 || out_len);
-
-	if (   allow_0x_prefix
-	    && hexstr[0] == '0'
-	    && hexstr[1] == 'x')
-		hexstr += 2;
-
-	if (!hexstr[0])
-		goto fail;
-
-	if (required_len > 0)
-		buffer_len = required_len;
-	else
-		buffer_len = strlen (hexstr) / 2 + 3;
-
-	buffer = g_malloc (buffer_len);
-
-	if (_nm_utils_hexstr2bin_full (hexstr,
-	                               FALSE,
-	                               delimiter_required,
-	                               delimiter_candidates,
-	                               required_len,
-	                               buffer,
-	                               buffer_len,
-	                               &len)) {
-		NM_SET_OUT (out_len, len);
-		return buffer;
-	}
-
-	g_free (buffer);
-
-fail:
-	NM_SET_OUT (out_len, 0);
-	return NULL;
-}
-
 /**
  * nm_utils_hexstr2bin:
  * @hex: a string of hexadecimal characters with optional ':' separators
@@ -3761,14 +3891,14 @@ nm_utils_hexstr2bin (const char *hex)
 	guint8 *buffer;
 	gsize len;
 
-	buffer = _nm_utils_hexstr2bin_alloc (hex, TRUE, FALSE, ":", 0, &len);
+	buffer = nm_utils_hexstr2bin_alloc (hex, TRUE, FALSE, ":", 0, &len);
 	if (!buffer)
 		return NULL;
 	buffer = g_realloc (buffer, len);
 	return g_bytes_new_take (buffer, len);
 }
 
-#define hwaddr_aton(asc, buffer, buffer_len, out_len) _nm_utils_hexstr2bin_full ((asc), FALSE, TRUE, ":-", 0, (buffer), (buffer_len), (out_len))
+#define hwaddr_aton(asc, buffer, buffer_len, out_len) nm_utils_hexstr2bin_full ((asc), FALSE, TRUE, ":-", 0, (buffer), (buffer_len), (out_len))
 
 /**
  * nm_utils_hwaddr_atoba:
@@ -3862,64 +3992,7 @@ nm_utils_hwaddr_aton (const char *asc, gpointer buffer, gsize length)
 	return buffer;
 }
 
-/**
- * _nm_utils_bin2hexstr_full:
- * @addr: pointer of @length bytes.
- * @length: number of bytes in @addr
- * @delimiter: either '\0', otherwise the output string will have the
- *   given delimiter character between each two hex numbers.
- * @upper_case: if TRUE, use upper case ASCII characters for hex.
- * @out: if %NULL, the function will allocate a new buffer of
- *   either (@length*2+1) or (@length*3) bytes, depending on whether
- *   a @delimiter is specified. In that case, the allocated buffer will
- *   be returned and must be freed by the caller.
- *   If not %NULL, the buffer must already be preallocated and contain
- *   at least (@length*2+1) or (@length*3) bytes, depending on the delimiter.
- *
- * Returns: the binary value converted to a hex string. If @out is given,
- *   this always returns @out. If @out is %NULL, a newly allocated string
- *   is returned.
- */
-char *
-_nm_utils_bin2hexstr_full (gconstpointer addr,
-                           gsize length,
-                           char delimiter,
-                           gboolean upper_case,
-                           char *out)
-{
-	const guint8 *in = addr;
-	const char *LOOKUP = upper_case ? "0123456789ABCDEF" : "0123456789abcdef";
-	char *out0;
 
-	nm_assert (addr);
-	nm_assert (length > 0);
-
-	if (out)
-		out0 = out;
-	else {
-		out0 = out = g_new (char, delimiter == '\0'
-		                          ? length * 2 + 1
-		                          : length * 3);
-	}
-
-	/* @out must contain at least @length*3 bytes if @delimiter is set,
-	 * otherwise, @length*2+1. */
-
-	for (;;) {
-		const guint8 v = *in++;
-
-		*out++ = LOOKUP[v >> 4];
-		*out++ = LOOKUP[v & 0x0F];
-		length--;
-		if (!length)
-			break;
-		if (delimiter)
-			*out++ = delimiter;
-	}
-
-	*out = 0;
-	return out0;
-}
 
 /**
  * nm_utils_bin2hexstr:
@@ -3945,7 +4018,7 @@ nm_utils_bin2hexstr (gconstpointer src, gsize len, int final_len)
 
 	result = g_malloc (buflen);
 
-	_nm_utils_bin2hexstr_full (src, len, '\0', FALSE, result);
+	nm_utils_bin2hexstr_full (src, len, '\0', FALSE, result);
 
 	/* Cut converted key off at the correct length for this cipher type */
 	if (final_len >= 0 && (gsize) final_len < buflen)
@@ -3969,7 +4042,7 @@ nm_utils_hwaddr_ntoa (gconstpointer addr, gsize length)
 	g_return_val_if_fail (addr, g_strdup (""));
 	g_return_val_if_fail (length > 0, g_strdup (""));
 
-	return _nm_utils_bin2hexstr_full (addr, length, ':', TRUE, NULL);
+	return nm_utils_bin2hexstr_full (addr, length, ':', TRUE, NULL);
 }
 
 const char *
@@ -3981,7 +4054,7 @@ nm_utils_hwaddr_ntoa_buf (gconstpointer addr, gsize addr_len, gboolean upper_cas
 	if (buf_len < addr_len * 3)
 		g_return_val_if_reached (NULL);
 
-	return _nm_utils_bin2hexstr_full (addr, addr_len, ':', upper_case, buf);
+	return nm_utils_bin2hexstr_full (addr, addr_len, ':', upper_case, buf);
 }
 
 /**
@@ -4311,6 +4384,102 @@ _nm_utils_hwaddr_from_dbus (GVariant *dbus_value,
 
 /*****************************************************************************/
 
+/* Validate secret-flags. Most settings don't validate them, which is a bug.
+ * But we possibly cannot enforce a strict validation now.
+ *
+ * For new settings, they shall validate the secret-flags strictly. */
+gboolean
+_nm_utils_secret_flags_validate (NMSettingSecretFlags secret_flags,
+                                 const char *setting_name,
+                                 const char *property_name,
+                                 NMSettingSecretFlags disallowed_flags,
+                                 GError **error)
+{
+	if (secret_flags == NM_SETTING_SECRET_FLAG_NONE)
+		return TRUE;
+
+	if (NM_FLAGS_ANY (secret_flags, ~NM_SETTING_SECRET_FLAG_ALL)) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("unknown secret flags"));
+		if (setting_name)
+			g_prefix_error (error, "%s.%s: ", setting_name, property_name);
+		return FALSE;
+	}
+
+	if (!nm_utils_is_power_of_two (secret_flags)) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("conflicting secret flags"));
+		if (setting_name)
+			g_prefix_error (error, "%s.%s: ", setting_name, property_name);
+		return FALSE;
+	}
+
+	if (NM_FLAGS_ANY (secret_flags, disallowed_flags)) {
+		if (NM_FLAGS_HAS (secret_flags, NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) {
+			g_set_error_literal (error,
+			                     NM_CONNECTION_ERROR,
+			                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			                     _("secret flags must not be \"not-required\""));
+			if (setting_name)
+				g_prefix_error (error, "%s.%s: ", setting_name, property_name);
+			return FALSE;
+		}
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("unsupported secret flags"));
+		if (setting_name)
+			g_prefix_error (error, "%s.%s: ", setting_name, property_name);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+gboolean
+_nm_utils_wps_method_validate (NMSettingWirelessSecurityWpsMethod wps_method,
+                               const char *setting_name,
+                               const char *property_name,
+                               gboolean wps_required,
+                               GError **error)
+{
+	if (wps_method > NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PIN) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("property is invalid"));
+		g_prefix_error (error, "%s.%s: ", setting_name, property_name);
+		return FALSE;
+	}
+
+	if (NM_FLAGS_HAS (wps_method, NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DISABLED)) {
+		if (wps_method != NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DISABLED) {
+			g_set_error_literal (error,
+			                     NM_CONNECTION_ERROR,
+			                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			                     _("can't be simultaneously disabled and enabled"));
+			g_prefix_error (error, "%s.%s: ", setting_name, property_name);
+			return FALSE;
+		}
+		if (wps_required) {
+			g_set_error_literal (error,
+			                     NM_CONNECTION_ERROR,
+			                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			                     _("WPS is required"));
+			g_prefix_error (error, "%s.%s: ", setting_name, property_name);
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+/*****************************************************************************/
+
 static char *
 _split_word (char *s)
 {
@@ -4666,7 +4835,7 @@ _nm_utils_dhcp_duid_valid (const char *duid, GBytes **out_duid_bin)
 		return TRUE;
 	}
 
-	if (_nm_utils_hexstr2bin_full (duid, FALSE, FALSE, ":", 0, duid_arr, sizeof (duid_arr), &duid_len)) {
+	if (nm_utils_hexstr2bin_full (duid, FALSE, FALSE, ":", 0, duid_arr, sizeof (duid_arr), &duid_len)) {
 		/* MAX DUID length is 128 octects + the type code (2 octects). */
 		if (   duid_len > 2
 		    && duid_len <= (128 + 2)) {
@@ -5316,7 +5485,7 @@ _json_find_object (json_t *json,
 	return json_element;
 }
 
-static inline void
+static void
 _json_delete_object_on_int_match (json_t *json,
                                   const char *key1,
                                   const char *key2,
@@ -5332,7 +5501,7 @@ _json_delete_object_on_int_match (json_t *json,
 		_json_del_object (json, key1, key2, key3);
 }
 
-static inline void
+static void
 _json_delete_object_on_bool_match (json_t *json,
                                    const char *key1,
                                    const char *key2,
@@ -5348,7 +5517,7 @@ _json_delete_object_on_bool_match (json_t *json,
 		_json_del_object (json, key1, key2, key3);
 }
 
-static inline void
+static void
 _json_delete_object_on_string_match (json_t *json,
                                      const char *key1,
                                      const char *key2,
@@ -5982,15 +6151,15 @@ _nm_utils_team_link_watchers_to_variant (GPtrArray *link_watchers)
 
 		name = nm_team_link_watcher_get_name (watcher);
 		g_variant_builder_add (&watcher_builder, "{sv}",
-				       "name",
-				       g_variant_new_string (name));
+		                       "name",
+		                       g_variant_new_string (name));
 
-		if nm_streq (name, NM_TEAM_LINK_WATCHER_ETHTOOL) {
+		if (nm_streq (name, NM_TEAM_LINK_WATCHER_ETHTOOL)) {
 			int_val = nm_team_link_watcher_get_delay_up (watcher);
 			if (int_val) {
 				g_variant_builder_add (&watcher_builder, "{sv}",
-						       "delay-up",
-						       g_variant_new_int32 (int_val));
+				                       "delay-up",
+				                       g_variant_new_int32 (int_val));
 			}
 			int_val = nm_team_link_watcher_get_delay_down (watcher);
 			if (int_val) {
@@ -6025,7 +6194,7 @@ _nm_utils_team_link_watchers_to_variant (GPtrArray *link_watchers)
 		                       "target-host",
 		                       g_variant_new_string (nm_team_link_watcher_get_target_host (watcher)));
 
-		if nm_streq (name, NM_TEAM_LINK_WATCHER_NSNA_PING) {
+		if (nm_streq (name, NM_TEAM_LINK_WATCHER_NSNA_PING)) {
 			g_variant_builder_add (&builder, "a{sv}", &watcher_builder);
 			continue;
 		}
@@ -6117,7 +6286,7 @@ _nm_utils_team_link_watchers_from_variant (GVariant *value)
 				val2 = 0;
 			if (!g_variant_lookup (watcher_var, "missed-max", "i", &val3))
 				val3 = 3;
-			if nm_streq (name, NM_TEAM_LINK_WATCHER_ARP_PING) {
+			if (nm_streq (name, NM_TEAM_LINK_WATCHER_ARP_PING)) {
 				if (!g_variant_lookup (watcher_var, "vlanid", "i", &val4))
 					val4 = -1;
 				if (!g_variant_lookup (watcher_var, "source-host", "&s", &source_host))
@@ -6501,3 +6670,73 @@ nm_utils_version (void)
 	return NM_VERSION;
 }
 
+/*****************************************************************************/
+
+/**
+ * nm_utils_base64secret_decode:
+ * @base64_key: the (possibly invalid) base64 encode key.
+ * @required_key_len: the expected (binary) length of the key after
+ *   decoding. If the length does not match, the validation fails.
+ * @out_key: (allow-none): (out): an optional output buffer for the binary
+ *   key. If given, it will be filled with exactly @required_key_len
+ *   bytes.
+ *
+ * Returns: %TRUE if the input key is a valid base64 encoded key
+ *   with @required_key_len bytes.
+ *
+ * Since: 1.16
+ */
+gboolean
+nm_utils_base64secret_decode (const char *base64_key,
+                              gsize required_key_len,
+                              guint8 *out_key)
+{
+	gs_free guint8 *bin_arr = NULL;
+	gsize base64_key_len;
+	gsize bin_len;
+	int r;
+
+	if (!base64_key)
+		return FALSE;
+
+	base64_key_len = strlen (base64_key);
+
+	r = nm_sd_utils_unbase64mem (base64_key, base64_key_len, &bin_arr, &bin_len);
+	if (r < 0)
+		return FALSE;
+	if (bin_len != required_key_len) {
+		nm_explicit_bzero (bin_arr, bin_len);
+		return FALSE;
+	}
+
+	if (out_key)
+		memcpy (out_key, bin_arr, required_key_len);
+
+	nm_explicit_bzero (bin_arr, bin_len);
+	return TRUE;
+}
+
+gboolean
+nm_utils_base64secret_normalize (const char *base64_key,
+                                 gsize required_key_len,
+                                 char **out_base64_key_norm)
+{
+	gs_free guint8 *buf_free = NULL;
+	guint8 buf_static[200];
+	guint8 *buf;
+
+	if (required_key_len > sizeof (buf_static)) {
+		buf_free = g_new (guint8, required_key_len);
+		buf = buf_free;
+	} else
+		buf = buf_static;
+
+	if (!nm_utils_base64secret_decode (base64_key, required_key_len, buf)) {
+		NM_SET_OUT (out_base64_key_norm, NULL);
+		return FALSE;
+	}
+
+	NM_SET_OUT (out_base64_key_norm, g_base64_encode (buf, required_key_len));
+	nm_explicit_bzero (buf, required_key_len);
+	return TRUE;
+}