diff options
author | Thomas Haller <thaller@redhat.com> | 2020-05-13 10:28:10 +0200 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2020-05-13 10:28:10 +0200 |
commit | f6b1b2401d7412260361e5f4a29921b65e6baf9e (patch) | |
tree | 6b1f949e8e26eea94b5dcfddbf3cedb3f8c94e46 | |
parent | 79f254850c39da4af311d8746c2d57cfaed45749 (diff) | |
parent | 797ee7d5ea720883a9645ebae5aaa8bd845c91d0 (diff) | |
download | NetworkManager-f6b1b2401d7412260361e5f4a29921b65e6baf9e.tar.gz |
cli: merge branch 'th/nmcli-passwd-file-parsing'
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/488
-rw-r--r-- | clients/cli/connections.c | 109 | ||||
-rw-r--r-- | clients/common/nm-client-utils.c | 191 | ||||
-rw-r--r-- | clients/common/nm-client-utils.h | 8 | ||||
-rw-r--r-- | clients/common/tests/test-clients-common.c | 73 | ||||
-rw-r--r-- | libnm-core/nm-setting-ip-config.c | 2 | ||||
-rw-r--r-- | libnm-core/nm-utils.c | 2 | ||||
-rw-r--r-- | libnm-core/tests/test-general.c | 12 | ||||
-rw-r--r-- | libnm/nm-libnm-utils.c | 2 | ||||
-rw-r--r-- | shared/nm-glib-aux/nm-shared-utils.c | 53 | ||||
-rw-r--r-- | shared/nm-glib-aux/nm-shared-utils.h | 26 | ||||
-rw-r--r-- | src/nm-config.c | 2 |
11 files changed, 369 insertions, 111 deletions
diff --git a/clients/cli/connections.c b/clients/cli/connections.c index e6d6c69905..a6bbe5669b 100644 --- a/clients/cli/connections.c +++ b/clients/cli/connections.c @@ -2694,91 +2694,6 @@ activate_connection_cb (GObject *client, GAsyncResult *result, gpointer user_dat } } -/** - * parse_passwords: - * @passwd_file: file with passwords to parse - * @error: location to store error, or %NULL - * - * Parse passwords given in @passwd_file and insert them into a hash table. - * Example of @passwd_file contents: - * wifi.psk:tajne heslo - * 802-1x.password:krakonos - * 802-11-wireless-security:leap-password:my leap password - * - * Returns: hash table with parsed passwords, or %NULL on an error - */ -static GHashTable * -parse_passwords (const char *passwd_file, GError **error) -{ - gs_unref_hashtable GHashTable *pwds_hash = NULL; - gs_free char *contents = NULL; - gsize len = 0; - GError *local_err = NULL; - gs_free const char **strv = NULL; - const char *const*iter; - char *pwd_spec, *pwd, *prop; - const char *setting; - - pwds_hash = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free); - - if (!passwd_file) - return g_steal_pointer (&pwds_hash); - - if (!g_file_get_contents (passwd_file, &contents, &len, &local_err)) { - g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, - _("failed to read passwd-file '%s': %s"), - passwd_file, local_err->message); - g_error_free (local_err); - return NULL; - } - - strv = nm_utils_strsplit_set (contents, "\r\n"); - for (iter = strv; strv && *iter; iter++) { - gs_free char *iter_s = g_strdup (*iter); - - pwd = strchr (iter_s, ':'); - if (!pwd) { - g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, - _("missing colon in 'password' entry '%s'"), *iter); - return NULL; - } - *(pwd++) = '\0'; - - prop = strchr (iter_s, '.'); - if (!prop) { - g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, - _("missing dot in 'password' entry '%s'"), *iter); - return NULL; - } - *(prop++) = '\0'; - - setting = iter_s; - while (g_ascii_isspace (*setting)) - setting++; - /* Accept wifi-sec or wifi instead of cumbersome '802-11-wireless-security' */ - if (!strcmp (setting, "wifi-sec") || !strcmp (setting, "wifi")) - setting = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME; - if (nm_setting_lookup_type (setting) == G_TYPE_INVALID) { - g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, - _("invalid setting name in 'password' entry '%s'"), setting); - return NULL; - } - - if ( nm_streq (setting, "vpn") - && g_str_has_prefix (prop, "secret.")) { - /* in 1.12.0, we wrongly required the VPN secrets to be named - * "vpn.secret". It should be "vpn.secrets". Work around it - * (rh#1628833). */ - pwd_spec = g_strdup_printf ("vpn.secrets.%s", &prop[NM_STRLEN ("secret.")]); - } else - pwd_spec = g_strdup_printf ("%s.%s", setting, prop); - - g_hash_table_insert (pwds_hash, pwd_spec, g_strdup (pwd)); - } - - return g_steal_pointer (&pwds_hash); -} - static gboolean nmc_activate_connection (NmCli *nmc, NMConnection *connection, @@ -2827,9 +2742,27 @@ nmc_activate_connection (NmCli *nmc, } /* Parse passwords given in passwords file */ - pwds_hash = parse_passwords (pwds, error); - if (!pwds_hash) - return FALSE; + { + gs_free_error GError *local = NULL; + gssize error_line; + + pwds_hash = nmc_utils_read_passwd_file (pwds, &error_line, &local); + if (!pwds_hash) { + if (error_line >= 0) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("invalid passwd-file '%s' at line %zd: %s"), + pwds, + error_line, + local->message); + } else { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("invalid passwd-file '%s': %s"), + pwds, + local->message); + } + return FALSE; + } + } if (nmc->pwds_hash) g_hash_table_destroy (nmc->pwds_hash); diff --git a/clients/common/nm-client-utils.c b/clients/common/nm-client-utils.c index cf8b478560..97885291d6 100644 --- a/clients/common/nm-client-utils.c +++ b/clients/common/nm-client-utils.c @@ -6,8 +6,10 @@ #include "nm-default.h" #include "nm-client-utils.h" -#include "nm-utils.h" +#include "nm-glib-aux/nm-secret-utils.h" +#include "nm-glib-aux/nm-io-utils.h" +#include "nm-utils.h" #include "nm-device-bond.h" #include "nm-device-bridge.h" #include "nm-device-team.h" @@ -587,3 +589,190 @@ nmc_print_qrcode (const char *str) } } } + +/** + * nmc_utils_read_passwd_file: + * @passwd_file: file with passwords to parse + * @out_error_line: returns in case of a syntax error in the file, the line + * on which it occurred. + * @error: location to store error, or %NULL + * + * Parse passwords given in @passwd_file and insert them into a hash table. + * Example of @passwd_file contents: + * wifi.psk:tajne heslo + * 802-1x.password:krakonos + * 802-11-wireless-security:leap-password:my leap password + * + * Returns: (transfer full): hash table with parsed passwords, or %NULL on an error + */ +GHashTable * +nmc_utils_read_passwd_file (const char *passwd_file, + gssize *out_error_line, + GError **error) +{ + nm_auto_clear_secret_ptr NMSecretPtr contents = { 0 }; + + NM_SET_OUT (out_error_line, -1); + + if (!passwd_file) + return g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, (GDestroyNotify) nm_free_secret); + + if (!nm_utils_file_get_contents (-1, + passwd_file, + 1024*1024, + NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET, + &contents.str, + &contents.len, + NULL, + error)) + return NULL; + + return nmc_utils_parse_passwd_file (contents.str, out_error_line, error); +} + +GHashTable * +nmc_utils_parse_passwd_file (char *contents /* will be modified */, + gssize *out_error_line, + GError **error) +{ + gs_unref_hashtable GHashTable *pwds_hash = NULL; + const char *contents_str; + gsize contents_line; + + pwds_hash = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, (GDestroyNotify) nm_free_secret); + + NM_SET_OUT (out_error_line, -1); + + contents_str = contents; + contents_line = 0; + while (contents_str[0]) { + nm_auto_free_secret char *l_hash_key = NULL; + nm_auto_free_secret char *l_hash_val = NULL; + const char *l_content_line; + const char *l_setting; + const char *l_prop; + const char *l_val; + const char *s; + gsize l_hash_val_len; + + /* consume first line. As line delimiters we accept "\r\n", "\n", and "\r". */ + l_content_line = contents_str; + s = l_content_line; + while (!NM_IN_SET (s[0], '\0', '\r', '\n')) + s++; + if (s[0] != '\0') { + if ( s[0] == '\r' + && s[1] == '\n') { + ((char *) s)[0] = '\0'; + s += 2; + } else { + ((char *) s)[0] = '\0'; + s += 1; + } + } + contents_str = s; + contents_line++; + + l_content_line = nm_str_skip_leading_spaces (l_content_line); + if (NM_IN_SET (l_content_line[0], '\0', '#')) { + /* a comment or empty line. Ignore. */ + continue; + } + + l_setting = l_content_line; + + s = l_setting; + while (!NM_IN_SET (s[0], '\0', ':', '=')) + s++; + if (s[0] == '\0') { + NM_SET_OUT (out_error_line, contents_line); + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("missing colon for \"<setting>.<property>:<secret>\" format")); + return NULL; + } + ((char *) s)[0] = '\0'; + s++; + + l_val = s; + + g_strchomp ((char *) l_setting); + + nm_assert (nm_str_is_stripped (l_setting)); + + s = strchr (l_setting, '.'); + if (!s) { + NM_SET_OUT (out_error_line, contents_line); + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("missing dot for \"<setting>.<property>:<secret>\" format")); + return NULL; + } else if (s == l_setting) { + NM_SET_OUT (out_error_line, contents_line); + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("missing setting for \"<setting>.<property>:<secret>\" format")); + return NULL; + } + ((char *) s)[0] = '\0'; + s++; + + l_prop = s; + if (l_prop[0] == '\0') { + NM_SET_OUT (out_error_line, contents_line); + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("missing property for \"<setting>.<property>:<secret>\" format")); + return NULL; + } + + /* Accept wifi-sec or wifi instead of cumbersome '802-11-wireless-security' */ + if (NM_IN_STRSET (l_setting, "wifi-sec", "wifi")) + l_setting = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME; + + if (nm_setting_lookup_type (l_setting) == G_TYPE_INVALID) { + NM_SET_OUT (out_error_line, contents_line); + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("invalid setting name")); + return NULL; + } + + if ( nm_streq (l_setting, "vpn") + && NM_STR_HAS_PREFIX (l_prop, "secret.")) { + /* in 1.12.0, we wrongly required the VPN secrets to be named + * "vpn.secret". It should be "vpn.secrets". Work around it + * (rh#1628833). */ + l_hash_key = g_strdup_printf ("vpn.secrets.%s", &l_prop[NM_STRLEN ("secret.")]); + } else + l_hash_key = g_strdup_printf ("%s.%s", l_setting, l_prop); + + if (!g_utf8_validate (l_hash_key, -1, NULL)) { + NM_SET_OUT (out_error_line, contents_line); + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("property name is not UTF-8")); + return NULL; + } + + /* Support backslash escaping in the secret value. We strip non-escaped leading/trailing whitespaces. */ + s = nm_utils_buf_utf8safe_unescape (l_val, NM_UTILS_STR_UTF8_SAFE_UNESCAPE_STRIP_SPACES, &l_hash_val_len, (gpointer *) &l_hash_val); + if (!l_hash_val) + l_hash_val = g_strdup (s); + + if (!g_utf8_validate (l_hash_val, -1, NULL)) { + /* In some cases it might make sense to support binary secrets (like the WPA-PSK which has no + * defined encoding. However, all API that follows can only handle UTF-8, and no mechanism + * to escape the secrets. Reject non-UTF-8 early. */ + NM_SET_OUT (out_error_line, contents_line); + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("secret is not UTF-8")); + return NULL; + } + + if (strlen (l_hash_val) != l_hash_val_len) { + NM_SET_OUT (out_error_line, contents_line); + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("secret is not UTF-8")); + return NULL; + } + + g_hash_table_insert (pwds_hash, g_steal_pointer (&l_hash_key), g_steal_pointer (&l_hash_val)); + } + + return g_steal_pointer (&pwds_hash); +} diff --git a/clients/common/nm-client-utils.h b/clients/common/nm-client-utils.h index 1e3085d56d..8983e6c89b 100644 --- a/clients/common/nm-client-utils.h +++ b/clients/common/nm-client-utils.h @@ -43,4 +43,12 @@ const char *nmc_password_subst_char (void); void nmc_print_qrcode (const char *str); +GHashTable *nmc_utils_parse_passwd_file (char *contents, + gssize *out_error_line, + GError **error); + +GHashTable *nmc_utils_read_passwd_file (const char *passwd_file, + gssize *out_error_line, + GError **error); + #endif /* __NM_CLIENT_UTILS_H__ */ diff --git a/clients/common/tests/test-clients-common.c b/clients/common/tests/test-clients-common.c index 70ee2cb19a..0b61e7bb56 100644 --- a/clients/common/tests/test-clients-common.c +++ b/clients/common/tests/test-clients-common.c @@ -7,6 +7,7 @@ #include "nm-meta-setting-access.h" #include "nm-vpn-helpers.h" +#include "nm-client-utils.h" #include "nm-utils/nm-test-utils.h" @@ -235,6 +236,77 @@ test_client_import_wireguard_missing (void) /*****************************************************************************/ +#define _do_test_parse_passwd_file(contents, success, exp_error_line, ...) \ + G_STMT_START { \ + static const NMUtilsNamedValue _values[] = { \ + __VA_ARGS__ \ + }; \ + gs_free char *_contents = g_strndup (contents, NM_STRLEN (contents)); \ + gs_unref_hashtable GHashTable *_secrets = NULL; \ + gs_free_error GError *_local = NULL; \ + gssize _error_line; \ + GError **_p_local = nmtst_get_rand_bool () ? &_local : NULL; \ + gssize *_p_error_line = nmtst_get_rand_bool () ? &_error_line : NULL; \ + gboolean _success = !!(success); \ + gssize _exp_error_line = (exp_error_line); \ + int _i; \ + \ + g_assert (_success || (G_N_ELEMENTS (_values) == 0)); \ + \ + _secrets = nmc_utils_parse_passwd_file (_contents, _p_error_line, _p_local); \ + \ + g_assert (_success == (!!_secrets)); \ + if (!_success) { \ + if (_p_error_line) \ + g_assert_cmpint (_exp_error_line, ==, *_p_error_line); \ + if (_p_local) \ + g_assert (_local); \ + } else { \ + if (_p_error_line) \ + g_assert_cmpint (-1, ==, *_p_error_line); \ + g_assert (!_local); \ + \ + for (_i = 0; _i < G_N_ELEMENTS (_values); _i++) { \ + const NMUtilsNamedValue *_n = &_values[_i]; \ + const char *_v; \ + \ + _v = g_hash_table_lookup (_secrets, _n->name); \ + if (!_v) \ + g_error ("cannot find key \"%s\"", _n->name); \ + g_assert_cmpstr (_v, ==, _n->value_str); \ + } \ + \ + g_assert_cmpint (g_hash_table_size (_secrets), ==, G_N_ELEMENTS (_values)); \ + } \ + } G_STMT_END + +#define _do_test_parse_passwd_file_bad( contents, exp_error_line) _do_test_parse_passwd_file (contents, FALSE, exp_error_line) +#define _do_test_parse_passwd_file_good(contents, ...) _do_test_parse_passwd_file (contents, TRUE, -1, __VA_ARGS__) + +static void +test_nmc_utils_parse_passwd_file (void) +{ + _do_test_parse_passwd_file_good (""); + _do_test_parse_passwd_file_bad ("x", 1); + _do_test_parse_passwd_file_bad ("\r\rx", 3); + _do_test_parse_passwd_file_good ("wifi.psk=abc", + NM_UTILS_NAMED_VALUE_INIT ("802-11-wireless-security.psk", "abc") ); + _do_test_parse_passwd_file_good ("wifi.psk:ABC\r" + "wifi-sec.psk = abc ", + NM_UTILS_NAMED_VALUE_INIT ("802-11-wireless-security.psk", "abc") ); + _do_test_parse_passwd_file_good ("wifi.psk: abc\r" + "wifi-sec.psk2 = d\\145f\r\n" + " wifi.psk3 = e\\ \n" + " #wifi-sec.psk2 = \r\n" + " wifi-sec.psk4:", + NM_UTILS_NAMED_VALUE_INIT ("802-11-wireless-security.psk", "abc"), + NM_UTILS_NAMED_VALUE_INIT ("802-11-wireless-security.psk2", "def"), + NM_UTILS_NAMED_VALUE_INIT ("802-11-wireless-security.psk3", "e "), + NM_UTILS_NAMED_VALUE_INIT ("802-11-wireless-security.psk4", "") ); +} + +/*****************************************************************************/ + NMTST_DEFINE (); int @@ -248,6 +320,7 @@ main (int argc, char **argv) g_test_add_func ("/client/import/wireguard/test2", test_client_import_wireguard_test2); g_test_add_func ("/client/import/wireguard/test3", test_client_import_wireguard_test3); g_test_add_func ("/client/import/wireguard/missing", test_client_import_wireguard_missing); + g_test_add_func ("/client/test_nmc_utils_parse_passwd_file", test_nmc_utils_parse_passwd_file); return g_test_run (); } diff --git a/libnm-core/nm-setting-ip-config.c b/libnm-core/nm-setting-ip-config.c index 36f09b694b..09a3428362 100644 --- a/libnm-core/nm-setting-ip-config.c +++ b/libnm-core/nm-setting-ip-config.c @@ -2186,7 +2186,7 @@ nm_ip_routing_rule_get_xifname_bin (const NMIPRoutingRule *self, if (!xifname) return FALSE; - bin = nm_utils_buf_utf8safe_unescape (xifname, &len, &bin_to_free); + bin = nm_utils_buf_utf8safe_unescape (xifname, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE, &len, &bin_to_free); strncpy (out_xifname, bin, 16 /* IFNAMSIZ */); out_xifname[15] = '\0'; diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c index 7b69b4ef7a..f9bccea930 100644 --- a/libnm-core/nm-utils.c +++ b/libnm-core/nm-utils.c @@ -4673,7 +4673,7 @@ nm_utils_is_valid_iface_name_utf8safe (const char *utf8safe_name) g_return_val_if_fail (utf8safe_name, FALSE); - bin = nm_utils_buf_utf8safe_unescape (utf8safe_name, &len, &bin_to_free); + bin = nm_utils_buf_utf8safe_unescape (utf8safe_name, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE, &len, &bin_to_free); if (bin_to_free) { /* some unescaping happened... */ diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c index 7da2479647..52f05dc306 100644 --- a/libnm-core/tests/test-general.c +++ b/libnm-core/tests/test-general.c @@ -7815,7 +7815,7 @@ _do_test_utils_str_utf8safe_unescape (const char *str, const char *expected, gsi gs_free gpointer buf_free_1 = NULL; gs_free char *str_free_1 = NULL; - s = nm_utils_buf_utf8safe_unescape (str, &l, &buf_free_1); + s = nm_utils_buf_utf8safe_unescape (str, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE, &l, &buf_free_1); g_assert_cmpint (expected_len, ==, l); g_assert_cmpstr (s, ==, expected); @@ -7839,7 +7839,7 @@ _do_test_utils_str_utf8safe_unescape (const char *str, const char *expected, gsi if ( expected && l == strlen (expected)) { /* there are no embeeded NULs. Check that nm_utils_str_utf8safe_unescape() yields the same result. */ - s = nm_utils_str_utf8safe_unescape (str, &str_free_1); + s = nm_utils_str_utf8safe_unescape (str, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE, &str_free_1); g_assert_cmpstr (s, ==, expected); if (strchr (str, '\\')) { g_assert (str_free_1 != str); @@ -7908,10 +7908,10 @@ _do_test_utils_str_utf8safe (const char *str, gsize str_len, const char *expecte g_assert (g_utf8_validate (str, -1, NULL)); } - g_assert (str == nm_utils_str_utf8safe_unescape (str_safe, &str_free_4)); + g_assert (str == nm_utils_str_utf8safe_unescape (str_safe, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE, &str_free_4)); g_assert (!str_free_4); - str_free_5 = nm_utils_str_utf8safe_unescape_cp (str_safe); + str_free_5 = nm_utils_str_utf8safe_unescape_cp (str_safe, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); if (str) { g_assert (str_free_5 != str); g_assert_cmpstr (str_free_5, ==, str); @@ -7935,11 +7935,11 @@ _do_test_utils_str_utf8safe (const char *str, gsize str_len, const char *expecte str_free_6 = g_strcompress (str_safe); g_assert_cmpstr (str, ==, str_free_6); - str_free_7 = nm_utils_str_utf8safe_unescape_cp (str_safe); + str_free_7 = nm_utils_str_utf8safe_unescape_cp (str_safe, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); g_assert (str_free_7 != str); g_assert_cmpstr (str_free_7, ==, str); - s = nm_utils_str_utf8safe_unescape (str_safe, &str_free_8); + s = nm_utils_str_utf8safe_unescape (str_safe, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE, &str_free_8); g_assert (str_free_8 != str); g_assert (s == str_free_8); g_assert_cmpstr (str_free_8, ==, str); diff --git a/libnm/nm-libnm-utils.c b/libnm/nm-libnm-utils.c index 7b4db11ef3..f8d1ff560c 100644 --- a/libnm/nm-libnm-utils.c +++ b/libnm/nm-libnm-utils.c @@ -139,7 +139,7 @@ _fixup_string (const char *desc, return NULL; /* restore original non-UTF-8-safe text. */ - desc_full = nm_utils_str_utf8safe_unescape_cp (desc); + desc_full = nm_utils_str_utf8safe_unescape_cp (desc, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); /* replace all invalid UTF-8 bytes with space. */ p = desc_full; diff --git a/shared/nm-glib-aux/nm-shared-utils.c b/shared/nm-glib-aux/nm-shared-utils.c index e64ef71378..39e75a89fe 100644 --- a/shared/nm-glib-aux/nm-shared-utils.c +++ b/shared/nm-glib-aux/nm-shared-utils.c @@ -17,6 +17,9 @@ #include "nm-errno.h" #include "nm-str-buf.h" +G_STATIC_ASSERT (sizeof (NMUtilsNamedEntry) == sizeof (const char *)); +G_STATIC_ASSERT (G_STRUCT_OFFSET (NMUtilsNamedValue, value_ptr) == sizeof (const char *)); + /*****************************************************************************/ const void *const _NM_PTRARRAY_EMPTY[1] = { NULL }; @@ -2456,11 +2459,12 @@ _str_buf_append_c_escape_octal (NMStrBuf *strbuf, } gconstpointer -nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_free) +nm_utils_buf_utf8safe_unescape (const char *str, NMUtilsStrUtf8SafeFlags flags, gsize *out_len, gpointer *to_free) { + gboolean strip_spaces = NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_UNESCAPE_STRIP_SPACES); NMStrBuf strbuf; - gsize len; const char *s; + gsize len; g_return_val_if_fail (to_free, NULL); g_return_val_if_fail (out_len, NULL); @@ -2471,16 +2475,29 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr return NULL; } + if (strip_spaces) + str = nm_str_skip_leading_spaces (str); + len = strlen (str); s = memchr (str, '\\', len); if (!s) { + if ( strip_spaces + && len > 0 + && g_ascii_isspace (str[len - 1])) { + len--; + while ( len > 0 + && g_ascii_isspace (str[len - 1])) + len--; + *out_len = len; + return (*to_free = g_strndup (str, len)); + } *out_len = len; *to_free = NULL; return str; } - nm_str_buf_init (&strbuf, len, FALSE); + nm_str_buf_init (&strbuf, len + 1u, FALSE); nm_str_buf_append_len (&strbuf, str, s - str); str = s; @@ -2494,7 +2511,7 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr ch = (++str)[0]; if (ch == '\0') { - // error. Trailing '\\' + /* error. Trailing '\\' */ break; } @@ -2533,7 +2550,14 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr s = strchr (str, '\\'); if (!s) { - nm_str_buf_append (&strbuf, str); + gsize l = strlen (str); + + if (strip_spaces) { + while ( l > 0 + && g_ascii_isspace (str[l - 1])) + l--; + } + nm_str_buf_append_len (&strbuf, str, l); break; } @@ -2541,6 +2565,11 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr str = s; } + /* assert that no reallocation was necessary. For one, unescaping should + * never result in a longer string than the input. Also, when unescaping + * secrets, we want to ensure that we don't leak secrets in memory. */ + nm_assert (strbuf.allocated == len + 1u); + return (*to_free = nm_str_buf_finalize (&strbuf, out_len)); } @@ -2673,13 +2702,19 @@ nm_utils_buf_utf8safe_escape_bytes (GBytes *bytes, NMUtilsStrUtf8SafeFlags flags /*****************************************************************************/ const char * -nm_utils_str_utf8safe_unescape (const char *str, char **to_free) +nm_utils_str_utf8safe_unescape (const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free) { + const char *res; gsize len; g_return_val_if_fail (to_free, NULL); - return nm_utils_buf_utf8safe_unescape (str, &len, (gpointer *) to_free); + res = nm_utils_buf_utf8safe_unescape (str, flags, &len, (gpointer *) to_free); + + nm_assert ( (!res && len == 0) + || (strlen (res) <= len)); + + return res; } /** @@ -2737,11 +2772,11 @@ nm_utils_str_utf8safe_escape_cp (const char *str, NMUtilsStrUtf8SafeFlags flags) } char * -nm_utils_str_utf8safe_unescape_cp (const char *str) +nm_utils_str_utf8safe_unescape_cp (const char *str, NMUtilsStrUtf8SafeFlags flags) { char *s; - str = nm_utils_str_utf8safe_unescape (str, &s); + str = nm_utils_str_utf8safe_unescape (str, flags, &s); return s ?: g_strdup (str); } diff --git a/shared/nm-glib-aux/nm-shared-utils.h b/shared/nm-glib-aux/nm-shared-utils.h index fb9e3ac903..cdf6729076 100644 --- a/shared/nm-glib-aux/nm-shared-utils.h +++ b/shared/nm-glib-aux/nm-shared-utils.h @@ -1184,20 +1184,38 @@ GType nm_g_type_find_implementing_class_for_property (GType gtype, typedef enum { NM_UTILS_STR_UTF8_SAFE_FLAG_NONE = 0, + + /* This flag only has an effect during escaping. */ NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL = 0x0001, + + /* This flag only has an effect during escaping. */ NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII = 0x0002, + + /* This flag only has an effect during escaping to ensure we + * don't leak secrets in memory. Note that during unescape we + * know the maximum result size from the beginning, and no + * reallocation happens. Thus, unescape always avoids leaking + * secrets already. */ NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET = 0x0004, + + /* This flag only has an effect during unescaping. It means + * that non-escaped whitespaces (g_ascii_isspace()) will be + * stripped from the front and end of the string. Note that + * this flag is only useful for gracefully accepting user input + * with spaces. With this flag, escape and unescape may no longer + * yield the original input. */ + NM_UTILS_STR_UTF8_SAFE_UNESCAPE_STRIP_SPACES = 0x0008, } NMUtilsStrUtf8SafeFlags; const char *nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8SafeFlags flags, char **to_free); const char *nm_utils_buf_utf8safe_escape_bytes (GBytes *bytes, NMUtilsStrUtf8SafeFlags flags, char **to_free); -gconstpointer nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_free); +gconstpointer nm_utils_buf_utf8safe_unescape (const char *str, NMUtilsStrUtf8SafeFlags flags, gsize *out_len, gpointer *to_free); const char *nm_utils_str_utf8safe_escape (const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free); -const char *nm_utils_str_utf8safe_unescape (const char *str, char **to_free); +const char *nm_utils_str_utf8safe_unescape (const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free); char *nm_utils_str_utf8safe_escape_cp (const char *str, NMUtilsStrUtf8SafeFlags flags); -char *nm_utils_str_utf8safe_unescape_cp (const char *str); +char *nm_utils_str_utf8safe_unescape_cp (const char *str, NMUtilsStrUtf8SafeFlags flags); char *nm_utils_str_utf8safe_escape_take (char *str, NMUtilsStrUtf8SafeFlags flags); @@ -1389,6 +1407,8 @@ typedef struct { }; } NMUtilsNamedValue; +#define NM_UTILS_NAMED_VALUE_INIT(n, v) { .name = (n), .value_ptr = (v) } + NMUtilsNamedValue *nm_utils_named_values_from_str_dict_with_sort (GHashTable *hash, guint *out_len, GCompareDataFunc compare_func, diff --git a/src/nm-config.c b/src/nm-config.c index d638059588..4fa8531941 100644 --- a/src/nm-config.c +++ b/src/nm-config.c @@ -344,7 +344,7 @@ no_auto_default_from_file (const char *no_auto_default_file) if (list) { for (i = 0; list[i]; i++) - list[i] = nm_utils_str_utf8safe_unescape_cp (list[i]); + list[i] = nm_utils_str_utf8safe_unescape_cp (list[i], NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); } /* The returned buffer here is not at all compact. That means, it has additional |