From ad4e6f7fad77d4e877f87c9763771889b410f395 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 10 Mar 2015 17:16:34 +0100 Subject: [PATCH 01/23] build/trivial: cleanup Makefile.am by sorting EXTRA_DIST entries --- libnm-core/tests/Makefile.am | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/libnm-core/tests/Makefile.am b/libnm-core/tests/Makefile.am index 79aa73dbf9..457864c440 100644 --- a/libnm-core/tests/Makefile.am +++ b/libnm-core/tests/Makefile.am @@ -38,20 +38,20 @@ endif # -name "test-pkcs12" \ # -out test-cert.p12 -EXTRA_DIST = \ - certs/test_ca_cert.pem \ - certs/test_ca_cert.der \ - certs/test_key_and_cert.pem \ - certs/test-cert.p12 \ - certs/test2_ca_cert.pem \ - certs/test2_key_and_cert.pem \ - certs/test2-cert.p12 \ - certs/ca-no-ending-newline.pem \ - certs/test-key-only.pem \ - certs/test-key-only-decrypted.der \ - certs/test-key-only-decrypted.pem \ - certs/pkcs8-enc-key.pem \ - certs/pkcs8-noenc-key.pem \ - certs/pkcs8-decrypted.der \ - certs/test-aes-key.pem +EXTRA_DIST = \ + certs/ca-no-ending-newline.pem \ + certs/pkcs8-decrypted.der \ + certs/pkcs8-enc-key.pem \ + certs/pkcs8-noenc-key.pem \ + certs/test2_ca_cert.pem \ + certs/test2-cert.p12 \ + certs/test2_key_and_cert.pem \ + certs/test-aes-key.pem \ + certs/test_ca_cert.der \ + certs/test_ca_cert.pem \ + certs/test-cert.p12 \ + certs/test_key_and_cert.pem \ + certs/test-key-only-decrypted.der \ + certs/test-key-only-decrypted.pem \ + certs/test-key-only.pem From cbfa00219e77a0cbe9afa7aae548905f188bf6cf Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 10 Mar 2015 14:32:16 +0100 Subject: [PATCH 02/23] test: add nmtst_assert_resolve_relative_path_equals() function --- include/nm-test-utils.h | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/include/nm-test-utils.h b/include/nm-test-utils.h index 7961b03891..33b8fa51a7 100644 --- a/include/nm-test-utils.h +++ b/include/nm-test-utils.h @@ -576,6 +576,39 @@ __nmtst_spawn_sync (const char *working_directory, char **standard_out, char **s /*******************************************************************************/ +inline static char * +nmtst_file_resolve_relative_path (const char *rel, const char *cwd) +{ + gs_free char *cwd_free = NULL; + + g_assert (rel && *rel); + + if (g_path_is_absolute (rel)) + return g_strdup (rel); + + if (!cwd) + cwd = cwd_free = g_get_current_dir (); + return g_build_filename (cwd, rel, NULL); +} + +inline static void +_nmtst_assert_resolve_relative_path_equals (const char *f1, const char *f2, const char *file, int line) +{ + gs_free char *p1 = NULL, *p2 = NULL; + + p1 = nmtst_file_resolve_relative_path (f1, NULL); + p2 = nmtst_file_resolve_relative_path (f2, NULL); + g_assert (p1 && *p1); + + /* Fixme: later we might need to coalesce repeated '/', "./", and "../". + * For now, it's good enough. */ + if (g_strcmp0 (p1, p2) != 0) + g_error ("%s:%d : filenames don't match \"%s\" vs. \"%s\" // \"%s\" - \"%s\"", file, line, f1, f2, p1, p2); +} +#define nmtst_assert_resolve_relative_path_equals(f1, f2) _nmtst_assert_resolve_relative_path_equals (f1, f2, __FILE__, __LINE__); + +/*******************************************************************************/ + #ifdef __NETWORKMANAGER_PLATFORM_H__ inline static NMPlatformIP6Address * From 1e4612e476c48a4620a347948b5c1bf698dc1f43 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 23 Feb 2015 15:55:28 +0100 Subject: [PATCH 03/23] keyfile: add code comment to cert_writer() and downgrade assertion to g_critical() --- src/settings/plugins/keyfile/writer.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/settings/plugins/keyfile/writer.c b/src/settings/plugins/keyfile/writer.c index 314ff11608..483835048a 100644 --- a/src/settings/plugins/keyfile/writer.c +++ b/src/settings/plugins/keyfile/writer.c @@ -569,8 +569,15 @@ cert_writer (GKeyFile *file, g_error_free (error); } g_free (new_path); - } else - g_assert_not_reached (); + } else { + /* scheme_func() returns UNKNOWN in all other cases. The only valid case + * where a scheme is allowed to be UNKNOWN, is unsetting the value. In this + * case, we don't expect the writer to be called, because the default value + * will not be serialized. + * The only other reason for the scheme to be UNKNOWN is an invalid cert. + * But our connection verifies, so that cannot happen either. */ + g_return_if_reached (); + } } typedef struct { From e59e68c5286c59b8583d23651f2aea4440cfb147 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 23 Feb 2015 15:34:24 +0100 Subject: [PATCH 04/23] libnm: combine get_cert_scheme() and verify_cert() and ensure valid paths for NMSetting8021x get_cert_scheme() would return PATH scheme for binary data that later will be rejected by verify_cert(). Even worse, get_cert_scheme() would not check whether the path is NUL terminated, hence the following can crash for an invalid connection: if (nm_setting_802_1x_get_ca_cert_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH) g_print ("path: %s", nm_setting_802_1x_get_ca_cert_path (s_8021x)) Combine the two functions so that already get_cert_scheme() does the same validation as verify_cert(). Also change behavior and be more strict about invalid paths: - Now, the value is considered a PATH candidate if it starts with "file://", (sans NUL character). A change is that before, the "file://" (without NUL) would have been treated as BLOB, now it is an invalid PATH (UNKNOWN). - If the binary starts with "file://" it is considered as PATH but it is only valid, if all the fllowing is true: (a) the last character must be NUL. (b) there is no other intermediate NUL character. Before, an intermediate NUL character would have been accepted and the remainder would be ignored. (c) there is at least one non-NUL character after "file://". (d) the string must be fully valid utf8. The conditions (b) and (c) are new and some invalid(?) paths might no longer validate. Checking (d) moved from verify_cert() to get_cert_scheme(). As set_cert_prop_helper() already called verify_cert(), this causes no additional change beyond (b). --- libnm-core/nm-setting-8021x.c | 101 +++++++++++++++++++++------------- libnm-util/nm-setting-8021x.c | 38 ++++++------- 2 files changed, 81 insertions(+), 58 deletions(-) diff --git a/libnm-core/nm-setting-8021x.c b/libnm-core/nm-setting-8021x.c index 41559e0c8a..c07dbb38fb 100644 --- a/libnm-core/nm-setting-8021x.c +++ b/libnm-core/nm-setting-8021x.c @@ -31,6 +31,7 @@ #include "nm-utils-private.h" #include "nm-setting-private.h" #include "nm-core-enum-types.h" +#include "nm-utils-internal.h" /** * SECTION:nm-setting-8021x @@ -400,21 +401,64 @@ nm_setting_802_1x_get_system_ca_certs (NMSetting8021x *setting) } static NMSetting8021xCKScheme -get_cert_scheme (GBytes *bytes) +get_cert_scheme (GBytes *bytes, GError **error) { - gconstpointer data; + const char *data; gsize length; - if (!bytes) + if (!bytes) { + g_set_error_literal (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("data missing")); return NM_SETTING_802_1X_CK_SCHEME_UNKNOWN; + } data = g_bytes_get_data (bytes, &length); - if (!length) + if (!length) { + g_set_error_literal (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("binary data missing")); return NM_SETTING_802_1X_CK_SCHEME_UNKNOWN; + } + + /* interpret the blob as PATH if it starts with "file://". */ + if ( length >= STRLEN (SCHEME_PATH) + && !memcmp (data, SCHEME_PATH, STRLEN (SCHEME_PATH))) { + /* But it must also be NUL terminated, contain at least + * one non-NUL character, and contain only one trailing NUL + * chracter. + * And ensure it's UTF-8 valid too so we can pass it through + * D-Bus and stuff like that. */ + + if (data[length - 1] != '\0') { + g_set_error_literal (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("file:// URI not NUL terminated")); + return NM_SETTING_802_1X_CK_SCHEME_UNKNOWN; + } + length--; + + if (length <= STRLEN (SCHEME_PATH)) { + g_set_error_literal (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("file:// URI is empty")); + return NM_SETTING_802_1X_CK_SCHEME_UNKNOWN; + } + + if (!g_utf8_validate (data + STRLEN (SCHEME_PATH), length - STRLEN (SCHEME_PATH), NULL)) { + g_set_error_literal (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("file:// URI is not valid UTF-8")); + return NM_SETTING_802_1X_CK_SCHEME_UNKNOWN; + } - if ( (length > strlen (SCHEME_PATH)) - && !memcmp (data, SCHEME_PATH, strlen (SCHEME_PATH))) return NM_SETTING_802_1X_CK_SCHEME_PATH; + } return NM_SETTING_802_1X_CK_SCHEME_BLOB; } @@ -434,7 +478,7 @@ nm_setting_802_1x_get_ca_cert_scheme (NMSetting8021x *setting) { g_return_val_if_fail (NM_IS_SETTING_802_1X (setting), NM_SETTING_802_1X_CK_SCHEME_UNKNOWN); - return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->ca_cert); + return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->ca_cert, NULL); } /** @@ -766,7 +810,7 @@ nm_setting_802_1x_get_client_cert_scheme (NMSetting8021x *setting) { g_return_val_if_fail (NM_IS_SETTING_802_1X (setting), NM_SETTING_802_1X_CK_SCHEME_UNKNOWN); - return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->client_cert); + return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->client_cert, NULL); } /** @@ -1029,7 +1073,7 @@ nm_setting_802_1x_get_phase2_ca_cert_scheme (NMSetting8021x *setting) { g_return_val_if_fail (NM_IS_SETTING_802_1X (setting), NM_SETTING_802_1X_CK_SCHEME_UNKNOWN); - return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_ca_cert); + return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_ca_cert, NULL); } /** @@ -1349,7 +1393,7 @@ nm_setting_802_1x_get_phase2_client_cert_scheme (NMSetting8021x *setting) { g_return_val_if_fail (NM_IS_SETTING_802_1X (setting), NM_SETTING_802_1X_CK_SCHEME_UNKNOWN); - return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_client_cert); + return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_client_cert, NULL); } /** @@ -1604,7 +1648,7 @@ nm_setting_802_1x_get_private_key_scheme (NMSetting8021x *setting) { g_return_val_if_fail (NM_IS_SETTING_802_1X (setting), NM_SETTING_802_1X_CK_SCHEME_UNKNOWN); - return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->private_key); + return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->private_key, NULL); } /** @@ -1941,7 +1985,7 @@ nm_setting_802_1x_get_phase2_private_key_scheme (NMSetting8021x *setting) { g_return_val_if_fail (NM_IS_SETTING_802_1X (setting), NM_SETTING_802_1X_CK_SCHEME_UNKNOWN); - return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_private_key); + return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_private_key, NULL); } /** @@ -2575,35 +2619,18 @@ need_secrets (NMSetting *setting) static gboolean verify_cert (GBytes *bytes, const char *prop_name, GError **error) { - gconstpointer data; - gsize length; + GError *local = NULL; - if (!bytes) + if ( !bytes + || get_cert_scheme (bytes, &local) != NM_SETTING_802_1X_CK_SCHEME_UNKNOWN) return TRUE; - switch (get_cert_scheme (bytes)) { - case NM_SETTING_802_1X_CK_SCHEME_BLOB: - return TRUE; - case NM_SETTING_802_1X_CK_SCHEME_PATH: - /* For path-based schemes, verify that the path is zero-terminated */ - data = g_bytes_get_data (bytes, &length); - if (((const guchar *)data)[length - 1] == '\0') { - /* And ensure it's UTF-8 valid too so we can pass it through - * D-Bus and stuff like that. - */ - if (g_utf8_validate ((const char *)data + strlen (SCHEME_PATH), -1, NULL)) - return TRUE; - } - break; - default: - break; - } - - g_set_error_literal (error, - NM_CONNECTION_ERROR, - NM_CONNECTION_ERROR_INVALID_PROPERTY, - _("property is invalid")); + g_set_error (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("certificate is invalid: %s"), local->message); g_prefix_error (error, "%s.%s: ", NM_SETTING_802_1X_SETTING_NAME, prop_name); + g_error_free (local); return FALSE; } diff --git a/libnm-util/nm-setting-8021x.c b/libnm-util/nm-setting-8021x.c index 6d5268a17b..ffbac56fe4 100644 --- a/libnm-util/nm-setting-8021x.c +++ b/libnm-util/nm-setting-8021x.c @@ -33,6 +33,7 @@ #include "crypto.h" #include "nm-utils-private.h" #include "nm-setting-private.h" +#include "nm-utils-internal.h" /** * SECTION:nm-setting-8021x @@ -430,9 +431,20 @@ get_cert_scheme (GByteArray *array) if (!array || !array->len) return NM_SETTING_802_1X_CK_SCHEME_UNKNOWN; - if ( (array->len > strlen (SCHEME_PATH)) - && !memcmp (array->data, SCHEME_PATH, strlen (SCHEME_PATH))) - return NM_SETTING_802_1X_CK_SCHEME_PATH; + /* interpret the blob as PATH if it starts with "file://". */ + if ( array->len >= STRLEN (SCHEME_PATH) + && !memcmp (array->data, SCHEME_PATH, STRLEN (SCHEME_PATH))) { + /* But it must also be NUL terminated, contain at least + * one non-NUL character, and contain only one trailing NUL + * chracter. + * And ensure it's UTF-8 valid too so we can pass it through + * D-Bus and stuff like that. */ + if ( array->len > STRLEN (SCHEME_PATH) + 1 + && array->data[array->len - 1] == '\0' + && g_utf8_validate ((const char *) &array->data[STRLEN (SCHEME_PATH)], array->len - (STRLEN (SCHEME_PATH) + 1), NULL)) + return NM_SETTING_802_1X_CK_SCHEME_PATH; + return NM_SETTING_802_1X_CK_SCHEME_UNKNOWN; + } return NM_SETTING_802_1X_CK_SCHEME_BLOB; } @@ -2604,26 +2616,10 @@ need_secrets (NMSetting *setting) static gboolean verify_cert (GByteArray *array, const char *prop_name, GError **error) { - if (!array) + if ( !array + || get_cert_scheme (array) != NM_SETTING_802_1X_CK_SCHEME_UNKNOWN) return TRUE; - switch (get_cert_scheme (array)) { - case NM_SETTING_802_1X_CK_SCHEME_BLOB: - return TRUE; - case NM_SETTING_802_1X_CK_SCHEME_PATH: - /* For path-based schemes, verify that the path is zero-terminated */ - if (array->data[array->len - 1] == '\0') { - /* And ensure it's UTF-8 valid too so we can pass it through - * D-Bus and stuff like that. - */ - if (g_utf8_validate ((const char *) (array->data + strlen (SCHEME_PATH)), -1, NULL)) - return TRUE; - } - break; - default: - break; - } - g_set_error_literal (error, NM_SETTING_802_1X_ERROR, NM_SETTING_802_1X_ERROR_INVALID_PROPERTY, From cda7b158e2b87fc4a397334c50448ecb9d2457c2 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 26 Feb 2015 01:48:50 +0100 Subject: [PATCH 05/23] libnm: ensure valid blob for nm_setting_802_1x_set_*_cert() A valid blob cannot start with "file://", otherwise it would break the implementation of the certificate properties in NMSetting8021x. Simply reject every blob in nm_setting_802_1x_set_ca_cert() et al. that is not valid according to get_cert_scheme(). --- libnm-core/nm-setting-8021x.c | 41 +++++++++++++++++++++++++++++++---- libnm-util/nm-setting-8021x.c | 36 ++++++++++++++++++++++++++---- 2 files changed, 69 insertions(+), 8 deletions(-) diff --git a/libnm-core/nm-setting-8021x.c b/libnm-core/nm-setting-8021x.c index c07dbb38fb..ab22b2f98d 100644 --- a/libnm-core/nm-setting-8021x.c +++ b/libnm-core/nm-setting-8021x.c @@ -32,6 +32,7 @@ #include "nm-setting-private.h" #include "nm-core-enum-types.h" #include "nm-utils-internal.h" +#include "gsystem-local-alloc.h" /** * SECTION:nm-setting-8021x @@ -463,6 +464,38 @@ get_cert_scheme (GBytes *bytes, GError **error) return NM_SETTING_802_1X_CK_SCHEME_BLOB; } +static GByteArray * +load_and_verify_certificate (const char *cert_path, + NMSetting8021xCKScheme scheme, + NMCryptoFileFormat *out_file_format, + GError **error) +{ + NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; + GByteArray *array; + + array = crypto_load_and_verify_certificate (cert_path, &format, error); + + if (!array || !array->len || format == NM_CRYPTO_FILE_FORMAT_UNKNOWN) { + /* the array is empty or the format is already unknown. */ + format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; + } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) { + /* If we load the file as blob, we must ensure that the binary data does not + * start with file://. NMSetting8021x cannot represent blobs that start with + * file://. + * If that's the case, coerce the format to UNKNOWN. The callers will take care + * of that and not set the blob. */ + GBytes *bytes = g_bytes_new_static (array->data, array->len); + + if (get_cert_scheme (bytes, NULL) != NM_SETTING_802_1X_CK_SCHEME_BLOB) + format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; + g_bytes_unref (bytes); + } + + if (out_file_format) + *out_file_format = format; + return array; +} + /** * nm_setting_802_1x_get_ca_cert_scheme: * @setting: the #NMSetting8021x @@ -602,7 +635,7 @@ nm_setting_802_1x_set_ca_cert (NMSetting8021x *setting, return TRUE; } - data = crypto_load_and_verify_certificate (cert_path, &format, error); + data = load_and_verify_certificate (cert_path, scheme, &format, error); if (data) { /* wpa_supplicant can only use raw x509 CA certs */ if (format == NM_CRYPTO_FILE_FORMAT_X509) { @@ -916,7 +949,7 @@ nm_setting_802_1x_set_client_cert (NMSetting8021x *setting, return TRUE; } - data = crypto_load_and_verify_certificate (cert_path, &format, error); + data = load_and_verify_certificate (cert_path, scheme, &format, error); if (data) { gboolean valid = FALSE; @@ -1181,7 +1214,7 @@ nm_setting_802_1x_set_phase2_ca_cert (NMSetting8021x *setting, return TRUE; } - data = crypto_load_and_verify_certificate (cert_path, &format, error); + data = load_and_verify_certificate (cert_path, scheme, &format, error); if (data) { /* wpa_supplicant can only use raw x509 CA certs */ if (format == NM_CRYPTO_FILE_FORMAT_X509) { @@ -1499,7 +1532,7 @@ nm_setting_802_1x_set_phase2_client_cert (NMSetting8021x *setting, return TRUE; } - data = crypto_load_and_verify_certificate (cert_path, &format, error); + data = load_and_verify_certificate (cert_path, scheme, &format, error); if (data) { gboolean valid = FALSE; diff --git a/libnm-util/nm-setting-8021x.c b/libnm-util/nm-setting-8021x.c index ffbac56fe4..456d5eee68 100644 --- a/libnm-util/nm-setting-8021x.c +++ b/libnm-util/nm-setting-8021x.c @@ -449,6 +449,34 @@ get_cert_scheme (GByteArray *array) return NM_SETTING_802_1X_CK_SCHEME_BLOB; } +static GByteArray * +load_and_verify_certificate (const char *cert_path, + NMSetting8021xCKScheme scheme, + NMCryptoFileFormat *out_file_format, + GError **error) +{ + NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; + GByteArray *array; + + array = crypto_load_and_verify_certificate (cert_path, &format, error); + + if (!array || !array->len || format == NM_CRYPTO_FILE_FORMAT_UNKNOWN) + format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; + else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) { + /* If we load the file as blob, we must ensure that the binary data does not + * start with file://. NMSetting8021x cannot represent blobs that start with + * file://. + * If that's the case, coerce the format to UNKNOWN. The callers will take care + * of that and not set the blob. */ + if (get_cert_scheme (array) != NM_SETTING_802_1X_CK_SCHEME_BLOB) + format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; + } + + if (out_file_format) + *out_file_format = format; + return array; +} + /** * nm_setting_802_1x_get_ca_cert_scheme: * @setting: the #NMSetting8021x @@ -590,7 +618,7 @@ nm_setting_802_1x_set_ca_cert (NMSetting8021x *setting, return TRUE; } - data = crypto_load_and_verify_certificate (cert_path, &format, error); + data = load_and_verify_certificate (cert_path, scheme, &format, error); if (data) { /* wpa_supplicant can only use raw x509 CA certs */ if (format == NM_CRYPTO_FILE_FORMAT_X509) { @@ -906,7 +934,7 @@ nm_setting_802_1x_set_client_cert (NMSetting8021x *setting, return TRUE; } - data = crypto_load_and_verify_certificate (cert_path, &format, error); + data = load_and_verify_certificate (cert_path, scheme, &format, error); if (data) { gboolean valid = FALSE; @@ -1171,7 +1199,7 @@ nm_setting_802_1x_set_phase2_ca_cert (NMSetting8021x *setting, return TRUE; } - data = crypto_load_and_verify_certificate (cert_path, &format, error); + data = load_and_verify_certificate (cert_path, scheme, &format, error); if (data) { /* wpa_supplicant can only use raw x509 CA certs */ if (format == NM_CRYPTO_FILE_FORMAT_X509) { @@ -1491,7 +1519,7 @@ nm_setting_802_1x_set_phase2_client_cert (NMSetting8021x *setting, return TRUE; } - data = crypto_load_and_verify_certificate (cert_path, &format, error); + data = load_and_verify_certificate (cert_path, scheme, &format, error); if (data) { gboolean valid = FALSE; From 88a79625a667a562831925d4636bf819e0aa0f6b Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 26 Feb 2015 02:28:52 +0100 Subject: [PATCH 06/23] libnm: fix clearing memory in file_to_secure_bytes() If we go through the lengths of clearing the allocated memory, we must not forget @contents. --- libnm-core/nm-setting-8021x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libnm-core/nm-setting-8021x.c b/libnm-core/nm-setting-8021x.c index ab22b2f98d..10d29d980f 100644 --- a/libnm-core/nm-setting-8021x.c +++ b/libnm-core/nm-setting-8021x.c @@ -1755,7 +1755,7 @@ file_to_secure_bytes (const char *filename) if (g_file_get_contents (filename, &contents, &length, NULL)) { array = g_byte_array_sized_new (length); g_byte_array_append (array, (guint8 *) contents, length); - g_assert (array->len == length); + memset (contents, 0, length); g_free (contents); return g_bytes_new_with_free_func (array->data, array->len, free_secure_bytes, array); } From 11371b5470a890512b697d0926a242d9aefe9d1d Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 26 Feb 2015 02:27:54 +0100 Subject: [PATCH 07/23] libnm: only call strlen() once in NMSetting8021x:path_to_scheme_value() Also assert that path is not empty. --- libnm-core/nm-setting-8021x.c | 11 +++++++---- libnm-util/nm-setting-8021x.c | 12 +++++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/libnm-core/nm-setting-8021x.c b/libnm-core/nm-setting-8021x.c index 10d29d980f..a02e7114d5 100644 --- a/libnm-core/nm-setting-8021x.c +++ b/libnm-core/nm-setting-8021x.c @@ -572,13 +572,16 @@ static GBytes * path_to_scheme_value (const char *path) { GByteArray *array; + gsize len; - g_return_val_if_fail (path != NULL, NULL); + g_return_val_if_fail (path != NULL && path[0], NULL); - /* Add the path scheme tag to the front, then the fielname */ - array = g_byte_array_sized_new (strlen (path) + strlen (SCHEME_PATH) + 1); + len = strlen (path); + + /* Add the path scheme tag to the front, then the filename */ + array = g_byte_array_sized_new (len + strlen (SCHEME_PATH) + 1); g_byte_array_append (array, (const guint8 *) SCHEME_PATH, strlen (SCHEME_PATH)); - g_byte_array_append (array, (const guint8 *) path, strlen (path)); + g_byte_array_append (array, (const guint8 *) path, len); g_byte_array_append (array, (const guint8 *) "\0", 1); return g_byte_array_free_to_bytes (array); diff --git a/libnm-util/nm-setting-8021x.c b/libnm-util/nm-setting-8021x.c index 456d5eee68..1dc768c210 100644 --- a/libnm-util/nm-setting-8021x.c +++ b/libnm-util/nm-setting-8021x.c @@ -551,14 +551,16 @@ static GByteArray * path_to_scheme_value (const char *path) { GByteArray *array; + gsize len; - g_return_val_if_fail (path != NULL, NULL); + g_return_val_if_fail (path != NULL && path[0], NULL); - /* Add the path scheme tag to the front, then the fielname */ - array = g_byte_array_sized_new (strlen (path) + strlen (SCHEME_PATH) + 1); - g_assert (array); + len = strlen (path); + + /* Add the path scheme tag to the front, then the filename */ + array = g_byte_array_sized_new (len + strlen (SCHEME_PATH) + 1); g_byte_array_append (array, (const guint8 *) SCHEME_PATH, strlen (SCHEME_PATH)); - g_byte_array_append (array, (const guint8 *) path, strlen (path)); + g_byte_array_append (array, (const guint8 *) path, len); g_byte_array_append (array, (const guint8 *) "\0", 1); return array; } From 0f1fe6942253bfdfa35c1108049baeb2be048bb7 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 26 Feb 2015 02:38:14 +0100 Subject: [PATCH 08/23] trivial: add FIXME code comment about potential race --- libnm-core/nm-setting-8021x.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libnm-core/nm-setting-8021x.c b/libnm-core/nm-setting-8021x.c index a02e7114d5..dbead46253 100644 --- a/libnm-core/nm-setting-8021x.c +++ b/libnm-core/nm-setting-8021x.c @@ -1870,7 +1870,8 @@ nm_setting_802_1x_set_private_key (NMSetting8021x *setting, priv->private_key_password = g_strdup (password); if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) { - /* Shouldn't fail this since we just verified the private key above */ + /* FIXME: potential race after verifying the private key above */ + /* FIXME: ensure blob doesn't start with file:// */ priv->private_key = file_to_secure_bytes (key_path); g_assert (priv->private_key); } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) @@ -2181,7 +2182,8 @@ nm_setting_802_1x_set_phase2_private_key (NMSetting8021x *setting, priv->phase2_private_key_password = g_strdup (password); if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) { - /* Shouldn't fail this since we just verified the private key above */ + /* FIXME: potential race after verifying the private key above */ + /* FIXME: ensure blob doesn't start with file:// */ priv->phase2_private_key = file_to_secure_bytes (key_path); g_assert (priv->phase2_private_key); } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) From 15926e9eb359d84a4bc039e4a5e3c20604a3d4f7 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 26 Feb 2015 10:12:11 +0100 Subject: [PATCH 09/23] libnm: add function nm_setting_802_1x_check_cert_scheme() When setting the certificate glib properties directly, we raise a g_warning() when the binary data is invalid. But since the caller has no access to the validation function, he cannot easily check whether his action will result in a warning. Add nm_setting_802_1x_check_cert_scheme() for that. --- libnm-core/nm-setting-8021x.c | 32 +++++++++++++++++++++++++++----- libnm-core/nm-setting-8021x.h | 3 +++ libnm/libnm.ver | 1 + 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/libnm-core/nm-setting-8021x.c b/libnm-core/nm-setting-8021x.c index dbead46253..6a54b575c9 100644 --- a/libnm-core/nm-setting-8021x.c +++ b/libnm-core/nm-setting-8021x.c @@ -416,7 +416,32 @@ get_cert_scheme (GBytes *bytes, GError **error) } data = g_bytes_get_data (bytes, &length); - if (!length) { + return nm_setting_802_1x_check_cert_scheme (data, length, error); +} + +/** + * nm_setting_802_1x_check_cert_scheme: + * @pdata: (allow-none): the data pointer + * @length: the length of the data + * @error: (allow-none): (out): validation reason + * + * Determines and verifies the blob type. + * When setting certificate properties of NMSetting8021x + * the blob must be not UNKNOWN (or NULL). + * + * Returns: the scheme of the blob or %NM_SETTING_802_1X_CK_SCHEME_UNKNOWN. + * For NULL it also returns NM_SETTING_802_1X_CK_SCHEME_UNKNOWN. + * + * Since: 1.2 + **/ +NMSetting8021xCKScheme +nm_setting_802_1x_check_cert_scheme (gconstpointer pdata, gsize length, GError **error) +{ + const char *data = pdata; + + g_return_val_if_fail (!length || data, NM_SETTING_802_1X_CK_SCHEME_UNKNOWN); + + if (!length || !data) { g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, @@ -484,11 +509,8 @@ load_and_verify_certificate (const char *cert_path, * file://. * If that's the case, coerce the format to UNKNOWN. The callers will take care * of that and not set the blob. */ - GBytes *bytes = g_bytes_new_static (array->data, array->len); - - if (get_cert_scheme (bytes, NULL) != NM_SETTING_802_1X_CK_SCHEME_BLOB) + if (nm_setting_802_1x_check_cert_scheme (array->data, array->len, NULL) != NM_SETTING_802_1X_CK_SCHEME_BLOB) format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; - g_bytes_unref (bytes); } if (out_file_format) diff --git a/libnm-core/nm-setting-8021x.h b/libnm-core/nm-setting-8021x.h index 43885daaa3..326165aa59 100644 --- a/libnm-core/nm-setting-8021x.h +++ b/libnm-core/nm-setting-8021x.h @@ -147,6 +147,9 @@ GType nm_setting_802_1x_get_type (void); NMSetting *nm_setting_802_1x_new (void); +NM_AVAILABLE_IN_1_2 +NMSetting8021xCKScheme nm_setting_802_1x_check_cert_scheme (gconstpointer pdata, gsize length, GError **error); + guint32 nm_setting_802_1x_get_num_eap_methods (NMSetting8021x *setting); const char * nm_setting_802_1x_get_eap_method (NMSetting8021x *setting, guint32 i); gboolean nm_setting_802_1x_add_eap_method (NMSetting8021x *setting, const char *eap); diff --git a/libnm/libnm.ver b/libnm/libnm.ver index 258336ae3d..44b83f6ecb 100644 --- a/libnm/libnm.ver +++ b/libnm/libnm.ver @@ -847,6 +847,7 @@ local: libnm_1_2_0 { global: + nm_setting_802_1x_check_cert_scheme; nm_setting_bridge_get_multicast_snooping; nm_setting_wireless_get_powersave; nm_utils_bond_mode_int_to_string; From 7b6759b76427868723f480fc3516d964df237542 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 25 Feb 2015 21:52:00 +0100 Subject: [PATCH 10/23] keyfile: make reader more strict in handle_as_path() When interpreting a blob as filename, ensure that it contains no NUL character (except the last char). --- src/settings/plugins/keyfile/reader.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/settings/plugins/keyfile/reader.c b/src/settings/plugins/keyfile/reader.c index 1a1d9663d3..d1ea6e2c27 100644 --- a/src/settings/plugins/keyfile/reader.c +++ b/src/settings/plugins/keyfile/reader.c @@ -777,14 +777,17 @@ handle_as_path (GBytes *bytes, if (data_len > 500 || data_len < 1) return FALSE; - /* If there's a trailing NULL tell g_utf8_validate() to to until the NULL */ - if (data[data_len - 1] == '\0') - validate_len = -1; - else + /* If there's a trailing zero tell g_utf8_validate() to validate until the zero */ + if (data[data_len - 1] == '\0') { + /* setting it to -1, would mean we accept data to contain NUL characters before the + * end. Don't accept any NUL in [0 .. data_len-1[ . */ + validate_len = data_len - 1; + } else validate_len = data_len; - if (g_utf8_validate ((const char *) data, validate_len, NULL) == FALSE) - return FALSE; + if ( validate_len == 0 + || g_utf8_validate ((const char *) data, validate_len, NULL) == FALSE) + return FALSE; /* Might be a bare path without the file:// prefix; in that case * if it's an absolute path, use that, otherwise treat it as a From f430774ca0f5a4542b5d1faa59240a952c5e19ce Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 24 Feb 2015 23:27:36 +0100 Subject: [PATCH 11/23] keyfile: handle invalid integer list in keyfile reader get_bytes() nm_keyfile_plugin_kf_get_integer_list() should always set @length to zero when returning no integer list. So, this is probably correct. Still, just to be explicit, anticipate and handle a missing @tmp_list. --- src/settings/plugins/keyfile/reader.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/settings/plugins/keyfile/reader.c b/src/settings/plugins/keyfile/reader.c index d1ea6e2c27..bb9667b59d 100644 --- a/src/settings/plugins/keyfile/reader.c +++ b/src/settings/plugins/keyfile/reader.c @@ -618,6 +618,9 @@ get_bytes (GKeyFile *keyfile, gsize length; int i; + if (!nm_keyfile_plugin_kf_has_key (keyfile, setting_name, key, NULL)) + return NULL; + /* New format: just a string * Old format: integer list; e.g. 11;25;38; */ @@ -647,6 +650,11 @@ get_bytes (GKeyFile *keyfile, if (!array) { /* Old format; list of ints */ tmp_list = nm_keyfile_plugin_kf_get_integer_list (keyfile, setting_name, key, &length, NULL); + if (!tmp_list) { + nm_log_warn (LOGD_SETTINGS, "%s: %s / %s ignoring invalid binary property", + __func__, setting_name, key); + return NULL; + } array = g_byte_array_sized_new (length); for (i = 0; i < length; i++) { int val = tmp_list[i]; From b66deb67faa09597a8b6e52bbf07c39622220f38 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Fri, 27 Feb 2015 00:27:13 +0100 Subject: [PATCH 12/23] keyfile: remove unused struct member ObjectType.privkey_pw_prop in writer.c --- src/settings/plugins/keyfile/writer.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/settings/plugins/keyfile/writer.c b/src/settings/plugins/keyfile/writer.c index 483835048a..a707ac3287 100644 --- a/src/settings/plugins/keyfile/writer.c +++ b/src/settings/plugins/keyfile/writer.c @@ -367,7 +367,6 @@ password_raw_writer (GKeyFile *file, typedef struct ObjectType { const char *key; const char *suffix; - const char *privkey_pw_prop; NMSetting8021xCKScheme (*scheme_func) (NMSetting8021x *setting); NMSetting8021xCKFormat (*format_func) (NMSetting8021x *setting); const char * (*path_func) (NMSetting8021x *setting); @@ -377,7 +376,6 @@ typedef struct ObjectType { static const ObjectType objtypes[10] = { { NM_SETTING_802_1X_CA_CERT, "ca-cert", - NULL, nm_setting_802_1x_get_ca_cert_scheme, NULL, nm_setting_802_1x_get_ca_cert_path, @@ -385,7 +383,6 @@ static const ObjectType objtypes[10] = { { NM_SETTING_802_1X_PHASE2_CA_CERT, "inner-ca-cert", - NULL, nm_setting_802_1x_get_phase2_ca_cert_scheme, NULL, nm_setting_802_1x_get_phase2_ca_cert_path, @@ -393,7 +390,6 @@ static const ObjectType objtypes[10] = { { NM_SETTING_802_1X_CLIENT_CERT, "client-cert", - NULL, nm_setting_802_1x_get_client_cert_scheme, NULL, nm_setting_802_1x_get_client_cert_path, @@ -401,7 +397,6 @@ static const ObjectType objtypes[10] = { { NM_SETTING_802_1X_PHASE2_CLIENT_CERT, "inner-client-cert", - NULL, nm_setting_802_1x_get_phase2_client_cert_scheme, NULL, nm_setting_802_1x_get_phase2_client_cert_path, @@ -409,7 +404,6 @@ static const ObjectType objtypes[10] = { { NM_SETTING_802_1X_PRIVATE_KEY, "private-key", - NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD, nm_setting_802_1x_get_private_key_scheme, nm_setting_802_1x_get_private_key_format, nm_setting_802_1x_get_private_key_path, @@ -417,7 +411,6 @@ static const ObjectType objtypes[10] = { { NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, "inner-private-key", - NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD, nm_setting_802_1x_get_phase2_private_key_scheme, nm_setting_802_1x_get_phase2_private_key_format, nm_setting_802_1x_get_phase2_private_key_path, From 093f6d477bc86d4e607b3f3299ba4fbc2e7bc104 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Sun, 22 Feb 2015 11:55:31 +0100 Subject: [PATCH 13/23] trivial: rename nm_utils_ascii_str_to_int64() to _nm_utils_ascii_str_to_int64() --- src/NetworkManagerUtils.c | 4 ++-- src/NetworkManagerUtils.h | 2 +- src/dhcp-manager/nm-dhcp-listener.c | 2 +- src/platform/nm-linux-platform.c | 6 +++--- src/platform/nm-platform.c | 2 +- src/settings/plugins/ibft/reader.c | 4 ++-- src/settings/plugins/ifcfg-rh/reader.c | 6 +++--- src/settings/plugins/ifcfg-rh/shvar.c | 4 ++-- src/settings/plugins/ifnet/connection_parser.c | 8 ++++---- src/settings/plugins/keyfile/reader.c | 2 +- src/tests/test-general.c | 4 ++-- 11 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c index 67633bccef..5b772fccd8 100644 --- a/src/NetworkManagerUtils.c +++ b/src/NetworkManagerUtils.c @@ -1798,7 +1798,7 @@ nm_utils_cmp_connection_by_autoconnect_priority (NMConnection **a, NMConnection return 0; } -/* nm_utils_ascii_str_to_int64: +/* _nm_utils_ascii_str_to_int64: * * A wrapper for g_ascii_strtoll, that checks whether the whole string * can be successfully converted to a number and is within a given @@ -1813,7 +1813,7 @@ nm_utils_cmp_connection_by_autoconnect_priority (NMConnection **a, NMConnection * white space. **/ gint64 -nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max, gint64 fallback) +_nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max, gint64 fallback) { gint64 v; size_t len; diff --git a/src/NetworkManagerUtils.h b/src/NetworkManagerUtils.h index 25b3e27a0d..adc3d563bb 100644 --- a/src/NetworkManagerUtils.h +++ b/src/NetworkManagerUtils.h @@ -163,7 +163,7 @@ int nm_utils_cmp_connection_by_autoconnect_priority (NMConnection **a, NMConnect void nm_utils_log_connection_diff (NMConnection *connection, NMConnection *diff_base, guint32 level, guint64 domain, const char *name, const char *prefix); -gint64 nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max, gint64 fallback); +gint64 _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max, gint64 fallback); #define NM_UTILS_UUID_NS "b425e9fb-7598-44b4-9e3b-5a2e3aaa4905" diff --git a/src/dhcp-manager/nm-dhcp-listener.c b/src/dhcp-manager/nm-dhcp-listener.c index bb29717a97..f1accf4655 100644 --- a/src/dhcp-manager/nm-dhcp-listener.c +++ b/src/dhcp-manager/nm-dhcp-listener.c @@ -132,7 +132,7 @@ handle_event (DBusGProxy *proxy, } pid_str = get_option (options, "pid"); - pid = nm_utils_ascii_str_to_int64 (pid_str, 10, 0, G_MAXINT32, -1); + pid = _nm_utils_ascii_str_to_int64 (pid_str, 10, 0, G_MAXINT32, -1); if (pid == -1) { nm_log_warn (LOGD_DHCP, "DHCP event: couldn't convert PID '%s' to an integer", pid_str ? pid_str : "(null)"); goto out; diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c index e1887e5a4b..12f7741c3e 100644 --- a/src/platform/nm-linux-platform.c +++ b/src/platform/nm-linux-platform.c @@ -3025,7 +3025,7 @@ tun_get_properties (NMPlatform *platform, int ifindex, NMPlatformTunProperties * val = nm_platform_sysctl_get (path); g_free (path); if (val) { - props->owner = nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1); + props->owner = _nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1); if (errno) success = FALSE; g_free (val); @@ -3036,7 +3036,7 @@ tun_get_properties (NMPlatform *platform, int ifindex, NMPlatformTunProperties * val = nm_platform_sysctl_get (path); g_free (path); if (val) { - props->group = nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1); + props->group = _nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1); if (errno) success = FALSE; g_free (val); @@ -3049,7 +3049,7 @@ tun_get_properties (NMPlatform *platform, int ifindex, NMPlatformTunProperties * if (val) { gint64 flags; - flags = nm_utils_ascii_str_to_int64 (val, 16, 0, G_MAXINT64, 0); + flags = _nm_utils_ascii_str_to_int64 (val, 16, 0, G_MAXINT64, 0); if (!errno) { #ifndef IFF_MULTI_QUEUE const int IFF_MULTI_QUEUE = 0x0100; diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c index cbf2c8cd61..87d983f000 100644 --- a/src/platform/nm-platform.c +++ b/src/platform/nm-platform.c @@ -322,7 +322,7 @@ nm_platform_sysctl_get_int_checked (const char *path, guint base, gint64 min, gi return fallback; } - ret = nm_utils_ascii_str_to_int64 (value, base, min, max, fallback); + ret = _nm_utils_ascii_str_to_int64 (value, base, min, max, fallback); g_free (value); return ret; } diff --git a/src/settings/plugins/ibft/reader.c b/src/settings/plugins/ibft/reader.c index e3a0168a6c..9f95123282 100644 --- a/src/settings/plugins/ibft/reader.c +++ b/src/settings/plugins/ibft/reader.c @@ -431,7 +431,7 @@ is_ibft_vlan_device (const GPtrArray *block) /* VLAN 0 is normally a valid VLAN ID, but in the iBFT case it * means "no VLAN". */ - if (nm_utils_ascii_str_to_int64 (s_vlan_id, 10, 1, 4095, -1) != -1) + if (_nm_utils_ascii_str_to_int64 (s_vlan_id, 10, 1, 4095, -1) != -1) return TRUE; } return FALSE; @@ -458,7 +458,7 @@ vlan_setting_add_from_block (const GPtrArray *block, g_assert (vlan_id_str); /* VLAN 0 is normally a valid VLAN ID, but in the iBFT case it means "no VLAN" */ - vlan_id = nm_utils_ascii_str_to_int64 (vlan_id_str, 10, 1, 4095, -1); + vlan_id = _nm_utils_ascii_str_to_int64 (vlan_id_str, 10, 1, 4095, -1); if (vlan_id == -1) { g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, "Invalid VLAN_ID '%s'", vlan_id_str); diff --git a/src/settings/plugins/ifcfg-rh/reader.c b/src/settings/plugins/ifcfg-rh/reader.c index c40fecf50b..febbd90ac6 100644 --- a/src/settings/plugins/ifcfg-rh/reader.c +++ b/src/settings/plugins/ifcfg-rh/reader.c @@ -503,7 +503,7 @@ read_one_ip4_route (shvarFile *ifcfg, /* Metric */ value = svGetValue (ifcfg, metric_tag, FALSE); if (value) { - metric = nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT32, -1); + metric = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT32, -1); if (metric < 0) { g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, "Invalid IP4 route metric '%s'", value); @@ -3344,7 +3344,7 @@ make_wireless_setting (shvarFile *ifcfg, value = svGetValue (ifcfg, "CHANNEL", FALSE); if (value) { errno = 0; - chan = nm_utils_ascii_str_to_int64 (value, 10, 1, 196, 0); + chan = _nm_utils_ascii_str_to_int64 (value, 10, 1, 196, 0); if (errno || (chan == 0)) { g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, "Invalid wireless channel '%s'", value); @@ -4889,7 +4889,7 @@ devtimeout_from_file (const char *filename) devtimeout_str = svGetValue (ifcfg, "DEVTIMEOUT", FALSE); if (devtimeout_str) { - devtimeout = nm_utils_ascii_str_to_int64 (devtimeout_str, 10, 0, G_MAXUINT, 0); + devtimeout = _nm_utils_ascii_str_to_int64 (devtimeout_str, 10, 0, G_MAXUINT, 0); g_free (devtimeout_str); } else devtimeout = 0; diff --git a/src/settings/plugins/ifcfg-rh/shvar.c b/src/settings/plugins/ifcfg-rh/shvar.c index 5f3d173b2a..812f83aca8 100644 --- a/src/settings/plugins/ifcfg-rh/shvar.c +++ b/src/settings/plugins/ifcfg-rh/shvar.c @@ -321,7 +321,7 @@ svTrueValue (shvarFile *s, const char *key, gboolean def) * @max: the maximum for range-check * @fallback: the fallback value in any error case * - * Reads a value @key and converts it to an integer using nm_utils_ascii_str_to_int64(). + * Reads a value @key and converts it to an integer using _nm_utils_ascii_str_to_int64(). * In case of error, @errno will be set and @fallback returned. */ gint64 svGetValueInt64 (shvarFile *s, const char *key, guint base, gint64 min, gint64 max, gint64 fallback) @@ -336,7 +336,7 @@ svGetValueInt64 (shvarFile *s, const char *key, guint base, gint64 min, gint64 m return fallback; } - result = nm_utils_ascii_str_to_int64 (tmp, base, min, max, fallback); + result = _nm_utils_ascii_str_to_int64 (tmp, base, min, max, fallback); errsv = errno; if (errsv != 0) PARSE_WARNING ("Error reading '%s' value '%s' as integer (%d)", key, tmp, errsv); diff --git a/src/settings/plugins/ifnet/connection_parser.c b/src/settings/plugins/ifnet/connection_parser.c index 41baf31507..888ee589ad 100644 --- a/src/settings/plugins/ifnet/connection_parser.c +++ b/src/settings/plugins/ifnet/connection_parser.c @@ -706,13 +706,13 @@ make_ip4_setting (NMConnection *connection, GError *local = NULL; if ((metric_str = ifnet_get_data (conn_name, "metric")) != NULL) { - metric = nm_utils_ascii_str_to_int64 (metric_str, 10, 0, G_MAXUINT32, -1); + metric = _nm_utils_ascii_str_to_int64 (metric_str, 10, 0, G_MAXUINT32, -1); } else { metric_str = ifnet_get_global_data ("metric"); if (metric_str) { stripped = g_strdup (metric_str); strip_string (stripped, '"'); - metric = nm_utils_ascii_str_to_int64 (metric_str, 10, 0, G_MAXUINT32, -1); + metric = _nm_utils_ascii_str_to_int64 (metric_str, 10, 0, G_MAXUINT32, -1); g_free (stripped); } else metric = -1; @@ -846,13 +846,13 @@ make_ip6_setting (NMConnection *connection, /* metric is not per routes configuration right now * global metric is also supported (metric="x") */ if ((metric_str = ifnet_get_data (conn_name, "metric")) != NULL) - metric = nm_utils_ascii_str_to_int64 (metric_str, 10, 0, G_MAXUINT32, -1); + metric = _nm_utils_ascii_str_to_int64 (metric_str, 10, 0, G_MAXUINT32, -1); else { metric_str = ifnet_get_global_data ("metric"); if (metric_str) { stripped = g_strdup (metric_str); strip_string (stripped, '"'); - metric = nm_utils_ascii_str_to_int64 (metric_str, 10, 0, G_MAXUINT32, -1); + metric = _nm_utils_ascii_str_to_int64 (metric_str, 10, 0, G_MAXUINT32, -1); g_free (stripped); } else metric = 1; diff --git a/src/settings/plugins/keyfile/reader.c b/src/settings/plugins/keyfile/reader.c index bb9667b59d..8f50b4bf4f 100644 --- a/src/settings/plugins/keyfile/reader.c +++ b/src/settings/plugins/keyfile/reader.c @@ -1145,7 +1145,7 @@ read_one_setting_value (NMSetting *setting, gint64 int_val; tmp_str = nm_keyfile_plugin_kf_get_value (info->keyfile, setting_name, key, NULL); - int_val = nm_utils_ascii_str_to_int64 (tmp_str, 10, G_MININT64, G_MAXINT64, 0); + int_val = _nm_utils_ascii_str_to_int64 (tmp_str, 10, G_MININT64, G_MAXINT64, 0); errsv = errno; if (errsv) nm_log_warn (LOGD_SETTINGS, "Invalid int64 value (%s)", tmp_str); diff --git a/src/tests/test-general.c b/src/tests/test-general.c index 21533cc743..be1ee5cbab 100644 --- a/src/tests/test-general.c +++ b/src/tests/test-general.c @@ -38,7 +38,7 @@ test_nm_utils_ascii_str_to_int64_check (const char *str, guint base, gint64 min, gint64 v; errno = 1; - v = nm_utils_ascii_str_to_int64 (str, base, min, max, fallback); + v = _nm_utils_ascii_str_to_int64 (str, base, min, max, fallback); g_assert_cmpint (errno, ==, exp_errno); g_assert_cmpint (v, ==, exp_val); } @@ -893,7 +893,7 @@ main (int argc, char **argv) { nmtst_init_with_logging (&argc, &argv, NULL, "ALL"); - g_test_add_func ("/general/nm_utils_ascii_str_to_int64", test_nm_utils_ascii_str_to_int64); + g_test_add_func ("/general/_nm_utils_ascii_str_to_int64", test_nm_utils_ascii_str_to_int64); g_test_add_func ("/general/nm_utils_ip6_address_clear_host_address", test_nm_utils_ip6_address_clear_host_address); g_test_add_func ("/general/nm_utils_log_connection_diff", test_nm_utils_log_connection_diff); From c5d23737fdabb1c55074427eec8b2d97cf1b7706 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Sun, 22 Feb 2015 11:54:03 +0100 Subject: [PATCH 14/23] libnm: move _nm_utils_ascii_str_to_int64() from src/ to libnm-core/ --- libnm-core/nm-core-internal.h | 2 + libnm-core/nm-utils.c | 79 +++++++++++++++ libnm-core/tests/test-general.c | 133 ++++++++++++++++++++++++++ src/NetworkManagerUtils.c | 75 --------------- src/NetworkManagerUtils.h | 2 - src/dhcp-manager/nm-dhcp-listener.c | 1 + src/platform/nm-linux-platform.c | 1 + src/platform/nm-platform.c | 1 + src/settings/plugins/ifcfg-rh/shvar.c | 2 +- src/tests/test-general.c | 130 ------------------------- 10 files changed, 218 insertions(+), 208 deletions(-) diff --git a/libnm-core/nm-core-internal.h b/libnm-core/nm-core-internal.h index 5af90267a5..a7598cc6ad 100644 --- a/libnm-core/nm-core-internal.h +++ b/libnm-core/nm-core-internal.h @@ -132,4 +132,6 @@ GByteArray *nm_utils_rsa_key_encrypt (const guint8 *data, char **out_password, GError **error); +gint64 _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max, gint64 fallback); + #endif diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c index 321c03f18c..776d7da4c0 100644 --- a/libnm-core/nm-utils.c +++ b/libnm-core/nm-utils.c @@ -22,6 +22,7 @@ #include "config.h" #include +#include #include #include #include @@ -3278,3 +3279,81 @@ nm_utils_bond_mode_string_to_int (const char *mode) } return -1; } + +/**********************************************************************************************/ + +/* _nm_utils_ascii_str_to_int64: + * + * A wrapper for g_ascii_strtoll, that checks whether the whole string + * can be successfully converted to a number and is within a given + * range. On any error, @fallback will be returned and %errno will be set + * to a non-zero value. On success, %errno will be set to zero, check %errno + * for errors. Any trailing or leading (ascii) white space is ignored and the + * functions is locale independent. + * + * The function is guaranteed to return a value between @min and @max + * (inclusive) or @fallback. Also, the parsing is rather strict, it does + * not allow for any unrecognized characters, except leading and trailing + * white space. + **/ +gint64 +_nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max, gint64 fallback) +{ + gint64 v; + size_t len; + char buf[64], *s, *str_free = NULL; + + if (str) { + while (g_ascii_isspace (str[0])) + str++; + } + if (!str || !str[0]) { + errno = EINVAL; + return fallback; + } + + len = strlen (str); + if (g_ascii_isspace (str[--len])) { + /* backward search the first non-ws character. + * We already know that str[0] is non-ws. */ + while (g_ascii_isspace (str[--len])) + ; + + /* str[len] is now the last non-ws character... */ + len++; + + if (len >= sizeof (buf)) + s = str_free = g_malloc (len + 1); + else + s = buf; + + memcpy (s, str, len); + s[len] = 0; + + /* + g_assert (len > 0 && len < strlen (str) && len == strlen (s)); + g_assert (!g_ascii_isspace (str[len-1]) && g_ascii_isspace (str[len])); + g_assert (strncmp (str, s, len) == 0); + */ + + str = s; + } + + errno = 0; + v = g_ascii_strtoll (str, &s, base); + + if (errno != 0) + v = fallback; + else if (s[0] != 0) { + errno = EINVAL; + v = fallback; + } else if (v > max || v < min) { + errno = ERANGE; + v = fallback; + } + + if (G_UNLIKELY (str_free)) + g_free (str_free); + return v; +} + diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c index 7df4e57aa5..202326ffa5 100644 --- a/libnm-core/tests/test-general.c +++ b/libnm-core/tests/test-general.c @@ -4040,6 +4040,137 @@ test_nm_utils_uuid_generate_from_string (void) /******************************************************************************/ +static void +test_nm_utils_ascii_str_to_int64_check (const char *str, guint base, gint64 min, + gint64 max, gint64 fallback, int exp_errno, + gint64 exp_val) +{ + gint64 v; + + errno = 1; + v = _nm_utils_ascii_str_to_int64 (str, base, min, max, fallback); + g_assert_cmpint (errno, ==, exp_errno); + g_assert_cmpint (v, ==, exp_val); +} + +static void +test_nm_utils_ascii_str_to_int64_do (const char *str, guint base, gint64 min, + gint64 max, gint64 fallback, int exp_errno, + gint64 exp_val) +{ + const char *sign = ""; + const char *val; + static const char *whitespaces[] = { + "", + " ", + "\r\n\t", + " \r\n\t ", + " \r\n\t \t\r\n\t", + NULL, + }; + static const char *nulls[] = { + "", + "0", + "00", + "0000", + "0000000000000000", + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + NULL, + }; + const char **ws_pre, **ws_post, **null; + guint i; + + if (str == NULL || exp_errno != 0) { + test_nm_utils_ascii_str_to_int64_check (str, base, min, max, fallback, exp_errno, exp_val); + return; + } + + if (strncmp (str, "-", 1) == 0) + sign = "-"; + + val = str + strlen (sign); + + for (ws_pre = whitespaces; *ws_pre; ws_pre++) { + for (ws_post = whitespaces; *ws_post; ws_post++) { + for (null = nulls; *null; null++) { + for (i = 0; ; i++) { + char *s; + const char *str_base = ""; + + if (base == 16) { + if (i == 1) + str_base = "0x"; + else if (i > 1) + break; + } else if (base == 8) { + if (i == 1) + str_base = "0"; + else if (i > 1) + break; + } else if (base == 0) { + if (i > 0) + break; + /* with base==0, a leading zero would be interpreted as octal. Only test without *null */ + if ((*null)[0]) + break; + } else { + if (i > 0) + break; + } + + s = g_strdup_printf ("%s%s%s%s%s%s", *ws_pre, sign, str_base, *null, val, *ws_post); + + test_nm_utils_ascii_str_to_int64_check (s, base, min, max, fallback, exp_errno, exp_val); + g_free (s); + } + } + } + } +} + +static void +test_nm_utils_ascii_str_to_int64 (void) +{ + test_nm_utils_ascii_str_to_int64_do (NULL, 10, 0, 10000, -1, EINVAL, -1); + test_nm_utils_ascii_str_to_int64_do ("", 10, 0, 10000, -1, EINVAL, -1); + test_nm_utils_ascii_str_to_int64_do ("1x", 10, 0, 10000, -1, EINVAL, -1); + test_nm_utils_ascii_str_to_int64_do ("4711", 10, 0, 10000, -1, 0, 4711); + test_nm_utils_ascii_str_to_int64_do ("10000", 10, 0, 10000, -1, 0, 10000); + test_nm_utils_ascii_str_to_int64_do ("10001", 10, 0, 10000, -1, ERANGE, -1); + test_nm_utils_ascii_str_to_int64_do ("FF", 16, 0, 10000, -1, 0, 255); + test_nm_utils_ascii_str_to_int64_do ("FF", 10, 0, 10000, -2, EINVAL, -2); + test_nm_utils_ascii_str_to_int64_do ("9223372036854775807", 10, 0, G_MAXINT64, -2, 0, G_MAXINT64); + test_nm_utils_ascii_str_to_int64_do ("7FFFFFFFFFFFFFFF", 16, 0, G_MAXINT64, -2, 0, G_MAXINT64); + test_nm_utils_ascii_str_to_int64_do ("9223372036854775808", 10, 0, G_MAXINT64, -2, ERANGE, -2); + test_nm_utils_ascii_str_to_int64_do ("-9223372036854775808", 10, G_MININT64, 0, -2, 0, G_MININT64); + test_nm_utils_ascii_str_to_int64_do ("-9223372036854775808", 10, G_MININT64+1, 0, -2, ERANGE, -2); + test_nm_utils_ascii_str_to_int64_do ("-9223372036854775809", 10, G_MININT64, 0, -2, ERANGE, -2); + test_nm_utils_ascii_str_to_int64_do ("1.0", 10, 1, 1, -1, EINVAL, -1); + test_nm_utils_ascii_str_to_int64_do ("1x0", 16, -10, 10, -100, EINVAL, -100); + test_nm_utils_ascii_str_to_int64_do ("0", 16, -10, 10, -100, 0, 0); + test_nm_utils_ascii_str_to_int64_do ("10001111", 2, -1000, 1000, -100000, 0, 0x8F); + test_nm_utils_ascii_str_to_int64_do ("-10001111", 2, -1000, 1000, -100000, 0, -0x8F); + test_nm_utils_ascii_str_to_int64_do ("1111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7F); + test_nm_utils_ascii_str_to_int64_do ("111111111111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFF); + test_nm_utils_ascii_str_to_int64_do ("11111111111111111111111111111111111111111111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFFFFFFFFFF); + test_nm_utils_ascii_str_to_int64_do ("111111111111111111111111111111111111111111111111111111111111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFFFFFFFFFFFFFF); + test_nm_utils_ascii_str_to_int64_do ("100000000000000000000000000000000000000000000000000000000000000", 2, G_MININT64, G_MAXINT64, -1, 0, 0x4000000000000000); + test_nm_utils_ascii_str_to_int64_do ("1000000000000000000000000000000000000000000000000000000000000000", 2, G_MININT64, G_MAXINT64, -1, ERANGE, -1); + test_nm_utils_ascii_str_to_int64_do ("-100000000000000000000000000000000000000000000000000000000000000", 2, G_MININT64, G_MAXINT64, -1, 0, -0x4000000000000000); + test_nm_utils_ascii_str_to_int64_do ("111111111111111111111111111111111111111111111111111111111111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFFFFFFFFFFFFFF); + test_nm_utils_ascii_str_to_int64_do ("-100000000000000000000000000000000000000000000000000000000000000", 2, G_MININT64, G_MAXINT64, -1, 0, -0x4000000000000000); + test_nm_utils_ascii_str_to_int64_do ("0x70", 10, G_MININT64, G_MAXINT64, -1, EINVAL, -1); + test_nm_utils_ascii_str_to_int64_do ("4711", 0, G_MININT64, G_MAXINT64, -1, 0, 4711); + test_nm_utils_ascii_str_to_int64_do ("04711", 0, G_MININT64, G_MAXINT64, -1, 0, 04711); + test_nm_utils_ascii_str_to_int64_do ("0x4711", 0, G_MININT64, G_MAXINT64, -1, 0, 0x4711); + test_nm_utils_ascii_str_to_int64_do ("080", 0, G_MININT64, G_MAXINT64, -1, EINVAL, -1); + test_nm_utils_ascii_str_to_int64_do ("070", 0, G_MININT64, G_MAXINT64, -1, 0, 7*8); + test_nm_utils_ascii_str_to_int64_do ("0x70", 0, G_MININT64, G_MAXINT64, -1, 0, 0x70); +} + +/******************************************************************************/ + NMTST_DEFINE (); int main (int argc, char **argv) @@ -4138,6 +4269,8 @@ int main (int argc, char **argv) g_test_add_func ("/core/general/hexstr2bin", test_hexstr2bin); g_test_add_func ("/core/general/test_nm_utils_uuid_generate_from_string", test_nm_utils_uuid_generate_from_string); + g_test_add_func ("/core/general/_nm_utils_ascii_str_to_int64", test_nm_utils_ascii_str_to_int64); + return g_test_run (); } diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c index 5b772fccd8..bce5467e99 100644 --- a/src/NetworkManagerUtils.c +++ b/src/NetworkManagerUtils.c @@ -1798,81 +1798,6 @@ nm_utils_cmp_connection_by_autoconnect_priority (NMConnection **a, NMConnection return 0; } -/* _nm_utils_ascii_str_to_int64: - * - * A wrapper for g_ascii_strtoll, that checks whether the whole string - * can be successfully converted to a number and is within a given - * range. On any error, @fallback will be returned and %errno will be set - * to a non-zero value. On success, %errno will be set to zero, check %errno - * for errors. Any trailing or leading (ascii) white space is ignored and the - * functions is locale independent. - * - * The function is guaranteed to return a value between @min and @max - * (inclusive) or @fallback. Also, the parsing is rather strict, it does - * not allow for any unrecognized characters, except leading and trailing - * white space. - **/ -gint64 -_nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max, gint64 fallback) -{ - gint64 v; - size_t len; - char buf[64], *s, *str_free = NULL; - - if (str) { - while (g_ascii_isspace (str[0])) - str++; - } - if (!str || !str[0]) { - errno = EINVAL; - return fallback; - } - - len = strlen (str); - if (g_ascii_isspace (str[--len])) { - /* backward search the first non-ws character. - * We already know that str[0] is non-ws. */ - while (g_ascii_isspace (str[--len])) - ; - - /* str[len] is now the last non-ws character... */ - len++; - - if (len >= sizeof (buf)) - s = str_free = g_malloc (len + 1); - else - s = buf; - - memcpy (s, str, len); - s[len] = 0; - - /* - g_assert (len > 0 && len < strlen (str) && len == strlen (s)); - g_assert (!g_ascii_isspace (str[len-1]) && g_ascii_isspace (str[len])); - g_assert (strncmp (str, s, len) == 0); - */ - - str = s; - } - - errno = 0; - v = g_ascii_strtoll (str, &s, base); - - if (errno != 0) - v = fallback; - else if (s[0] != 0) { - errno = EINVAL; - v = fallback; - } else if (v > max || v < min) { - errno = ERANGE; - v = fallback; - } - - if (G_UNLIKELY (str_free)) - g_free (str_free); - return v; -} - /** * nm_utils_uuid_generate_from_strings: * @string1: a variadic list of strings. Must be NULL terminated. diff --git a/src/NetworkManagerUtils.h b/src/NetworkManagerUtils.h index adc3d563bb..d8c9f5f87f 100644 --- a/src/NetworkManagerUtils.h +++ b/src/NetworkManagerUtils.h @@ -163,8 +163,6 @@ int nm_utils_cmp_connection_by_autoconnect_priority (NMConnection **a, NMConnect void nm_utils_log_connection_diff (NMConnection *connection, NMConnection *diff_base, guint32 level, guint64 domain, const char *name, const char *prefix); -gint64 _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max, gint64 fallback); - #define NM_UTILS_UUID_NS "b425e9fb-7598-44b4-9e3b-5a2e3aaa4905" char *nm_utils_uuid_generate_from_strings (const char *string1, ...) G_GNUC_NULL_TERMINATED; diff --git a/src/dhcp-manager/nm-dhcp-listener.c b/src/dhcp-manager/nm-dhcp-listener.c index f1accf4655..ca57ae595a 100644 --- a/src/dhcp-manager/nm-dhcp-listener.c +++ b/src/dhcp-manager/nm-dhcp-listener.c @@ -31,6 +31,7 @@ #include #include "nm-dhcp-listener.h" +#include "nm-core-internal.h" #include "nm-logging.h" #include "nm-dbus-manager.h" #include "nm-dbus-glib-types.h" diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c index 12f7741c3e..47075f226f 100644 --- a/src/platform/nm-linux-platform.c +++ b/src/platform/nm-linux-platform.c @@ -55,6 +55,7 @@ #endif #include "gsystem-local-alloc.h" +#include "nm-core-internal.h" #include "NetworkManagerUtils.h" #include "nm-linux-platform.h" #include "NetworkManagerUtils.h" diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c index 87d983f000..9500a75424 100644 --- a/src/platform/nm-platform.c +++ b/src/platform/nm-platform.c @@ -35,6 +35,7 @@ #include "NetworkManagerUtils.h" #include "nm-logging.h" #include "nm-enum-types.h" +#include "nm-core-internal.h" #define debug(...) nm_log_dbg (LOGD_PLATFORM, __VA_ARGS__) diff --git a/src/settings/plugins/ifcfg-rh/shvar.c b/src/settings/plugins/ifcfg-rh/shvar.c index 812f83aca8..ed12683072 100644 --- a/src/settings/plugins/ifcfg-rh/shvar.c +++ b/src/settings/plugins/ifcfg-rh/shvar.c @@ -36,7 +36,7 @@ #include "shvar.h" -#include "NetworkManagerUtils.h" +#include "nm-core-internal.h" #include "nm-logging.h" #define PARSE_WARNING(msg...) nm_log_warn (LOGD_SETTINGS, " " msg) diff --git a/src/tests/test-general.c b/src/tests/test-general.c index be1ee5cbab..e93f351c2f 100644 --- a/src/tests/test-general.c +++ b/src/tests/test-general.c @@ -30,135 +30,6 @@ #include "nm-test-utils.h" -static void -test_nm_utils_ascii_str_to_int64_check (const char *str, guint base, gint64 min, - gint64 max, gint64 fallback, int exp_errno, - gint64 exp_val) -{ - gint64 v; - - errno = 1; - v = _nm_utils_ascii_str_to_int64 (str, base, min, max, fallback); - g_assert_cmpint (errno, ==, exp_errno); - g_assert_cmpint (v, ==, exp_val); -} - -static void -test_nm_utils_ascii_str_to_int64_do (const char *str, guint base, gint64 min, - gint64 max, gint64 fallback, int exp_errno, - gint64 exp_val) -{ - const char *sign = ""; - const char *val; - static const char *whitespaces[] = { - "", - " ", - "\r\n\t", - " \r\n\t ", - " \r\n\t \t\r\n\t", - NULL, - }; - static const char *nulls[] = { - "", - "0", - "00", - "0000", - "0000000000000000", - "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - NULL, - }; - const char **ws_pre, **ws_post, **null; - guint i; - - if (str == NULL || exp_errno != 0) { - test_nm_utils_ascii_str_to_int64_check (str, base, min, max, fallback, exp_errno, exp_val); - return; - } - - if (strncmp (str, "-", 1) == 0) - sign = "-"; - - val = str + strlen (sign); - - for (ws_pre = whitespaces; *ws_pre; ws_pre++) { - for (ws_post = whitespaces; *ws_post; ws_post++) { - for (null = nulls; *null; null++) { - for (i = 0; ; i++) { - char *s; - const char *str_base = ""; - - if (base == 16) { - if (i == 1) - str_base = "0x"; - else if (i > 1) - break; - } else if (base == 8) { - if (i == 1) - str_base = "0"; - else if (i > 1) - break; - } else if (base == 0) { - if (i > 0) - break; - /* with base==0, a leading zero would be interpreted as octal. Only test without *null */ - if ((*null)[0]) - break; - } else { - if (i > 0) - break; - } - - s = g_strdup_printf ("%s%s%s%s%s%s", *ws_pre, sign, str_base, *null, val, *ws_post); - - test_nm_utils_ascii_str_to_int64_check (s, base, min, max, fallback, exp_errno, exp_val); - g_free (s); - } - } - } - } -} - -static void -test_nm_utils_ascii_str_to_int64 (void) -{ - test_nm_utils_ascii_str_to_int64_do (NULL, 10, 0, 10000, -1, EINVAL, -1); - test_nm_utils_ascii_str_to_int64_do ("", 10, 0, 10000, -1, EINVAL, -1); - test_nm_utils_ascii_str_to_int64_do ("1x", 10, 0, 10000, -1, EINVAL, -1); - test_nm_utils_ascii_str_to_int64_do ("4711", 10, 0, 10000, -1, 0, 4711); - test_nm_utils_ascii_str_to_int64_do ("10000", 10, 0, 10000, -1, 0, 10000); - test_nm_utils_ascii_str_to_int64_do ("10001", 10, 0, 10000, -1, ERANGE, -1); - test_nm_utils_ascii_str_to_int64_do ("FF", 16, 0, 10000, -1, 0, 255); - test_nm_utils_ascii_str_to_int64_do ("FF", 10, 0, 10000, -2, EINVAL, -2); - test_nm_utils_ascii_str_to_int64_do ("9223372036854775807", 10, 0, G_MAXINT64, -2, 0, G_MAXINT64); - test_nm_utils_ascii_str_to_int64_do ("7FFFFFFFFFFFFFFF", 16, 0, G_MAXINT64, -2, 0, G_MAXINT64); - test_nm_utils_ascii_str_to_int64_do ("9223372036854775808", 10, 0, G_MAXINT64, -2, ERANGE, -2); - test_nm_utils_ascii_str_to_int64_do ("-9223372036854775808", 10, G_MININT64, 0, -2, 0, G_MININT64); - test_nm_utils_ascii_str_to_int64_do ("-9223372036854775808", 10, G_MININT64+1, 0, -2, ERANGE, -2); - test_nm_utils_ascii_str_to_int64_do ("-9223372036854775809", 10, G_MININT64, 0, -2, ERANGE, -2); - test_nm_utils_ascii_str_to_int64_do ("1.0", 10, 1, 1, -1, EINVAL, -1); - test_nm_utils_ascii_str_to_int64_do ("1x0", 16, -10, 10, -100, EINVAL, -100); - test_nm_utils_ascii_str_to_int64_do ("0", 16, -10, 10, -100, 0, 0); - test_nm_utils_ascii_str_to_int64_do ("10001111", 2, -1000, 1000, -100000, 0, 0x8F); - test_nm_utils_ascii_str_to_int64_do ("-10001111", 2, -1000, 1000, -100000, 0, -0x8F); - test_nm_utils_ascii_str_to_int64_do ("1111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7F); - test_nm_utils_ascii_str_to_int64_do ("111111111111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFF); - test_nm_utils_ascii_str_to_int64_do ("11111111111111111111111111111111111111111111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFFFFFFFFFF); - test_nm_utils_ascii_str_to_int64_do ("111111111111111111111111111111111111111111111111111111111111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFFFFFFFFFFFFFF); - test_nm_utils_ascii_str_to_int64_do ("100000000000000000000000000000000000000000000000000000000000000", 2, G_MININT64, G_MAXINT64, -1, 0, 0x4000000000000000); - test_nm_utils_ascii_str_to_int64_do ("1000000000000000000000000000000000000000000000000000000000000000", 2, G_MININT64, G_MAXINT64, -1, ERANGE, -1); - test_nm_utils_ascii_str_to_int64_do ("-100000000000000000000000000000000000000000000000000000000000000", 2, G_MININT64, G_MAXINT64, -1, 0, -0x4000000000000000); - test_nm_utils_ascii_str_to_int64_do ("111111111111111111111111111111111111111111111111111111111111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFFFFFFFFFFFFFF); - test_nm_utils_ascii_str_to_int64_do ("-100000000000000000000000000000000000000000000000000000000000000", 2, G_MININT64, G_MAXINT64, -1, 0, -0x4000000000000000); - test_nm_utils_ascii_str_to_int64_do ("0x70", 10, G_MININT64, G_MAXINT64, -1, EINVAL, -1); - test_nm_utils_ascii_str_to_int64_do ("4711", 0, G_MININT64, G_MAXINT64, -1, 0, 4711); - test_nm_utils_ascii_str_to_int64_do ("04711", 0, G_MININT64, G_MAXINT64, -1, 0, 04711); - test_nm_utils_ascii_str_to_int64_do ("0x4711", 0, G_MININT64, G_MAXINT64, -1, 0, 0x4711); - test_nm_utils_ascii_str_to_int64_do ("080", 0, G_MININT64, G_MAXINT64, -1, EINVAL, -1); - test_nm_utils_ascii_str_to_int64_do ("070", 0, G_MININT64, G_MAXINT64, -1, 0, 7*8); - test_nm_utils_ascii_str_to_int64_do ("0x70", 0, G_MININT64, G_MAXINT64, -1, 0, 0x70); -} - /* Reference implementation for nm_utils_ip6_address_clear_host_address. * Taken originally from set_address_masked(), src/rdisc/nm-lndp-rdisc.c **/ @@ -893,7 +764,6 @@ main (int argc, char **argv) { nmtst_init_with_logging (&argc, &argv, NULL, "ALL"); - g_test_add_func ("/general/_nm_utils_ascii_str_to_int64", test_nm_utils_ascii_str_to_int64); g_test_add_func ("/general/nm_utils_ip6_address_clear_host_address", test_nm_utils_ip6_address_clear_host_address); g_test_add_func ("/general/nm_utils_log_connection_diff", test_nm_utils_log_connection_diff); From 67510e323a69a6724c9f454fa040b993e1d9b7f4 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Sun, 22 Feb 2015 20:15:40 +0100 Subject: [PATCH 15/23] trivial: rename nm_utils_uuid_generate_from_strings() to _nm_utils_uuid_generate_from_strings() --- src/NetworkManagerUtils.c | 4 ++-- src/NetworkManagerUtils.h | 2 +- src/settings/plugins/ibft/reader.c | 14 +++++++------- src/settings/plugins/keyfile/reader.c | 2 +- src/tests/test-general.c | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c index bce5467e99..dd328a32fd 100644 --- a/src/NetworkManagerUtils.c +++ b/src/NetworkManagerUtils.c @@ -1799,7 +1799,7 @@ nm_utils_cmp_connection_by_autoconnect_priority (NMConnection **a, NMConnection } /** - * nm_utils_uuid_generate_from_strings: + * _nm_utils_uuid_generate_from_strings: * @string1: a variadic list of strings. Must be NULL terminated. * * Returns a variant3 UUID based on the concatenated C strings. @@ -1812,7 +1812,7 @@ nm_utils_cmp_connection_by_autoconnect_priority (NMConnection **a, NMConnection * ("aa"), ("aa", ""), ("", "aa"), ... */ char * -nm_utils_uuid_generate_from_strings (const char *string1, ...) +_nm_utils_uuid_generate_from_strings (const char *string1, ...) { GString *str; va_list args; diff --git a/src/NetworkManagerUtils.h b/src/NetworkManagerUtils.h index d8c9f5f87f..0229efd733 100644 --- a/src/NetworkManagerUtils.h +++ b/src/NetworkManagerUtils.h @@ -165,7 +165,7 @@ void nm_utils_log_connection_diff (NMConnection *connection, NMConnection *diff_ #define NM_UTILS_UUID_NS "b425e9fb-7598-44b4-9e3b-5a2e3aaa4905" -char *nm_utils_uuid_generate_from_strings (const char *string1, ...) G_GNUC_NULL_TERMINATED; +char *_nm_utils_uuid_generate_from_strings (const char *string1, ...) G_GNUC_NULL_TERMINATED; #define NM_UTILS_NS_PER_SECOND ((gint64) 1000000000) gint64 nm_utils_get_monotonic_timestamp_ns (void); diff --git a/src/settings/plugins/ibft/reader.c b/src/settings/plugins/ibft/reader.c index 9f95123282..9bc6346278 100644 --- a/src/settings/plugins/ibft/reader.c +++ b/src/settings/plugins/ibft/reader.c @@ -397,13 +397,13 @@ connection_setting_add (const GPtrArray *block, prefix ? prefix : "", iface); - uuid = nm_utils_uuid_generate_from_strings ("ibft", - s_hwaddr, - s_vlanid ? "V" : "v", - s_vlanid ? s_vlanid : "", - s_ip4addr ? "A" : "DHCP", - s_ip4addr ? s_ip4addr : "", - NULL); + uuid = _nm_utils_uuid_generate_from_strings ("ibft", + s_hwaddr, + s_vlanid ? "V" : "v", + s_vlanid ? s_vlanid : "", + s_ip4addr ? "A" : "DHCP", + s_ip4addr ? s_ip4addr : "", + NULL); s_con = nm_setting_connection_new (); g_object_set (s_con, diff --git a/src/settings/plugins/keyfile/reader.c b/src/settings/plugins/keyfile/reader.c index 8f50b4bf4f..97b6567028 100644 --- a/src/settings/plugins/keyfile/reader.c +++ b/src/settings/plugins/keyfile/reader.c @@ -1327,7 +1327,7 @@ nm_keyfile_plugin_connection_from_file (const char *filename, GError **error) if (!nm_setting_connection_get_uuid (s_con)) { char *hashed_uuid; - hashed_uuid = nm_utils_uuid_generate_from_strings ("keyfile", filename, NULL); + hashed_uuid = _nm_utils_uuid_generate_from_strings ("keyfile", filename, NULL); g_object_set (s_con, NM_SETTING_CONNECTION_UUID, hashed_uuid, NULL); g_free (hashed_uuid); } diff --git a/src/tests/test-general.c b/src/tests/test-general.c index e93f351c2f..63ea258a84 100644 --- a/src/tests/test-general.c +++ b/src/tests/test-general.c @@ -622,7 +622,7 @@ __test_uuid (const char *expected_uuid, const char *str, gssize slen, char *uuid g_free (uuid_test); } -#define _test_uuid(expected_uuid, str, strlen, ...) __test_uuid (expected_uuid, str, strlen, nm_utils_uuid_generate_from_strings(__VA_ARGS__, NULL)) +#define _test_uuid(expected_uuid, str, strlen, ...) __test_uuid (expected_uuid, str, strlen, _nm_utils_uuid_generate_from_strings(__VA_ARGS__, NULL)) static void test_nm_utils_uuid_generate_from_strings (void) @@ -778,7 +778,7 @@ main (int argc, char **argv) g_test_add_func ("/general/connection-sort/autoconnect-priority", test_connection_sort_autoconnect_priority); - g_test_add_func ("/general/nm_utils_uuid_generate_from_strings", test_nm_utils_uuid_generate_from_strings); + g_test_add_func ("/general/_nm_utils_uuid_generate_from_strings", test_nm_utils_uuid_generate_from_strings); g_test_add_func ("/general/nm_match_spec_interface_name", test_nm_match_spec_interface_name); return g_test_run (); From e82293ebf68a68fe59adc138bcbecfb978d7e135 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Sun, 22 Feb 2015 20:15:52 +0100 Subject: [PATCH 16/23] libnm: move _nm_utils_uuid_generate_from_strings() from src/ to libnm-core/ --- libnm-core/nm-core-internal.h | 4 +++ libnm-core/nm-utils.c | 46 +++++++++++++++++++++++++++++++ libnm-core/tests/test-general.c | 48 +++++++++++++++++++++++++++++++++ src/NetworkManagerUtils.c | 42 ----------------------------- src/NetworkManagerUtils.h | 4 --- src/tests/test-general.c | 48 --------------------------------- 6 files changed, 98 insertions(+), 94 deletions(-) diff --git a/libnm-core/nm-core-internal.h b/libnm-core/nm-core-internal.h index a7598cc6ad..86a301bd1b 100644 --- a/libnm-core/nm-core-internal.h +++ b/libnm-core/nm-core-internal.h @@ -122,6 +122,10 @@ char ** _nm_utils_strsplit_set (const char *str, char *nm_utils_uuid_generate_from_string (const char *s, gssize slen, int uuid_type, gpointer type_args); +#define NM_UTILS_UUID_NS "b425e9fb-7598-44b4-9e3b-5a2e3aaa4905" + +char *_nm_utils_uuid_generate_from_strings (const char *string1, ...) G_GNUC_NULL_TERMINATED; + void _nm_dbus_errors_init (void); extern gboolean _nm_utils_is_manager_process; diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c index 776d7da4c0..7e0c6d9217 100644 --- a/libnm-core/nm-utils.c +++ b/libnm-core/nm-utils.c @@ -1940,6 +1940,8 @@ nm_utils_ip_routes_from_variant (GVariant *value, return routes; } +/**********************************************************************************************/ + /** * nm_utils_uuid_generate: * @@ -2012,6 +2014,50 @@ nm_utils_uuid_generate_from_string (const char *s, gssize slen, int uuid_type, g return buf; } +/** + * _nm_utils_uuid_generate_from_strings: + * @string1: a variadic list of strings. Must be NULL terminated. + * + * Returns a variant3 UUID based on the concatenated C strings. + * It does not simply concatenate them, but also includes the + * terminating '\0' character. For example "a", "b", gives + * "a\0b\0". + * + * This has the advantage, that the following invocations + * all give different UUIDs: (NULL), (""), ("",""), ("","a"), ("a",""), + * ("aa"), ("aa", ""), ("", "aa"), ... + */ +char * +_nm_utils_uuid_generate_from_strings (const char *string1, ...) +{ + GString *str; + va_list args; + const char *s; + char *uuid; + + if (!string1) + return nm_utils_uuid_generate_from_string (NULL, 0, NM_UTILS_UUID_TYPE_VARIANT3, NM_UTILS_UUID_NS); + + str = g_string_sized_new (120); /* effectively allocates power of 2 (128)*/ + + g_string_append_len (str, string1, strlen (string1) + 1); + + va_start (args, string1); + s = va_arg (args, const char *); + while (s) { + g_string_append_len (str, s, strlen (s) + 1); + s = va_arg (args, const char *); + } + va_end (args); + + uuid = nm_utils_uuid_generate_from_string (str->str, str->len, NM_UTILS_UUID_TYPE_VARIANT3, NM_UTILS_UUID_NS); + + g_string_free (str, TRUE); + return uuid; +} + +/**********************************************************************************************/ + /** * nm_utils_rsa_key_encrypt: * @data: (array length=len): RSA private key data to be encrypted diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c index 202326ffa5..a417d3fe31 100644 --- a/libnm-core/tests/test-general.c +++ b/libnm-core/tests/test-general.c @@ -4038,6 +4038,53 @@ test_nm_utils_uuid_generate_from_string (void) _test_uuid (NM_UTILS_UUID_TYPE_VARIANT3, "002a0ada-f547-375a-bab5-896a11d1927e", "a\0b", 3, UUID_NS_DNS); } +/*******************************************/ + +static void +__test_uuid (const char *expected_uuid, const char *str, gssize slen, char *uuid_test) +{ + g_assert (uuid_test); + g_assert (nm_utils_is_uuid (uuid_test)); + + if (strcmp (uuid_test, expected_uuid)) { + g_error ("UUID test failed (1): text=%s, len=%lld, expected=%s, uuid_test=%s", + str, (long long) slen, expected_uuid, uuid_test); + } + g_free (uuid_test); + + uuid_test = nm_utils_uuid_generate_from_string (str, slen, NM_UTILS_UUID_TYPE_VARIANT3, NM_UTILS_UUID_NS); + + g_assert (uuid_test); + g_assert (nm_utils_is_uuid (uuid_test)); + + if (strcmp (uuid_test, expected_uuid)) { + g_error ("UUID test failed (2): text=%s; len=%lld, expected=%s, uuid2=%s", + str, (long long) slen, expected_uuid, uuid_test); + } + g_free (uuid_test); +} + +#define _test_uuid(expected_uuid, str, strlen, ...) __test_uuid (expected_uuid, str, strlen, _nm_utils_uuid_generate_from_strings(__VA_ARGS__, NULL)) + +static void +test_nm_utils_uuid_generate_from_strings (void) +{ + _test_uuid ("b07c334a-399b-32de-8d50-58e4e08f98e3", "", 0, NULL); + _test_uuid ("b8a426cb-bcb5-30a3-bd8f-6786fea72df9", "\0", 1, ""); + _test_uuid ("12a4a982-7aae-39e1-951e-41aeb1250959", "a\0", 2, "a"); + _test_uuid ("69e22c7e-f89f-3a43-b239-1cb52ed8db69", "aa\0", 3, "aa"); + _test_uuid ("59829fd3-5ad5-3d90-a7b0-4911747e4088", "\0\0", 2, "", ""); + _test_uuid ("01ad0e06-6c50-3384-8d86-ddab81421425", "a\0\0", 3, "a", ""); + _test_uuid ("e1ed8647-9ed3-3ec8-8c6d-e8204524d71d", "aa\0\0", 4, "aa", ""); + _test_uuid ("fb1c7cd6-275c-3489-9382-83b900da8af0", "\0a\0", 3, "", "a"); + _test_uuid ("5d79494e-c4ba-31a6-80a2-d6016ccd7e17", "a\0a\0", 4, "a", "a"); + _test_uuid ("fd698d86-1b60-3ebe-855f-7aada9950a8d", "aa\0a\0", 5, "aa", "a"); + _test_uuid ("8c573b48-0f01-30ba-bb94-c5f59f4fe517", "\0aa\0", 4, "", "aa"); + _test_uuid ("2bdd3d46-eb83-3c53-a41b-a724d04b5544", "a\0aa\0", 5, "a", "aa"); + _test_uuid ("13d4b780-07c1-3ba7-b449-81c4844ef039", "aa\0aa\0", 6, "aa", "aa"); + _test_uuid ("dd265bf7-c05a-3037-9939-b9629858a477", "a\0b\0", 4, "a", "b"); +} + /******************************************************************************/ static void @@ -4268,6 +4315,7 @@ int main (int argc, char **argv) g_test_add_func ("/core/general/hexstr2bin", test_hexstr2bin); g_test_add_func ("/core/general/test_nm_utils_uuid_generate_from_string", test_nm_utils_uuid_generate_from_string); + g_test_add_func ("/core/general/_nm_utils_uuid_generate_from_strings", test_nm_utils_uuid_generate_from_strings); g_test_add_func ("/core/general/_nm_utils_ascii_str_to_int64", test_nm_utils_ascii_str_to_int64); diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c index dd328a32fd..57f817649e 100644 --- a/src/NetworkManagerUtils.c +++ b/src/NetworkManagerUtils.c @@ -1798,48 +1798,6 @@ nm_utils_cmp_connection_by_autoconnect_priority (NMConnection **a, NMConnection return 0; } -/** - * _nm_utils_uuid_generate_from_strings: - * @string1: a variadic list of strings. Must be NULL terminated. - * - * Returns a variant3 UUID based on the concatenated C strings. - * It does not simply concatenate them, but also includes the - * terminating '\0' character. For example "a", "b", gives - * "a\0b\0". - * - * This has the advantage, that the following invocations - * all give different UUIDs: (NULL), (""), ("",""), ("","a"), ("a",""), - * ("aa"), ("aa", ""), ("", "aa"), ... - */ -char * -_nm_utils_uuid_generate_from_strings (const char *string1, ...) -{ - GString *str; - va_list args; - const char *s; - char *uuid; - - if (!string1) - return nm_utils_uuid_generate_from_string (NULL, 0, NM_UTILS_UUID_TYPE_VARIANT3, NM_UTILS_UUID_NS); - - str = g_string_sized_new (120); /* effectively allocates power of 2 (128)*/ - - g_string_append_len (str, string1, strlen (string1) + 1); - - va_start (args, string1); - s = va_arg (args, const char *); - while (s) { - g_string_append_len (str, s, strlen (s) + 1); - s = va_arg (args, const char *); - } - va_end (args); - - uuid = nm_utils_uuid_generate_from_string (str->str, str->len, NM_UTILS_UUID_TYPE_VARIANT3, NM_UTILS_UUID_NS); - - g_string_free (str, TRUE); - return uuid; -} - /**************************************************************************/ static gint64 monotonic_timestamp_offset_sec; diff --git a/src/NetworkManagerUtils.h b/src/NetworkManagerUtils.h index 0229efd733..23d8330a34 100644 --- a/src/NetworkManagerUtils.h +++ b/src/NetworkManagerUtils.h @@ -163,10 +163,6 @@ int nm_utils_cmp_connection_by_autoconnect_priority (NMConnection **a, NMConnect void nm_utils_log_connection_diff (NMConnection *connection, NMConnection *diff_base, guint32 level, guint64 domain, const char *name, const char *prefix); -#define NM_UTILS_UUID_NS "b425e9fb-7598-44b4-9e3b-5a2e3aaa4905" - -char *_nm_utils_uuid_generate_from_strings (const char *string1, ...) G_GNUC_NULL_TERMINATED; - #define NM_UTILS_NS_PER_SECOND ((gint64) 1000000000) gint64 nm_utils_get_monotonic_timestamp_ns (void); gint64 nm_utils_get_monotonic_timestamp_us (void); diff --git a/src/tests/test-general.c b/src/tests/test-general.c index 63ea258a84..dae872c86d 100644 --- a/src/tests/test-general.c +++ b/src/tests/test-general.c @@ -598,53 +598,6 @@ test_connection_sort_autoconnect_priority (void) /*******************************************/ -static void -__test_uuid (const char *expected_uuid, const char *str, gssize slen, char *uuid_test) -{ - g_assert (uuid_test); - g_assert (nm_utils_is_uuid (uuid_test)); - - if (strcmp (uuid_test, expected_uuid)) { - g_error ("UUID test failed (1): text=%s, len=%lld, expected=%s, uuid_test=%s", - str, (long long) slen, expected_uuid, uuid_test); - } - g_free (uuid_test); - - uuid_test = nm_utils_uuid_generate_from_string (str, slen, NM_UTILS_UUID_TYPE_VARIANT3, NM_UTILS_UUID_NS); - - g_assert (uuid_test); - g_assert (nm_utils_is_uuid (uuid_test)); - - if (strcmp (uuid_test, expected_uuid)) { - g_error ("UUID test failed (2): text=%s; len=%lld, expected=%s, uuid2=%s", - str, (long long) slen, expected_uuid, uuid_test); - } - g_free (uuid_test); -} - -#define _test_uuid(expected_uuid, str, strlen, ...) __test_uuid (expected_uuid, str, strlen, _nm_utils_uuid_generate_from_strings(__VA_ARGS__, NULL)) - -static void -test_nm_utils_uuid_generate_from_strings (void) -{ - _test_uuid ("b07c334a-399b-32de-8d50-58e4e08f98e3", "", 0, NULL); - _test_uuid ("b8a426cb-bcb5-30a3-bd8f-6786fea72df9", "\0", 1, ""); - _test_uuid ("12a4a982-7aae-39e1-951e-41aeb1250959", "a\0", 2, "a"); - _test_uuid ("69e22c7e-f89f-3a43-b239-1cb52ed8db69", "aa\0", 3, "aa"); - _test_uuid ("59829fd3-5ad5-3d90-a7b0-4911747e4088", "\0\0", 2, "", ""); - _test_uuid ("01ad0e06-6c50-3384-8d86-ddab81421425", "a\0\0", 3, "a", ""); - _test_uuid ("e1ed8647-9ed3-3ec8-8c6d-e8204524d71d", "aa\0\0", 4, "aa", ""); - _test_uuid ("fb1c7cd6-275c-3489-9382-83b900da8af0", "\0a\0", 3, "", "a"); - _test_uuid ("5d79494e-c4ba-31a6-80a2-d6016ccd7e17", "a\0a\0", 4, "a", "a"); - _test_uuid ("fd698d86-1b60-3ebe-855f-7aada9950a8d", "aa\0a\0", 5, "aa", "a"); - _test_uuid ("8c573b48-0f01-30ba-bb94-c5f59f4fe517", "\0aa\0", 4, "", "aa"); - _test_uuid ("2bdd3d46-eb83-3c53-a41b-a724d04b5544", "a\0aa\0", 5, "a", "aa"); - _test_uuid ("13d4b780-07c1-3ba7-b449-81c4844ef039", "aa\0aa\0", 6, "aa", "aa"); - _test_uuid ("dd265bf7-c05a-3037-9939-b9629858a477", "a\0b\0", 4, "a", "b"); -} - -/*******************************************/ - static const char *_test_match_spec_all[] = { "e", "em", @@ -778,7 +731,6 @@ main (int argc, char **argv) g_test_add_func ("/general/connection-sort/autoconnect-priority", test_connection_sort_autoconnect_priority); - g_test_add_func ("/general/_nm_utils_uuid_generate_from_strings", test_nm_utils_uuid_generate_from_strings); g_test_add_func ("/general/nm_match_spec_interface_name", test_nm_match_spec_interface_name); return g_test_run (); From 1fc9bc401e1de676bac3a3220af1bc094bed6e22 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 18 Feb 2015 19:59:28 +0100 Subject: [PATCH 17/23] keyfile: copy read/writer files to libnm-core This is the first step to move keyfile to libnm. For now, only copy the files to make later changes nicer in git-history. /bin/cp src/settings/plugins/keyfile/reader.c libnm-core/nm-keyfile-reader.c /bin/cp src/settings/plugins/keyfile/reader.h libnm-core/nm-keyfile-reader.h /bin/cp src/settings/plugins/keyfile/utils.c libnm-core/nm-keyfile-utils.c /bin/cp src/settings/plugins/keyfile/utils.h libnm-core/nm-keyfile-utils.h /bin/cp src/settings/plugins/keyfile/writer.c libnm-core/nm-keyfile-writer.c /bin/cp src/settings/plugins/keyfile/writer.h libnm-core/nm-keyfile-writer.h --- libnm-core/nm-keyfile-reader.c | 1376 ++++++++++++++++++++++++++++++++ libnm-core/nm-keyfile-reader.h | 30 + libnm-core/nm-keyfile-utils.c | 323 ++++++++ libnm-core/nm-keyfile-utils.h | 89 +++ libnm-core/nm-keyfile-writer.c | 946 ++++++++++++++++++++++ libnm-core/nm-keyfile-writer.h | 41 + 6 files changed, 2805 insertions(+) create mode 100644 libnm-core/nm-keyfile-reader.c create mode 100644 libnm-core/nm-keyfile-reader.h create mode 100644 libnm-core/nm-keyfile-utils.c create mode 100644 libnm-core/nm-keyfile-utils.h create mode 100644 libnm-core/nm-keyfile-writer.c create mode 100644 libnm-core/nm-keyfile-writer.h diff --git a/libnm-core/nm-keyfile-reader.c b/libnm-core/nm-keyfile-reader.c new file mode 100644 index 0000000000..97b6567028 --- /dev/null +++ b/libnm-core/nm-keyfile-reader.c @@ -0,0 +1,1376 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager system settings service - keyfile plugin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2008 - 2011 Red Hat, Inc. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "nm-core-internal.h" +#include "nm-dbus-glib-types.h" +#include "nm-glib-compat.h" +#include "nm-system-config-interface.h" +#include "nm-logging.h" +#include "reader.h" +#include "common.h" +#include "utils.h" +#include "nm-core-internal.h" +#include "NetworkManagerUtils.h" + +/* Some setting properties also contain setting names, such as + * NMSettingConnection's 'type' property (which specifies the base type of the + * connection, e.g. ethernet or wifi) or 'slave-type' (specifies type of slave + * connection, e.g. bond or bridge). This function handles translating those + * properties' values to the real setting name if they are an alias. + */ +static void +setting_alias_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) +{ + const char *setting_name = nm_setting_get_name (setting); + char *s; + const char *key_setting_name; + + s = nm_keyfile_plugin_kf_get_string (keyfile, setting_name, key, NULL); + if (s) { + key_setting_name = nm_keyfile_plugin_get_setting_name_for_alias (s); + g_object_set (G_OBJECT (setting), + key, key_setting_name ? key_setting_name : s, + NULL); + g_free (s); + } +} + +static gboolean +read_array_of_uint (GKeyFile *file, + NMSetting *setting, + const char *key) +{ + GArray *array = NULL; + gsize length; + int i; + gint *tmp; + + tmp = nm_keyfile_plugin_kf_get_integer_list (file, nm_setting_get_name (setting), key, &length, NULL); + array = g_array_sized_new (FALSE, FALSE, sizeof (guint32), length); + g_return_val_if_fail (array != NULL, FALSE); + + for (i = 0; i < length; i++) + g_array_append_val (array, tmp[i]); + + g_object_set (setting, key, array, NULL); + g_array_unref (array); + + return TRUE; +} + +static gboolean +get_one_int (const char *str, guint32 max_val, const char *key_name, guint32 *out) +{ + long tmp; + char *endptr; + + if (!str || !str[0]) { + if (key_name) + nm_log_warn (LOGD_SETTINGS, "%s: ignoring missing number %s", __func__, key_name); + return FALSE; + } + + errno = 0; + tmp = strtol (str, &endptr, 10); + if (errno || (tmp < 0) || (tmp > max_val) || *endptr != 0) { + if (key_name) + nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid number %s '%s'", __func__, key_name, str); + return FALSE; + } + + *out = (guint32) tmp; + return TRUE; +} + +static gpointer +build_address (int family, const char *address_str, guint32 plen) +{ + NMIPAddress *addr; + GError *error = NULL; + + g_return_val_if_fail (address_str, NULL); + + addr = nm_ip_address_new (family, address_str, plen, &error); + if (!addr) { + nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid %s address: %s", __func__, + family == AF_INET ? "IPv4" : "IPv6", + error->message); + g_error_free (error); + } + + return addr; +} + +static gpointer +build_route (int family, + const char *dest_str, guint32 plen, + const char *gateway_str, const char *metric_str, + const char *key_name) +{ + NMIPRoute *route; + guint32 metric = 0; + GError *error = NULL; + + g_return_val_if_fail (plen, NULL); + g_return_val_if_fail (dest_str, NULL); + + /* Next hop */ + if (gateway_str && gateway_str[0]) { + if (!nm_utils_ipaddr_valid (family, gateway_str)) { + /* Try workaround for routes written by broken keyfile writer. + * Due to bug bgo#719851, an older version of writer would have + * written "a:b:c:d::/plen,metric" if the gateway was ::, instead + * of "a:b:c:d::/plen,,metric" or "a:b:c:d::/plen,::,metric" + * Try workaround by interpreting gateway_str as metric to accept such + * invalid routes. This broken syntax should not be not officially + * supported. + **/ + if ( family == AF_INET6 + && !metric_str + && get_one_int (gateway_str, G_MAXUINT32, NULL, &metric)) + gateway_str = NULL; + else { + nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid gateway '%s'", __func__, gateway_str); + return NULL; + } + } + } else + gateway_str = NULL; + + /* parse metric, default to 0 */ + if (metric_str) { + if (!get_one_int (metric_str, G_MAXUINT32, key_name, &metric)) + return NULL; + } + + route = nm_ip_route_new (family, dest_str, plen, gateway_str, + metric ? (gint64) metric : -1, + &error); + if (!route) { + nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid %s route: %s", __func__, + family == AF_INET ? "IPv4" : "IPv6", + error->message); + g_error_free (error); + } + + return route; +} + +/* On success, returns pointer to the zero-terminated field (original @current). + * The @current * pointer target is set to point to the rest of the input + * or %NULL if there is no more input. Sets error to %NULL for convenience. + * + * On failure, returns %NULL (unspecified). The @current pointer target is + * resets to its original value to allow skipping fields. The @error target + * is set to the character that breaks the parsing or %NULL if @current was %NULL. + * + * When @current target is %NULL, gracefully fail returning %NULL while + * leaving the @current target %NULL end setting @error to %NULL; + */ +static char * +read_field (char **current, char **error, const char *characters, const char *delimiters) +{ + char *start; + + g_return_val_if_fail (current, NULL); + g_return_val_if_fail (error, NULL); + g_return_val_if_fail (characters, NULL); + g_return_val_if_fail (delimiters, NULL); + + *error = NULL; + + if (!*current) { + /* graceful failure, leave '*current' NULL */ + return NULL; + } + + /* fail on empty input */ + if (!**current) + return NULL; + + /* remember beginning of input */ + start = *current; + + while (**current && strchr (characters, **current)) + (*current)++; + if (**current) + if (strchr (delimiters, **current)) { + /* success, more data available */ + *(*current)++ = '\0'; + return start; + } else { + /* error, bad character */ + *error = *current; + *current = start; + return NULL; + } + else { + /* success, end of input */ + *current = NULL; + return start; + } +} + +#define IP_ADDRESS_CHARS "0123456789abcdefABCDEF:.%" +#define DIGITS "0123456789" +#define DELIMITERS "/;," + + +/* The following IPv4 and IPv6 address formats are supported: + * + * address (DEPRECATED) + * address/plen + * address/gateway (DEPRECATED) + * address/plen,gateway + * + * The following IPv4 and IPv6 route formats are supported: + * + * address/plen (NETWORK dev DEVICE) + * address/plen,gateway (NETWORK via GATEWAY dev DEVICE) + * address/plen,,metric (NETWORK dev DEVICE metric METRIC) + * address/plen,gateway,metric (NETWORK via GATEWAY dev DEVICE metric METRIC) + * + * For backward, forward and sideward compatibility, slash (/), + * semicolon (;) and comma (,) are interchangable. The choice of + * separator in the above examples is therefore not significant. + * + * Leaving out the prefix length is discouraged and DEPRECATED. The + * default value of IPv6 prefix length was 64 and has not been + * changed. The default for IPv4 is now 24, which is the closest + * IPv4 equivalent. These defaults may just as well be changed to + * match the iproute2 defaults (32 for IPv4 and 128 for IPv6). + */ +static gpointer +read_one_ip_address_or_route (GKeyFile *file, + const char *setting_name, + const char *key_name, + gboolean ipv6, + gboolean route, + char **out_gateway) +{ + guint32 plen = G_MAXUINT32; + gpointer result; + char *address_str, *plen_str, *gateway_str, *metric_str, *value, *current, *error; + + current = value = nm_keyfile_plugin_kf_get_string (file, setting_name, key_name, NULL); + if (!value) + return NULL; + + /* get address field */ + address_str = read_field (¤t, &error, IP_ADDRESS_CHARS, DELIMITERS); + if (error) { + nm_log_warn (LOGD_SETTINGS, "keyfile: Unexpected character '%c' in '%s.%s' address (position %td of '%s').", + *error, setting_name, key_name, error - current, current); + goto error; + } + /* get prefix length field (skippable) */ + plen_str = read_field (¤t, &error, DIGITS, DELIMITERS); + /* get gateway field */ + gateway_str = read_field (¤t, &error, IP_ADDRESS_CHARS, DELIMITERS); + if (error) { + nm_log_warn (LOGD_SETTINGS, "keyfile: Unexpected character '%c' in '%s.%s' %s (position %td of '%s').", + *error, setting_name, key_name, + plen_str ? "gateway" : "gateway or prefix length", + error - current, current); + goto error; + } + /* for routes, get metric */ + if (route) { + metric_str = read_field (¤t, &error, DIGITS, DELIMITERS); + if (error) { + nm_log_warn (LOGD_SETTINGS, "keyfile: Unexpected character '%c' in '%s.%s' prefix length (position %td of '%s').", + *error, setting_name, key_name, error - current, current); + goto error; + } + } else + metric_str = NULL; + if (current) { + /* there is still some data */ + if (*current) { + /* another field follows */ + nm_log_warn (LOGD_SETTINGS, "keyfile: %s.%s: Garbage at the and of the line: %s", + setting_name, key_name, current); + goto error; + } else { + /* semicolon at the end of input */ + nm_log_info (LOGD_SETTINGS, "keyfile: %s.%s: Deprecated semicolon at the end of value.", + setting_name, key_name); + } + } + +#define DEFAULT_PREFIX(for_route, for_ipv6) ( (for_route) ? ( (for_ipv6) ? 128 : 24 ) : ( (for_ipv6) ? 64 : 24 ) ) + + /* parse plen, fallback to defaults */ + if (plen_str) { + if (!get_one_int (plen_str, ipv6 ? 128 : 32, key_name, &plen) + || (route && plen == 0)) { + plen = DEFAULT_PREFIX (route, ipv6); + nm_log_warn (LOGD_SETTINGS, "keyfile: invalid prefix length '%s' in '%s.%s', defaulting to %d", + plen_str, setting_name, key_name, plen); + } + } else { + plen = DEFAULT_PREFIX (route, ipv6); + nm_log_warn (LOGD_SETTINGS, "keyfile: Missing prefix length in '%s.%s', defaulting to %d", + setting_name, key_name, plen); + } + + /* build the appropriate data structure for NetworkManager settings */ + if (route) { + result = build_route (ipv6 ? AF_INET6 : AF_INET, + address_str, plen, gateway_str, metric_str, + key_name); + } else { + result = build_address (ipv6 ? AF_INET6 : AF_INET, + address_str, plen); + if (out_gateway && gateway_str) + *out_gateway = g_strdup (gateway_str); + } + + g_free (value); + return result; +error: + g_free (value); + return NULL; +} + +static void +ip_address_or_route_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) +{ + const char *setting_name = nm_setting_get_name (setting); + gboolean ipv6 = !strcmp (setting_name, "ipv6"); + gboolean routes = !strcmp (key, "routes"); + static const char *key_names_routes[] = { "route", "routes", NULL }; + static const char *key_names_addresses[] = { "address", "addresses", NULL }; + const char **key_names = routes ? key_names_routes : key_names_addresses; + char *gateway = NULL; + GPtrArray *list; + GDestroyNotify free_func; + int i; + + if (routes) + free_func = (GDestroyNotify) nm_ip_route_unref; + else + free_func = (GDestroyNotify) nm_ip_address_unref; + list = g_ptr_array_new_with_free_func (free_func); + + for (i = -1; i < 1000; i++) { + const char **key_basename; + + for (key_basename = key_names; *key_basename; key_basename++) { + char *key_name; + gpointer item; + + /* -1 means no suffix */ + if (i >= 0) + key_name = g_strdup_printf ("%s%d", *key_basename, i); + else + key_name = g_strdup (*key_basename); + + item = read_one_ip_address_or_route (keyfile, setting_name, key_name, ipv6, routes, + gateway ? NULL : &gateway); + if (item) + g_ptr_array_add (list, item); + + g_free (key_name); + } + } + + if (list->len >= 1) + g_object_set (setting, key, list, NULL); + + if (gateway) { + g_object_set (setting, "gateway", gateway, NULL); + g_free (gateway); + } + + g_ptr_array_unref (list); +} + +static void +ip4_dns_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) +{ + const char *setting_name = nm_setting_get_name (setting); + GPtrArray *array; + gsize length; + char **list, **iter; + int ret; + + list = nm_keyfile_plugin_kf_get_string_list (keyfile, setting_name, key, &length, NULL); + if (!list || !g_strv_length (list)) + return; + + array = g_ptr_array_sized_new (length + 1); + for (iter = list; *iter; iter++) { + guint32 addr; + + ret = inet_pton (AF_INET, *iter, &addr); + if (ret <= 0) { + nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid DNS server address '%s'", __func__, *iter); + continue; + } + + g_ptr_array_add (array, *iter); + } + g_ptr_array_add (array, NULL); + + g_object_set (setting, key, array->pdata, NULL); + g_ptr_array_unref (array); + g_strfreev (list); +} + +static void +ip6_dns_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) +{ + const char *setting_name = nm_setting_get_name (setting); + GPtrArray *array = NULL; + gsize length; + char **list, **iter; + int ret; + + list = nm_keyfile_plugin_kf_get_string_list (keyfile, setting_name, key, &length, NULL); + if (!list || !g_strv_length (list)) + return; + + array = g_ptr_array_sized_new (length + 1); + + for (iter = list; *iter; iter++) { + struct in6_addr addr; + + ret = inet_pton (AF_INET6, *iter, &addr); + if (ret <= 0) { + nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid DNS server IPv6 address '%s'", __func__, *iter); + continue; + } + + g_ptr_array_add (array, *iter); + } + g_ptr_array_add (array, NULL); + + g_object_set (setting, key, array->pdata, NULL); + g_ptr_array_unref (array); + g_strfreev (list); +} + +static void +mac_address_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path, gsize enforce_length) +{ + const char *setting_name = nm_setting_get_name (setting); + char *tmp_string = NULL, *p, *mac_str; + gint *tmp_list; + GByteArray *array = NULL; + gsize length; + + p = tmp_string = nm_keyfile_plugin_kf_get_string (keyfile, setting_name, key, NULL); + if (tmp_string && tmp_string[0]) { + /* Look for enough ':' characters to signify a MAC address */ + guint i = 0; + + while (*p) { + if (*p == ':') + i++; + p++; + } + + if (enforce_length == 0 || enforce_length == i+1) { + /* If we found enough it's probably a string-format MAC address */ + array = g_byte_array_sized_new (i+1); + g_byte_array_set_size (array, i+1); + if (!nm_utils_hwaddr_aton (tmp_string, array->data, array->len)) { + g_byte_array_unref (array); + array = NULL; + } + } + } + g_free (tmp_string); + + if (array == NULL) { + /* Old format; list of ints */ + tmp_list = nm_keyfile_plugin_kf_get_integer_list (keyfile, setting_name, key, &length, NULL); + if (length > 0 && (enforce_length == 0 || enforce_length == length)) { + gsize i; + + array = g_byte_array_sized_new (length); + for (i = 0; i < length; i++) { + int val = tmp_list[i]; + const guint8 v = (guint8) (val & 0xFF); + + if (val < 0 || val > 255) { + nm_log_warn (LOGD_SETTINGS, "%s: %s / %s ignoring invalid byte element '%d' (not " + " between 0 and 255 inclusive)", __func__, setting_name, + key, val); + g_byte_array_free (array, TRUE); + array = NULL; + break; + } + g_byte_array_append (array, &v, 1); + } + } + g_free (tmp_list); + } + + if (!array) { + nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid MAC address for %s / %s", + __func__, setting_name, key); + return; + } + + mac_str = nm_utils_hwaddr_ntoa (array->data, array->len); + g_object_set (setting, key, mac_str, NULL); + g_free (mac_str); + g_byte_array_free (array, TRUE); +} + +static void +mac_address_parser_ETHER (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) +{ + mac_address_parser (setting, key, keyfile, keyfile_path, ETH_ALEN); +} + +static void +mac_address_parser_INFINIBAND (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) +{ + mac_address_parser (setting, key, keyfile, keyfile_path, INFINIBAND_ALEN); +} + +static void +read_hash_of_string (GKeyFile *file, NMSetting *setting, const char *key) +{ + char **keys, **iter; + char *value; + const char *setting_name = nm_setting_get_name (setting); + + keys = nm_keyfile_plugin_kf_get_keys (file, setting_name, NULL, NULL); + if (!keys || !*keys) + return; + + for (iter = keys; *iter; iter++) { + value = nm_keyfile_plugin_kf_get_string (file, setting_name, *iter, NULL); + if (!value) + continue; + + if (NM_IS_SETTING_VPN (setting)) { + /* Add any item that's not a class property to the data hash */ + if (!g_object_class_find_property (G_OBJECT_GET_CLASS (setting), *iter)) + nm_setting_vpn_add_data_item (NM_SETTING_VPN (setting), *iter, value); + } + if (NM_IS_SETTING_BOND (setting)) { + if (strcmp (*iter, "interface-name")) + nm_setting_bond_add_option (NM_SETTING_BOND (setting), *iter, value); + } + g_free (value); + } + g_strfreev (keys); +} + +static void +unescape_semicolons (char *str) +{ + int i; + gsize len = strlen (str); + + for (i = 0; i < len; i++) { + if (str[i] == '\\' && str[i+1] == ';') { + memmove(str + i, str + i + 1, len - (i + 1)); + len--; + } + str[len] = '\0'; + } +} + +static GBytes * +get_bytes (GKeyFile *keyfile, + const char *setting_name, + const char *key, + gboolean zero_terminate, + gboolean unescape_semicolon) +{ + GByteArray *array = NULL; + char *tmp_string; + gint *tmp_list; + gsize length; + int i; + + if (!nm_keyfile_plugin_kf_has_key (keyfile, setting_name, key, NULL)) + return NULL; + + /* New format: just a string + * Old format: integer list; e.g. 11;25;38; + */ + tmp_string = nm_keyfile_plugin_kf_get_string (keyfile, setting_name, key, NULL); + if (tmp_string) { + GRegex *regex; + GMatchInfo *match_info; + const char *pattern = "^[[:space:]]*[[:digit:]]{1,3}[[:space:]]*;([[:space:]]*[[:digit:]]{1,3}[[:space:]]*;)*([[:space:]]*)?$"; + + regex = g_regex_new (pattern, 0, 0, NULL); + g_regex_match (regex, tmp_string, 0, &match_info); + if (!g_match_info_matches (match_info)) { + /* Handle as a simple string (ie, new format) */ + if (unescape_semicolon) + unescape_semicolons (tmp_string); + length = strlen (tmp_string); + if (zero_terminate) + length++; + array = g_byte_array_sized_new (length); + g_byte_array_append (array, (guint8 *) tmp_string, length); + } + g_match_info_free (match_info); + g_regex_unref (regex); + g_free (tmp_string); + } + + if (!array) { + /* Old format; list of ints */ + tmp_list = nm_keyfile_plugin_kf_get_integer_list (keyfile, setting_name, key, &length, NULL); + if (!tmp_list) { + nm_log_warn (LOGD_SETTINGS, "%s: %s / %s ignoring invalid binary property", + __func__, setting_name, key); + return NULL; + } + array = g_byte_array_sized_new (length); + for (i = 0; i < length; i++) { + int val = tmp_list[i]; + unsigned char v = (unsigned char) (val & 0xFF); + + if (val < 0 || val > 255) { + nm_log_warn (LOGD_SETTINGS, "%s: %s / %s ignoring invalid byte element '%d' (not " + " between 0 and 255 inclusive)", __func__, setting_name, + key, val); + } else + g_byte_array_append (array, (const unsigned char *) &v, sizeof (v)); + } + g_free (tmp_list); + } + + if (array->len == 0) { + g_byte_array_free (array, TRUE); + return NULL; + } else + return g_byte_array_free_to_bytes (array); +} + +static void +ssid_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) +{ + const char *setting_name = nm_setting_get_name (setting); + GBytes *bytes; + + bytes = get_bytes (keyfile, setting_name, key, FALSE, TRUE); + if (bytes) { + g_object_set (setting, key, bytes, NULL); + g_bytes_unref (bytes); + } else { + nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid SSID for %s / %s", + __func__, setting_name, key); + } +} + +static void +password_raw_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) +{ + const char *setting_name = nm_setting_get_name (setting); + GBytes *bytes; + + bytes = get_bytes (keyfile, setting_name, key, FALSE, TRUE); + if (bytes) { + g_object_set (setting, key, bytes, NULL); + g_bytes_unref (bytes); + } else { + nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid raw password for %s / %s", + __func__, setting_name, key); + } +} + +static char * +get_cert_path (const char *keyfile_path, const guint8 *cert_path, gsize cert_path_len) +{ + const char *base; + char *p = NULL, *path, *dirname, *tmp; + + g_return_val_if_fail (keyfile_path != NULL, NULL); + g_return_val_if_fail (cert_path != NULL, NULL); + + base = path = g_malloc0 (cert_path_len + 1); + memcpy (path, cert_path, cert_path_len); + + if (path[0] == '/') + return path; + + p = strrchr (path, '/'); + if (p) + base = p + 1; + + dirname = g_path_get_dirname (keyfile_path); + tmp = g_build_path ("/", dirname, base, NULL); + g_free (dirname); + g_free (path); + return tmp; +} + +#define SCHEME_PATH "file://" + +static const char *certext[] = { ".pem", ".cert", ".crt", ".cer", ".p12", ".der", ".key" }; + +static gboolean +has_cert_ext (const char *path) +{ + int i; + + for (i = 0; i < G_N_ELEMENTS (certext); i++) { + if (g_str_has_suffix (path, certext[i])) + return TRUE; + } + return FALSE; +} + +static gboolean +handle_as_scheme (GBytes *bytes, NMSetting *setting, const char *key) +{ + const guint8 *data; + gsize data_len; + + data = g_bytes_get_data (bytes, &data_len); + + /* It's the PATH scheme, can just set plain data */ + if ( (data_len > strlen (SCHEME_PATH)) + && g_str_has_prefix ((const char *) data, SCHEME_PATH) + && (data[data_len - 1] == '\0')) { + g_object_set (setting, key, bytes, NULL); + return TRUE; + } + return FALSE; +} + +static gboolean +handle_as_path (GBytes *bytes, + NMSetting *setting, + const char *key, + const char *keyfile_path) +{ + const guint8 *data; + gsize data_len; + gsize validate_len; + char *path; + gboolean exists, success = FALSE; + + data = g_bytes_get_data (bytes, &data_len); + if (data_len > 500 || data_len < 1) + return FALSE; + + /* If there's a trailing zero tell g_utf8_validate() to validate until the zero */ + if (data[data_len - 1] == '\0') { + /* setting it to -1, would mean we accept data to contain NUL characters before the + * end. Don't accept any NUL in [0 .. data_len-1[ . */ + validate_len = data_len - 1; + } else + validate_len = data_len; + + if ( validate_len == 0 + || g_utf8_validate ((const char *) data, validate_len, NULL) == FALSE) + return FALSE; + + /* Might be a bare path without the file:// prefix; in that case + * if it's an absolute path, use that, otherwise treat it as a + * relative path to the current directory. + */ + + path = get_cert_path (keyfile_path, data, data_len); + exists = g_file_test (path, G_FILE_TEST_EXISTS); + if ( exists + || memchr (data, '/', data_len) + || has_cert_ext (path)) { + GByteArray *tmp; + GBytes *val; + + /* Construct the proper value as required for the PATH scheme */ + tmp = g_byte_array_sized_new (strlen (SCHEME_PATH) + strlen (path) + 1); + g_byte_array_append (tmp, (const guint8 *) SCHEME_PATH, strlen (SCHEME_PATH)); + g_byte_array_append (tmp, (const guint8 *) path, strlen (path)); + g_byte_array_append (tmp, (const guint8 *) "\0", 1); + val = g_byte_array_free_to_bytes (tmp); + g_object_set (setting, key, val, NULL); + g_bytes_unref (val); + success = TRUE; + + /* Warn if the certificate didn't exist */ + if (exists == FALSE) + nm_log_warn (LOGD_SETTINGS, "certificate or key %s does not exist", path); + } + g_free (path); + + return success; +} + +static void +cert_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) +{ + const char *setting_name = nm_setting_get_name (setting); + GBytes *bytes; + gboolean success = FALSE; + + bytes = get_bytes (keyfile, setting_name, key, TRUE, FALSE); + if (bytes) { + /* Try as a path + scheme (ie, starts with "file://") */ + success = handle_as_scheme (bytes, setting, key); + + /* If not, it might be a plain path */ + if (success == FALSE) + success = handle_as_path (bytes, setting, key, keyfile_path); + + /* If neither of those two, assume blob with certificate data */ + if (success == FALSE) + g_object_set (setting, key, bytes, NULL); + } else { + nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid key/cert value for %s / %s", + __func__, setting_name, key); + } + + if (bytes) + g_bytes_unref (bytes); +} + +static void +parity_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) +{ + const char *setting_name = nm_setting_get_name (setting); + NMSettingSerialParity parity; + int int_val; + char *str_val; + + /* Keyfile traditionally stored this as the ASCII value for 'E', 'o', or 'n'. + * We now accept either that or the (case-insensitive) character itself (but + * still always write it the old way, for backward compatibility). + */ + int_val = nm_keyfile_plugin_kf_get_integer (keyfile, setting_name, key, NULL); + if (!int_val) { + str_val = nm_keyfile_plugin_kf_get_string (keyfile, setting_name, key, NULL); + if (str_val) { + if (str_val[0] && !str_val[1]) + int_val = str_val[0]; + else { + /* This will hit the warning below */ + int_val = 'X'; + } + } + g_free (str_val); + } + + if (!int_val) + return; + + switch (int_val) { + case 'E': + case 'e': + parity = NM_SETTING_SERIAL_PARITY_EVEN; + break; + case 'O': + case 'o': + parity = NM_SETTING_SERIAL_PARITY_ODD; + break; + case 'N': + case 'n': + parity = NM_SETTING_SERIAL_PARITY_NONE; + break; + default: + nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid value for %s / %s", + __func__, setting_name, key); + return; + } + + g_object_set (setting, key, parity, NULL); +} + +typedef struct { + const char *setting_name; + const char *key; + gboolean check_for_key; + void (*parser) (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path); +} KeyParser; + +/* A table of keys that require further parsing/conversion because they are + * stored in a format that can't be automatically read using the key's type. + * i.e. IPv4 addresses, which are stored in NetworkManager as guint32, but are + * stored in keyfiles as strings, eg "10.1.1.2" or IPv6 addresses stored + * in struct in6_addr internally, but as string in keyfiles. + */ +static KeyParser key_parsers[] = { + { NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_TYPE, + TRUE, + setting_alias_parser }, + { NM_SETTING_BRIDGE_SETTING_NAME, + NM_SETTING_BRIDGE_MAC_ADDRESS, + TRUE, + mac_address_parser_ETHER }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_ADDRESSES, + FALSE, + ip_address_or_route_parser }, + { NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_ADDRESSES, + FALSE, + ip_address_or_route_parser }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_ROUTES, + FALSE, + ip_address_or_route_parser }, + { NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_ROUTES, + FALSE, + ip_address_or_route_parser }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_DNS, + FALSE, + ip4_dns_parser }, + { NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_DNS, + FALSE, + ip6_dns_parser }, + { NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_MAC_ADDRESS, + TRUE, + mac_address_parser_ETHER }, + { NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_CLONED_MAC_ADDRESS, + TRUE, + mac_address_parser_ETHER }, + { NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_MAC_ADDRESS, + TRUE, + mac_address_parser_ETHER }, + { NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, + TRUE, + mac_address_parser_ETHER }, + { NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_BSSID, + TRUE, + mac_address_parser_ETHER }, + { NM_SETTING_BLUETOOTH_SETTING_NAME, + NM_SETTING_BLUETOOTH_BDADDR, + TRUE, + mac_address_parser_ETHER }, + { NM_SETTING_INFINIBAND_SETTING_NAME, + NM_SETTING_INFINIBAND_MAC_ADDRESS, + TRUE, + mac_address_parser_INFINIBAND }, + { NM_SETTING_WIMAX_SETTING_NAME, + NM_SETTING_WIMAX_MAC_ADDRESS, + TRUE, + mac_address_parser_ETHER }, + { NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_SSID, + TRUE, + ssid_parser }, + { NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PASSWORD_RAW, + TRUE, + password_raw_parser }, + { NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_CA_CERT, + TRUE, + cert_parser }, + { NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_CLIENT_CERT, + TRUE, + cert_parser }, + { NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PRIVATE_KEY, + TRUE, + cert_parser }, + { NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PHASE2_CA_CERT, + TRUE, + cert_parser }, + { NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PHASE2_CLIENT_CERT, + TRUE, + cert_parser }, + { NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, + TRUE, + cert_parser }, + { NM_SETTING_SERIAL_SETTING_NAME, + NM_SETTING_SERIAL_PARITY, + TRUE, + parity_parser }, + { NULL, NULL, FALSE } +}; + +typedef struct { + GKeyFile *keyfile; + const char *keyfile_path; +} ReadInfo; + +static void +read_one_setting_value (NMSetting *setting, + const char *key, + const GValue *value, + GParamFlags flags, + gpointer user_data) +{ + ReadInfo *info = user_data; + const char *setting_name; + int errsv; + GType type; + GError *err = NULL; + gboolean check_for_key = TRUE; + KeyParser *parser = &key_parsers[0]; + + /* Property is not writable */ + if (!(flags & G_PARAM_WRITABLE)) + return; + + /* Setting name gets picked up from the keyfile's section name instead */ + if (!strcmp (key, NM_SETTING_NAME)) + return; + + /* Don't read the NMSettingConnection object's 'read-only' property */ + if ( NM_IS_SETTING_CONNECTION (setting) + && !strcmp (key, NM_SETTING_CONNECTION_READ_ONLY)) + return; + + setting_name = nm_setting_get_name (setting); + + /* Look through the list of handlers for non-standard format key values */ + while (parser->setting_name) { + if (!strcmp (parser->setting_name, setting_name) && !strcmp (parser->key, key)) { + check_for_key = parser->check_for_key; + break; + } + parser++; + } + + /* VPN properties don't have the exact key name */ + if (NM_IS_SETTING_VPN (setting)) + check_for_key = FALSE; + + /* Bonding 'options' don't have the exact key name. The options are right under [bond] group. */ + if (NM_IS_SETTING_BOND (setting)) + check_for_key = FALSE; + + /* Check for the exact key in the GKeyFile if required. Most setting + * properties map 1:1 to a key in the GKeyFile, but for those properties + * like IP addresses and routes where more than one value is actually + * encoded by the setting property, this won't be true. + */ + if (check_for_key && !nm_keyfile_plugin_kf_has_key (info->keyfile, setting_name, key, &err)) { + /* Key doesn't exist or an error ocurred, thus nothing to do. */ + if (err) { + nm_log_warn (LOGD_SETTINGS, "Error loading setting '%s' value: %s", setting_name, err->message); + g_error_free (err); + } + return; + } + + /* If there's a custom parser for this key, handle that before the generic + * parsers below. + */ + if (parser->setting_name) { + (*parser->parser) (setting, key, info->keyfile, info->keyfile_path); + return; + } + + type = G_VALUE_TYPE (value); + + if (type == G_TYPE_STRING) { + char *str_val; + + str_val = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL); + g_object_set (setting, key, str_val, NULL); + g_free (str_val); + } else if (type == G_TYPE_UINT) { + int int_val; + + int_val = nm_keyfile_plugin_kf_get_integer (info->keyfile, setting_name, key, NULL); + if (int_val < 0) + nm_log_warn (LOGD_SETTINGS, "Casting negative value (%i) to uint", int_val); + g_object_set (setting, key, int_val, NULL); + } else if (type == G_TYPE_INT) { + int int_val; + + int_val = nm_keyfile_plugin_kf_get_integer (info->keyfile, setting_name, key, NULL); + g_object_set (setting, key, int_val, NULL); + } else if (type == G_TYPE_BOOLEAN) { + gboolean bool_val; + + bool_val = nm_keyfile_plugin_kf_get_boolean (info->keyfile, setting_name, key, NULL); + g_object_set (setting, key, bool_val, NULL); + } else if (type == G_TYPE_CHAR) { + int int_val; + + int_val = nm_keyfile_plugin_kf_get_integer (info->keyfile, setting_name, key, NULL); + if (int_val < G_MININT8 || int_val > G_MAXINT8) + nm_log_warn (LOGD_SETTINGS, "Casting value (%i) to char", int_val); + + g_object_set (setting, key, int_val, NULL); + } else if (type == G_TYPE_UINT64) { + char *tmp_str; + guint64 uint_val; + + tmp_str = nm_keyfile_plugin_kf_get_value (info->keyfile, setting_name, key, NULL); + uint_val = g_ascii_strtoull (tmp_str, NULL, 10); + g_free (tmp_str); + g_object_set (setting, key, uint_val, NULL); + } else if (type == G_TYPE_INT64) { + char *tmp_str; + gint64 int_val; + + tmp_str = nm_keyfile_plugin_kf_get_value (info->keyfile, setting_name, key, NULL); + int_val = _nm_utils_ascii_str_to_int64 (tmp_str, 10, G_MININT64, G_MAXINT64, 0); + errsv = errno; + if (errsv) + nm_log_warn (LOGD_SETTINGS, "Invalid int64 value (%s)", tmp_str); + else + g_object_set (setting, key, int_val, NULL); + g_free (tmp_str); + } else if (type == G_TYPE_BYTES) { + gint *tmp; + GByteArray *array; + GBytes *bytes; + gsize length; + int i; + + tmp = nm_keyfile_plugin_kf_get_integer_list (info->keyfile, setting_name, key, &length, NULL); + + array = g_byte_array_sized_new (length); + for (i = 0; i < length; i++) { + int val = tmp[i]; + unsigned char v = (unsigned char) (val & 0xFF); + + if (val < 0 || val > 255) { + nm_log_warn (LOGD_SETTINGS, "%s: %s / %s ignoring invalid byte element '%d' (not " + " between 0 and 255 inclusive)", __func__, setting_name, + key, val); + } else + g_byte_array_append (array, (const unsigned char *) &v, sizeof (v)); + } + + bytes = g_byte_array_free_to_bytes (array); + g_object_set (setting, key, bytes, NULL); + g_bytes_unref (bytes); + g_free (tmp); + } else if (type == G_TYPE_STRV) { + gchar **sa; + gsize length; + + sa = nm_keyfile_plugin_kf_get_string_list (info->keyfile, setting_name, key, &length, NULL); + g_object_set (setting, key, sa, NULL); + g_strfreev (sa); + } else if (type == G_TYPE_HASH_TABLE) { + read_hash_of_string (info->keyfile, setting, key); + } else if (type == G_TYPE_ARRAY) { + if (!read_array_of_uint (info->keyfile, setting, key)) { + nm_log_warn (LOGD_SETTINGS, "Unhandled setting property type (read): '%s/%s' : '%s'", + setting_name, key, G_VALUE_TYPE_NAME (value)); + } + } else if (G_VALUE_HOLDS_FLAGS (value)) { + guint64 uint_val; + + /* Flags are guint but GKeyFile has no uint reader, just uint64 */ + uint_val = nm_keyfile_plugin_kf_get_uint64 (info->keyfile, setting_name, key, &err); + if (!err) { + if (uint_val <= G_MAXUINT) + g_object_set (setting, key, (guint) uint_val, NULL); + else { + nm_log_warn (LOGD_SETTINGS, "Too large FLAGS property (read): '%s/%s' : '%s'", + setting_name, key, G_VALUE_TYPE_NAME (value)); + } + } + g_clear_error (&err); + } else if (G_VALUE_HOLDS_ENUM (value)) { + gint int_val; + + int_val = nm_keyfile_plugin_kf_get_integer (info->keyfile, setting_name, key, &err); + if (!err) + g_object_set (setting, key, (gint) int_val, NULL); + g_clear_error (&err); + } else { + nm_log_warn (LOGD_SETTINGS, "Unhandled setting property type (read): '%s/%s' : '%s'", + setting_name, key, G_VALUE_TYPE_NAME (value)); + } +} + +static NMSetting * +read_setting (GKeyFile *file, const char *keyfile_path, const char *group) +{ + NMSetting *setting = NULL; + ReadInfo info = { file, keyfile_path }; + const char *alias; + GType type; + + alias = nm_keyfile_plugin_get_setting_name_for_alias (group); + if (alias) + group = alias; + + type = nm_setting_lookup_type (group); + if (type) { + setting = g_object_new (type, NULL); + nm_setting_enumerate_values (setting, read_one_setting_value, &info); + } else + nm_log_warn (LOGD_SETTINGS, "Invalid setting name '%s'", group); + + return setting; +} + +static void +read_vpn_secrets (GKeyFile *file, NMSettingVpn *s_vpn) +{ + char **keys, **iter; + + keys = nm_keyfile_plugin_kf_get_keys (file, VPN_SECRETS_GROUP, NULL, NULL); + for (iter = keys; *iter; iter++) { + char *secret; + + secret = nm_keyfile_plugin_kf_get_string (file, VPN_SECRETS_GROUP, *iter, NULL); + if (secret) { + nm_setting_vpn_add_secret (s_vpn, *iter, secret); + g_free (secret); + } + } + g_strfreev (keys); +} + +NMConnection * +nm_keyfile_plugin_connection_from_file (const char *filename, GError **error) +{ + GKeyFile *key_file; + struct stat statbuf; + gboolean bad_permissions; + NMConnection *connection = NULL; + NMSettingConnection *s_con; + NMSetting *setting; + gchar **groups; + gsize length; + int i; + gboolean vpn_secrets = FALSE; + GError *verify_error = NULL; + + if (stat (filename, &statbuf) != 0 || !S_ISREG (statbuf.st_mode)) { + g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, + "File did not exist or was not a regular file"); + return NULL; + } + + bad_permissions = statbuf.st_mode & 0077; + + if (bad_permissions) { + g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, + "File permissions (%o) were insecure", + statbuf.st_mode); + return NULL; + } + + key_file = g_key_file_new (); + if (!g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, error)) + goto out; + + connection = nm_simple_connection_new (); + + groups = g_key_file_get_groups (key_file, &length); + for (i = 0; i < length; i++) { + /* Only read out secrets when needed */ + if (!strcmp (groups[i], VPN_SECRETS_GROUP)) { + vpn_secrets = TRUE; + continue; + } + + setting = read_setting (key_file, filename, groups[i]); + if (setting) + nm_connection_add_setting (connection, setting); + } + + s_con = nm_connection_get_setting_connection (connection); + if (!s_con) { + s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ()); + nm_connection_add_setting (connection, NM_SETTING (s_con)); + } + + /* Make sure that we have 'id' even if not explictly specified in the keyfile */ + if (!nm_setting_connection_get_id (s_con)) { + char *base_name; + + base_name = g_path_get_basename (filename); + g_object_set (s_con, NM_SETTING_CONNECTION_ID, base_name, NULL); + g_free (base_name); + } + + /* Make sure that we have 'uuid' even if not explictly specified in the keyfile */ + if (!nm_setting_connection_get_uuid (s_con)) { + char *hashed_uuid; + + hashed_uuid = _nm_utils_uuid_generate_from_strings ("keyfile", filename, NULL); + g_object_set (s_con, NM_SETTING_CONNECTION_UUID, hashed_uuid, NULL); + g_free (hashed_uuid); + } + + /* Make sure that we have 'interface-name' even if it was specified in the + * "wrong" (ie, deprecated) group. + */ + if ( !nm_setting_connection_get_interface_name (s_con) + && nm_setting_connection_get_connection_type (s_con)) { + char *interface_name; + + interface_name = g_key_file_get_string (key_file, + nm_setting_connection_get_connection_type (s_con), + "interface-name", + NULL); + if (interface_name) { + g_object_set (s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, interface_name, NULL); + g_free (interface_name); + } + } + + /* Handle vpn secrets after the 'vpn' setting was read */ + if (vpn_secrets) { + NMSettingVpn *s_vpn; + + s_vpn = nm_connection_get_setting_vpn (connection); + if (s_vpn) + read_vpn_secrets (key_file, s_vpn); + } + + g_strfreev (groups); + + /* Normalize and verify the connection */ + if (!nm_connection_normalize (connection, NULL, NULL, &verify_error)) { + g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, + "invalid connection: %s", + verify_error->message); + g_clear_error (&verify_error); + g_object_unref (connection); + connection = NULL; + } + +out: + g_key_file_free (key_file); + return connection; +} diff --git a/libnm-core/nm-keyfile-reader.h b/libnm-core/nm-keyfile-reader.h new file mode 100644 index 0000000000..55819630eb --- /dev/null +++ b/libnm-core/nm-keyfile-reader.h @@ -0,0 +1,30 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager system settings service - keyfile plugin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + */ + +#ifndef _KEYFILE_PLUGIN_READER_H +#define _KEYFILE_PLUGIN_READER_H + +#include +#include + +NMConnection *nm_keyfile_plugin_connection_from_file (const char *filename, GError **error); + +#endif /* _KEYFILE_PLUGIN_READER_H */ diff --git a/libnm-core/nm-keyfile-utils.c b/libnm-core/nm-keyfile-utils.c new file mode 100644 index 0000000000..30400b1bc4 --- /dev/null +++ b/libnm-core/nm-keyfile-utils.c @@ -0,0 +1,323 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager system settings service + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * (C) Copyright 2010 Red Hat, Inc. + */ + +#include "config.h" + +#include +#include +#include +#include "gsystem-local-alloc.h" +#include "utils.h" +#include +#include +#include + + +static const char temp_letters[] = +"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + +/* + * Check '.[a-zA-Z0-9]{6}' file suffix used for temporary files by g_file_set_contents() (mkstemp()). + */ +static gboolean +check_mkstemp_suffix (const char *path) +{ + const char *ptr; + + g_return_val_if_fail (path != NULL, FALSE); + + /* Matches *.[a-zA-Z0-9]{6} suffix of mkstemp()'s temporary files */ + ptr = strrchr (path, '.'); + if (ptr && (strspn (ptr + 1, temp_letters) == 6) && (! ptr[7])) + return TRUE; + return FALSE; +} + +static gboolean +check_prefix (const char *base, const char *tag) +{ + int len, tag_len; + + g_return_val_if_fail (base != NULL, TRUE); + g_return_val_if_fail (tag != NULL, TRUE); + + len = strlen (base); + tag_len = strlen (tag); + if ((len > tag_len) && !g_ascii_strncasecmp (base, tag, tag_len)) + return TRUE; + return FALSE; +} + +static gboolean +check_suffix (const char *base, const char *tag) +{ + int len, tag_len; + + g_return_val_if_fail (base != NULL, TRUE); + g_return_val_if_fail (tag != NULL, TRUE); + + len = strlen (base); + tag_len = strlen (tag); + if ((len > tag_len) && !g_ascii_strcasecmp (base + len - tag_len, tag)) + return TRUE; + return FALSE; +} + +#define SWP_TAG ".swp" +#define SWPX_TAG ".swpx" +#define PEM_TAG ".pem" +#define DER_TAG ".der" + +gboolean +nm_keyfile_plugin_utils_should_ignore_file (const char *filename) +{ + gs_free char *base = NULL; + + g_return_val_if_fail (filename != NULL, TRUE); + + base = g_path_get_basename (filename); + g_return_val_if_fail (base != NULL, TRUE); + + /* Ignore hidden and backup files */ + /* should_ignore_file() must mirror escape_filename() */ + if (check_prefix (base, ".") || check_suffix (base, "~")) + return TRUE; + /* Ignore temporary files */ + if (check_mkstemp_suffix (base)) + return TRUE; + /* Ignore 802.1x certificates and keys */ + if (check_suffix (base, PEM_TAG) || check_suffix (base, DER_TAG)) + return TRUE; + + return FALSE; +} + +char * +nm_keyfile_plugin_utils_escape_filename (const char *filename) +{ + GString *str; + const char *f = filename; + const char ESCAPE_CHAR = '*'; + + /* keyfile used to escape with '*', do not change that behavior. + * But for newly added escapings, use '_' instead. */ + const char ESCAPE_CHAR2 = '_'; + + g_return_val_if_fail (filename && filename[0], NULL); + + str = g_string_sized_new (60); + + /* Convert '/' to ESCAPE_CHAR */ + for (f = filename; f[0]; f++) { + if (f[0] == '/') + g_string_append_c (str, ESCAPE_CHAR); + else + g_string_append_c (str, f[0]); + } + + /* escape_filename() must avoid anything that should_ignore_file() would reject. + * We can escape here more aggressivly then what we would read back. */ + if (check_prefix (str->str, ".")) + str->str[0] = ESCAPE_CHAR2; + if (check_suffix (str->str, "~")) + str->str[str->len - 1] = ESCAPE_CHAR2; + if ( check_mkstemp_suffix (str->str) + || check_suffix (str->str, PEM_TAG) + || check_suffix (str->str, DER_TAG)) + g_string_append_c (str, ESCAPE_CHAR2); + + return g_string_free (str, FALSE);; +} + + +typedef struct { + const char *setting; + const char *alias; +} SettingAlias; + +static const SettingAlias alias_list[] = { + { NM_SETTING_WIRED_SETTING_NAME, "ethernet" }, + { NM_SETTING_WIRELESS_SETTING_NAME, "wifi" }, + { NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, "wifi-security" }, +}; + +const char * +nm_keyfile_plugin_get_alias_for_setting_name (const char *setting_name) +{ + guint i; + + g_return_val_if_fail (setting_name != NULL, NULL); + + for (i = 0; i < G_N_ELEMENTS (alias_list); i++) { + if (strcmp (setting_name, alias_list[i].setting) == 0) + return alias_list[i].alias; + } + return NULL; +} + +const char * +nm_keyfile_plugin_get_setting_name_for_alias (const char *alias) +{ + guint i; + + g_return_val_if_fail (alias != NULL, NULL); + + for (i = 0; i < G_N_ELEMENTS (alias_list); i++) { + if (strcmp (alias, alias_list[i].alias) == 0) + return alias_list[i].setting; + } + return NULL; +} + +/**********************************************************************/ + +/* List helpers */ +#define DEFINE_KF_LIST_WRAPPER(stype, get_ctype, set_ctype) \ +get_ctype \ +nm_keyfile_plugin_kf_get_##stype##_list (GKeyFile *kf, \ + const char *group, \ + const char *key, \ + gsize *out_length, \ + GError **error) \ +{ \ + get_ctype list; \ + const char *alias; \ + GError *local = NULL; \ + \ + list = g_key_file_get_##stype##_list (kf, group, key, out_length, &local); \ + if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { \ + alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \ + if (alias) { \ + g_clear_error (&local); \ + list = g_key_file_get_##stype##_list (kf, alias, key, out_length, &local); \ + } \ + } \ + if (local) \ + g_propagate_error (error, local); \ + return list; \ +} \ + \ +void \ +nm_keyfile_plugin_kf_set_##stype##_list (GKeyFile *kf, \ + const char *group, \ + const char *key, \ + set_ctype list[], \ + gsize length) \ +{ \ + const char *alias; \ + \ + alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \ + g_key_file_set_##stype##_list (kf, alias ? alias : group, key, list, length); \ +} + +DEFINE_KF_LIST_WRAPPER(integer, gint*, gint); +DEFINE_KF_LIST_WRAPPER(string, gchar **, const gchar* const); + +/* Single value helpers */ +#define DEFINE_KF_WRAPPER(stype, get_ctype, set_ctype) \ +get_ctype \ +nm_keyfile_plugin_kf_get_##stype (GKeyFile *kf, \ + const char *group, \ + const char *key, \ + GError **error) \ +{ \ + get_ctype val; \ + const char *alias; \ + GError *local = NULL; \ + \ + val = g_key_file_get_##stype (kf, group, key, &local); \ + if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { \ + alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \ + if (alias) { \ + g_clear_error (&local); \ + val = g_key_file_get_##stype (kf, alias, key, &local); \ + } \ + } \ + if (local) \ + g_propagate_error (error, local); \ + return val; \ +} \ + \ +void \ +nm_keyfile_plugin_kf_set_##stype (GKeyFile *kf, \ + const char *group, \ + const char *key, \ + set_ctype value) \ +{ \ + const char *alias; \ + \ + alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \ + g_key_file_set_##stype (kf, alias ? alias : group, key, value); \ +} + +DEFINE_KF_WRAPPER(string, gchar*, const gchar*); +DEFINE_KF_WRAPPER(integer, gint, gint); +DEFINE_KF_WRAPPER(uint64, guint64, guint64); +DEFINE_KF_WRAPPER(boolean, gboolean, gboolean); +DEFINE_KF_WRAPPER(value, gchar*, const gchar*); + + +gchar ** +nm_keyfile_plugin_kf_get_keys (GKeyFile *kf, + const char *group, + gsize *out_length, + GError **error) +{ + gchar **keys; + const char *alias; + GError *local = NULL; + + keys = g_key_file_get_keys (kf, group, out_length, &local); + if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { + alias = nm_keyfile_plugin_get_alias_for_setting_name (group); + if (alias) { + g_clear_error (&local); + keys = g_key_file_get_keys (kf, alias, out_length, &local); + } + } + if (local) + g_propagate_error (error, local); + return keys; +} + +gboolean +nm_keyfile_plugin_kf_has_key (GKeyFile *kf, + const char *group, + const char *key, + GError **error) +{ + gboolean has; + const char *alias; + GError *local = NULL; + + has = g_key_file_has_key (kf, group, key, &local); + if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { + alias = nm_keyfile_plugin_get_alias_for_setting_name (group); + if (alias) { + g_clear_error (&local); + has = g_key_file_has_key (kf, alias, key, &local); + } + } + if (local) + g_propagate_error (error, local); + return has; +} + + diff --git a/libnm-core/nm-keyfile-utils.h b/libnm-core/nm-keyfile-utils.h new file mode 100644 index 0000000000..456cd1a88f --- /dev/null +++ b/libnm-core/nm-keyfile-utils.h @@ -0,0 +1,89 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager system settings service + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * (C) Copyright 2010 Red Hat, Inc. + */ + +#ifndef _UTILS_H_ +#define _UTILS_H_ + +#include +#include "common.h" +#include "NetworkManagerUtils.h" + +#define NM_KEYFILE_CONNECTION_LOG_PATH(path) str_if_set (path,"in-memory") +#define NM_KEYFILE_CONNECTION_LOG_FMT "%s (%s,\"%s\")" +#define NM_KEYFILE_CONNECTION_LOG_ARG(con) NM_KEYFILE_CONNECTION_LOG_PATH (nm_settings_connection_get_filename ((NMSettingsConnection *) (con))), nm_connection_get_uuid ((NMConnection *) (con)), nm_connection_get_id ((NMConnection *) (con)) +#define NM_KEYFILE_CONNECTION_LOG_FMTD "%s (%s,\"%s\",%p)" +#define NM_KEYFILE_CONNECTION_LOG_ARGD(con) NM_KEYFILE_CONNECTION_LOG_PATH (nm_settings_connection_get_filename ((NMSettingsConnection *) (con))), nm_connection_get_uuid ((NMConnection *) (con)), nm_connection_get_id ((NMConnection *) (con)), (con) + +gboolean nm_keyfile_plugin_utils_should_ignore_file (const char *filename); + +char *nm_keyfile_plugin_utils_escape_filename (const char *filename); + +const char *nm_keyfile_plugin_get_alias_for_setting_name (const char *setting_name); + +const char *nm_keyfile_plugin_get_setting_name_for_alias (const char *alias); + +/*********************************************************/ + +/* List helpers */ +#define DEFINE_KF_LIST_WRAPPER_PROTO(stype, get_ctype, set_ctype) \ +get_ctype nm_keyfile_plugin_kf_get_##stype##_list (GKeyFile *kf, \ + const char *group, \ + const char *key, \ + gsize *out_length, \ + GError **error); \ +\ +void nm_keyfile_plugin_kf_set_##stype##_list (GKeyFile *kf, \ + const char *group, \ + const char *key, \ + set_ctype list[], \ + gsize length); +DEFINE_KF_LIST_WRAPPER_PROTO(integer, gint*, gint) +DEFINE_KF_LIST_WRAPPER_PROTO(string, gchar**, const gchar* const) + +/* Single-value helpers */ +#define DEFINE_KF_WRAPPER_PROTO(stype, get_ctype, set_ctype) \ +get_ctype nm_keyfile_plugin_kf_get_##stype (GKeyFile *kf, \ + const char *group, \ + const char *key, \ + GError **error); \ +\ +void nm_keyfile_plugin_kf_set_##stype (GKeyFile *kf, \ + const char *group, \ + const char *key, \ + set_ctype value); +DEFINE_KF_WRAPPER_PROTO(string, gchar*, const gchar*) +DEFINE_KF_WRAPPER_PROTO(integer, gint, gint) +DEFINE_KF_WRAPPER_PROTO(uint64, guint64, guint64) +DEFINE_KF_WRAPPER_PROTO(boolean, gboolean, gboolean) +DEFINE_KF_WRAPPER_PROTO(value, gchar*, const gchar*) + +/* Misc */ +gchar ** nm_keyfile_plugin_kf_get_keys (GKeyFile *kf, + const char *group, + gsize *out_length, + GError **error); + +gboolean nm_keyfile_plugin_kf_has_key (GKeyFile *kf, + const char *group, + const char *key, + GError **error); + +#endif /* _UTILS_H_ */ + diff --git a/libnm-core/nm-keyfile-writer.c b/libnm-core/nm-keyfile-writer.c new file mode 100644 index 0000000000..a707ac3287 --- /dev/null +++ b/libnm-core/nm-keyfile-writer.c @@ -0,0 +1,946 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager system settings service - keyfile plugin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2008 - 2012 Red Hat, Inc. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nm-glib-compat.h" +#include "nm-logging.h" +#include "writer.h" +#include "common.h" +#include "utils.h" + +/* Some setting properties also contain setting names, such as + * NMSettingConnection's 'type' property (which specifies the base type of the + * connection, eg ethernet or wifi) or the 802-11-wireless setting's + * 'security' property which specifies whether or not the AP requires + * encrpytion. This function handles translating those properties' values + * from the real setting name to the more-readable alias. + */ +static void +setting_alias_writer (GKeyFile *file, + const char *keyfile_dir, + const char *uuid, + NMSetting *setting, + const char *key, + const GValue *value) +{ + const char *str, *alias; + + str = g_value_get_string (value); + alias = nm_keyfile_plugin_get_alias_for_setting_name (str); + nm_keyfile_plugin_kf_set_string (file, + nm_setting_get_name (setting), + key, + alias ? alias : str); +} + +static gboolean +write_array_of_uint (GKeyFile *file, + NMSetting *setting, + const char *key, + const GValue *value) +{ + GArray *array; + int i; + int *tmp_array; + + array = (GArray *) g_value_get_boxed (value); + if (!array || !array->len) + return TRUE; + + tmp_array = g_new (gint, array->len); + for (i = 0; i < array->len; i++) + tmp_array[i] = g_array_index (array, int, i); + + nm_keyfile_plugin_kf_set_integer_list (file, nm_setting_get_name (setting), key, tmp_array, array->len); + g_free (tmp_array); + return TRUE; +} + +static void +dns_writer (GKeyFile *file, + const char *keyfile_dir, + const char *uuid, + NMSetting *setting, + const char *key, + const GValue *value) +{ + char **list; + + list = g_value_get_boxed (value); + if (list && list[0]) { + nm_keyfile_plugin_kf_set_string_list (file, nm_setting_get_name (setting), key, + (const char **) list, g_strv_length (list)); + } +} + +static void +write_ip_values (GKeyFile *file, + const char *setting_name, + GPtrArray *array, + const char *gateway, + gboolean is_route) +{ + GString *output; + int family, i; + const char *addr, *gw; + guint32 plen, metric; + char key_name[30], *key_name_idx; + + if (!array->len) + return; + + family = !strcmp (setting_name, NM_SETTING_IP4_CONFIG_SETTING_NAME) ? AF_INET : AF_INET6; + + strcpy (key_name, is_route ? "route" : "address"); + key_name_idx = key_name + strlen (key_name); + + output = g_string_sized_new (2*INET_ADDRSTRLEN + 10); + for (i = 0; i < array->len; i++) { + if (is_route) { + NMIPRoute *route = array->pdata[i]; + + addr = nm_ip_route_get_dest (route); + plen = nm_ip_route_get_prefix (route); + gw = nm_ip_route_get_next_hop (route); + metric = MAX (0, nm_ip_route_get_metric (route)); + } else { + NMIPAddress *address = array->pdata[i]; + + addr = nm_ip_address_get_address (address); + plen = nm_ip_address_get_prefix (address); + gw = i == 0 ? gateway : NULL; + metric = 0; + } + + g_string_set_size (output, 0); + g_string_append_printf (output, "%s/%u", addr, plen); + if (metric || gw) { + /* Older versions of the plugin do not support the form + * "a.b.c.d/plen,,metric", so, we always have to write the + * gateway, even if there isn't one. + * The current version supports reading of the above form. + */ + if (!gw) { + if (family == AF_INET) + gw = "0.0.0.0"; + else + gw = "::"; + } + + g_string_append_printf (output, ",%s", gw); + if (metric) + g_string_append_printf (output, ",%lu", (unsigned long) metric); + } + + sprintf (key_name_idx, "%d", i + 1); + nm_keyfile_plugin_kf_set_string (file, setting_name, key_name, output->str); + } + g_string_free (output, TRUE); +} + +static void +addr_writer (GKeyFile *file, + const char *keyfile_dir, + const char *uuid, + NMSetting *setting, + const char *key, + const GValue *value) +{ + GPtrArray *array; + const char *setting_name = nm_setting_get_name (setting); + const char *gateway = nm_setting_ip_config_get_gateway (NM_SETTING_IP_CONFIG (setting)); + + array = (GPtrArray *) g_value_get_boxed (value); + if (array && array->len) + write_ip_values (file, setting_name, array, gateway, FALSE); +} + +static void +ip4_addr_label_writer (GKeyFile *file, + const char *keyfile_dir, + const char *uuid, + NMSetting *setting, + const char *key, + const GValue *value) +{ + /* skip */ +} + +static void +gateway_writer (GKeyFile *file, + const char *keyfile_dir, + const char *uuid, + NMSetting *setting, + const char *key, + const GValue *value) +{ + /* skip */ +} + +static void +route_writer (GKeyFile *file, + const char *keyfile_dir, + const char *uuid, + NMSetting *setting, + const char *key, + const GValue *value) +{ + GPtrArray *array; + const char *setting_name = nm_setting_get_name (setting); + + array = (GPtrArray *) g_value_get_boxed (value); + if (array && array->len) + write_ip_values (file, setting_name, array, NULL, TRUE); +} + +static void +write_hash_of_string (GKeyFile *file, + NMSetting *setting, + const char *key, + const GValue *value) +{ + GHashTableIter iter; + const char *property = NULL, *data = NULL; + const char *group_name = nm_setting_get_name (setting); + gboolean vpn_secrets = FALSE; + + /* Write VPN secrets out to a different group to keep them separate */ + if (NM_IS_SETTING_VPN (setting) && !strcmp (key, NM_SETTING_VPN_SECRETS)) { + group_name = VPN_SECRETS_GROUP; + vpn_secrets = TRUE; + } + + g_hash_table_iter_init (&iter, (GHashTable *) g_value_get_boxed (value)); + while (g_hash_table_iter_next (&iter, (gpointer *) &property, (gpointer *) &data)) { + gboolean write_item = TRUE; + + /* Handle VPN secrets specially; they are nested in the property's hash; + * we don't want to write them if the secret is not saved, not required, + * or owned by a user's secret agent. + */ + if (vpn_secrets) { + NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE; + + nm_setting_get_secret_flags (setting, property, &secret_flags, NULL); + if (secret_flags != NM_SETTING_SECRET_FLAG_NONE) + write_item = FALSE; + } + + if (write_item) + nm_keyfile_plugin_kf_set_string (file, group_name, property, data); + } +} + +static void +ssid_writer (GKeyFile *file, + const char *keyfile_dir, + const char *uuid, + NMSetting *setting, + const char *key, + const GValue *value) +{ + GBytes *bytes; + const guint8 *ssid_data; + gsize ssid_len; + const char *setting_name = nm_setting_get_name (setting); + gboolean new_format = TRUE; + unsigned int semicolons = 0; + int i, *tmp_array; + char *ssid; + + g_return_if_fail (G_VALUE_HOLDS (value, G_TYPE_BYTES)); + + bytes = g_value_get_boxed (value); + if (!bytes) + return; + ssid_data = g_bytes_get_data (bytes, &ssid_len); + if (ssid_len == 0) + return; + + /* Check whether each byte is printable. If not, we have to use an + * integer list, otherwise we can just use a string. + */ + for (i = 0; i < ssid_len; i++) { + char c = ssid_data[i] & 0xFF; + if (!g_ascii_isprint (c)) { + new_format = FALSE; + break; + } + if (c == ';') + semicolons++; + } + + if (new_format) { + ssid = g_malloc0 (ssid_len + semicolons + 1); + if (semicolons == 0) + memcpy (ssid, ssid_data, ssid_len); + else { + /* Escape semicolons with backslashes to make strings + * containing ';', such as '16;17;' unambiguous */ + int j = 0; + for (i = 0; i < ssid_len; i++) { + if (ssid_data[i] == ';') + ssid[j++] = '\\'; + ssid[j++] = ssid_data[i]; + } + } + nm_keyfile_plugin_kf_set_string (file, setting_name, key, ssid); + g_free (ssid); + } else { + tmp_array = g_new (gint, ssid_len); + for (i = 0; i < ssid_len; i++) + tmp_array[i] = (int) ssid_data[i]; + nm_keyfile_plugin_kf_set_integer_list (file, setting_name, key, tmp_array, ssid_len); + g_free (tmp_array); + } +} + +static void +password_raw_writer (GKeyFile *file, + const char *keyfile_dir, + const char *uuid, + NMSetting *setting, + const char *key, + const GValue *value) +{ + const char *setting_name = nm_setting_get_name (setting); + GBytes *array; + int *tmp_array; + gsize i, len; + const char *data; + + g_return_if_fail (G_VALUE_HOLDS (value, G_TYPE_BYTES)); + + array = (GBytes *) g_value_get_boxed (value); + if (!array) + return; + data = g_bytes_get_data (array, &len); + if (!data || !len) + return; + + tmp_array = g_new (gint, len); + for (i = 0; i < len; i++) + tmp_array[i] = (int) data[i]; + nm_keyfile_plugin_kf_set_integer_list (file, setting_name, key, tmp_array, len); + g_free (tmp_array); +} + +typedef struct ObjectType { + const char *key; + const char *suffix; + NMSetting8021xCKScheme (*scheme_func) (NMSetting8021x *setting); + NMSetting8021xCKFormat (*format_func) (NMSetting8021x *setting); + const char * (*path_func) (NMSetting8021x *setting); + GBytes * (*blob_func) (NMSetting8021x *setting); +} ObjectType; + +static const ObjectType objtypes[10] = { + { NM_SETTING_802_1X_CA_CERT, + "ca-cert", + nm_setting_802_1x_get_ca_cert_scheme, + NULL, + nm_setting_802_1x_get_ca_cert_path, + nm_setting_802_1x_get_ca_cert_blob }, + + { NM_SETTING_802_1X_PHASE2_CA_CERT, + "inner-ca-cert", + nm_setting_802_1x_get_phase2_ca_cert_scheme, + NULL, + nm_setting_802_1x_get_phase2_ca_cert_path, + nm_setting_802_1x_get_phase2_ca_cert_blob }, + + { NM_SETTING_802_1X_CLIENT_CERT, + "client-cert", + nm_setting_802_1x_get_client_cert_scheme, + NULL, + nm_setting_802_1x_get_client_cert_path, + nm_setting_802_1x_get_client_cert_blob }, + + { NM_SETTING_802_1X_PHASE2_CLIENT_CERT, + "inner-client-cert", + nm_setting_802_1x_get_phase2_client_cert_scheme, + NULL, + nm_setting_802_1x_get_phase2_client_cert_path, + nm_setting_802_1x_get_phase2_client_cert_blob }, + + { NM_SETTING_802_1X_PRIVATE_KEY, + "private-key", + nm_setting_802_1x_get_private_key_scheme, + nm_setting_802_1x_get_private_key_format, + nm_setting_802_1x_get_private_key_path, + nm_setting_802_1x_get_private_key_blob }, + + { NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, + "inner-private-key", + nm_setting_802_1x_get_phase2_private_key_scheme, + nm_setting_802_1x_get_phase2_private_key_format, + nm_setting_802_1x_get_phase2_private_key_path, + nm_setting_802_1x_get_phase2_private_key_blob }, + + { NULL }, +}; + +static gboolean +write_cert_key_file (const char *path, + const guint8 *data, + gsize data_len, + GError **error) +{ + char *tmppath; + int fd = -1, written; + gboolean success = FALSE; + + tmppath = g_malloc0 (strlen (path) + 10); + g_assert (tmppath); + memcpy (tmppath, path, strlen (path)); + strcat (tmppath, ".XXXXXX"); + + errno = 0; + fd = mkstemp (tmppath); + if (fd < 0) { + g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, + "Could not create temporary file for '%s': %d", + path, errno); + goto out; + } + + /* Only readable by root */ + errno = 0; + if (fchmod (fd, S_IRUSR | S_IWUSR) != 0) { + close (fd); + unlink (tmppath); + g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, + "Could not set permissions for temporary file '%s': %d", + path, errno); + goto out; + } + + errno = 0; + written = write (fd, data, data_len); + if (written != data_len) { + close (fd); + unlink (tmppath); + g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, + "Could not write temporary file for '%s': %d", + path, errno); + goto out; + } + close (fd); + + /* Try to rename */ + errno = 0; + if (rename (tmppath, path) == 0) + success = TRUE; + else { + unlink (tmppath); + g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, + "Could not rename temporary file to '%s': %d", + path, errno); + } + +out: + g_free (tmppath); + return success; +} + +static void +cert_writer (GKeyFile *file, + const char *keyfile_dir, + const char *uuid, + NMSetting *setting, + const char *key, + const GValue *value) +{ + const char *setting_name = nm_setting_get_name (setting); + NMSetting8021xCKScheme scheme; + NMSetting8021xCKFormat format; + const char *path = NULL, *ext = "pem"; + const ObjectType *objtype = NULL; + int i; + + for (i = 0; i < G_N_ELEMENTS (objtypes) && objtypes[i].key; i++) { + if (g_strcmp0 (objtypes[i].key, key) == 0) { + objtype = &objtypes[i]; + break; + } + } + if (!objtype) { + g_return_if_fail (objtype); + return; + } + + scheme = objtype->scheme_func (NM_SETTING_802_1X (setting)); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) { + path = objtype->path_func (NM_SETTING_802_1X (setting)); + g_assert (path); + + /* If the path is rooted in the keyfile directory, just use a + * relative path instead of an absolute one. + */ + if (g_str_has_prefix (path, keyfile_dir)) { + path += strlen (keyfile_dir); + while (*path == '/') + path++; + } + + nm_keyfile_plugin_kf_set_string (file, setting_name, key, path); + } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) { + GBytes *blob; + const guint8 *blob_data; + gsize blob_len; + gboolean success; + GError *error = NULL; + char *new_path; + + blob = objtype->blob_func (NM_SETTING_802_1X (setting)); + g_assert (blob); + blob_data = g_bytes_get_data (blob, &blob_len); + + if (objtype->format_func) { + /* Get the extension for a private key */ + format = objtype->format_func (NM_SETTING_802_1X (setting)); + if (format == NM_SETTING_802_1X_CK_FORMAT_PKCS12) + ext = "p12"; + } else { + /* DER or PEM format certificate? */ + if (blob_len > 2 && blob_data[0] == 0x30 && blob_data[1] == 0x82) + ext = "der"; + } + + /* Write the raw data out to the standard file so that we can use paths + * from now on instead of pushing around the certificate data. + */ + new_path = g_strdup_printf ("%s/%s-%s.%s", keyfile_dir, uuid, objtype->suffix, ext); + g_assert (new_path); + + success = write_cert_key_file (new_path, blob_data, blob_len, &error); + if (success) { + /* Write the path value to the keyfile */ + nm_keyfile_plugin_kf_set_string (file, setting_name, key, new_path); + } else { + nm_log_warn (LOGD_SETTINGS, "Failed to write certificate/key %s: %s", + new_path, error->message); + g_error_free (error); + } + g_free (new_path); + } else { + /* scheme_func() returns UNKNOWN in all other cases. The only valid case + * where a scheme is allowed to be UNKNOWN, is unsetting the value. In this + * case, we don't expect the writer to be called, because the default value + * will not be serialized. + * The only other reason for the scheme to be UNKNOWN is an invalid cert. + * But our connection verifies, so that cannot happen either. */ + g_return_if_reached (); + } +} + +typedef struct { + const char *setting_name; + const char *key; + void (*writer) (GKeyFile *keyfile, + const char *keyfile_dir, + const char *uuid, + NMSetting *setting, + const char *key, + const GValue *value); +} KeyWriter; + +/* A table of keys that require further parsing/conversion because they are + * stored in a format that can't be automatically read using the key's type. + * i.e. IPv4 addresses, which are stored in NetworkManager as guint32, but are + * stored in keyfiles as strings, eg "10.1.1.2" or IPv6 addresses stored + * in struct in6_addr internally, but as string in keyfiles. + */ +static KeyWriter key_writers[] = { + { NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_TYPE, + setting_alias_writer }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_ADDRESSES, + addr_writer }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, + "address-labels", + ip4_addr_label_writer }, + { NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_ADDRESSES, + addr_writer }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_GATEWAY, + gateway_writer }, + { NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_GATEWAY, + gateway_writer }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_ROUTES, + route_writer }, + { NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_ROUTES, + route_writer }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_DNS, + dns_writer }, + { NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_DNS, + dns_writer }, + { NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_SSID, + ssid_writer }, + { NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PASSWORD_RAW, + password_raw_writer }, + { NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_CA_CERT, + cert_writer }, + { NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_CLIENT_CERT, + cert_writer }, + { NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PRIVATE_KEY, + cert_writer }, + { NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PHASE2_CA_CERT, + cert_writer }, + { NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PHASE2_CLIENT_CERT, + cert_writer }, + { NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, + cert_writer }, + { NULL, NULL, NULL } +}; + +typedef struct { + GKeyFile *keyfile; + const char *keyfile_dir; + const char *uuid; +} WriteInfo; + +static void +write_setting_value (NMSetting *setting, + const char *key, + const GValue *value, + GParamFlags flag, + gpointer user_data) +{ + WriteInfo *info = user_data; + const char *setting_name; + GType type = G_VALUE_TYPE (value); + KeyWriter *writer = &key_writers[0]; + GParamSpec *pspec; + + /* Setting name gets picked up from the keyfile's section name instead */ + if (!strcmp (key, NM_SETTING_NAME)) + return; + + /* Don't write the NMSettingConnection object's 'read-only' property */ + if ( NM_IS_SETTING_CONNECTION (setting) + && !strcmp (key, NM_SETTING_CONNECTION_READ_ONLY)) + return; + + setting_name = nm_setting_get_name (setting); + + /* If the value is the default value, remove the item from the keyfile */ + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (setting), key); + if (pspec) { + if (g_param_value_defaults (pspec, (GValue *) value)) { + g_key_file_remove_key (info->keyfile, setting_name, key, NULL); + return; + } + } + + /* Don't write secrets that are owned by user secret agents or aren't + * supposed to be saved. VPN secrets are handled specially though since + * the secret flags there are in a third-level hash in the 'secrets' + * property. + */ + if (pspec && (pspec->flags & NM_SETTING_PARAM_SECRET) && !NM_IS_SETTING_VPN (setting)) { + NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE; + + if (!nm_setting_get_secret_flags (setting, key, &secret_flags, NULL)) + g_assert_not_reached (); + if (secret_flags != NM_SETTING_SECRET_FLAG_NONE) + return; + } + + /* Look through the list of handlers for non-standard format key values */ + while (writer->setting_name) { + if (!strcmp (writer->setting_name, setting_name) && !strcmp (writer->key, key)) { + (*writer->writer) (info->keyfile, info->keyfile_dir, info->uuid, setting, key, value); + return; + } + writer++; + } + + if (type == G_TYPE_STRING) { + const char *str; + + str = g_value_get_string (value); + if (str) + nm_keyfile_plugin_kf_set_string (info->keyfile, setting_name, key, str); + } else if (type == G_TYPE_UINT) + nm_keyfile_plugin_kf_set_integer (info->keyfile, setting_name, key, (int) g_value_get_uint (value)); + else if (type == G_TYPE_INT) + nm_keyfile_plugin_kf_set_integer (info->keyfile, setting_name, key, g_value_get_int (value)); + else if (type == G_TYPE_UINT64) { + char *numstr; + + numstr = g_strdup_printf ("%" G_GUINT64_FORMAT, g_value_get_uint64 (value)); + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_name, key, numstr); + g_free (numstr); + } else if (type == G_TYPE_INT64) { + char *numstr; + + numstr = g_strdup_printf ("%" G_GINT64_FORMAT, g_value_get_int64 (value)); + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_name, key, numstr); + g_free (numstr); + } else if (type == G_TYPE_BOOLEAN) { + nm_keyfile_plugin_kf_set_boolean (info->keyfile, setting_name, key, g_value_get_boolean (value)); + } else if (type == G_TYPE_CHAR) { + nm_keyfile_plugin_kf_set_integer (info->keyfile, setting_name, key, (int) g_value_get_schar (value)); + } else if (type == G_TYPE_BYTES) { + GBytes *bytes; + const guint8 *data; + gsize len = 0; + + bytes = g_value_get_boxed (value); + data = bytes ? g_bytes_get_data (bytes, &len) : NULL; + + if (data != NULL && len > 0) { + int *tmp_array; + int i; + + tmp_array = g_new (gint, len); + for (i = 0; i < len; i++) + tmp_array[i] = (int) data[i]; + + nm_keyfile_plugin_kf_set_integer_list (info->keyfile, setting_name, key, tmp_array, len); + g_free (tmp_array); + } + } else if (type == G_TYPE_STRV) { + char **array; + + array = (char **) g_value_get_boxed (value); + nm_keyfile_plugin_kf_set_string_list (info->keyfile, setting_name, key, (const gchar **const) array, g_strv_length (array)); + } else if (type == G_TYPE_HASH_TABLE) { + write_hash_of_string (info->keyfile, setting, key, value); + } else if (type == G_TYPE_ARRAY) { + if (!write_array_of_uint (info->keyfile, setting, key, value)) { + nm_log_warn (LOGD_SETTINGS, "Unhandled setting property type (write) '%s/%s' : '%s'", + setting_name, key, g_type_name (type)); + } + } else if (G_VALUE_HOLDS_FLAGS (value)) { + /* Flags are guint but GKeyFile has no uint reader, just uint64 */ + nm_keyfile_plugin_kf_set_uint64 (info->keyfile, setting_name, key, (guint64) g_value_get_flags (value)); + } else if (G_VALUE_HOLDS_ENUM (value)) + nm_keyfile_plugin_kf_set_integer (info->keyfile, setting_name, key, (gint) g_value_get_enum (value)); + else { + nm_log_warn (LOGD_SETTINGS, "Unhandled setting property type (write) '%s/%s' : '%s'", + setting_name, key, g_type_name (type)); + } +} + +static gboolean +_internal_write_connection (NMConnection *connection, + const char *keyfile_dir, + uid_t owner_uid, + pid_t owner_grp, + const char *existing_path, + char **out_path, + GError **error) +{ + GKeyFile *key_file; + char *data; + gsize len; + gboolean success = FALSE; + char *path; + const char *id; + WriteInfo info; + GError *local_err = NULL; + + g_return_val_if_fail (!out_path || !*out_path, FALSE); + + if (!nm_connection_verify (connection, error)) + g_return_val_if_reached (FALSE); + + id = nm_connection_get_id (connection); + g_assert (id && *id); + + info.keyfile = key_file = g_key_file_new (); + info.keyfile_dir = keyfile_dir; + info.uuid = nm_connection_get_uuid (connection); + g_assert (info.uuid); + nm_connection_for_each_setting_value (connection, write_setting_value, &info); + data = g_key_file_to_data (key_file, &len, error); + if (!data) + goto out; + + /* If we have existing file path, use it. Else generate one from + * connection's ID. + */ + if (existing_path != NULL) { + path = g_strdup (existing_path); + } else { + char *filename_escaped = nm_keyfile_plugin_utils_escape_filename (id); + + path = g_build_filename (keyfile_dir, filename_escaped, NULL); + g_free (filename_escaped); + } + + /* If a file with this path already exists (but isn't the existing path + * of the connection) then we need another name. Multiple connections + * can have the same ID (ie if two connections with the same ID are visible + * to different users) but of course can't have the same path. Yeah, + * there's a race here, but there's not a lot we can do about it, and + * we shouldn't get more than one connection with the same UUID either. + */ + if (g_strcmp0 (path, existing_path) != 0 && g_file_test (path, G_FILE_TEST_EXISTS)) { + guint i; + gboolean name_found = FALSE; + + /* A keyfile with this connection's ID already exists. Pick another name. */ + for (i = 0; i < 100; i++) { + char *filename, *filename_escaped; + + if (i == 0) + filename = g_strdup_printf ("%s-%s", id, nm_connection_get_uuid (connection)); + else + filename = g_strdup_printf ("%s-%s-%u", id, nm_connection_get_uuid (connection), i); + + filename_escaped = nm_keyfile_plugin_utils_escape_filename (filename); + + g_free (path); + path = g_strdup_printf ("%s/%s", keyfile_dir, filename_escaped); + g_free (filename); + g_free (filename_escaped); + if (g_strcmp0 (path, existing_path) == 0 || !g_file_test (path, G_FILE_TEST_EXISTS)) { + name_found = TRUE; + break; + } + } + if (!name_found) { + if (existing_path == NULL) { + /* this really should not happen, we tried hard to find an unused name... bail out. */ + g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, + "could not find suitable keyfile file name (%s already used)", path); + g_free (path); + goto out; + } + /* Both our preferred path based on connection id and id-uuid are taken. + * Fallback to @existing_path */ + g_free (path); + path = g_strdup (existing_path); + } + } + + /* In case of updating the connection and changing the file path, + * we need to remove the old one, not to end up with two connections. + */ + if (existing_path != NULL && strcmp (path, existing_path) != 0) + unlink (existing_path); + + g_file_set_contents (path, data, len, &local_err); + if (local_err) { + g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, + "%s.%d: error writing to file '%s': %s", __FILE__, __LINE__, + path, local_err->message); + g_error_free (local_err); + g_free (path); + goto out; + } + + if (chown (path, owner_uid, owner_grp) < 0) { + g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, + "%s.%d: error chowning '%s': %d", __FILE__, __LINE__, + path, errno); + unlink (path); + } else { + if (chmod (path, S_IRUSR | S_IWUSR) < 0) { + g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, + "%s.%d: error setting permissions on '%s': %d", __FILE__, + __LINE__, path, errno); + unlink (path); + } else { + if (out_path && g_strcmp0 (existing_path, path)) { + *out_path = path; /* pass path out to caller */ + path = NULL; + } + success = TRUE; + } + } + g_free (path); + +out: + g_free (data); + g_key_file_free (key_file); + return success; +} + +gboolean +nm_keyfile_plugin_write_connection (NMConnection *connection, + const char *existing_path, + char **out_path, + GError **error) +{ + return _internal_write_connection (connection, + KEYFILE_DIR, + 0, 0, + existing_path, + out_path, + error); +} + +gboolean +nm_keyfile_plugin_write_test_connection (NMConnection *connection, + const char *keyfile_dir, + uid_t owner_uid, + pid_t owner_grp, + char **out_path, + GError **error) +{ + return _internal_write_connection (connection, + keyfile_dir, + owner_uid, owner_grp, + NULL, + out_path, + error); +} + diff --git a/libnm-core/nm-keyfile-writer.h b/libnm-core/nm-keyfile-writer.h new file mode 100644 index 0000000000..a602f2f4a3 --- /dev/null +++ b/libnm-core/nm-keyfile-writer.h @@ -0,0 +1,41 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager system settings service - keyfile plugin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2008 - 2011 Red Hat, Inc. + */ + +#ifndef _KEYFILE_PLUGIN_WRITER_H +#define _KEYFILE_PLUGIN_WRITER_H + +#include +#include +#include + +gboolean nm_keyfile_plugin_write_connection (NMConnection *connection, + const char *existing_path, + char **out_path, + GError **error); + +gboolean nm_keyfile_plugin_write_test_connection (NMConnection *connection, + const char *keyfile_dir, + uid_t owner_uid, + pid_t owner_grp, + char **out_path, + GError **error); + +#endif /* _KEYFILE_PLUGIN_WRITER_H */ From 04df4edf48e55478d0f360ea566f5f398aa76268 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 18 Feb 2015 18:59:35 +0100 Subject: [PATCH 18/23] libnm: add keyfile support to libnm-core https://bugzilla.gnome.org/show_bug.cgi?id=744699 --- libnm-core/Makefile.libnm-core | 6 + libnm-core/nm-keyfile-reader.c | 627 ++++++++++++++++++++------------- libnm-core/nm-keyfile-reader.h | 67 +++- libnm-core/nm-keyfile-utils.c | 125 +------ libnm-core/nm-keyfile-utils.h | 20 +- libnm-core/nm-keyfile-writer.c | 472 ++++++------------------- libnm-core/nm-keyfile-writer.h | 78 +++- po/POTFILES.in | 2 + 8 files changed, 637 insertions(+), 760 deletions(-) diff --git a/libnm-core/Makefile.libnm-core b/libnm-core/Makefile.libnm-core index fc6d779b5e..798714600e 100644 --- a/libnm-core/Makefile.libnm-core +++ b/libnm-core/Makefile.libnm-core @@ -48,6 +48,9 @@ libnm_core_private_headers = \ $(core)/crypto.h \ $(core)/nm-connection-private.h \ $(core)/nm-core-internal.h \ + $(core)/nm-keyfile-reader.h \ + $(core)/nm-keyfile-utils.h \ + $(core)/nm-keyfile-writer.h \ $(core)/nm-property-compare.h \ $(core)/nm-setting-private.h \ $(core)/nm-utils-private.h @@ -57,6 +60,9 @@ libnm_core_sources = \ $(core)/crypto.c \ $(core)/nm-connection.c \ $(core)/nm-errors.c \ + $(core)/nm-keyfile-reader.c \ + $(core)/nm-keyfile-utils.c \ + $(core)/nm-keyfile-writer.c \ $(core)/nm-property-compare.c \ $(core)/nm-setting-8021x.c \ $(core)/nm-setting-adsl.c \ diff --git a/libnm-core/nm-keyfile-reader.c b/libnm-core/nm-keyfile-reader.c index 97b6567028..9da3e1eca8 100644 --- a/libnm-core/nm-keyfile-reader.c +++ b/libnm-core/nm-keyfile-reader.c @@ -16,7 +16,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Copyright (C) 2008 - 2009 Novell, Inc. - * Copyright (C) 2008 - 2011 Red Hat, Inc. + * Copyright (C) 2008 - 2015 Red Hat, Inc. */ #include "config.h" @@ -28,17 +28,59 @@ #include #include #include +#include #include "nm-core-internal.h" -#include "nm-dbus-glib-types.h" +#include "gsystem-local-alloc.h" #include "nm-glib-compat.h" -#include "nm-system-config-interface.h" -#include "nm-logging.h" -#include "reader.h" -#include "common.h" -#include "utils.h" -#include "nm-core-internal.h" -#include "NetworkManagerUtils.h" +#include "nm-keyfile-reader.h" +#include "nm-keyfile-utils.h" + + +typedef struct { + NMConnection *connection; + GKeyFile *keyfile; + const char *base_dir; + NMKeyfileReadHandler handler; + void *user_data; + GError *error; + const char *group; + NMSetting *setting; +} KeyfileReaderInfo; + + +static void +_handle_warn (KeyfileReaderInfo *info, + const char *property_name, + NMKeyfileWarnSeverity severity, + char *message) +{ + NMKeyfileReadTypeDataWarn type_data = { + .group = info->group, + .setting = info->setting, + .property_name = property_name, + .severity = severity, + .message = message, + }; + + info->handler (info->keyfile, + info->connection, + NM_KEYFILE_READ_TYPE_WARN, + &type_data, + info->user_data, + &info->error); + g_free (message); +} +#define handle_warn(arg_info, arg_property_name, arg_severity, ...) \ + ({ \ + KeyfileReaderInfo *_info = (arg_info); \ + \ + if (_info->handler) { \ + _handle_warn (_info, (arg_property_name), (arg_severity), \ + g_strdup_printf (__VA_ARGS__)); \ + } \ + _info->error == NULL; \ + }) /* Some setting properties also contain setting names, such as * NMSettingConnection's 'type' property (which specifies the base type of the @@ -47,13 +89,13 @@ * properties' values to the real setting name if they are an alias. */ static void -setting_alias_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) +setting_alias_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) { const char *setting_name = nm_setting_get_name (setting); char *s; const char *key_setting_name; - s = nm_keyfile_plugin_kf_get_string (keyfile, setting_name, key, NULL); + s = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL); if (s) { key_setting_name = nm_keyfile_plugin_get_setting_name_for_alias (s); g_object_set (G_OBJECT (setting), @@ -63,7 +105,7 @@ setting_alias_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, co } } -static gboolean +static void read_array_of_uint (GKeyFile *file, NMSetting *setting, const char *key) @@ -75,34 +117,36 @@ read_array_of_uint (GKeyFile *file, tmp = nm_keyfile_plugin_kf_get_integer_list (file, nm_setting_get_name (setting), key, &length, NULL); array = g_array_sized_new (FALSE, FALSE, sizeof (guint32), length); - g_return_val_if_fail (array != NULL, FALSE); for (i = 0; i < length; i++) g_array_append_val (array, tmp[i]); g_object_set (setting, key, array, NULL); g_array_unref (array); - - return TRUE; } static gboolean -get_one_int (const char *str, guint32 max_val, const char *key_name, guint32 *out) +get_one_int (KeyfileReaderInfo *info, const char *property_name, const char *str, guint32 max_val, guint32 *out) { long tmp; char *endptr; + g_return_val_if_fail (!info == !property_name, FALSE); + if (!str || !str[0]) { - if (key_name) - nm_log_warn (LOGD_SETTINGS, "%s: ignoring missing number %s", __func__, key_name); + if (property_name) + handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring missing number")); return FALSE; } errno = 0; tmp = strtol (str, &endptr, 10); if (errno || (tmp < 0) || (tmp > max_val) || *endptr != 0) { - if (key_name) - nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid number %s '%s'", __func__, key_name, str); + if (property_name) + handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid number '%s'"), + str); return FALSE; } @@ -111,7 +155,7 @@ get_one_int (const char *str, guint32 max_val, const char *key_name, guint32 *ou } static gpointer -build_address (int family, const char *address_str, guint32 plen) +build_address (KeyfileReaderInfo *info, int family, const char *address_str, guint32 plen, const char *property_name) { NMIPAddress *addr; GError *error = NULL; @@ -120,9 +164,9 @@ build_address (int family, const char *address_str, guint32 plen) addr = nm_ip_address_new (family, address_str, plen, &error); if (!addr) { - nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid %s address: %s", __func__, - family == AF_INET ? "IPv4" : "IPv6", - error->message); + handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid %s addresss: %s"), + family == AF_INET ? "IPv4" : "IPv6", error->message); g_error_free (error); } @@ -130,10 +174,11 @@ build_address (int family, const char *address_str, guint32 plen) } static gpointer -build_route (int family, +build_route (KeyfileReaderInfo *info, + const char *property_name, + int family, const char *dest_str, guint32 plen, - const char *gateway_str, const char *metric_str, - const char *key_name) + const char *gateway_str, const char *metric_str) { NMIPRoute *route; guint32 metric = 0; @@ -155,10 +200,14 @@ build_route (int family, **/ if ( family == AF_INET6 && !metric_str - && get_one_int (gateway_str, G_MAXUINT32, NULL, &metric)) + && get_one_int (NULL, NULL, gateway_str, G_MAXUINT32, &metric)) gateway_str = NULL; else { - nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid gateway '%s'", __func__, gateway_str); + if (!info->error) { + handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid gateway '%s' for %s route"), + gateway_str, family == AF_INET ? "IPv4" : "IPv6"); + } return NULL; } } @@ -167,7 +216,7 @@ build_route (int family, /* parse metric, default to 0 */ if (metric_str) { - if (!get_one_int (metric_str, G_MAXUINT32, key_name, &metric)) + if (!get_one_int (info, property_name, metric_str, G_MAXUINT32, &metric)) return NULL; } @@ -175,7 +224,8 @@ build_route (int family, metric ? (gint64) metric : -1, &error); if (!route) { - nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid %s route: %s", __func__, + handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid %s route: %s"), family == AF_INET ? "IPv4" : "IPv6", error->message); g_error_free (error); @@ -269,46 +319,52 @@ read_field (char **current, char **error, const char *characters, const char *de * match the iproute2 defaults (32 for IPv4 and 128 for IPv6). */ static gpointer -read_one_ip_address_or_route (GKeyFile *file, +read_one_ip_address_or_route (KeyfileReaderInfo *info, + const char *property_name, const char *setting_name, const char *key_name, gboolean ipv6, gboolean route, - char **out_gateway) + char **out_gateway, + NMSetting *setting) { guint32 plen = G_MAXUINT32; gpointer result; - char *address_str, *plen_str, *gateway_str, *metric_str, *value, *current, *error; + char *address_str, *plen_str, *gateway_str, *metric_str, *current, *error; + gs_free char *value = NULL, *value_orig = NULL; - current = value = nm_keyfile_plugin_kf_get_string (file, setting_name, key_name, NULL); +#define VALUE_ORIG() (value_orig ? value_orig : (value_orig = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key_name, NULL))) + + current = value = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key_name, NULL); if (!value) return NULL; /* get address field */ address_str = read_field (¤t, &error, IP_ADDRESS_CHARS, DELIMITERS); if (error) { - nm_log_warn (LOGD_SETTINGS, "keyfile: Unexpected character '%c' in '%s.%s' address (position %td of '%s').", - *error, setting_name, key_name, error - current, current); - goto error; + handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("unexpected character '%c' for address %s: '%s' (position %td)"), + *error, key_name, VALUE_ORIG (), error - current); + return NULL; } /* get prefix length field (skippable) */ plen_str = read_field (¤t, &error, DIGITS, DELIMITERS); /* get gateway field */ gateway_str = read_field (¤t, &error, IP_ADDRESS_CHARS, DELIMITERS); if (error) { - nm_log_warn (LOGD_SETTINGS, "keyfile: Unexpected character '%c' in '%s.%s' %s (position %td of '%s').", - *error, setting_name, key_name, - plen_str ? "gateway" : "gateway or prefix length", - error - current, current); - goto error; + handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("unexpected character '%c' for %s: '%s' (position %td)"), + *error, key_name, VALUE_ORIG (), error - current); + return NULL; } /* for routes, get metric */ if (route) { metric_str = read_field (¤t, &error, DIGITS, DELIMITERS); if (error) { - nm_log_warn (LOGD_SETTINGS, "keyfile: Unexpected character '%c' in '%s.%s' prefix length (position %td of '%s').", - *error, setting_name, key_name, error - current, current); - goto error; + handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("unexpected character '%c' in prefix length for %s: '%s' (position %td)"), + *error, key_name, VALUE_ORIG (), error - current); + return NULL; } } else metric_str = NULL; @@ -316,13 +372,16 @@ read_one_ip_address_or_route (GKeyFile *file, /* there is still some data */ if (*current) { /* another field follows */ - nm_log_warn (LOGD_SETTINGS, "keyfile: %s.%s: Garbage at the and of the line: %s", - setting_name, key_name, current); - goto error; + handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("garbage at the end of value %s: '%s'"), + key_name, VALUE_ORIG ()); + return NULL; } else { /* semicolon at the end of input */ - nm_log_info (LOGD_SETTINGS, "keyfile: %s.%s: Deprecated semicolon at the end of value.", - setting_name, key_name); + if (!handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_INFO, + _("deprecated semicolon at the end of value %s: '%s'"), + key_name, VALUE_ORIG ())) + return NULL; } } @@ -330,39 +389,44 @@ read_one_ip_address_or_route (GKeyFile *file, /* parse plen, fallback to defaults */ if (plen_str) { - if (!get_one_int (plen_str, ipv6 ? 128 : 32, key_name, &plen) + if (!get_one_int (info, property_name, plen_str, ipv6 ? 128 : 32, &plen) || (route && plen == 0)) { plen = DEFAULT_PREFIX (route, ipv6); - nm_log_warn (LOGD_SETTINGS, "keyfile: invalid prefix length '%s' in '%s.%s', defaulting to %d", - plen_str, setting_name, key_name, plen); + if ( info->error + || !handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid prefix length for %s '%s', defaulting to %d"), + key_name, VALUE_ORIG (), plen)) + return NULL; } } else { plen = DEFAULT_PREFIX (route, ipv6); - nm_log_warn (LOGD_SETTINGS, "keyfile: Missing prefix length in '%s.%s', defaulting to %d", - setting_name, key_name, plen); + if (!handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("missing prefix length for %s '%s', defaulting to %d"), + key_name, VALUE_ORIG (), plen)) + return NULL; } /* build the appropriate data structure for NetworkManager settings */ if (route) { - result = build_route (ipv6 ? AF_INET6 : AF_INET, - address_str, plen, gateway_str, metric_str, - key_name); + result = build_route (info, property_name, + ipv6 ? AF_INET6 : AF_INET, + address_str, plen, gateway_str, metric_str); } else { - result = build_address (ipv6 ? AF_INET6 : AF_INET, - address_str, plen); + result = build_address (info, ipv6 ? AF_INET6 : AF_INET, + address_str, plen, property_name); + if (!result) + return NULL; if (out_gateway && gateway_str) *out_gateway = g_strdup (gateway_str); } - g_free (value); +#undef VALUE_ORIG + return result; -error: - g_free (value); - return NULL; } static void -ip_address_or_route_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) +ip_address_or_route_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) { const char *setting_name = nm_setting_get_name (setting); gboolean ipv6 = !strcmp (setting_name, "ipv6"); @@ -383,7 +447,7 @@ ip_address_or_route_parser (NMSetting *setting, const char *key, GKeyFile *keyfi for (i = -1; i < 1000; i++) { const char **key_basename; - + for (key_basename = key_names; *key_basename; key_basename++) { char *key_name; gpointer item; @@ -394,12 +458,18 @@ ip_address_or_route_parser (NMSetting *setting, const char *key, GKeyFile *keyfi else key_name = g_strdup (*key_basename); - item = read_one_ip_address_or_route (keyfile, setting_name, key_name, ipv6, routes, - gateway ? NULL : &gateway); + item = read_one_ip_address_or_route (info, key, setting_name, key_name, ipv6, routes, + gateway ? NULL : &gateway, setting); + g_free (key_name); + + if (info->error) { + g_ptr_array_unref (list); + g_free (gateway); + return; + } if (item) g_ptr_array_add (list, item); - g_free (key_name); } } @@ -415,7 +485,7 @@ ip_address_or_route_parser (NMSetting *setting, const char *key, GKeyFile *keyfi } static void -ip4_dns_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) +ip4_dns_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) { const char *setting_name = nm_setting_get_name (setting); GPtrArray *array; @@ -423,7 +493,7 @@ ip4_dns_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const ch char **list, **iter; int ret; - list = nm_keyfile_plugin_kf_get_string_list (keyfile, setting_name, key, &length, NULL); + list = nm_keyfile_plugin_kf_get_string_list (info->keyfile, setting_name, key, &length, NULL); if (!list || !g_strv_length (list)) return; @@ -433,7 +503,13 @@ ip4_dns_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const ch ret = inet_pton (AF_INET, *iter, &addr); if (ret <= 0) { - nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid DNS server address '%s'", __func__, *iter); + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid DNS server IPv4 address '%s'"), + *iter)) { + g_ptr_array_unref (array); + g_strfreev (list); + return; + } continue; } @@ -447,7 +523,7 @@ ip4_dns_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const ch } static void -ip6_dns_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) +ip6_dns_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) { const char *setting_name = nm_setting_get_name (setting); GPtrArray *array = NULL; @@ -455,7 +531,7 @@ ip6_dns_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const ch char **list, **iter; int ret; - list = nm_keyfile_plugin_kf_get_string_list (keyfile, setting_name, key, &length, NULL); + list = nm_keyfile_plugin_kf_get_string_list (info->keyfile, setting_name, key, &length, NULL); if (!list || !g_strv_length (list)) return; @@ -466,7 +542,13 @@ ip6_dns_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const ch ret = inet_pton (AF_INET6, *iter, &addr); if (ret <= 0) { - nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid DNS server IPv6 address '%s'", __func__, *iter); + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid DNS server IPv6 address '%s'"), + *iter)) { + g_ptr_array_unref (array); + g_strfreev (list); + return; + } continue; } @@ -480,7 +562,7 @@ ip6_dns_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const ch } static void -mac_address_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path, gsize enforce_length) +mac_address_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key, gsize enforce_length) { const char *setting_name = nm_setting_get_name (setting); char *tmp_string = NULL, *p, *mac_str; @@ -488,7 +570,7 @@ mac_address_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, cons GByteArray *array = NULL; gsize length; - p = tmp_string = nm_keyfile_plugin_kf_get_string (keyfile, setting_name, key, NULL); + p = tmp_string = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL); if (tmp_string && tmp_string[0]) { /* Look for enough ':' characters to signify a MAC address */ guint i = 0; @@ -513,7 +595,7 @@ mac_address_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, cons if (array == NULL) { /* Old format; list of ints */ - tmp_list = nm_keyfile_plugin_kf_get_integer_list (keyfile, setting_name, key, &length, NULL); + tmp_list = nm_keyfile_plugin_kf_get_integer_list (info->keyfile, setting_name, key, &length, NULL); if (length > 0 && (enforce_length == 0 || enforce_length == length)) { gsize i; @@ -523,12 +605,12 @@ mac_address_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, cons const guint8 v = (guint8) (val & 0xFF); if (val < 0 || val > 255) { - nm_log_warn (LOGD_SETTINGS, "%s: %s / %s ignoring invalid byte element '%d' (not " - " between 0 and 255 inclusive)", __func__, setting_name, - key, val); + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid byte element '%d' (not between 0 and 255 inclusive)"), + val); g_byte_array_free (array, TRUE); - array = NULL; - break; + g_free (tmp_list); + return; } g_byte_array_append (array, &v, 1); } @@ -537,8 +619,8 @@ mac_address_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, cons } if (!array) { - nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid MAC address for %s / %s", - __func__, setting_name, key); + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid MAC address")); return; } @@ -549,15 +631,15 @@ mac_address_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, cons } static void -mac_address_parser_ETHER (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) +mac_address_parser_ETHER (KeyfileReaderInfo *info, NMSetting *setting, const char *key) { - mac_address_parser (setting, key, keyfile, keyfile_path, ETH_ALEN); + mac_address_parser (info, setting, key, ETH_ALEN); } static void -mac_address_parser_INFINIBAND (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) +mac_address_parser_INFINIBAND (KeyfileReaderInfo *info, NMSetting *setting, const char *key) { - mac_address_parser (setting, key, keyfile, keyfile_path, INFINIBAND_ALEN); + mac_address_parser (info, setting, key, INFINIBAND_ALEN); } static void @@ -606,7 +688,7 @@ unescape_semicolons (char *str) } static GBytes * -get_bytes (GKeyFile *keyfile, +get_bytes (KeyfileReaderInfo *info, const char *setting_name, const char *key, gboolean zero_terminate, @@ -618,13 +700,13 @@ get_bytes (GKeyFile *keyfile, gsize length; int i; - if (!nm_keyfile_plugin_kf_has_key (keyfile, setting_name, key, NULL)) + if (!nm_keyfile_plugin_kf_has_key (info->keyfile, setting_name, key, NULL)) return NULL; /* New format: just a string * Old format: integer list; e.g. 11;25;38; */ - tmp_string = nm_keyfile_plugin_kf_get_string (keyfile, setting_name, key, NULL); + tmp_string = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL); if (tmp_string) { GRegex *regex; GMatchInfo *match_info; @@ -648,11 +730,13 @@ get_bytes (GKeyFile *keyfile, } if (!array) { + gboolean already_warned = FALSE; + /* Old format; list of ints */ - tmp_list = nm_keyfile_plugin_kf_get_integer_list (keyfile, setting_name, key, &length, NULL); + tmp_list = nm_keyfile_plugin_kf_get_integer_list (info->keyfile, setting_name, key, &length, NULL); if (!tmp_list) { - nm_log_warn (LOGD_SETTINGS, "%s: %s / %s ignoring invalid binary property", - __func__, setting_name, key); + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid binary property")); return NULL; } array = g_byte_array_sized_new (length); @@ -661,9 +745,15 @@ get_bytes (GKeyFile *keyfile, unsigned char v = (unsigned char) (val & 0xFF); if (val < 0 || val > 255) { - nm_log_warn (LOGD_SETTINGS, "%s: %s / %s ignoring invalid byte element '%d' (not " - " between 0 and 255 inclusive)", __func__, setting_name, - key, val); + if ( !already_warned + && !handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid byte element '%d' (not between 0 and 255 inclusive)"), + val)) { + g_free (tmp_list); + g_byte_array_free (array, TRUE); + return NULL; + } + already_warned = TRUE; } else g_byte_array_append (array, (const unsigned char *) &v, sizeof (v)); } @@ -678,44 +768,44 @@ get_bytes (GKeyFile *keyfile, } static void -ssid_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) +ssid_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) { const char *setting_name = nm_setting_get_name (setting); GBytes *bytes; - bytes = get_bytes (keyfile, setting_name, key, FALSE, TRUE); + bytes = get_bytes (info, setting_name, key, FALSE, TRUE); if (bytes) { g_object_set (setting, key, bytes, NULL); g_bytes_unref (bytes); - } else { - nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid SSID for %s / %s", - __func__, setting_name, key); + } else if (!info->error) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid SSID")); } } static void -password_raw_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) +password_raw_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) { const char *setting_name = nm_setting_get_name (setting); GBytes *bytes; - bytes = get_bytes (keyfile, setting_name, key, FALSE, TRUE); + bytes = get_bytes (info, setting_name, key, FALSE, TRUE); if (bytes) { g_object_set (setting, key, bytes, NULL); g_bytes_unref (bytes); - } else { - nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid raw password for %s / %s", - __func__, setting_name, key); + } else if (!info->error) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid raw password")); } } static char * -get_cert_path (const char *keyfile_path, const guint8 *cert_path, gsize cert_path_len) +get_cert_path (const char *base_dir, const guint8 *cert_path, gsize cert_path_len) { const char *base; - char *p = NULL, *path, *dirname, *tmp; + char *p = NULL, *path, *tmp; - g_return_val_if_fail (keyfile_path != NULL, NULL); + g_return_val_if_fail (base_dir != NULL, NULL); g_return_val_if_fail (cert_path != NULL, NULL); base = path = g_malloc0 (cert_path_len + 1); @@ -728,9 +818,7 @@ get_cert_path (const char *keyfile_path, const guint8 *cert_path, gsize cert_pat if (p) base = p + 1; - dirname = g_path_get_dirname (keyfile_path); - tmp = g_build_path ("/", dirname, base, NULL); - g_free (dirname); + tmp = g_build_path ("/", base_dir, base, NULL); g_free (path); return tmp; } @@ -770,10 +858,10 @@ handle_as_scheme (GBytes *bytes, NMSetting *setting, const char *key) } static gboolean -handle_as_path (GBytes *bytes, +handle_as_path (KeyfileReaderInfo *info, + GBytes *bytes, NMSetting *setting, - const char *key, - const char *keyfile_path) + const char *key) { const guint8 *data; gsize data_len; @@ -802,7 +890,7 @@ handle_as_path (GBytes *bytes, * relative path to the current directory. */ - path = get_cert_path (keyfile_path, data, data_len); + path = get_cert_path (info->base_dir, data, data_len); exists = g_file_test (path, G_FILE_TEST_EXISTS); if ( exists || memchr (data, '/', data_len) @@ -822,7 +910,9 @@ handle_as_path (GBytes *bytes, /* Warn if the certificate didn't exist */ if (exists == FALSE) - nm_log_warn (LOGD_SETTINGS, "certificate or key %s does not exist", path); + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("certificate or key '%s' does not exist"), + path); } g_free (path); @@ -830,48 +920,51 @@ handle_as_path (GBytes *bytes, } static void -cert_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) +cert_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) { const char *setting_name = nm_setting_get_name (setting); GBytes *bytes; gboolean success = FALSE; - bytes = get_bytes (keyfile, setting_name, key, TRUE, FALSE); + bytes = get_bytes (info, setting_name, key, TRUE, FALSE); if (bytes) { /* Try as a path + scheme (ie, starts with "file://") */ success = handle_as_scheme (bytes, setting, key); /* If not, it might be a plain path */ if (success == FALSE) - success = handle_as_path (bytes, setting, key, keyfile_path); + success = handle_as_path (info, bytes, setting, key); + if (info->error) + goto out_error; /* If neither of those two, assume blob with certificate data */ if (success == FALSE) g_object_set (setting, key, bytes, NULL); - } else { - nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid key/cert value for %s / %s", - __func__, setting_name, key); + } else if (!info->error) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid key/cert value")); } +out_error: if (bytes) g_bytes_unref (bytes); } static void -parity_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) +parity_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) { const char *setting_name = nm_setting_get_name (setting); NMSettingSerialParity parity; int int_val; - char *str_val; + gs_free char *str_val = NULL; /* Keyfile traditionally stored this as the ASCII value for 'E', 'o', or 'n'. * We now accept either that or the (case-insensitive) character itself (but * still always write it the old way, for backward compatibility). */ - int_val = nm_keyfile_plugin_kf_get_integer (keyfile, setting_name, key, NULL); + int_val = nm_keyfile_plugin_kf_get_integer (info->keyfile, setting_name, key, NULL); if (!int_val) { - str_val = nm_keyfile_plugin_kf_get_string (keyfile, setting_name, key, NULL); + str_val = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL); if (str_val) { if (str_val[0] && !str_val[1]) int_val = str_val[0]; @@ -880,7 +973,6 @@ parity_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const cha int_val = 'X'; } } - g_free (str_val); } if (!int_val) @@ -900,8 +992,9 @@ parity_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const cha parity = NM_SETTING_SERIAL_PARITY_NONE; break; default: - nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid value for %s / %s", - __func__, setting_name, key); + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid parity value '%s'"), + str_val ? str_val : ""); return; } @@ -912,7 +1005,7 @@ typedef struct { const char *setting_name; const char *key; gboolean check_for_key; - void (*parser) (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path); + void (*parser) (KeyfileReaderInfo *info, NMSetting *setting, const char *key); } KeyParser; /* A table of keys that require further parsing/conversion because they are @@ -1025,11 +1118,6 @@ static KeyParser key_parsers[] = { { NULL, NULL, FALSE } }; -typedef struct { - GKeyFile *keyfile; - const char *keyfile_path; -} ReadInfo; - static void read_one_setting_value (NMSetting *setting, const char *key, @@ -1037,14 +1125,18 @@ read_one_setting_value (NMSetting *setting, GParamFlags flags, gpointer user_data) { - ReadInfo *info = user_data; + KeyfileReaderInfo *info = user_data; + GKeyFile *keyfile = info->keyfile; const char *setting_name; int errsv; GType type; - GError *err = NULL; + gs_free_error GError *err = NULL; gboolean check_for_key = TRUE; KeyParser *parser = &key_parsers[0]; + if (info->error) + return; + /* Property is not writable */ if (!(flags & G_PARAM_WRITABLE)) return; @@ -1082,11 +1174,13 @@ read_one_setting_value (NMSetting *setting, * like IP addresses and routes where more than one value is actually * encoded by the setting property, this won't be true. */ - if (check_for_key && !nm_keyfile_plugin_kf_has_key (info->keyfile, setting_name, key, &err)) { + if (check_for_key && !nm_keyfile_plugin_kf_has_key (keyfile, setting_name, key, &err)) { /* Key doesn't exist or an error ocurred, thus nothing to do. */ if (err) { - nm_log_warn (LOGD_SETTINGS, "Error loading setting '%s' value: %s", setting_name, err->message); - g_error_free (err); + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("error loading setting value: %s"), + err->message)) + goto out_error; } return; } @@ -1095,7 +1189,7 @@ read_one_setting_value (NMSetting *setting, * parsers below. */ if (parser->setting_name) { - (*parser->parser) (setting, key, info->keyfile, info->keyfile_path); + (*parser->parser) (info, setting, key); return; } @@ -1104,62 +1198,73 @@ read_one_setting_value (NMSetting *setting, if (type == G_TYPE_STRING) { char *str_val; - str_val = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL); + str_val = nm_keyfile_plugin_kf_get_string (keyfile, setting_name, key, NULL); g_object_set (setting, key, str_val, NULL); g_free (str_val); } else if (type == G_TYPE_UINT) { int int_val; - int_val = nm_keyfile_plugin_kf_get_integer (info->keyfile, setting_name, key, NULL); - if (int_val < 0) - nm_log_warn (LOGD_SETTINGS, "Casting negative value (%i) to uint", int_val); + int_val = nm_keyfile_plugin_kf_get_integer (keyfile, setting_name, key, NULL); + if (int_val < 0) { + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid negative value (%i)"), + int_val)) + goto out_error; + } g_object_set (setting, key, int_val, NULL); } else if (type == G_TYPE_INT) { int int_val; - int_val = nm_keyfile_plugin_kf_get_integer (info->keyfile, setting_name, key, NULL); + int_val = nm_keyfile_plugin_kf_get_integer (keyfile, setting_name, key, NULL); g_object_set (setting, key, int_val, NULL); } else if (type == G_TYPE_BOOLEAN) { gboolean bool_val; - bool_val = nm_keyfile_plugin_kf_get_boolean (info->keyfile, setting_name, key, NULL); + bool_val = nm_keyfile_plugin_kf_get_boolean (keyfile, setting_name, key, NULL); g_object_set (setting, key, bool_val, NULL); } else if (type == G_TYPE_CHAR) { int int_val; - int_val = nm_keyfile_plugin_kf_get_integer (info->keyfile, setting_name, key, NULL); - if (int_val < G_MININT8 || int_val > G_MAXINT8) - nm_log_warn (LOGD_SETTINGS, "Casting value (%i) to char", int_val); + int_val = nm_keyfile_plugin_kf_get_integer (keyfile, setting_name, key, NULL); + if (int_val < G_MININT8 || int_val > G_MAXINT8) { + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid char value (%i)"), + int_val)) + goto out_error; + } g_object_set (setting, key, int_val, NULL); } else if (type == G_TYPE_UINT64) { char *tmp_str; guint64 uint_val; - tmp_str = nm_keyfile_plugin_kf_get_value (info->keyfile, setting_name, key, NULL); + tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_name, key, NULL); uint_val = g_ascii_strtoull (tmp_str, NULL, 10); g_free (tmp_str); g_object_set (setting, key, uint_val, NULL); } else if (type == G_TYPE_INT64) { - char *tmp_str; + gs_free char *tmp_str = NULL; gint64 int_val; - tmp_str = nm_keyfile_plugin_kf_get_value (info->keyfile, setting_name, key, NULL); + tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_name, key, NULL); int_val = _nm_utils_ascii_str_to_int64 (tmp_str, 10, G_MININT64, G_MAXINT64, 0); errsv = errno; - if (errsv) - nm_log_warn (LOGD_SETTINGS, "Invalid int64 value (%s)", tmp_str); - else + if (errsv) { + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid int64 value (%s)"), + tmp_str)) + goto out_error; + } else g_object_set (setting, key, int_val, NULL); - g_free (tmp_str); - } else if (type == G_TYPE_BYTES) { + } else if (type == G_TYPE_BYTES) { gint *tmp; GByteArray *array; GBytes *bytes; gsize length; int i; + gboolean already_warned = FALSE; - tmp = nm_keyfile_plugin_kf_get_integer_list (info->keyfile, setting_name, key, &length, NULL); + tmp = nm_keyfile_plugin_kf_get_integer_list (keyfile, setting_name, key, &length, NULL); array = g_byte_array_sized_new (length); for (i = 0; i < length; i++) { @@ -1167,9 +1272,15 @@ read_one_setting_value (NMSetting *setting, unsigned char v = (unsigned char) (val & 0xFF); if (val < 0 || val > 255) { - nm_log_warn (LOGD_SETTINGS, "%s: %s / %s ignoring invalid byte element '%d' (not " - " between 0 and 255 inclusive)", __func__, setting_name, - key, val); + if ( !already_warned + && !handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid byte element '%d' (not between 0 and 255 inclusive)"), + val)) { + g_byte_array_unref (array); + g_free (tmp); + goto out_error; + } + already_warned = TRUE; } else g_byte_array_append (array, (const unsigned char *) &v, sizeof (v)); } @@ -1182,75 +1293,83 @@ read_one_setting_value (NMSetting *setting, gchar **sa; gsize length; - sa = nm_keyfile_plugin_kf_get_string_list (info->keyfile, setting_name, key, &length, NULL); + sa = nm_keyfile_plugin_kf_get_string_list (keyfile, setting_name, key, &length, NULL); g_object_set (setting, key, sa, NULL); g_strfreev (sa); } else if (type == G_TYPE_HASH_TABLE) { - read_hash_of_string (info->keyfile, setting, key); + read_hash_of_string (keyfile, setting, key); } else if (type == G_TYPE_ARRAY) { - if (!read_array_of_uint (info->keyfile, setting, key)) { - nm_log_warn (LOGD_SETTINGS, "Unhandled setting property type (read): '%s/%s' : '%s'", - setting_name, key, G_VALUE_TYPE_NAME (value)); - } + read_array_of_uint (keyfile, setting, key); } else if (G_VALUE_HOLDS_FLAGS (value)) { guint64 uint_val; /* Flags are guint but GKeyFile has no uint reader, just uint64 */ - uint_val = nm_keyfile_plugin_kf_get_uint64 (info->keyfile, setting_name, key, &err); + uint_val = nm_keyfile_plugin_kf_get_uint64 (keyfile, setting_name, key, &err); if (!err) { if (uint_val <= G_MAXUINT) g_object_set (setting, key, (guint) uint_val, NULL); else { - nm_log_warn (LOGD_SETTINGS, "Too large FLAGS property (read): '%s/%s' : '%s'", - setting_name, key, G_VALUE_TYPE_NAME (value)); + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("too large FLAGS property '%s' (%lld)"), + G_VALUE_TYPE_NAME (value), (long long unsigned) uint_val)) + goto out_error; } } - g_clear_error (&err); } else if (G_VALUE_HOLDS_ENUM (value)) { gint int_val; - int_val = nm_keyfile_plugin_kf_get_integer (info->keyfile, setting_name, key, &err); + int_val = nm_keyfile_plugin_kf_get_integer (keyfile, setting_name, key, &err); if (!err) g_object_set (setting, key, (gint) int_val, NULL); - g_clear_error (&err); } else { - nm_log_warn (LOGD_SETTINGS, "Unhandled setting property type (read): '%s/%s' : '%s'", - setting_name, key, G_VALUE_TYPE_NAME (value)); + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("unhandled setting property type '%s'"), + G_VALUE_TYPE_NAME (value))) + goto out_error; } +out_error: + return; } static NMSetting * -read_setting (GKeyFile *file, const char *keyfile_path, const char *group) +read_setting (KeyfileReaderInfo *info) { - NMSetting *setting = NULL; - ReadInfo info = { file, keyfile_path }; const char *alias; GType type; - alias = nm_keyfile_plugin_get_setting_name_for_alias (group); - if (alias) - group = alias; + alias = nm_keyfile_plugin_get_setting_name_for_alias (info->group); + if (!alias) + alias = info->group; - type = nm_setting_lookup_type (group); + type = nm_setting_lookup_type (alias); if (type) { - setting = g_object_new (type, NULL); - nm_setting_enumerate_values (setting, read_one_setting_value, &info); - } else - nm_log_warn (LOGD_SETTINGS, "Invalid setting name '%s'", group); + NMSetting *setting = g_object_new (type, NULL); - return setting; + info->setting = setting; + nm_setting_enumerate_values (setting, read_one_setting_value, info); + info->setting = NULL; + if (!info->error) + return setting; + + g_object_unref (setting); + } else { + handle_warn (info, NULL, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid setting name '%s'"), info->group); + } + + return NULL; } static void -read_vpn_secrets (GKeyFile *file, NMSettingVpn *s_vpn) +read_vpn_secrets (KeyfileReaderInfo *info, NMSettingVpn *s_vpn) { char **keys, **iter; - keys = nm_keyfile_plugin_kf_get_keys (file, VPN_SECRETS_GROUP, NULL, NULL); + keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, VPN_SECRETS_GROUP, NULL, NULL); for (iter = keys; *iter; iter++) { char *secret; - secret = nm_keyfile_plugin_kf_get_string (file, VPN_SECRETS_GROUP, *iter, NULL); + secret = nm_keyfile_plugin_kf_get_string (info->keyfile, VPN_SECRETS_GROUP, *iter, NULL); if (secret) { nm_setting_vpn_add_secret (s_vpn, *iter, secret); g_free (secret); @@ -1259,12 +1378,37 @@ read_vpn_secrets (GKeyFile *file, NMSettingVpn *s_vpn) g_strfreev (keys); } +/** + * nm_keyfile_read: + * @keyfile: the keyfile from which to create the connection + * @keyfile_name: keyfile allows missing connection id and uuid + * and NetworkManager will create those when reading a connection + * from file. By providing a filename you can reproduce that behavior, + * but of course, it can only recreate the same UUID if you provide the + * same filename as NetworkManager core daemon would. + * @keyfile_name has only a relevance for setting the id or uuid if it + * is missing and as fallback for @base_dir. + * @base_dir: when reading certificates from files with relative name, + * the relative path is made absolute using @base_dir. + * If @base_dir is missing, first try to get the pathname from @keyfile_name + * (if it is given as absolute path). As last, fallback to the current path. + * @handler: read handler + * @user_data: user data for read handler + * @error: error + * + * Tries to create a NMConnection from a keyfile. The resulting keyfile is + * not normalized and might not even verify. + * + * Returns: (transfer full): on success, returns the created connection. + */ NMConnection * -nm_keyfile_plugin_connection_from_file (const char *filename, GError **error) +nm_keyfile_read (GKeyFile *keyfile, + const char *keyfile_name, + const char *base_dir, + NMKeyfileReadHandler handler, + void *user_data, + GError **error) { - GKeyFile *key_file; - struct stat statbuf; - gboolean bad_permissions; NMConnection *connection = NULL; NMSettingConnection *s_con; NMSetting *setting; @@ -1272,30 +1416,33 @@ nm_keyfile_plugin_connection_from_file (const char *filename, GError **error) gsize length; int i; gboolean vpn_secrets = FALSE; - GError *verify_error = NULL; + KeyfileReaderInfo info = { 0 }; + gs_free char *base_dir_free = NULL; - if (stat (filename, &statbuf) != 0 || !S_ISREG (statbuf.st_mode)) { - g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, - "File did not exist or was not a regular file"); - return NULL; - } + g_return_val_if_fail (keyfile, NULL); + g_return_val_if_fail (!error || !*error, NULL); - bad_permissions = statbuf.st_mode & 0077; - - if (bad_permissions) { - g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, - "File permissions (%o) were insecure", - statbuf.st_mode); - return NULL; - } - - key_file = g_key_file_new (); - if (!g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, error)) - goto out; + if (!base_dir) { + /* basedir is not given. Prefer it from the keyfile_name */ + if (keyfile_name && keyfile_name[0] == '/') { + base_dir = base_dir_free = g_path_get_dirname (keyfile_name); + } else { + /* if keyfile is not given or not an absolute path, fallback + * to current working directory. */ + base_dir = base_dir_free = g_get_current_dir (); + } + } else + g_return_val_if_fail ("/", NULL); connection = nm_simple_connection_new (); - groups = g_key_file_get_groups (key_file, &length); + info.connection = connection; + info.keyfile = (GKeyFile *) keyfile; + info.base_dir = base_dir; + info.handler = handler; + info.user_data = user_data; + + groups = g_key_file_get_groups (keyfile, &length); for (i = 0; i < length; i++) { /* Only read out secrets when needed */ if (!strcmp (groups[i], VPN_SECRETS_GROUP)) { @@ -1303,10 +1450,15 @@ nm_keyfile_plugin_connection_from_file (const char *filename, GError **error) continue; } - setting = read_setting (key_file, filename, groups[i]); + info.group = groups[i]; + setting = read_setting (&info); + info.group = NULL; + if (info.error) + goto out_error; if (setting) nm_connection_add_setting (connection, setting); } + g_strfreev (groups); s_con = nm_connection_get_setting_connection (connection); if (!s_con) { @@ -1315,19 +1467,21 @@ nm_keyfile_plugin_connection_from_file (const char *filename, GError **error) } /* Make sure that we have 'id' even if not explictly specified in the keyfile */ - if (!nm_setting_connection_get_id (s_con)) { + if ( keyfile_name + && !nm_setting_connection_get_id (s_con)) { char *base_name; - base_name = g_path_get_basename (filename); + base_name = g_path_get_basename (keyfile_name); g_object_set (s_con, NM_SETTING_CONNECTION_ID, base_name, NULL); g_free (base_name); } /* Make sure that we have 'uuid' even if not explictly specified in the keyfile */ - if (!nm_setting_connection_get_uuid (s_con)) { + if ( keyfile_name + && !nm_setting_connection_get_uuid (s_con)) { char *hashed_uuid; - hashed_uuid = _nm_utils_uuid_generate_from_strings ("keyfile", filename, NULL); + hashed_uuid = _nm_utils_uuid_generate_from_strings ("keyfile", keyfile_name, NULL); g_object_set (s_con, NM_SETTING_CONNECTION_UUID, hashed_uuid, NULL); g_free (hashed_uuid); } @@ -1339,7 +1493,7 @@ nm_keyfile_plugin_connection_from_file (const char *filename, GError **error) && nm_setting_connection_get_connection_type (s_con)) { char *interface_name; - interface_name = g_key_file_get_string (key_file, + interface_name = g_key_file_get_string (keyfile, nm_setting_connection_get_connection_type (s_con), "interface-name", NULL); @@ -1354,23 +1508,16 @@ nm_keyfile_plugin_connection_from_file (const char *filename, GError **error) NMSettingVpn *s_vpn; s_vpn = nm_connection_get_setting_vpn (connection); - if (s_vpn) - read_vpn_secrets (key_file, s_vpn); + if (s_vpn) { + read_vpn_secrets (&info, s_vpn); + if (info.error) + goto out_error; + } } - g_strfreev (groups); - - /* Normalize and verify the connection */ - if (!nm_connection_normalize (connection, NULL, NULL, &verify_error)) { - g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, - "invalid connection: %s", - verify_error->message); - g_clear_error (&verify_error); - g_object_unref (connection); - connection = NULL; - } - -out: - g_key_file_free (key_file); return connection; +out_error: + g_propagate_error (error, info.error); + g_free (connection); + return NULL; } diff --git a/libnm-core/nm-keyfile-reader.h b/libnm-core/nm-keyfile-reader.h index 55819630eb..73752ff22b 100644 --- a/libnm-core/nm-keyfile-reader.h +++ b/libnm-core/nm-keyfile-reader.h @@ -16,15 +16,70 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Copyright (C) 2008 Novell, Inc. - * Copyright (C) 2008 Red Hat, Inc. + * Copyright (C) 2015 Red Hat, Inc. */ -#ifndef _KEYFILE_PLUGIN_READER_H -#define _KEYFILE_PLUGIN_READER_H +#ifndef __NM_KEYFILE_READER_H__ +#define __NM_KEYFILE_READER_H__ #include -#include -NMConnection *nm_keyfile_plugin_connection_from_file (const char *filename, GError **error); +#include "nm-connection.h" -#endif /* _KEYFILE_PLUGIN_READER_H */ + +typedef enum { + NM_KEYFILE_READ_TYPE_WARN = 1, +} NMKeyfileReadType; + +/** + * NMKeyfileReadHandler: + * + * Hook to nm_keyfile_read(). The user might fail the reading by setting + * @error. + * + * Returns: should return TRUE, if the reading was handled. Otherwise, + * a default action will be performed that depends on the @type. + * For %NM_KEYFILE_READ_TYPE_WARN type, the default action is doing nothing. + */ +typedef gboolean (*NMKeyfileReadHandler) (GKeyFile *keyfile, + NMConnection *connection, + NMKeyfileReadType type, + void *type_data, + void *user_data, + GError **error); + +typedef enum { + NM_KEYFILE_WARN_SEVERITY_DEBUG = 1000, + NM_KEYFILE_WARN_SEVERITY_INFO = 2000, + NM_KEYFILE_WARN_SEVERITY_WARN = 3000, +} NMKeyfileWarnSeverity; + +/** + * NMKeyfileReadTypeDataWarn: + * + * this struct is passed as @type_data for the @NMKeyfileReadHandler of + * type %NM_KEYFILE_READ_TYPE_WARN. + */ +typedef struct { + /* might be %NULL, if the warning is not about a group. */ + const char *group; + + /* might be %NULL, if the warning is not about a setting. */ + NMSetting *setting; + + /* might be %NULL, if the warning is not about a property. */ + const char *property_name; + + NMKeyfileWarnSeverity severity; + const char *message; +} NMKeyfileReadTypeDataWarn; + + +NMConnection *nm_keyfile_read (GKeyFile *keyfile, + const char *keyfile_name, + const char *base_dir, + NMKeyfileReadHandler handler, + void *user_data, + GError **error); + +#endif /* __NM_KEYFILE_READER_H__ */ diff --git a/libnm-core/nm-keyfile-utils.c b/libnm-core/nm-keyfile-utils.c index 30400b1bc4..e2198712bb 100644 --- a/libnm-core/nm-keyfile-utils.c +++ b/libnm-core/nm-keyfile-utils.c @@ -23,128 +23,11 @@ #include #include #include -#include "gsystem-local-alloc.h" -#include "utils.h" -#include -#include -#include - -static const char temp_letters[] = -"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - -/* - * Check '.[a-zA-Z0-9]{6}' file suffix used for temporary files by g_file_set_contents() (mkstemp()). - */ -static gboolean -check_mkstemp_suffix (const char *path) -{ - const char *ptr; - - g_return_val_if_fail (path != NULL, FALSE); - - /* Matches *.[a-zA-Z0-9]{6} suffix of mkstemp()'s temporary files */ - ptr = strrchr (path, '.'); - if (ptr && (strspn (ptr + 1, temp_letters) == 6) && (! ptr[7])) - return TRUE; - return FALSE; -} - -static gboolean -check_prefix (const char *base, const char *tag) -{ - int len, tag_len; - - g_return_val_if_fail (base != NULL, TRUE); - g_return_val_if_fail (tag != NULL, TRUE); - - len = strlen (base); - tag_len = strlen (tag); - if ((len > tag_len) && !g_ascii_strncasecmp (base, tag, tag_len)) - return TRUE; - return FALSE; -} - -static gboolean -check_suffix (const char *base, const char *tag) -{ - int len, tag_len; - - g_return_val_if_fail (base != NULL, TRUE); - g_return_val_if_fail (tag != NULL, TRUE); - - len = strlen (base); - tag_len = strlen (tag); - if ((len > tag_len) && !g_ascii_strcasecmp (base + len - tag_len, tag)) - return TRUE; - return FALSE; -} - -#define SWP_TAG ".swp" -#define SWPX_TAG ".swpx" -#define PEM_TAG ".pem" -#define DER_TAG ".der" - -gboolean -nm_keyfile_plugin_utils_should_ignore_file (const char *filename) -{ - gs_free char *base = NULL; - - g_return_val_if_fail (filename != NULL, TRUE); - - base = g_path_get_basename (filename); - g_return_val_if_fail (base != NULL, TRUE); - - /* Ignore hidden and backup files */ - /* should_ignore_file() must mirror escape_filename() */ - if (check_prefix (base, ".") || check_suffix (base, "~")) - return TRUE; - /* Ignore temporary files */ - if (check_mkstemp_suffix (base)) - return TRUE; - /* Ignore 802.1x certificates and keys */ - if (check_suffix (base, PEM_TAG) || check_suffix (base, DER_TAG)) - return TRUE; - - return FALSE; -} - -char * -nm_keyfile_plugin_utils_escape_filename (const char *filename) -{ - GString *str; - const char *f = filename; - const char ESCAPE_CHAR = '*'; - - /* keyfile used to escape with '*', do not change that behavior. - * But for newly added escapings, use '_' instead. */ - const char ESCAPE_CHAR2 = '_'; - - g_return_val_if_fail (filename && filename[0], NULL); - - str = g_string_sized_new (60); - - /* Convert '/' to ESCAPE_CHAR */ - for (f = filename; f[0]; f++) { - if (f[0] == '/') - g_string_append_c (str, ESCAPE_CHAR); - else - g_string_append_c (str, f[0]); - } - - /* escape_filename() must avoid anything that should_ignore_file() would reject. - * We can escape here more aggressivly then what we would read back. */ - if (check_prefix (str->str, ".")) - str->str[0] = ESCAPE_CHAR2; - if (check_suffix (str->str, "~")) - str->str[str->len - 1] = ESCAPE_CHAR2; - if ( check_mkstemp_suffix (str->str) - || check_suffix (str->str, PEM_TAG) - || check_suffix (str->str, DER_TAG)) - g_string_append_c (str, ESCAPE_CHAR2); - - return g_string_free (str, FALSE);; -} +#include "nm-keyfile-utils.h" +#include "nm-setting-wired.h" +#include "nm-setting-wireless.h" +#include "nm-setting-wireless-security.h" typedef struct { diff --git a/libnm-core/nm-keyfile-utils.h b/libnm-core/nm-keyfile-utils.h index 456cd1a88f..fd4334d112 100644 --- a/libnm-core/nm-keyfile-utils.h +++ b/libnm-core/nm-keyfile-utils.h @@ -15,25 +15,15 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * - * (C) Copyright 2010 Red Hat, Inc. + * (C) Copyright 2010-2015 Red Hat, Inc. */ -#ifndef _UTILS_H_ -#define _UTILS_H_ +#ifndef __NM_KEYFILE_UTILS_H__ +#define __NM_KEYFILE_UTILS_H__ #include -#include "common.h" -#include "NetworkManagerUtils.h" -#define NM_KEYFILE_CONNECTION_LOG_PATH(path) str_if_set (path,"in-memory") -#define NM_KEYFILE_CONNECTION_LOG_FMT "%s (%s,\"%s\")" -#define NM_KEYFILE_CONNECTION_LOG_ARG(con) NM_KEYFILE_CONNECTION_LOG_PATH (nm_settings_connection_get_filename ((NMSettingsConnection *) (con))), nm_connection_get_uuid ((NMConnection *) (con)), nm_connection_get_id ((NMConnection *) (con)) -#define NM_KEYFILE_CONNECTION_LOG_FMTD "%s (%s,\"%s\",%p)" -#define NM_KEYFILE_CONNECTION_LOG_ARGD(con) NM_KEYFILE_CONNECTION_LOG_PATH (nm_settings_connection_get_filename ((NMSettingsConnection *) (con))), nm_connection_get_uuid ((NMConnection *) (con)), nm_connection_get_id ((NMConnection *) (con)), (con) - -gboolean nm_keyfile_plugin_utils_should_ignore_file (const char *filename); - -char *nm_keyfile_plugin_utils_escape_filename (const char *filename); +#define VPN_SECRETS_GROUP "vpn-secrets" const char *nm_keyfile_plugin_get_alias_for_setting_name (const char *setting_name); @@ -85,5 +75,5 @@ gboolean nm_keyfile_plugin_kf_has_key (GKeyFile *kf, const char *key, GError **error); -#endif /* _UTILS_H_ */ +#endif /* __NM_KEYFILE_UTILS_H__ */ diff --git a/libnm-core/nm-keyfile-writer.c b/libnm-core/nm-keyfile-writer.c index a707ac3287..ec934fafdb 100644 --- a/libnm-core/nm-keyfile-writer.c +++ b/libnm-core/nm-keyfile-writer.c @@ -16,7 +16,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Copyright (C) 2008 Novell, Inc. - * Copyright (C) 2008 - 2012 Red Hat, Inc. + * Copyright (C) 2008 - 2015 Red Hat, Inc. */ #include "config.h" @@ -26,26 +26,34 @@ #include #include #include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include + +#include "nm-setting.h" +#include "nm-setting-connection.h" +#include "nm-setting-ip4-config.h" +#include "nm-setting-ip6-config.h" +#include "nm-setting-vpn.h" +#include "nm-setting-wired.h" +#include "nm-setting-wireless.h" +#include "nm-setting-ip4-config.h" +#include "nm-setting-bluetooth.h" +#include "nm-setting-8021x.h" +#include "nm-utils.h" #include "nm-glib-compat.h" -#include "nm-logging.h" -#include "writer.h" -#include "common.h" -#include "utils.h" +#include "nm-keyfile-writer.h" +#include "nm-keyfile-utils.h" + +typedef struct { + NMConnection *connection; + GKeyFile *keyfile; + GError *error; + NMKeyfileWriteHandler handler; + void *user_data; +} KeyfileWriterInfo; + /* Some setting properties also contain setting names, such as * NMSettingConnection's 'type' property (which specifies the base type of the @@ -55,9 +63,7 @@ * from the real setting name to the more-readable alias. */ static void -setting_alias_writer (GKeyFile *file, - const char *keyfile_dir, - const char *uuid, +setting_alias_writer (KeyfileWriterInfo *info, NMSetting *setting, const char *key, const GValue *value) @@ -66,13 +72,13 @@ setting_alias_writer (GKeyFile *file, str = g_value_get_string (value); alias = nm_keyfile_plugin_get_alias_for_setting_name (str); - nm_keyfile_plugin_kf_set_string (file, + nm_keyfile_plugin_kf_set_string (info->keyfile, nm_setting_get_name (setting), key, alias ? alias : str); } -static gboolean +static void write_array_of_uint (GKeyFile *file, NMSetting *setting, const char *key, @@ -84,7 +90,7 @@ write_array_of_uint (GKeyFile *file, array = (GArray *) g_value_get_boxed (value); if (!array || !array->len) - return TRUE; + return; tmp_array = g_new (gint, array->len); for (i = 0; i < array->len; i++) @@ -92,13 +98,10 @@ write_array_of_uint (GKeyFile *file, nm_keyfile_plugin_kf_set_integer_list (file, nm_setting_get_name (setting), key, tmp_array, array->len); g_free (tmp_array); - return TRUE; } static void -dns_writer (GKeyFile *file, - const char *keyfile_dir, - const char *uuid, +dns_writer (KeyfileWriterInfo *info, NMSetting *setting, const char *key, const GValue *value) @@ -107,7 +110,7 @@ dns_writer (GKeyFile *file, list = g_value_get_boxed (value); if (list && list[0]) { - nm_keyfile_plugin_kf_set_string_list (file, nm_setting_get_name (setting), key, + nm_keyfile_plugin_kf_set_string_list (info->keyfile, nm_setting_get_name (setting), key, (const char **) list, g_strv_length (list)); } } @@ -178,9 +181,7 @@ write_ip_values (GKeyFile *file, } static void -addr_writer (GKeyFile *file, - const char *keyfile_dir, - const char *uuid, +addr_writer (KeyfileWriterInfo *info, NMSetting *setting, const char *key, const GValue *value) @@ -191,13 +192,11 @@ addr_writer (GKeyFile *file, array = (GPtrArray *) g_value_get_boxed (value); if (array && array->len) - write_ip_values (file, setting_name, array, gateway, FALSE); + write_ip_values (info->keyfile, setting_name, array, gateway, FALSE); } static void -ip4_addr_label_writer (GKeyFile *file, - const char *keyfile_dir, - const char *uuid, +ip4_addr_label_writer (KeyfileWriterInfo *info, NMSetting *setting, const char *key, const GValue *value) @@ -206,9 +205,7 @@ ip4_addr_label_writer (GKeyFile *file, } static void -gateway_writer (GKeyFile *file, - const char *keyfile_dir, - const char *uuid, +gateway_writer (KeyfileWriterInfo *info, NMSetting *setting, const char *key, const GValue *value) @@ -217,9 +214,7 @@ gateway_writer (GKeyFile *file, } static void -route_writer (GKeyFile *file, - const char *keyfile_dir, - const char *uuid, +route_writer (KeyfileWriterInfo *info, NMSetting *setting, const char *key, const GValue *value) @@ -229,7 +224,7 @@ route_writer (GKeyFile *file, array = (GPtrArray *) g_value_get_boxed (value); if (array && array->len) - write_ip_values (file, setting_name, array, NULL, TRUE); + write_ip_values (info->keyfile, setting_name, array, NULL, TRUE); } static void @@ -271,9 +266,7 @@ write_hash_of_string (GKeyFile *file, } static void -ssid_writer (GKeyFile *file, - const char *keyfile_dir, - const char *uuid, +ssid_writer (KeyfileWriterInfo *info, NMSetting *setting, const char *key, const GValue *value) @@ -323,21 +316,19 @@ ssid_writer (GKeyFile *file, ssid[j++] = ssid_data[i]; } } - nm_keyfile_plugin_kf_set_string (file, setting_name, key, ssid); + nm_keyfile_plugin_kf_set_string (info->keyfile, setting_name, key, ssid); g_free (ssid); } else { tmp_array = g_new (gint, ssid_len); for (i = 0; i < ssid_len; i++) tmp_array[i] = (int) ssid_data[i]; - nm_keyfile_plugin_kf_set_integer_list (file, setting_name, key, tmp_array, ssid_len); + nm_keyfile_plugin_kf_set_integer_list (info->keyfile, setting_name, key, tmp_array, ssid_len); g_free (tmp_array); } } static void -password_raw_writer (GKeyFile *file, - const char *keyfile_dir, - const char *uuid, +password_raw_writer (KeyfileWriterInfo *info, NMSetting *setting, const char *key, const GValue *value) @@ -360,7 +351,7 @@ password_raw_writer (GKeyFile *file, tmp_array = g_new (gint, len); for (i = 0; i < len; i++) tmp_array[i] = (int) data[i]; - nm_keyfile_plugin_kf_set_integer_list (file, setting_name, key, tmp_array, len); + nm_keyfile_plugin_kf_set_integer_list (info->keyfile, setting_name, key, tmp_array, len); g_free (tmp_array); } @@ -419,83 +410,15 @@ static const ObjectType objtypes[10] = { { NULL }, }; -static gboolean -write_cert_key_file (const char *path, - const guint8 *data, - gsize data_len, - GError **error) -{ - char *tmppath; - int fd = -1, written; - gboolean success = FALSE; - - tmppath = g_malloc0 (strlen (path) + 10); - g_assert (tmppath); - memcpy (tmppath, path, strlen (path)); - strcat (tmppath, ".XXXXXX"); - - errno = 0; - fd = mkstemp (tmppath); - if (fd < 0) { - g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, - "Could not create temporary file for '%s': %d", - path, errno); - goto out; - } - - /* Only readable by root */ - errno = 0; - if (fchmod (fd, S_IRUSR | S_IWUSR) != 0) { - close (fd); - unlink (tmppath); - g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, - "Could not set permissions for temporary file '%s': %d", - path, errno); - goto out; - } - - errno = 0; - written = write (fd, data, data_len); - if (written != data_len) { - close (fd); - unlink (tmppath); - g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, - "Could not write temporary file for '%s': %d", - path, errno); - goto out; - } - close (fd); - - /* Try to rename */ - errno = 0; - if (rename (tmppath, path) == 0) - success = TRUE; - else { - unlink (tmppath); - g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, - "Could not rename temporary file to '%s': %d", - path, errno); - } - -out: - g_free (tmppath); - return success; -} - static void -cert_writer (GKeyFile *file, - const char *keyfile_dir, - const char *uuid, +cert_writer (KeyfileWriterInfo *info, NMSetting *setting, const char *key, const GValue *value) { - const char *setting_name = nm_setting_get_name (setting); - NMSetting8021xCKScheme scheme; - NMSetting8021xCKFormat format; - const char *path = NULL, *ext = "pem"; const ObjectType *objtype = NULL; - int i; + guint i; + NMKeyfileWriteTypeDataCert type_data = { 0 }; for (i = 0; i < G_N_ELEMENTS (objtypes) && objtypes[i].key; i++) { if (g_strcmp0 (objtypes[i].key, key) == 0) { @@ -503,82 +426,48 @@ cert_writer (GKeyFile *file, break; } } - if (!objtype) { - g_return_if_fail (objtype); - return; - } - - scheme = objtype->scheme_func (NM_SETTING_802_1X (setting)); - if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) { - path = objtype->path_func (NM_SETTING_802_1X (setting)); - g_assert (path); - - /* If the path is rooted in the keyfile directory, just use a - * relative path instead of an absolute one. - */ - if (g_str_has_prefix (path, keyfile_dir)) { - path += strlen (keyfile_dir); - while (*path == '/') - path++; - } - - nm_keyfile_plugin_kf_set_string (file, setting_name, key, path); - } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) { - GBytes *blob; - const guint8 *blob_data; - gsize blob_len; - gboolean success; - GError *error = NULL; - char *new_path; - - blob = objtype->blob_func (NM_SETTING_802_1X (setting)); - g_assert (blob); - blob_data = g_bytes_get_data (blob, &blob_len); - - if (objtype->format_func) { - /* Get the extension for a private key */ - format = objtype->format_func (NM_SETTING_802_1X (setting)); - if (format == NM_SETTING_802_1X_CK_FORMAT_PKCS12) - ext = "p12"; - } else { - /* DER or PEM format certificate? */ - if (blob_len > 2 && blob_data[0] == 0x30 && blob_data[1] == 0x82) - ext = "der"; - } - - /* Write the raw data out to the standard file so that we can use paths - * from now on instead of pushing around the certificate data. - */ - new_path = g_strdup_printf ("%s/%s-%s.%s", keyfile_dir, uuid, objtype->suffix, ext); - g_assert (new_path); - - success = write_cert_key_file (new_path, blob_data, blob_len, &error); - if (success) { - /* Write the path value to the keyfile */ - nm_keyfile_plugin_kf_set_string (file, setting_name, key, new_path); - } else { - nm_log_warn (LOGD_SETTINGS, "Failed to write certificate/key %s: %s", - new_path, error->message); - g_error_free (error); - } - g_free (new_path); - } else { - /* scheme_func() returns UNKNOWN in all other cases. The only valid case - * where a scheme is allowed to be UNKNOWN, is unsetting the value. In this - * case, we don't expect the writer to be called, because the default value - * will not be serialized. - * The only other reason for the scheme to be UNKNOWN is an invalid cert. - * But our connection verifies, so that cannot happen either. */ + if (!objtype) g_return_if_reached (); + + if (!info->handler) + goto out_unhandled; + + type_data.setting = NM_SETTING_802_1X (setting); + type_data.property_name = key; + type_data.suffix = objtype->suffix; + type_data.scheme_func = objtype->scheme_func; + type_data.format_func = objtype->format_func; + type_data.path_func = objtype->path_func; + type_data.blob_func = objtype->blob_func; + + if (info->handler (info->connection, + info->keyfile, + NM_KEYFILE_WRITE_TYPE_CERT, + &type_data, + info->user_data, + &info->error)) + return; + +out_unhandled: + + /* scheme_func() would not return UNKNOWN, because UNKNOWN happens only + * if the cert is unset (1) or if the cert is invalid (2). + * (1) cannot happen, because we only reach cert_writer() for non-default + * properties. (2) cannot happen, because we verified the connection. + * + * Hence, at this point we do have a certifiacte, but no default implementation + * to write it. The handler *must* do something with these certifications. */ + if (!info->error) { + g_set_error (&info->error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, + _("Failed to write unhandled certificate property %s.%s"), + nm_setting_get_name (setting), key); } } typedef struct { const char *setting_name; const char *key; - void (*writer) (GKeyFile *keyfile, - const char *keyfile_dir, - const char *uuid, + void (*writer) (KeyfileWriterInfo *info, NMSetting *setting, const char *key, const GValue *value); @@ -648,12 +537,6 @@ static KeyWriter key_writers[] = { { NULL, NULL, NULL } }; -typedef struct { - GKeyFile *keyfile; - const char *keyfile_dir; - const char *uuid; -} WriteInfo; - static void write_setting_value (NMSetting *setting, const char *key, @@ -661,12 +544,15 @@ write_setting_value (NMSetting *setting, GParamFlags flag, gpointer user_data) { - WriteInfo *info = user_data; + KeyfileWriterInfo *info = user_data; const char *setting_name; GType type = G_VALUE_TYPE (value); KeyWriter *writer = &key_writers[0]; GParamSpec *pspec; + if (info->error) + return; + /* Setting name gets picked up from the keyfile's section name instead */ if (!strcmp (key, NM_SETTING_NAME)) return; @@ -704,7 +590,7 @@ write_setting_value (NMSetting *setting, /* Look through the list of handlers for non-standard format key values */ while (writer->setting_name) { if (!strcmp (writer->setting_name, setting_name) && !strcmp (writer->key, key)) { - (*writer->writer) (info->keyfile, info->keyfile_dir, info->uuid, setting, key, value); + (*writer->writer) (info, setting, key, value); return; } writer++; @@ -763,184 +649,42 @@ write_setting_value (NMSetting *setting, } else if (type == G_TYPE_HASH_TABLE) { write_hash_of_string (info->keyfile, setting, key, value); } else if (type == G_TYPE_ARRAY) { - if (!write_array_of_uint (info->keyfile, setting, key, value)) { - nm_log_warn (LOGD_SETTINGS, "Unhandled setting property type (write) '%s/%s' : '%s'", - setting_name, key, g_type_name (type)); - } + write_array_of_uint (info->keyfile, setting, key, value); } else if (G_VALUE_HOLDS_FLAGS (value)) { /* Flags are guint but GKeyFile has no uint reader, just uint64 */ nm_keyfile_plugin_kf_set_uint64 (info->keyfile, setting_name, key, (guint64) g_value_get_flags (value)); } else if (G_VALUE_HOLDS_ENUM (value)) nm_keyfile_plugin_kf_set_integer (info->keyfile, setting_name, key, (gint) g_value_get_enum (value)); - else { - nm_log_warn (LOGD_SETTINGS, "Unhandled setting property type (write) '%s/%s' : '%s'", - setting_name, key, g_type_name (type)); - } + else + g_warn_if_reached (); } -static gboolean -_internal_write_connection (NMConnection *connection, - const char *keyfile_dir, - uid_t owner_uid, - pid_t owner_grp, - const char *existing_path, - char **out_path, - GError **error) +GKeyFile * +nm_keyfile_write (NMConnection *connection, + NMKeyfileWriteHandler handler, + void *user_data, + GError **error) { - GKeyFile *key_file; - char *data; - gsize len; - gboolean success = FALSE; - char *path; - const char *id; - WriteInfo info; - GError *local_err = NULL; + KeyfileWriterInfo info = { 0 }; - g_return_val_if_fail (!out_path || !*out_path, FALSE); + g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL); + g_return_val_if_fail (!error || !*error, NULL); if (!nm_connection_verify (connection, error)) - g_return_val_if_reached (FALSE); + return NULL; - id = nm_connection_get_id (connection); - g_assert (id && *id); - - info.keyfile = key_file = g_key_file_new (); - info.keyfile_dir = keyfile_dir; - info.uuid = nm_connection_get_uuid (connection); - g_assert (info.uuid); + info.connection = connection; + info.keyfile = g_key_file_new (); + info.error = NULL; + info.handler = handler; + info.user_data = user_data; nm_connection_for_each_setting_value (connection, write_setting_value, &info); - data = g_key_file_to_data (key_file, &len, error); - if (!data) - goto out; - /* If we have existing file path, use it. Else generate one from - * connection's ID. - */ - if (existing_path != NULL) { - path = g_strdup (existing_path); - } else { - char *filename_escaped = nm_keyfile_plugin_utils_escape_filename (id); - - path = g_build_filename (keyfile_dir, filename_escaped, NULL); - g_free (filename_escaped); + if (info.error) { + g_propagate_error (error, info.error); + g_key_file_unref (info.keyfile); + return NULL; } - - /* If a file with this path already exists (but isn't the existing path - * of the connection) then we need another name. Multiple connections - * can have the same ID (ie if two connections with the same ID are visible - * to different users) but of course can't have the same path. Yeah, - * there's a race here, but there's not a lot we can do about it, and - * we shouldn't get more than one connection with the same UUID either. - */ - if (g_strcmp0 (path, existing_path) != 0 && g_file_test (path, G_FILE_TEST_EXISTS)) { - guint i; - gboolean name_found = FALSE; - - /* A keyfile with this connection's ID already exists. Pick another name. */ - for (i = 0; i < 100; i++) { - char *filename, *filename_escaped; - - if (i == 0) - filename = g_strdup_printf ("%s-%s", id, nm_connection_get_uuid (connection)); - else - filename = g_strdup_printf ("%s-%s-%u", id, nm_connection_get_uuid (connection), i); - - filename_escaped = nm_keyfile_plugin_utils_escape_filename (filename); - - g_free (path); - path = g_strdup_printf ("%s/%s", keyfile_dir, filename_escaped); - g_free (filename); - g_free (filename_escaped); - if (g_strcmp0 (path, existing_path) == 0 || !g_file_test (path, G_FILE_TEST_EXISTS)) { - name_found = TRUE; - break; - } - } - if (!name_found) { - if (existing_path == NULL) { - /* this really should not happen, we tried hard to find an unused name... bail out. */ - g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, - "could not find suitable keyfile file name (%s already used)", path); - g_free (path); - goto out; - } - /* Both our preferred path based on connection id and id-uuid are taken. - * Fallback to @existing_path */ - g_free (path); - path = g_strdup (existing_path); - } - } - - /* In case of updating the connection and changing the file path, - * we need to remove the old one, not to end up with two connections. - */ - if (existing_path != NULL && strcmp (path, existing_path) != 0) - unlink (existing_path); - - g_file_set_contents (path, data, len, &local_err); - if (local_err) { - g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, - "%s.%d: error writing to file '%s': %s", __FILE__, __LINE__, - path, local_err->message); - g_error_free (local_err); - g_free (path); - goto out; - } - - if (chown (path, owner_uid, owner_grp) < 0) { - g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, - "%s.%d: error chowning '%s': %d", __FILE__, __LINE__, - path, errno); - unlink (path); - } else { - if (chmod (path, S_IRUSR | S_IWUSR) < 0) { - g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, - "%s.%d: error setting permissions on '%s': %d", __FILE__, - __LINE__, path, errno); - unlink (path); - } else { - if (out_path && g_strcmp0 (existing_path, path)) { - *out_path = path; /* pass path out to caller */ - path = NULL; - } - success = TRUE; - } - } - g_free (path); - -out: - g_free (data); - g_key_file_free (key_file); - return success; -} - -gboolean -nm_keyfile_plugin_write_connection (NMConnection *connection, - const char *existing_path, - char **out_path, - GError **error) -{ - return _internal_write_connection (connection, - KEYFILE_DIR, - 0, 0, - existing_path, - out_path, - error); -} - -gboolean -nm_keyfile_plugin_write_test_connection (NMConnection *connection, - const char *keyfile_dir, - uid_t owner_uid, - pid_t owner_grp, - char **out_path, - GError **error) -{ - return _internal_write_connection (connection, - keyfile_dir, - owner_uid, owner_grp, - NULL, - out_path, - error); + return info.keyfile; } diff --git a/libnm-core/nm-keyfile-writer.h b/libnm-core/nm-keyfile-writer.h index a602f2f4a3..891778a350 100644 --- a/libnm-core/nm-keyfile-writer.h +++ b/libnm-core/nm-keyfile-writer.h @@ -19,23 +19,73 @@ * Copyright (C) 2008 - 2011 Red Hat, Inc. */ -#ifndef _KEYFILE_PLUGIN_WRITER_H -#define _KEYFILE_PLUGIN_WRITER_H +#ifndef __NM_KEYFILE_WRITER_H__ +#define __NM_KEYFILE_WRITER_H__ #include #include -#include -gboolean nm_keyfile_plugin_write_connection (NMConnection *connection, - const char *existing_path, - char **out_path, - GError **error); +#include "nm-connection.h" +#include "nm-setting-8021x.h" -gboolean nm_keyfile_plugin_write_test_connection (NMConnection *connection, - const char *keyfile_dir, - uid_t owner_uid, - pid_t owner_grp, - char **out_path, - GError **error); -#endif /* _KEYFILE_PLUGIN_WRITER_H */ +typedef enum { + NM_KEYFILE_WRITE_TYPE_CERT = 1, +} NMKeyfileWriteType; + +/** + * NMKeyfileWriteHandler: + * + * This is a hook to tweak the serialization. + * + * Handler for certain properties or events that are not entirely contained + * within the keyfile or that might be serialized differently. The @type and + * @type_data arguments tell which kind of argument we have at hand. + * + * Currently only the type %NM_KEYFILE_WRITE_TYPE_CERT is supported, which provides + * @type_data as %NMKeyfileWriteTypeDataCert. However, this handler should be generic enough + * to support other types as well. + * + * This don't have to be only "properties". For example, nm_keyfile_read() uses + * a similar handler to push warnings to the caller. + * + * If the handler raises an error, it should set the @error value. This causes + * the an overall failure. + * + * Returns: whether the issue was handled. If the type was unhandled, + * a default action will be performed. This might be raise an error, + * do some fallback parsing, or do nothing. + */ +typedef gboolean (*NMKeyfileWriteHandler) (NMConnection *connection, + GKeyFile *keyfile, + NMKeyfileWriteType type, + void *type_data, + void *user_data, + GError **error); + +/** + * NMKeyfileWriteTypeDataCert: + * + * this struct is passed as @type_data for the @NMKeyfileWriteHandler of + * type %NM_KEYFILE_WRITE_TYPE_CERT. + */ +typedef struct { + NMSetting8021x *setting; + const char *property_name; + + /* The following functions are helpers that simplify the implementation + * of the handler. */ + const char *suffix; + NMSetting8021xCKScheme (*scheme_func) (NMSetting8021x *setting); + NMSetting8021xCKFormat (*format_func) (NMSetting8021x *setting); + const char * (*path_func) (NMSetting8021x *setting); + GBytes * (*blob_func) (NMSetting8021x *setting); +} NMKeyfileWriteTypeDataCert; + + +GKeyFile *nm_keyfile_write (NMConnection *connection, + NMKeyfileWriteHandler handler, + void *user_data, + GError **error); + +#endif /* __NM_KEYFILE_WRITER_H__ */ diff --git a/po/POTFILES.in b/po/POTFILES.in index 926dd6c39e..6c09b21ff0 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -48,6 +48,8 @@ libnm-core/crypto.c libnm-core/crypto_gnutls.c libnm-core/crypto_nss.c libnm-core/nm-connection.c +libnm-core/nm-keyfile-reader.c +libnm-core/nm-keyfile-writer.c libnm-core/nm-setting-8021x.c libnm-core/nm-setting-adsl.c libnm-core/nm-setting-bluetooth.c From 5e5afcffce1ab8ee189721678b1760f329f83cce Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 23 Feb 2015 11:41:22 +0100 Subject: [PATCH 19/23] libnm: merge nm-keyfile-reader.h and nm-keyfile-writer.h to internal header These headers are not entirely private to libnm-core as they are also used by keyfile plugin. Merge them to a new header file nm-keyfile-internal.h so that the name makes the internal nature of the header more apparent. --- libnm-core/Makefile.libnm-core | 3 +- ...keyfile-writer.h => nm-keyfile-internal.h} | 69 +++++++++++++-- libnm-core/nm-keyfile-reader.c | 2 +- libnm-core/nm-keyfile-reader.h | 85 ------------------- libnm-core/nm-keyfile-writer.c | 2 +- 5 files changed, 67 insertions(+), 94 deletions(-) rename libnm-core/{nm-keyfile-writer.h => nm-keyfile-internal.h} (60%) delete mode 100644 libnm-core/nm-keyfile-reader.h diff --git a/libnm-core/Makefile.libnm-core b/libnm-core/Makefile.libnm-core index 798714600e..09a80c714b 100644 --- a/libnm-core/Makefile.libnm-core +++ b/libnm-core/Makefile.libnm-core @@ -48,9 +48,8 @@ libnm_core_private_headers = \ $(core)/crypto.h \ $(core)/nm-connection-private.h \ $(core)/nm-core-internal.h \ - $(core)/nm-keyfile-reader.h \ + $(core)/nm-keyfile-internal.h \ $(core)/nm-keyfile-utils.h \ - $(core)/nm-keyfile-writer.h \ $(core)/nm-property-compare.h \ $(core)/nm-setting-private.h \ $(core)/nm-utils-private.h diff --git a/libnm-core/nm-keyfile-writer.h b/libnm-core/nm-keyfile-internal.h similarity index 60% rename from libnm-core/nm-keyfile-writer.h rename to libnm-core/nm-keyfile-internal.h index 891778a350..5916a6bffc 100644 --- a/libnm-core/nm-keyfile-writer.h +++ b/libnm-core/nm-keyfile-internal.h @@ -16,19 +16,78 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Copyright (C) 2008 Novell, Inc. - * Copyright (C) 2008 - 2011 Red Hat, Inc. + * Copyright (C) 2015 Red Hat, Inc. */ -#ifndef __NM_KEYFILE_WRITER_H__ -#define __NM_KEYFILE_WRITER_H__ +#ifndef __NM_KEYFILE_INTERNAL_H__ +#define __NM_KEYFILE_INTERNAL_H__ -#include #include +#include #include "nm-connection.h" #include "nm-setting-8021x.h" +/*********************************************************/ + +typedef enum { + NM_KEYFILE_READ_TYPE_WARN = 1, +} NMKeyfileReadType; + +/** + * NMKeyfileReadHandler: + * + * Hook to nm_keyfile_read(). The user might fail the reading by setting + * @error. + * + * Returns: should return TRUE, if the reading was handled. Otherwise, + * a default action will be performed that depends on the @type. + * For %NM_KEYFILE_READ_TYPE_WARN type, the default action is doing nothing. + */ +typedef gboolean (*NMKeyfileReadHandler) (GKeyFile *keyfile, + NMConnection *connection, + NMKeyfileReadType type, + void *type_data, + void *user_data, + GError **error); + +typedef enum { + NM_KEYFILE_WARN_SEVERITY_DEBUG = 1000, + NM_KEYFILE_WARN_SEVERITY_INFO = 2000, + NM_KEYFILE_WARN_SEVERITY_WARN = 3000, +} NMKeyfileWarnSeverity; + +/** + * NMKeyfileReadTypeDataWarn: + * + * this struct is passed as @type_data for the @NMKeyfileReadHandler of + * type %NM_KEYFILE_READ_TYPE_WARN. + */ +typedef struct { + /* might be %NULL, if the warning is not about a group. */ + const char *group; + + /* might be %NULL, if the warning is not about a setting. */ + NMSetting *setting; + + /* might be %NULL, if the warning is not about a property. */ + const char *property_name; + + NMKeyfileWarnSeverity severity; + const char *message; +} NMKeyfileReadTypeDataWarn; + + +NMConnection *nm_keyfile_read (GKeyFile *keyfile, + const char *keyfile_name, + const char *base_dir, + NMKeyfileReadHandler handler, + void *user_data, + GError **error); + +/*********************************************************/ + typedef enum { NM_KEYFILE_WRITE_TYPE_CERT = 1, } NMKeyfileWriteType; @@ -88,4 +147,4 @@ GKeyFile *nm_keyfile_write (NMConnection *connection, void *user_data, GError **error); -#endif /* __NM_KEYFILE_WRITER_H__ */ +#endif /* __NM_KEYFILE_INTERNAL_H__ */ diff --git a/libnm-core/nm-keyfile-reader.c b/libnm-core/nm-keyfile-reader.c index 9da3e1eca8..264acc8ba1 100644 --- a/libnm-core/nm-keyfile-reader.c +++ b/libnm-core/nm-keyfile-reader.c @@ -33,7 +33,7 @@ #include "nm-core-internal.h" #include "gsystem-local-alloc.h" #include "nm-glib-compat.h" -#include "nm-keyfile-reader.h" +#include "nm-keyfile-internal.h" #include "nm-keyfile-utils.h" diff --git a/libnm-core/nm-keyfile-reader.h b/libnm-core/nm-keyfile-reader.h deleted file mode 100644 index 73752ff22b..0000000000 --- a/libnm-core/nm-keyfile-reader.h +++ /dev/null @@ -1,85 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ -/* NetworkManager system settings service - keyfile plugin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Copyright (C) 2008 Novell, Inc. - * Copyright (C) 2015 Red Hat, Inc. - */ - -#ifndef __NM_KEYFILE_READER_H__ -#define __NM_KEYFILE_READER_H__ - -#include - -#include "nm-connection.h" - - -typedef enum { - NM_KEYFILE_READ_TYPE_WARN = 1, -} NMKeyfileReadType; - -/** - * NMKeyfileReadHandler: - * - * Hook to nm_keyfile_read(). The user might fail the reading by setting - * @error. - * - * Returns: should return TRUE, if the reading was handled. Otherwise, - * a default action will be performed that depends on the @type. - * For %NM_KEYFILE_READ_TYPE_WARN type, the default action is doing nothing. - */ -typedef gboolean (*NMKeyfileReadHandler) (GKeyFile *keyfile, - NMConnection *connection, - NMKeyfileReadType type, - void *type_data, - void *user_data, - GError **error); - -typedef enum { - NM_KEYFILE_WARN_SEVERITY_DEBUG = 1000, - NM_KEYFILE_WARN_SEVERITY_INFO = 2000, - NM_KEYFILE_WARN_SEVERITY_WARN = 3000, -} NMKeyfileWarnSeverity; - -/** - * NMKeyfileReadTypeDataWarn: - * - * this struct is passed as @type_data for the @NMKeyfileReadHandler of - * type %NM_KEYFILE_READ_TYPE_WARN. - */ -typedef struct { - /* might be %NULL, if the warning is not about a group. */ - const char *group; - - /* might be %NULL, if the warning is not about a setting. */ - NMSetting *setting; - - /* might be %NULL, if the warning is not about a property. */ - const char *property_name; - - NMKeyfileWarnSeverity severity; - const char *message; -} NMKeyfileReadTypeDataWarn; - - -NMConnection *nm_keyfile_read (GKeyFile *keyfile, - const char *keyfile_name, - const char *base_dir, - NMKeyfileReadHandler handler, - void *user_data, - GError **error); - -#endif /* __NM_KEYFILE_READER_H__ */ diff --git a/libnm-core/nm-keyfile-writer.c b/libnm-core/nm-keyfile-writer.c index ec934fafdb..f5b4ef234f 100644 --- a/libnm-core/nm-keyfile-writer.c +++ b/libnm-core/nm-keyfile-writer.c @@ -43,7 +43,7 @@ #include "nm-utils.h" #include "nm-glib-compat.h" -#include "nm-keyfile-writer.h" +#include "nm-keyfile-internal.h" #include "nm-keyfile-utils.h" typedef struct { From 57a432fa8a225dc5849251467d24c9927d210f37 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 23 Feb 2015 11:56:04 +0100 Subject: [PATCH 20/23] keyfile: refactor to use reading and writing of keyfile from libnm-core --- libnm-core/nm-keyfile-internal.h | 6 + libnm-core/nm-keyfile-utils.c | 1 + src/settings/plugins/keyfile/common.h | 2 - src/settings/plugins/keyfile/reader.c | 1340 +---------------- .../plugins/keyfile/tests/test-keyfile.c | 34 +- src/settings/plugins/keyfile/utils.c | 175 --- src/settings/plugins/keyfile/utils.h | 51 - src/settings/plugins/keyfile/writer.c | 686 +-------- src/settings/plugins/keyfile/writer.h | 1 - 9 files changed, 127 insertions(+), 2169 deletions(-) diff --git a/libnm-core/nm-keyfile-internal.h b/libnm-core/nm-keyfile-internal.h index 5916a6bffc..c5c17fe292 100644 --- a/libnm-core/nm-keyfile-internal.h +++ b/libnm-core/nm-keyfile-internal.h @@ -147,4 +147,10 @@ GKeyFile *nm_keyfile_write (NMConnection *connection, void *user_data, GError **error); +/*********************************************************/ + +char *nm_keyfile_plugin_kf_get_string (GKeyFile *kf, const char *group, const char *key, GError **error); +void nm_keyfile_plugin_kf_set_string (GKeyFile *kf, const char *group, const char *key, const char *value); + + #endif /* __NM_KEYFILE_INTERNAL_H__ */ diff --git a/libnm-core/nm-keyfile-utils.c b/libnm-core/nm-keyfile-utils.c index e2198712bb..61b30ab9c8 100644 --- a/libnm-core/nm-keyfile-utils.c +++ b/libnm-core/nm-keyfile-utils.c @@ -25,6 +25,7 @@ #include #include "nm-keyfile-utils.h" +#include "nm-keyfile-internal.h" #include "nm-setting-wired.h" #include "nm-setting-wireless.h" #include "nm-setting-wireless-security.h" diff --git a/src/settings/plugins/keyfile/common.h b/src/settings/plugins/keyfile/common.h index 7bde4bf3a9..86fe002855 100644 --- a/src/settings/plugins/keyfile/common.h +++ b/src/settings/plugins/keyfile/common.h @@ -28,7 +28,5 @@ #define KEYFILE_DIR NMCONFDIR "/system-connections" -#define VPN_SECRETS_GROUP "vpn-secrets" - #endif /* __COMMON_H__ */ diff --git a/src/settings/plugins/keyfile/reader.c b/src/settings/plugins/keyfile/reader.c index 97b6567028..3e4cfed8ed 100644 --- a/src/settings/plugins/keyfile/reader.c +++ b/src/settings/plugins/keyfile/reader.c @@ -15,1250 +15,74 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * - * Copyright (C) 2008 - 2009 Novell, Inc. - * Copyright (C) 2008 - 2011 Red Hat, Inc. + * Copyright (C) 2015 Red Hat, Inc. */ #include "config.h" -#include -#include #include -#include -#include -#include #include -#include "nm-core-internal.h" -#include "nm-dbus-glib-types.h" -#include "nm-glib-compat.h" -#include "nm-system-config-interface.h" -#include "nm-logging.h" #include "reader.h" -#include "common.h" -#include "utils.h" -#include "nm-core-internal.h" -#include "NetworkManagerUtils.h" -/* Some setting properties also contain setting names, such as - * NMSettingConnection's 'type' property (which specifies the base type of the - * connection, e.g. ethernet or wifi) or 'slave-type' (specifies type of slave - * connection, e.g. bond or bridge). This function handles translating those - * properties' values to the real setting name if they are an alias. - */ -static void -setting_alias_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) +#include "nm-logging.h" +#include "nm-keyfile-internal.h" + +static const char * +_fmt_warn (const char *group, NMSetting *setting, const char *property_name, const char *message, char **out_message) { - const char *setting_name = nm_setting_get_name (setting); - char *s; - const char *key_setting_name; + const char *setting_name = setting ? nm_setting_get_name (setting) : NULL; - s = nm_keyfile_plugin_kf_get_string (keyfile, setting_name, key, NULL); - if (s) { - key_setting_name = nm_keyfile_plugin_get_setting_name_for_alias (s); - g_object_set (G_OBJECT (setting), - key, key_setting_name ? key_setting_name : s, - NULL); - g_free (s); - } -} + if (group) { + char *res; -static gboolean -read_array_of_uint (GKeyFile *file, - NMSetting *setting, - const char *key) -{ - GArray *array = NULL; - gsize length; - int i; - gint *tmp; - - tmp = nm_keyfile_plugin_kf_get_integer_list (file, nm_setting_get_name (setting), key, &length, NULL); - array = g_array_sized_new (FALSE, FALSE, sizeof (guint32), length); - g_return_val_if_fail (array != NULL, FALSE); - - for (i = 0; i < length; i++) - g_array_append_val (array, tmp[i]); - - g_object_set (setting, key, array, NULL); - g_array_unref (array); - - return TRUE; -} - -static gboolean -get_one_int (const char *str, guint32 max_val, const char *key_name, guint32 *out) -{ - long tmp; - char *endptr; - - if (!str || !str[0]) { - if (key_name) - nm_log_warn (LOGD_SETTINGS, "%s: ignoring missing number %s", __func__, key_name); - return FALSE; - } - - errno = 0; - tmp = strtol (str, &endptr, 10); - if (errno || (tmp < 0) || (tmp > max_val) || *endptr != 0) { - if (key_name) - nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid number %s '%s'", __func__, key_name, str); - return FALSE; - } - - *out = (guint32) tmp; - return TRUE; -} - -static gpointer -build_address (int family, const char *address_str, guint32 plen) -{ - NMIPAddress *addr; - GError *error = NULL; - - g_return_val_if_fail (address_str, NULL); - - addr = nm_ip_address_new (family, address_str, plen, &error); - if (!addr) { - nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid %s address: %s", __func__, - family == AF_INET ? "IPv4" : "IPv6", - error->message); - g_error_free (error); - } - - return addr; -} - -static gpointer -build_route (int family, - const char *dest_str, guint32 plen, - const char *gateway_str, const char *metric_str, - const char *key_name) -{ - NMIPRoute *route; - guint32 metric = 0; - GError *error = NULL; - - g_return_val_if_fail (plen, NULL); - g_return_val_if_fail (dest_str, NULL); - - /* Next hop */ - if (gateway_str && gateway_str[0]) { - if (!nm_utils_ipaddr_valid (family, gateway_str)) { - /* Try workaround for routes written by broken keyfile writer. - * Due to bug bgo#719851, an older version of writer would have - * written "a:b:c:d::/plen,metric" if the gateway was ::, instead - * of "a:b:c:d::/plen,,metric" or "a:b:c:d::/plen,::,metric" - * Try workaround by interpreting gateway_str as metric to accept such - * invalid routes. This broken syntax should not be not officially - * supported. - **/ - if ( family == AF_INET6 - && !metric_str - && get_one_int (gateway_str, G_MAXUINT32, NULL, &metric)) - gateway_str = NULL; - else { - nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid gateway '%s'", __func__, gateway_str); - return NULL; - } - } - } else - gateway_str = NULL; - - /* parse metric, default to 0 */ - if (metric_str) { - if (!get_one_int (metric_str, G_MAXUINT32, key_name, &metric)) - return NULL; - } - - route = nm_ip_route_new (family, dest_str, plen, gateway_str, - metric ? (gint64) metric : -1, - &error); - if (!route) { - nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid %s route: %s", __func__, - family == AF_INET ? "IPv4" : "IPv6", - error->message); - g_error_free (error); - } - - return route; -} - -/* On success, returns pointer to the zero-terminated field (original @current). - * The @current * pointer target is set to point to the rest of the input - * or %NULL if there is no more input. Sets error to %NULL for convenience. - * - * On failure, returns %NULL (unspecified). The @current pointer target is - * resets to its original value to allow skipping fields. The @error target - * is set to the character that breaks the parsing or %NULL if @current was %NULL. - * - * When @current target is %NULL, gracefully fail returning %NULL while - * leaving the @current target %NULL end setting @error to %NULL; - */ -static char * -read_field (char **current, char **error, const char *characters, const char *delimiters) -{ - char *start; - - g_return_val_if_fail (current, NULL); - g_return_val_if_fail (error, NULL); - g_return_val_if_fail (characters, NULL); - g_return_val_if_fail (delimiters, NULL); - - *error = NULL; - - if (!*current) { - /* graceful failure, leave '*current' NULL */ - return NULL; - } - - /* fail on empty input */ - if (!**current) - return NULL; - - /* remember beginning of input */ - start = *current; - - while (**current && strchr (characters, **current)) - (*current)++; - if (**current) - if (strchr (delimiters, **current)) { - /* success, more data available */ - *(*current)++ = '\0'; - return start; - } else { - /* error, bad character */ - *error = *current; - *current = start; - return NULL; - } - else { - /* success, end of input */ - *current = NULL; - return start; - } -} - -#define IP_ADDRESS_CHARS "0123456789abcdefABCDEF:.%" -#define DIGITS "0123456789" -#define DELIMITERS "/;," - - -/* The following IPv4 and IPv6 address formats are supported: - * - * address (DEPRECATED) - * address/plen - * address/gateway (DEPRECATED) - * address/plen,gateway - * - * The following IPv4 and IPv6 route formats are supported: - * - * address/plen (NETWORK dev DEVICE) - * address/plen,gateway (NETWORK via GATEWAY dev DEVICE) - * address/plen,,metric (NETWORK dev DEVICE metric METRIC) - * address/plen,gateway,metric (NETWORK via GATEWAY dev DEVICE metric METRIC) - * - * For backward, forward and sideward compatibility, slash (/), - * semicolon (;) and comma (,) are interchangable. The choice of - * separator in the above examples is therefore not significant. - * - * Leaving out the prefix length is discouraged and DEPRECATED. The - * default value of IPv6 prefix length was 64 and has not been - * changed. The default for IPv4 is now 24, which is the closest - * IPv4 equivalent. These defaults may just as well be changed to - * match the iproute2 defaults (32 for IPv4 and 128 for IPv6). - */ -static gpointer -read_one_ip_address_or_route (GKeyFile *file, - const char *setting_name, - const char *key_name, - gboolean ipv6, - gboolean route, - char **out_gateway) -{ - guint32 plen = G_MAXUINT32; - gpointer result; - char *address_str, *plen_str, *gateway_str, *metric_str, *value, *current, *error; - - current = value = nm_keyfile_plugin_kf_get_string (file, setting_name, key_name, NULL); - if (!value) - return NULL; - - /* get address field */ - address_str = read_field (¤t, &error, IP_ADDRESS_CHARS, DELIMITERS); - if (error) { - nm_log_warn (LOGD_SETTINGS, "keyfile: Unexpected character '%c' in '%s.%s' address (position %td of '%s').", - *error, setting_name, key_name, error - current, current); - goto error; - } - /* get prefix length field (skippable) */ - plen_str = read_field (¤t, &error, DIGITS, DELIMITERS); - /* get gateway field */ - gateway_str = read_field (¤t, &error, IP_ADDRESS_CHARS, DELIMITERS); - if (error) { - nm_log_warn (LOGD_SETTINGS, "keyfile: Unexpected character '%c' in '%s.%s' %s (position %td of '%s').", - *error, setting_name, key_name, - plen_str ? "gateway" : "gateway or prefix length", - error - current, current); - goto error; - } - /* for routes, get metric */ - if (route) { - metric_str = read_field (¤t, &error, DIGITS, DELIMITERS); - if (error) { - nm_log_warn (LOGD_SETTINGS, "keyfile: Unexpected character '%c' in '%s.%s' prefix length (position %td of '%s').", - *error, setting_name, key_name, error - current, current); - goto error; - } - } else - metric_str = NULL; - if (current) { - /* there is still some data */ - if (*current) { - /* another field follows */ - nm_log_warn (LOGD_SETTINGS, "keyfile: %s.%s: Garbage at the and of the line: %s", - setting_name, key_name, current); - goto error; - } else { - /* semicolon at the end of input */ - nm_log_info (LOGD_SETTINGS, "keyfile: %s.%s: Deprecated semicolon at the end of value.", - setting_name, key_name); - } - } - -#define DEFAULT_PREFIX(for_route, for_ipv6) ( (for_route) ? ( (for_ipv6) ? 128 : 24 ) : ( (for_ipv6) ? 64 : 24 ) ) - - /* parse plen, fallback to defaults */ - if (plen_str) { - if (!get_one_int (plen_str, ipv6 ? 128 : 32, key_name, &plen) - || (route && plen == 0)) { - plen = DEFAULT_PREFIX (route, ipv6); - nm_log_warn (LOGD_SETTINGS, "keyfile: invalid prefix length '%s' in '%s.%s', defaulting to %d", - plen_str, setting_name, key_name, plen); - } - } else { - plen = DEFAULT_PREFIX (route, ipv6); - nm_log_warn (LOGD_SETTINGS, "keyfile: Missing prefix length in '%s.%s', defaulting to %d", - setting_name, key_name, plen); - } - - /* build the appropriate data structure for NetworkManager settings */ - if (route) { - result = build_route (ipv6 ? AF_INET6 : AF_INET, - address_str, plen, gateway_str, metric_str, - key_name); - } else { - result = build_address (ipv6 ? AF_INET6 : AF_INET, - address_str, plen); - if (out_gateway && gateway_str) - *out_gateway = g_strdup (gateway_str); - } - - g_free (value); - return result; -error: - g_free (value); - return NULL; -} - -static void -ip_address_or_route_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) -{ - const char *setting_name = nm_setting_get_name (setting); - gboolean ipv6 = !strcmp (setting_name, "ipv6"); - gboolean routes = !strcmp (key, "routes"); - static const char *key_names_routes[] = { "route", "routes", NULL }; - static const char *key_names_addresses[] = { "address", "addresses", NULL }; - const char **key_names = routes ? key_names_routes : key_names_addresses; - char *gateway = NULL; - GPtrArray *list; - GDestroyNotify free_func; - int i; - - if (routes) - free_func = (GDestroyNotify) nm_ip_route_unref; - else - free_func = (GDestroyNotify) nm_ip_address_unref; - list = g_ptr_array_new_with_free_func (free_func); - - for (i = -1; i < 1000; i++) { - const char **key_basename; - - for (key_basename = key_names; *key_basename; key_basename++) { - char *key_name; - gpointer item; - - /* -1 means no suffix */ - if (i >= 0) - key_name = g_strdup_printf ("%s%d", *key_basename, i); + if (setting_name) { + if (property_name && !strcmp (group, setting_name)) + res = g_strdup_printf ("%s.%s: %s", group, property_name, message); + else if (property_name) + res = g_strdup_printf ("%s/%s.%s: %s", group, setting_name, property_name, message); + else if (!strcmp (group, setting_name)) + res = g_strdup_printf ("%s: %s", group, message); else - key_name = g_strdup (*key_basename); - - item = read_one_ip_address_or_route (keyfile, setting_name, key_name, ipv6, routes, - gateway ? NULL : &gateway); - if (item) - g_ptr_array_add (list, item); - - g_free (key_name); - } - } - - if (list->len >= 1) - g_object_set (setting, key, list, NULL); - - if (gateway) { - g_object_set (setting, "gateway", gateway, NULL); - g_free (gateway); - } - - g_ptr_array_unref (list); -} - -static void -ip4_dns_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) -{ - const char *setting_name = nm_setting_get_name (setting); - GPtrArray *array; - gsize length; - char **list, **iter; - int ret; - - list = nm_keyfile_plugin_kf_get_string_list (keyfile, setting_name, key, &length, NULL); - if (!list || !g_strv_length (list)) - return; - - array = g_ptr_array_sized_new (length + 1); - for (iter = list; *iter; iter++) { - guint32 addr; - - ret = inet_pton (AF_INET, *iter, &addr); - if (ret <= 0) { - nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid DNS server address '%s'", __func__, *iter); - continue; - } - - g_ptr_array_add (array, *iter); - } - g_ptr_array_add (array, NULL); - - g_object_set (setting, key, array->pdata, NULL); - g_ptr_array_unref (array); - g_strfreev (list); -} - -static void -ip6_dns_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) -{ - const char *setting_name = nm_setting_get_name (setting); - GPtrArray *array = NULL; - gsize length; - char **list, **iter; - int ret; - - list = nm_keyfile_plugin_kf_get_string_list (keyfile, setting_name, key, &length, NULL); - if (!list || !g_strv_length (list)) - return; - - array = g_ptr_array_sized_new (length + 1); - - for (iter = list; *iter; iter++) { - struct in6_addr addr; - - ret = inet_pton (AF_INET6, *iter, &addr); - if (ret <= 0) { - nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid DNS server IPv6 address '%s'", __func__, *iter); - continue; - } - - g_ptr_array_add (array, *iter); - } - g_ptr_array_add (array, NULL); - - g_object_set (setting, key, array->pdata, NULL); - g_ptr_array_unref (array); - g_strfreev (list); -} - -static void -mac_address_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path, gsize enforce_length) -{ - const char *setting_name = nm_setting_get_name (setting); - char *tmp_string = NULL, *p, *mac_str; - gint *tmp_list; - GByteArray *array = NULL; - gsize length; - - p = tmp_string = nm_keyfile_plugin_kf_get_string (keyfile, setting_name, key, NULL); - if (tmp_string && tmp_string[0]) { - /* Look for enough ':' characters to signify a MAC address */ - guint i = 0; - - while (*p) { - if (*p == ':') - i++; - p++; - } - - if (enforce_length == 0 || enforce_length == i+1) { - /* If we found enough it's probably a string-format MAC address */ - array = g_byte_array_sized_new (i+1); - g_byte_array_set_size (array, i+1); - if (!nm_utils_hwaddr_aton (tmp_string, array->data, array->len)) { - g_byte_array_unref (array); - array = NULL; - } - } - } - g_free (tmp_string); - - if (array == NULL) { - /* Old format; list of ints */ - tmp_list = nm_keyfile_plugin_kf_get_integer_list (keyfile, setting_name, key, &length, NULL); - if (length > 0 && (enforce_length == 0 || enforce_length == length)) { - gsize i; - - array = g_byte_array_sized_new (length); - for (i = 0; i < length; i++) { - int val = tmp_list[i]; - const guint8 v = (guint8) (val & 0xFF); - - if (val < 0 || val > 255) { - nm_log_warn (LOGD_SETTINGS, "%s: %s / %s ignoring invalid byte element '%d' (not " - " between 0 and 255 inclusive)", __func__, setting_name, - key, val); - g_byte_array_free (array, TRUE); - array = NULL; - break; - } - g_byte_array_append (array, &v, 1); - } - } - g_free (tmp_list); - } - - if (!array) { - nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid MAC address for %s / %s", - __func__, setting_name, key); - return; - } - - mac_str = nm_utils_hwaddr_ntoa (array->data, array->len); - g_object_set (setting, key, mac_str, NULL); - g_free (mac_str); - g_byte_array_free (array, TRUE); -} - -static void -mac_address_parser_ETHER (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) -{ - mac_address_parser (setting, key, keyfile, keyfile_path, ETH_ALEN); -} - -static void -mac_address_parser_INFINIBAND (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) -{ - mac_address_parser (setting, key, keyfile, keyfile_path, INFINIBAND_ALEN); -} - -static void -read_hash_of_string (GKeyFile *file, NMSetting *setting, const char *key) -{ - char **keys, **iter; - char *value; - const char *setting_name = nm_setting_get_name (setting); - - keys = nm_keyfile_plugin_kf_get_keys (file, setting_name, NULL, NULL); - if (!keys || !*keys) - return; - - for (iter = keys; *iter; iter++) { - value = nm_keyfile_plugin_kf_get_string (file, setting_name, *iter, NULL); - if (!value) - continue; - - if (NM_IS_SETTING_VPN (setting)) { - /* Add any item that's not a class property to the data hash */ - if (!g_object_class_find_property (G_OBJECT_GET_CLASS (setting), *iter)) - nm_setting_vpn_add_data_item (NM_SETTING_VPN (setting), *iter, value); - } - if (NM_IS_SETTING_BOND (setting)) { - if (strcmp (*iter, "interface-name")) - nm_setting_bond_add_option (NM_SETTING_BOND (setting), *iter, value); - } - g_free (value); - } - g_strfreev (keys); -} - -static void -unescape_semicolons (char *str) -{ - int i; - gsize len = strlen (str); - - for (i = 0; i < len; i++) { - if (str[i] == '\\' && str[i+1] == ';') { - memmove(str + i, str + i + 1, len - (i + 1)); - len--; - } - str[len] = '\0'; - } -} - -static GBytes * -get_bytes (GKeyFile *keyfile, - const char *setting_name, - const char *key, - gboolean zero_terminate, - gboolean unescape_semicolon) -{ - GByteArray *array = NULL; - char *tmp_string; - gint *tmp_list; - gsize length; - int i; - - if (!nm_keyfile_plugin_kf_has_key (keyfile, setting_name, key, NULL)) - return NULL; - - /* New format: just a string - * Old format: integer list; e.g. 11;25;38; - */ - tmp_string = nm_keyfile_plugin_kf_get_string (keyfile, setting_name, key, NULL); - if (tmp_string) { - GRegex *regex; - GMatchInfo *match_info; - const char *pattern = "^[[:space:]]*[[:digit:]]{1,3}[[:space:]]*;([[:space:]]*[[:digit:]]{1,3}[[:space:]]*;)*([[:space:]]*)?$"; - - regex = g_regex_new (pattern, 0, 0, NULL); - g_regex_match (regex, tmp_string, 0, &match_info); - if (!g_match_info_matches (match_info)) { - /* Handle as a simple string (ie, new format) */ - if (unescape_semicolon) - unescape_semicolons (tmp_string); - length = strlen (tmp_string); - if (zero_terminate) - length++; - array = g_byte_array_sized_new (length); - g_byte_array_append (array, (guint8 *) tmp_string, length); - } - g_match_info_free (match_info); - g_regex_unref (regex); - g_free (tmp_string); - } - - if (!array) { - /* Old format; list of ints */ - tmp_list = nm_keyfile_plugin_kf_get_integer_list (keyfile, setting_name, key, &length, NULL); - if (!tmp_list) { - nm_log_warn (LOGD_SETTINGS, "%s: %s / %s ignoring invalid binary property", - __func__, setting_name, key); - return NULL; - } - array = g_byte_array_sized_new (length); - for (i = 0; i < length; i++) { - int val = tmp_list[i]; - unsigned char v = (unsigned char) (val & 0xFF); - - if (val < 0 || val > 255) { - nm_log_warn (LOGD_SETTINGS, "%s: %s / %s ignoring invalid byte element '%d' (not " - " between 0 and 255 inclusive)", __func__, setting_name, - key, val); - } else - g_byte_array_append (array, (const unsigned char *) &v, sizeof (v)); - } - g_free (tmp_list); - } - - if (array->len == 0) { - g_byte_array_free (array, TRUE); - return NULL; + res = g_strdup_printf ("%s/%s: %s", group, setting_name, message); + } else + res = g_strdup_printf ("%s: %s", group, message); + *out_message = res; + return res; } else - return g_byte_array_free_to_bytes (array); -} - -static void -ssid_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) -{ - const char *setting_name = nm_setting_get_name (setting); - GBytes *bytes; - - bytes = get_bytes (keyfile, setting_name, key, FALSE, TRUE); - if (bytes) { - g_object_set (setting, key, bytes, NULL); - g_bytes_unref (bytes); - } else { - nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid SSID for %s / %s", - __func__, setting_name, key); - } -} - -static void -password_raw_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) -{ - const char *setting_name = nm_setting_get_name (setting); - GBytes *bytes; - - bytes = get_bytes (keyfile, setting_name, key, FALSE, TRUE); - if (bytes) { - g_object_set (setting, key, bytes, NULL); - g_bytes_unref (bytes); - } else { - nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid raw password for %s / %s", - __func__, setting_name, key); - } -} - -static char * -get_cert_path (const char *keyfile_path, const guint8 *cert_path, gsize cert_path_len) -{ - const char *base; - char *p = NULL, *path, *dirname, *tmp; - - g_return_val_if_fail (keyfile_path != NULL, NULL); - g_return_val_if_fail (cert_path != NULL, NULL); - - base = path = g_malloc0 (cert_path_len + 1); - memcpy (path, cert_path, cert_path_len); - - if (path[0] == '/') - return path; - - p = strrchr (path, '/'); - if (p) - base = p + 1; - - dirname = g_path_get_dirname (keyfile_path); - tmp = g_build_path ("/", dirname, base, NULL); - g_free (dirname); - g_free (path); - return tmp; -} - -#define SCHEME_PATH "file://" - -static const char *certext[] = { ".pem", ".cert", ".crt", ".cer", ".p12", ".der", ".key" }; - -static gboolean -has_cert_ext (const char *path) -{ - int i; - - for (i = 0; i < G_N_ELEMENTS (certext); i++) { - if (g_str_has_suffix (path, certext[i])) - return TRUE; - } - return FALSE; + return message; } static gboolean -handle_as_scheme (GBytes *bytes, NMSetting *setting, const char *key) +_handler_read (GKeyFile *keyfile, + NMConnection *connection, + NMKeyfileReadType type, + void *type_data, + void *user_data, + GError **error) { - const guint8 *data; - gsize data_len; + if (type == NM_KEYFILE_READ_TYPE_WARN) { + NMKeyfileReadTypeDataWarn *warn_data = type_data; + NMLogLevel level; + char *message_free = NULL; - data = g_bytes_get_data (bytes, &data_len); + if (warn_data->severity > NM_KEYFILE_WARN_SEVERITY_WARN) + level = LOGL_ERR; + else if (warn_data->severity >= NM_KEYFILE_WARN_SEVERITY_WARN) + level = LOGL_WARN; + else + level = LOGL_INFO; - /* It's the PATH scheme, can just set plain data */ - if ( (data_len > strlen (SCHEME_PATH)) - && g_str_has_prefix ((const char *) data, SCHEME_PATH) - && (data[data_len - 1] == '\0')) { - g_object_set (setting, key, bytes, NULL); + nm_log (level, LOGD_SETTINGS, "keyfile: %s", + _fmt_warn (warn_data->group, warn_data->setting, + warn_data->property_name, warn_data->message, + &message_free)); + g_free (message_free); return TRUE; } return FALSE; } -static gboolean -handle_as_path (GBytes *bytes, - NMSetting *setting, - const char *key, - const char *keyfile_path) -{ - const guint8 *data; - gsize data_len; - gsize validate_len; - char *path; - gboolean exists, success = FALSE; - - data = g_bytes_get_data (bytes, &data_len); - if (data_len > 500 || data_len < 1) - return FALSE; - - /* If there's a trailing zero tell g_utf8_validate() to validate until the zero */ - if (data[data_len - 1] == '\0') { - /* setting it to -1, would mean we accept data to contain NUL characters before the - * end. Don't accept any NUL in [0 .. data_len-1[ . */ - validate_len = data_len - 1; - } else - validate_len = data_len; - - if ( validate_len == 0 - || g_utf8_validate ((const char *) data, validate_len, NULL) == FALSE) - return FALSE; - - /* Might be a bare path without the file:// prefix; in that case - * if it's an absolute path, use that, otherwise treat it as a - * relative path to the current directory. - */ - - path = get_cert_path (keyfile_path, data, data_len); - exists = g_file_test (path, G_FILE_TEST_EXISTS); - if ( exists - || memchr (data, '/', data_len) - || has_cert_ext (path)) { - GByteArray *tmp; - GBytes *val; - - /* Construct the proper value as required for the PATH scheme */ - tmp = g_byte_array_sized_new (strlen (SCHEME_PATH) + strlen (path) + 1); - g_byte_array_append (tmp, (const guint8 *) SCHEME_PATH, strlen (SCHEME_PATH)); - g_byte_array_append (tmp, (const guint8 *) path, strlen (path)); - g_byte_array_append (tmp, (const guint8 *) "\0", 1); - val = g_byte_array_free_to_bytes (tmp); - g_object_set (setting, key, val, NULL); - g_bytes_unref (val); - success = TRUE; - - /* Warn if the certificate didn't exist */ - if (exists == FALSE) - nm_log_warn (LOGD_SETTINGS, "certificate or key %s does not exist", path); - } - g_free (path); - - return success; -} - -static void -cert_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) -{ - const char *setting_name = nm_setting_get_name (setting); - GBytes *bytes; - gboolean success = FALSE; - - bytes = get_bytes (keyfile, setting_name, key, TRUE, FALSE); - if (bytes) { - /* Try as a path + scheme (ie, starts with "file://") */ - success = handle_as_scheme (bytes, setting, key); - - /* If not, it might be a plain path */ - if (success == FALSE) - success = handle_as_path (bytes, setting, key, keyfile_path); - - /* If neither of those two, assume blob with certificate data */ - if (success == FALSE) - g_object_set (setting, key, bytes, NULL); - } else { - nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid key/cert value for %s / %s", - __func__, setting_name, key); - } - - if (bytes) - g_bytes_unref (bytes); -} - -static void -parity_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) -{ - const char *setting_name = nm_setting_get_name (setting); - NMSettingSerialParity parity; - int int_val; - char *str_val; - - /* Keyfile traditionally stored this as the ASCII value for 'E', 'o', or 'n'. - * We now accept either that or the (case-insensitive) character itself (but - * still always write it the old way, for backward compatibility). - */ - int_val = nm_keyfile_plugin_kf_get_integer (keyfile, setting_name, key, NULL); - if (!int_val) { - str_val = nm_keyfile_plugin_kf_get_string (keyfile, setting_name, key, NULL); - if (str_val) { - if (str_val[0] && !str_val[1]) - int_val = str_val[0]; - else { - /* This will hit the warning below */ - int_val = 'X'; - } - } - g_free (str_val); - } - - if (!int_val) - return; - - switch (int_val) { - case 'E': - case 'e': - parity = NM_SETTING_SERIAL_PARITY_EVEN; - break; - case 'O': - case 'o': - parity = NM_SETTING_SERIAL_PARITY_ODD; - break; - case 'N': - case 'n': - parity = NM_SETTING_SERIAL_PARITY_NONE; - break; - default: - nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid value for %s / %s", - __func__, setting_name, key); - return; - } - - g_object_set (setting, key, parity, NULL); -} - -typedef struct { - const char *setting_name; - const char *key; - gboolean check_for_key; - void (*parser) (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path); -} KeyParser; - -/* A table of keys that require further parsing/conversion because they are - * stored in a format that can't be automatically read using the key's type. - * i.e. IPv4 addresses, which are stored in NetworkManager as guint32, but are - * stored in keyfiles as strings, eg "10.1.1.2" or IPv6 addresses stored - * in struct in6_addr internally, but as string in keyfiles. - */ -static KeyParser key_parsers[] = { - { NM_SETTING_CONNECTION_SETTING_NAME, - NM_SETTING_CONNECTION_TYPE, - TRUE, - setting_alias_parser }, - { NM_SETTING_BRIDGE_SETTING_NAME, - NM_SETTING_BRIDGE_MAC_ADDRESS, - TRUE, - mac_address_parser_ETHER }, - { NM_SETTING_IP4_CONFIG_SETTING_NAME, - NM_SETTING_IP_CONFIG_ADDRESSES, - FALSE, - ip_address_or_route_parser }, - { NM_SETTING_IP6_CONFIG_SETTING_NAME, - NM_SETTING_IP_CONFIG_ADDRESSES, - FALSE, - ip_address_or_route_parser }, - { NM_SETTING_IP4_CONFIG_SETTING_NAME, - NM_SETTING_IP_CONFIG_ROUTES, - FALSE, - ip_address_or_route_parser }, - { NM_SETTING_IP6_CONFIG_SETTING_NAME, - NM_SETTING_IP_CONFIG_ROUTES, - FALSE, - ip_address_or_route_parser }, - { NM_SETTING_IP4_CONFIG_SETTING_NAME, - NM_SETTING_IP_CONFIG_DNS, - FALSE, - ip4_dns_parser }, - { NM_SETTING_IP6_CONFIG_SETTING_NAME, - NM_SETTING_IP_CONFIG_DNS, - FALSE, - ip6_dns_parser }, - { NM_SETTING_WIRED_SETTING_NAME, - NM_SETTING_WIRED_MAC_ADDRESS, - TRUE, - mac_address_parser_ETHER }, - { NM_SETTING_WIRED_SETTING_NAME, - NM_SETTING_WIRED_CLONED_MAC_ADDRESS, - TRUE, - mac_address_parser_ETHER }, - { NM_SETTING_WIRELESS_SETTING_NAME, - NM_SETTING_WIRELESS_MAC_ADDRESS, - TRUE, - mac_address_parser_ETHER }, - { NM_SETTING_WIRELESS_SETTING_NAME, - NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, - TRUE, - mac_address_parser_ETHER }, - { NM_SETTING_WIRELESS_SETTING_NAME, - NM_SETTING_WIRELESS_BSSID, - TRUE, - mac_address_parser_ETHER }, - { NM_SETTING_BLUETOOTH_SETTING_NAME, - NM_SETTING_BLUETOOTH_BDADDR, - TRUE, - mac_address_parser_ETHER }, - { NM_SETTING_INFINIBAND_SETTING_NAME, - NM_SETTING_INFINIBAND_MAC_ADDRESS, - TRUE, - mac_address_parser_INFINIBAND }, - { NM_SETTING_WIMAX_SETTING_NAME, - NM_SETTING_WIMAX_MAC_ADDRESS, - TRUE, - mac_address_parser_ETHER }, - { NM_SETTING_WIRELESS_SETTING_NAME, - NM_SETTING_WIRELESS_SSID, - TRUE, - ssid_parser }, - { NM_SETTING_802_1X_SETTING_NAME, - NM_SETTING_802_1X_PASSWORD_RAW, - TRUE, - password_raw_parser }, - { NM_SETTING_802_1X_SETTING_NAME, - NM_SETTING_802_1X_CA_CERT, - TRUE, - cert_parser }, - { NM_SETTING_802_1X_SETTING_NAME, - NM_SETTING_802_1X_CLIENT_CERT, - TRUE, - cert_parser }, - { NM_SETTING_802_1X_SETTING_NAME, - NM_SETTING_802_1X_PRIVATE_KEY, - TRUE, - cert_parser }, - { NM_SETTING_802_1X_SETTING_NAME, - NM_SETTING_802_1X_PHASE2_CA_CERT, - TRUE, - cert_parser }, - { NM_SETTING_802_1X_SETTING_NAME, - NM_SETTING_802_1X_PHASE2_CLIENT_CERT, - TRUE, - cert_parser }, - { NM_SETTING_802_1X_SETTING_NAME, - NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, - TRUE, - cert_parser }, - { NM_SETTING_SERIAL_SETTING_NAME, - NM_SETTING_SERIAL_PARITY, - TRUE, - parity_parser }, - { NULL, NULL, FALSE } -}; - -typedef struct { - GKeyFile *keyfile; - const char *keyfile_path; -} ReadInfo; - -static void -read_one_setting_value (NMSetting *setting, - const char *key, - const GValue *value, - GParamFlags flags, - gpointer user_data) -{ - ReadInfo *info = user_data; - const char *setting_name; - int errsv; - GType type; - GError *err = NULL; - gboolean check_for_key = TRUE; - KeyParser *parser = &key_parsers[0]; - - /* Property is not writable */ - if (!(flags & G_PARAM_WRITABLE)) - return; - - /* Setting name gets picked up from the keyfile's section name instead */ - if (!strcmp (key, NM_SETTING_NAME)) - return; - - /* Don't read the NMSettingConnection object's 'read-only' property */ - if ( NM_IS_SETTING_CONNECTION (setting) - && !strcmp (key, NM_SETTING_CONNECTION_READ_ONLY)) - return; - - setting_name = nm_setting_get_name (setting); - - /* Look through the list of handlers for non-standard format key values */ - while (parser->setting_name) { - if (!strcmp (parser->setting_name, setting_name) && !strcmp (parser->key, key)) { - check_for_key = parser->check_for_key; - break; - } - parser++; - } - - /* VPN properties don't have the exact key name */ - if (NM_IS_SETTING_VPN (setting)) - check_for_key = FALSE; - - /* Bonding 'options' don't have the exact key name. The options are right under [bond] group. */ - if (NM_IS_SETTING_BOND (setting)) - check_for_key = FALSE; - - /* Check for the exact key in the GKeyFile if required. Most setting - * properties map 1:1 to a key in the GKeyFile, but for those properties - * like IP addresses and routes where more than one value is actually - * encoded by the setting property, this won't be true. - */ - if (check_for_key && !nm_keyfile_plugin_kf_has_key (info->keyfile, setting_name, key, &err)) { - /* Key doesn't exist or an error ocurred, thus nothing to do. */ - if (err) { - nm_log_warn (LOGD_SETTINGS, "Error loading setting '%s' value: %s", setting_name, err->message); - g_error_free (err); - } - return; - } - - /* If there's a custom parser for this key, handle that before the generic - * parsers below. - */ - if (parser->setting_name) { - (*parser->parser) (setting, key, info->keyfile, info->keyfile_path); - return; - } - - type = G_VALUE_TYPE (value); - - if (type == G_TYPE_STRING) { - char *str_val; - - str_val = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL); - g_object_set (setting, key, str_val, NULL); - g_free (str_val); - } else if (type == G_TYPE_UINT) { - int int_val; - - int_val = nm_keyfile_plugin_kf_get_integer (info->keyfile, setting_name, key, NULL); - if (int_val < 0) - nm_log_warn (LOGD_SETTINGS, "Casting negative value (%i) to uint", int_val); - g_object_set (setting, key, int_val, NULL); - } else if (type == G_TYPE_INT) { - int int_val; - - int_val = nm_keyfile_plugin_kf_get_integer (info->keyfile, setting_name, key, NULL); - g_object_set (setting, key, int_val, NULL); - } else if (type == G_TYPE_BOOLEAN) { - gboolean bool_val; - - bool_val = nm_keyfile_plugin_kf_get_boolean (info->keyfile, setting_name, key, NULL); - g_object_set (setting, key, bool_val, NULL); - } else if (type == G_TYPE_CHAR) { - int int_val; - - int_val = nm_keyfile_plugin_kf_get_integer (info->keyfile, setting_name, key, NULL); - if (int_val < G_MININT8 || int_val > G_MAXINT8) - nm_log_warn (LOGD_SETTINGS, "Casting value (%i) to char", int_val); - - g_object_set (setting, key, int_val, NULL); - } else if (type == G_TYPE_UINT64) { - char *tmp_str; - guint64 uint_val; - - tmp_str = nm_keyfile_plugin_kf_get_value (info->keyfile, setting_name, key, NULL); - uint_val = g_ascii_strtoull (tmp_str, NULL, 10); - g_free (tmp_str); - g_object_set (setting, key, uint_val, NULL); - } else if (type == G_TYPE_INT64) { - char *tmp_str; - gint64 int_val; - - tmp_str = nm_keyfile_plugin_kf_get_value (info->keyfile, setting_name, key, NULL); - int_val = _nm_utils_ascii_str_to_int64 (tmp_str, 10, G_MININT64, G_MAXINT64, 0); - errsv = errno; - if (errsv) - nm_log_warn (LOGD_SETTINGS, "Invalid int64 value (%s)", tmp_str); - else - g_object_set (setting, key, int_val, NULL); - g_free (tmp_str); - } else if (type == G_TYPE_BYTES) { - gint *tmp; - GByteArray *array; - GBytes *bytes; - gsize length; - int i; - - tmp = nm_keyfile_plugin_kf_get_integer_list (info->keyfile, setting_name, key, &length, NULL); - - array = g_byte_array_sized_new (length); - for (i = 0; i < length; i++) { - int val = tmp[i]; - unsigned char v = (unsigned char) (val & 0xFF); - - if (val < 0 || val > 255) { - nm_log_warn (LOGD_SETTINGS, "%s: %s / %s ignoring invalid byte element '%d' (not " - " between 0 and 255 inclusive)", __func__, setting_name, - key, val); - } else - g_byte_array_append (array, (const unsigned char *) &v, sizeof (v)); - } - - bytes = g_byte_array_free_to_bytes (array); - g_object_set (setting, key, bytes, NULL); - g_bytes_unref (bytes); - g_free (tmp); - } else if (type == G_TYPE_STRV) { - gchar **sa; - gsize length; - - sa = nm_keyfile_plugin_kf_get_string_list (info->keyfile, setting_name, key, &length, NULL); - g_object_set (setting, key, sa, NULL); - g_strfreev (sa); - } else if (type == G_TYPE_HASH_TABLE) { - read_hash_of_string (info->keyfile, setting, key); - } else if (type == G_TYPE_ARRAY) { - if (!read_array_of_uint (info->keyfile, setting, key)) { - nm_log_warn (LOGD_SETTINGS, "Unhandled setting property type (read): '%s/%s' : '%s'", - setting_name, key, G_VALUE_TYPE_NAME (value)); - } - } else if (G_VALUE_HOLDS_FLAGS (value)) { - guint64 uint_val; - - /* Flags are guint but GKeyFile has no uint reader, just uint64 */ - uint_val = nm_keyfile_plugin_kf_get_uint64 (info->keyfile, setting_name, key, &err); - if (!err) { - if (uint_val <= G_MAXUINT) - g_object_set (setting, key, (guint) uint_val, NULL); - else { - nm_log_warn (LOGD_SETTINGS, "Too large FLAGS property (read): '%s/%s' : '%s'", - setting_name, key, G_VALUE_TYPE_NAME (value)); - } - } - g_clear_error (&err); - } else if (G_VALUE_HOLDS_ENUM (value)) { - gint int_val; - - int_val = nm_keyfile_plugin_kf_get_integer (info->keyfile, setting_name, key, &err); - if (!err) - g_object_set (setting, key, (gint) int_val, NULL); - g_clear_error (&err); - } else { - nm_log_warn (LOGD_SETTINGS, "Unhandled setting property type (read): '%s/%s' : '%s'", - setting_name, key, G_VALUE_TYPE_NAME (value)); - } -} - -static NMSetting * -read_setting (GKeyFile *file, const char *keyfile_path, const char *group) -{ - NMSetting *setting = NULL; - ReadInfo info = { file, keyfile_path }; - const char *alias; - GType type; - - alias = nm_keyfile_plugin_get_setting_name_for_alias (group); - if (alias) - group = alias; - - type = nm_setting_lookup_type (group); - if (type) { - setting = g_object_new (type, NULL); - nm_setting_enumerate_values (setting, read_one_setting_value, &info); - } else - nm_log_warn (LOGD_SETTINGS, "Invalid setting name '%s'", group); - - return setting; -} - -static void -read_vpn_secrets (GKeyFile *file, NMSettingVpn *s_vpn) -{ - char **keys, **iter; - - keys = nm_keyfile_plugin_kf_get_keys (file, VPN_SECRETS_GROUP, NULL, NULL); - for (iter = keys; *iter; iter++) { - char *secret; - - secret = nm_keyfile_plugin_kf_get_string (file, VPN_SECRETS_GROUP, *iter, NULL); - if (secret) { - nm_setting_vpn_add_secret (s_vpn, *iter, secret); - g_free (secret); - } - } - g_strfreev (keys); -} - NMConnection * nm_keyfile_plugin_connection_from_file (const char *filename, GError **error) { @@ -1266,12 +90,6 @@ nm_keyfile_plugin_connection_from_file (const char *filename, GError **error) struct stat statbuf; gboolean bad_permissions; NMConnection *connection = NULL; - NMSettingConnection *s_con; - NMSetting *setting; - gchar **groups; - gsize length; - int i; - gboolean vpn_secrets = FALSE; GError *verify_error = NULL; if (stat (filename, &statbuf) != 0 || !S_ISREG (statbuf.st_mode)) { @@ -1293,77 +111,14 @@ nm_keyfile_plugin_connection_from_file (const char *filename, GError **error) if (!g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, error)) goto out; - connection = nm_simple_connection_new (); - - groups = g_key_file_get_groups (key_file, &length); - for (i = 0; i < length; i++) { - /* Only read out secrets when needed */ - if (!strcmp (groups[i], VPN_SECRETS_GROUP)) { - vpn_secrets = TRUE; - continue; - } - - setting = read_setting (key_file, filename, groups[i]); - if (setting) - nm_connection_add_setting (connection, setting); - } - - s_con = nm_connection_get_setting_connection (connection); - if (!s_con) { - s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ()); - nm_connection_add_setting (connection, NM_SETTING (s_con)); - } - - /* Make sure that we have 'id' even if not explictly specified in the keyfile */ - if (!nm_setting_connection_get_id (s_con)) { - char *base_name; - - base_name = g_path_get_basename (filename); - g_object_set (s_con, NM_SETTING_CONNECTION_ID, base_name, NULL); - g_free (base_name); - } - - /* Make sure that we have 'uuid' even if not explictly specified in the keyfile */ - if (!nm_setting_connection_get_uuid (s_con)) { - char *hashed_uuid; - - hashed_uuid = _nm_utils_uuid_generate_from_strings ("keyfile", filename, NULL); - g_object_set (s_con, NM_SETTING_CONNECTION_UUID, hashed_uuid, NULL); - g_free (hashed_uuid); - } - - /* Make sure that we have 'interface-name' even if it was specified in the - * "wrong" (ie, deprecated) group. - */ - if ( !nm_setting_connection_get_interface_name (s_con) - && nm_setting_connection_get_connection_type (s_con)) { - char *interface_name; - - interface_name = g_key_file_get_string (key_file, - nm_setting_connection_get_connection_type (s_con), - "interface-name", - NULL); - if (interface_name) { - g_object_set (s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, interface_name, NULL); - g_free (interface_name); - } - } - - /* Handle vpn secrets after the 'vpn' setting was read */ - if (vpn_secrets) { - NMSettingVpn *s_vpn; - - s_vpn = nm_connection_get_setting_vpn (connection); - if (s_vpn) - read_vpn_secrets (key_file, s_vpn); - } - - g_strfreev (groups); + connection = nm_keyfile_read (key_file, filename, NULL, _handler_read, NULL, error); + if (!connection) + goto out; /* Normalize and verify the connection */ if (!nm_connection_normalize (connection, NULL, NULL, &verify_error)) { g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, - "invalid connection: %s", + "invalid connection: %s", verify_error->message); g_clear_error (&verify_error); g_object_unref (connection); @@ -1374,3 +129,4 @@ out: g_key_file_free (key_file); return connection; } + diff --git a/src/settings/plugins/keyfile/tests/test-keyfile.c b/src/settings/plugins/keyfile/tests/test-keyfile.c index b1de5d3ca1..1e90824261 100644 --- a/src/settings/plugins/keyfile/tests/test-keyfile.c +++ b/src/settings/plugins/keyfile/tests/test-keyfile.c @@ -100,33 +100,33 @@ test_read_valid_wired_connection (void) const char *expected6_dnssearch3 = "gnu.org"; g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, - "*ipv4.addresses1*semicolon at the end*"); + "*ipv4.addresses:*semicolon at the end*addresses1*"); g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, - "*ipv4.addresses2*semicolon at the end*"); + "*ipv4.addresses:*semicolon at the end*addresses2*"); g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, - "*Missing prefix length*ipv4.address4*"); + "*missing prefix length*address4*"); g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, - "*Missing prefix length*ipv4.address5*"); + "*missing prefix length*address5*"); g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, - "*ipv4.routes2*semicolon at the end*"); + "*ipv4.routes*semicolon at the end*routes2*"); g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, - "*ipv4.routes3*semicolon at the end*"); + "*ipv4.routes*semicolon at the end*routes3*"); g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, - "*ipv4.routes5*semicolon at the end*"); + "*ipv4.routes*semicolon at the end*routes5*"); g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, - "*ipv4.routes8*semicolon at the end*"); + "*ipv4.routes*semicolon at the end*routes8*"); g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, - "*Missing prefix length*ipv6.address4*"); + "*missing prefix length*address4*"); g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, - "*ipv6.address5*semicolon at the end*"); + "*ipv6.address*semicolon at the end*address5*"); g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, - "*Missing prefix length*ipv6.address5*"); + "*missing prefix length*address5*"); g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, - "*ipv6.address7*semicolon at the end*"); + "*ipv6.address*semicolon at the end*address7*"); g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, - "*ipv6.routes1*semicolon at the end*"); + "*ipv6.routes*semicolon at the end*routes1*"); g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, - "*ipv6.route6*semicolon at the end*"); + "*ipv6.route*semicolon at the end*route6*"); connection = nm_keyfile_plugin_connection_from_file (TEST_WIRED_FILE, NULL); g_test_assert_expected_messages (); ASSERT (connection != NULL, @@ -773,11 +773,11 @@ test_read_wired_mac_case (void) const char *expected_uuid = "4e80a56d-c99f-4aad-a6dd-b449bc398c57"; g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, - "*ipv4.addresses1*semicolon at the end*"); + "*ipv4.addresses*semicolon at the end*addresses1*"); g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, - "*ipv4.addresses2*semicolon at the end*"); + "*ipv4.addresses*semicolon at the end*addresses2*"); g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, - "*ipv6.routes1*semicolon at the end*"); + "*ipv6.routes*semicolon at the end*routes1*"); connection = nm_keyfile_plugin_connection_from_file (TEST_WIRED_MAC_CASE_FILE, NULL); g_test_assert_expected_messages (); ASSERT (connection != NULL, diff --git a/src/settings/plugins/keyfile/utils.c b/src/settings/plugins/keyfile/utils.c index 30400b1bc4..4809c642c0 100644 --- a/src/settings/plugins/keyfile/utils.c +++ b/src/settings/plugins/keyfile/utils.c @@ -146,178 +146,3 @@ nm_keyfile_plugin_utils_escape_filename (const char *filename) return g_string_free (str, FALSE);; } - -typedef struct { - const char *setting; - const char *alias; -} SettingAlias; - -static const SettingAlias alias_list[] = { - { NM_SETTING_WIRED_SETTING_NAME, "ethernet" }, - { NM_SETTING_WIRELESS_SETTING_NAME, "wifi" }, - { NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, "wifi-security" }, -}; - -const char * -nm_keyfile_plugin_get_alias_for_setting_name (const char *setting_name) -{ - guint i; - - g_return_val_if_fail (setting_name != NULL, NULL); - - for (i = 0; i < G_N_ELEMENTS (alias_list); i++) { - if (strcmp (setting_name, alias_list[i].setting) == 0) - return alias_list[i].alias; - } - return NULL; -} - -const char * -nm_keyfile_plugin_get_setting_name_for_alias (const char *alias) -{ - guint i; - - g_return_val_if_fail (alias != NULL, NULL); - - for (i = 0; i < G_N_ELEMENTS (alias_list); i++) { - if (strcmp (alias, alias_list[i].alias) == 0) - return alias_list[i].setting; - } - return NULL; -} - -/**********************************************************************/ - -/* List helpers */ -#define DEFINE_KF_LIST_WRAPPER(stype, get_ctype, set_ctype) \ -get_ctype \ -nm_keyfile_plugin_kf_get_##stype##_list (GKeyFile *kf, \ - const char *group, \ - const char *key, \ - gsize *out_length, \ - GError **error) \ -{ \ - get_ctype list; \ - const char *alias; \ - GError *local = NULL; \ - \ - list = g_key_file_get_##stype##_list (kf, group, key, out_length, &local); \ - if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { \ - alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \ - if (alias) { \ - g_clear_error (&local); \ - list = g_key_file_get_##stype##_list (kf, alias, key, out_length, &local); \ - } \ - } \ - if (local) \ - g_propagate_error (error, local); \ - return list; \ -} \ - \ -void \ -nm_keyfile_plugin_kf_set_##stype##_list (GKeyFile *kf, \ - const char *group, \ - const char *key, \ - set_ctype list[], \ - gsize length) \ -{ \ - const char *alias; \ - \ - alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \ - g_key_file_set_##stype##_list (kf, alias ? alias : group, key, list, length); \ -} - -DEFINE_KF_LIST_WRAPPER(integer, gint*, gint); -DEFINE_KF_LIST_WRAPPER(string, gchar **, const gchar* const); - -/* Single value helpers */ -#define DEFINE_KF_WRAPPER(stype, get_ctype, set_ctype) \ -get_ctype \ -nm_keyfile_plugin_kf_get_##stype (GKeyFile *kf, \ - const char *group, \ - const char *key, \ - GError **error) \ -{ \ - get_ctype val; \ - const char *alias; \ - GError *local = NULL; \ - \ - val = g_key_file_get_##stype (kf, group, key, &local); \ - if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { \ - alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \ - if (alias) { \ - g_clear_error (&local); \ - val = g_key_file_get_##stype (kf, alias, key, &local); \ - } \ - } \ - if (local) \ - g_propagate_error (error, local); \ - return val; \ -} \ - \ -void \ -nm_keyfile_plugin_kf_set_##stype (GKeyFile *kf, \ - const char *group, \ - const char *key, \ - set_ctype value) \ -{ \ - const char *alias; \ - \ - alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \ - g_key_file_set_##stype (kf, alias ? alias : group, key, value); \ -} - -DEFINE_KF_WRAPPER(string, gchar*, const gchar*); -DEFINE_KF_WRAPPER(integer, gint, gint); -DEFINE_KF_WRAPPER(uint64, guint64, guint64); -DEFINE_KF_WRAPPER(boolean, gboolean, gboolean); -DEFINE_KF_WRAPPER(value, gchar*, const gchar*); - - -gchar ** -nm_keyfile_plugin_kf_get_keys (GKeyFile *kf, - const char *group, - gsize *out_length, - GError **error) -{ - gchar **keys; - const char *alias; - GError *local = NULL; - - keys = g_key_file_get_keys (kf, group, out_length, &local); - if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { - alias = nm_keyfile_plugin_get_alias_for_setting_name (group); - if (alias) { - g_clear_error (&local); - keys = g_key_file_get_keys (kf, alias, out_length, &local); - } - } - if (local) - g_propagate_error (error, local); - return keys; -} - -gboolean -nm_keyfile_plugin_kf_has_key (GKeyFile *kf, - const char *group, - const char *key, - GError **error) -{ - gboolean has; - const char *alias; - GError *local = NULL; - - has = g_key_file_has_key (kf, group, key, &local); - if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { - alias = nm_keyfile_plugin_get_alias_for_setting_name (group); - if (alias) { - g_clear_error (&local); - has = g_key_file_has_key (kf, alias, key, &local); - } - } - if (local) - g_propagate_error (error, local); - return has; -} - - diff --git a/src/settings/plugins/keyfile/utils.h b/src/settings/plugins/keyfile/utils.h index 456cd1a88f..d0862284cd 100644 --- a/src/settings/plugins/keyfile/utils.h +++ b/src/settings/plugins/keyfile/utils.h @@ -22,7 +22,6 @@ #define _UTILS_H_ #include -#include "common.h" #include "NetworkManagerUtils.h" #define NM_KEYFILE_CONNECTION_LOG_PATH(path) str_if_set (path,"in-memory") @@ -35,55 +34,5 @@ gboolean nm_keyfile_plugin_utils_should_ignore_file (const char *filename); char *nm_keyfile_plugin_utils_escape_filename (const char *filename); -const char *nm_keyfile_plugin_get_alias_for_setting_name (const char *setting_name); - -const char *nm_keyfile_plugin_get_setting_name_for_alias (const char *alias); - -/*********************************************************/ - -/* List helpers */ -#define DEFINE_KF_LIST_WRAPPER_PROTO(stype, get_ctype, set_ctype) \ -get_ctype nm_keyfile_plugin_kf_get_##stype##_list (GKeyFile *kf, \ - const char *group, \ - const char *key, \ - gsize *out_length, \ - GError **error); \ -\ -void nm_keyfile_plugin_kf_set_##stype##_list (GKeyFile *kf, \ - const char *group, \ - const char *key, \ - set_ctype list[], \ - gsize length); -DEFINE_KF_LIST_WRAPPER_PROTO(integer, gint*, gint) -DEFINE_KF_LIST_WRAPPER_PROTO(string, gchar**, const gchar* const) - -/* Single-value helpers */ -#define DEFINE_KF_WRAPPER_PROTO(stype, get_ctype, set_ctype) \ -get_ctype nm_keyfile_plugin_kf_get_##stype (GKeyFile *kf, \ - const char *group, \ - const char *key, \ - GError **error); \ -\ -void nm_keyfile_plugin_kf_set_##stype (GKeyFile *kf, \ - const char *group, \ - const char *key, \ - set_ctype value); -DEFINE_KF_WRAPPER_PROTO(string, gchar*, const gchar*) -DEFINE_KF_WRAPPER_PROTO(integer, gint, gint) -DEFINE_KF_WRAPPER_PROTO(uint64, guint64, guint64) -DEFINE_KF_WRAPPER_PROTO(boolean, gboolean, gboolean) -DEFINE_KF_WRAPPER_PROTO(value, gchar*, const gchar*) - -/* Misc */ -gchar ** nm_keyfile_plugin_kf_get_keys (GKeyFile *kf, - const char *group, - gsize *out_length, - GError **error); - -gboolean nm_keyfile_plugin_kf_has_key (GKeyFile *kf, - const char *group, - const char *key, - GError **error); - #endif /* _UTILS_H_ */ diff --git a/src/settings/plugins/keyfile/writer.c b/src/settings/plugins/keyfile/writer.c index a707ac3287..6b4a35b1b0 100644 --- a/src/settings/plugins/keyfile/writer.c +++ b/src/settings/plugins/keyfile/writer.c @@ -16,7 +16,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Copyright (C) 2008 Novell, Inc. - * Copyright (C) 2008 - 2012 Red Hat, Inc. + * Copyright (C) 2008 - 2015 Red Hat, Inc. */ #include "config.h" @@ -24,400 +24,20 @@ #include #include #include -#include #include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include "nm-glib-compat.h" #include "nm-logging.h" #include "writer.h" #include "common.h" #include "utils.h" +#include "nm-keyfile-internal.h" -/* Some setting properties also contain setting names, such as - * NMSettingConnection's 'type' property (which specifies the base type of the - * connection, eg ethernet or wifi) or the 802-11-wireless setting's - * 'security' property which specifies whether or not the AP requires - * encrpytion. This function handles translating those properties' values - * from the real setting name to the more-readable alias. - */ -static void -setting_alias_writer (GKeyFile *file, - const char *keyfile_dir, - const char *uuid, - NMSetting *setting, - const char *key, - const GValue *value) -{ - const char *str, *alias; - str = g_value_get_string (value); - alias = nm_keyfile_plugin_get_alias_for_setting_name (str); - nm_keyfile_plugin_kf_set_string (file, - nm_setting_get_name (setting), - key, - alias ? alias : str); -} +typedef struct { + const char *keyfile_dir; +} WriteInfo; -static gboolean -write_array_of_uint (GKeyFile *file, - NMSetting *setting, - const char *key, - const GValue *value) -{ - GArray *array; - int i; - int *tmp_array; - - array = (GArray *) g_value_get_boxed (value); - if (!array || !array->len) - return TRUE; - - tmp_array = g_new (gint, array->len); - for (i = 0; i < array->len; i++) - tmp_array[i] = g_array_index (array, int, i); - - nm_keyfile_plugin_kf_set_integer_list (file, nm_setting_get_name (setting), key, tmp_array, array->len); - g_free (tmp_array); - return TRUE; -} - -static void -dns_writer (GKeyFile *file, - const char *keyfile_dir, - const char *uuid, - NMSetting *setting, - const char *key, - const GValue *value) -{ - char **list; - - list = g_value_get_boxed (value); - if (list && list[0]) { - nm_keyfile_plugin_kf_set_string_list (file, nm_setting_get_name (setting), key, - (const char **) list, g_strv_length (list)); - } -} - -static void -write_ip_values (GKeyFile *file, - const char *setting_name, - GPtrArray *array, - const char *gateway, - gboolean is_route) -{ - GString *output; - int family, i; - const char *addr, *gw; - guint32 plen, metric; - char key_name[30], *key_name_idx; - - if (!array->len) - return; - - family = !strcmp (setting_name, NM_SETTING_IP4_CONFIG_SETTING_NAME) ? AF_INET : AF_INET6; - - strcpy (key_name, is_route ? "route" : "address"); - key_name_idx = key_name + strlen (key_name); - - output = g_string_sized_new (2*INET_ADDRSTRLEN + 10); - for (i = 0; i < array->len; i++) { - if (is_route) { - NMIPRoute *route = array->pdata[i]; - - addr = nm_ip_route_get_dest (route); - plen = nm_ip_route_get_prefix (route); - gw = nm_ip_route_get_next_hop (route); - metric = MAX (0, nm_ip_route_get_metric (route)); - } else { - NMIPAddress *address = array->pdata[i]; - - addr = nm_ip_address_get_address (address); - plen = nm_ip_address_get_prefix (address); - gw = i == 0 ? gateway : NULL; - metric = 0; - } - - g_string_set_size (output, 0); - g_string_append_printf (output, "%s/%u", addr, plen); - if (metric || gw) { - /* Older versions of the plugin do not support the form - * "a.b.c.d/plen,,metric", so, we always have to write the - * gateway, even if there isn't one. - * The current version supports reading of the above form. - */ - if (!gw) { - if (family == AF_INET) - gw = "0.0.0.0"; - else - gw = "::"; - } - - g_string_append_printf (output, ",%s", gw); - if (metric) - g_string_append_printf (output, ",%lu", (unsigned long) metric); - } - - sprintf (key_name_idx, "%d", i + 1); - nm_keyfile_plugin_kf_set_string (file, setting_name, key_name, output->str); - } - g_string_free (output, TRUE); -} - -static void -addr_writer (GKeyFile *file, - const char *keyfile_dir, - const char *uuid, - NMSetting *setting, - const char *key, - const GValue *value) -{ - GPtrArray *array; - const char *setting_name = nm_setting_get_name (setting); - const char *gateway = nm_setting_ip_config_get_gateway (NM_SETTING_IP_CONFIG (setting)); - - array = (GPtrArray *) g_value_get_boxed (value); - if (array && array->len) - write_ip_values (file, setting_name, array, gateway, FALSE); -} - -static void -ip4_addr_label_writer (GKeyFile *file, - const char *keyfile_dir, - const char *uuid, - NMSetting *setting, - const char *key, - const GValue *value) -{ - /* skip */ -} - -static void -gateway_writer (GKeyFile *file, - const char *keyfile_dir, - const char *uuid, - NMSetting *setting, - const char *key, - const GValue *value) -{ - /* skip */ -} - -static void -route_writer (GKeyFile *file, - const char *keyfile_dir, - const char *uuid, - NMSetting *setting, - const char *key, - const GValue *value) -{ - GPtrArray *array; - const char *setting_name = nm_setting_get_name (setting); - - array = (GPtrArray *) g_value_get_boxed (value); - if (array && array->len) - write_ip_values (file, setting_name, array, NULL, TRUE); -} - -static void -write_hash_of_string (GKeyFile *file, - NMSetting *setting, - const char *key, - const GValue *value) -{ - GHashTableIter iter; - const char *property = NULL, *data = NULL; - const char *group_name = nm_setting_get_name (setting); - gboolean vpn_secrets = FALSE; - - /* Write VPN secrets out to a different group to keep them separate */ - if (NM_IS_SETTING_VPN (setting) && !strcmp (key, NM_SETTING_VPN_SECRETS)) { - group_name = VPN_SECRETS_GROUP; - vpn_secrets = TRUE; - } - - g_hash_table_iter_init (&iter, (GHashTable *) g_value_get_boxed (value)); - while (g_hash_table_iter_next (&iter, (gpointer *) &property, (gpointer *) &data)) { - gboolean write_item = TRUE; - - /* Handle VPN secrets specially; they are nested in the property's hash; - * we don't want to write them if the secret is not saved, not required, - * or owned by a user's secret agent. - */ - if (vpn_secrets) { - NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE; - - nm_setting_get_secret_flags (setting, property, &secret_flags, NULL); - if (secret_flags != NM_SETTING_SECRET_FLAG_NONE) - write_item = FALSE; - } - - if (write_item) - nm_keyfile_plugin_kf_set_string (file, group_name, property, data); - } -} - -static void -ssid_writer (GKeyFile *file, - const char *keyfile_dir, - const char *uuid, - NMSetting *setting, - const char *key, - const GValue *value) -{ - GBytes *bytes; - const guint8 *ssid_data; - gsize ssid_len; - const char *setting_name = nm_setting_get_name (setting); - gboolean new_format = TRUE; - unsigned int semicolons = 0; - int i, *tmp_array; - char *ssid; - - g_return_if_fail (G_VALUE_HOLDS (value, G_TYPE_BYTES)); - - bytes = g_value_get_boxed (value); - if (!bytes) - return; - ssid_data = g_bytes_get_data (bytes, &ssid_len); - if (ssid_len == 0) - return; - - /* Check whether each byte is printable. If not, we have to use an - * integer list, otherwise we can just use a string. - */ - for (i = 0; i < ssid_len; i++) { - char c = ssid_data[i] & 0xFF; - if (!g_ascii_isprint (c)) { - new_format = FALSE; - break; - } - if (c == ';') - semicolons++; - } - - if (new_format) { - ssid = g_malloc0 (ssid_len + semicolons + 1); - if (semicolons == 0) - memcpy (ssid, ssid_data, ssid_len); - else { - /* Escape semicolons with backslashes to make strings - * containing ';', such as '16;17;' unambiguous */ - int j = 0; - for (i = 0; i < ssid_len; i++) { - if (ssid_data[i] == ';') - ssid[j++] = '\\'; - ssid[j++] = ssid_data[i]; - } - } - nm_keyfile_plugin_kf_set_string (file, setting_name, key, ssid); - g_free (ssid); - } else { - tmp_array = g_new (gint, ssid_len); - for (i = 0; i < ssid_len; i++) - tmp_array[i] = (int) ssid_data[i]; - nm_keyfile_plugin_kf_set_integer_list (file, setting_name, key, tmp_array, ssid_len); - g_free (tmp_array); - } -} - -static void -password_raw_writer (GKeyFile *file, - const char *keyfile_dir, - const char *uuid, - NMSetting *setting, - const char *key, - const GValue *value) -{ - const char *setting_name = nm_setting_get_name (setting); - GBytes *array; - int *tmp_array; - gsize i, len; - const char *data; - - g_return_if_fail (G_VALUE_HOLDS (value, G_TYPE_BYTES)); - - array = (GBytes *) g_value_get_boxed (value); - if (!array) - return; - data = g_bytes_get_data (array, &len); - if (!data || !len) - return; - - tmp_array = g_new (gint, len); - for (i = 0; i < len; i++) - tmp_array[i] = (int) data[i]; - nm_keyfile_plugin_kf_set_integer_list (file, setting_name, key, tmp_array, len); - g_free (tmp_array); -} - -typedef struct ObjectType { - const char *key; - const char *suffix; - NMSetting8021xCKScheme (*scheme_func) (NMSetting8021x *setting); - NMSetting8021xCKFormat (*format_func) (NMSetting8021x *setting); - const char * (*path_func) (NMSetting8021x *setting); - GBytes * (*blob_func) (NMSetting8021x *setting); -} ObjectType; - -static const ObjectType objtypes[10] = { - { NM_SETTING_802_1X_CA_CERT, - "ca-cert", - nm_setting_802_1x_get_ca_cert_scheme, - NULL, - nm_setting_802_1x_get_ca_cert_path, - nm_setting_802_1x_get_ca_cert_blob }, - - { NM_SETTING_802_1X_PHASE2_CA_CERT, - "inner-ca-cert", - nm_setting_802_1x_get_phase2_ca_cert_scheme, - NULL, - nm_setting_802_1x_get_phase2_ca_cert_path, - nm_setting_802_1x_get_phase2_ca_cert_blob }, - - { NM_SETTING_802_1X_CLIENT_CERT, - "client-cert", - nm_setting_802_1x_get_client_cert_scheme, - NULL, - nm_setting_802_1x_get_client_cert_path, - nm_setting_802_1x_get_client_cert_blob }, - - { NM_SETTING_802_1X_PHASE2_CLIENT_CERT, - "inner-client-cert", - nm_setting_802_1x_get_phase2_client_cert_scheme, - NULL, - nm_setting_802_1x_get_phase2_client_cert_path, - nm_setting_802_1x_get_phase2_client_cert_blob }, - - { NM_SETTING_802_1X_PRIVATE_KEY, - "private-key", - nm_setting_802_1x_get_private_key_scheme, - nm_setting_802_1x_get_private_key_format, - nm_setting_802_1x_get_private_key_path, - nm_setting_802_1x_get_private_key_blob }, - - { NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, - "inner-private-key", - nm_setting_802_1x_get_phase2_private_key_scheme, - nm_setting_802_1x_get_phase2_private_key_format, - nm_setting_802_1x_get_phase2_private_key_path, - nm_setting_802_1x_get_phase2_private_key_blob }, - - { NULL }, -}; static gboolean write_cert_key_file (const char *path, @@ -483,61 +103,52 @@ out: } static void -cert_writer (GKeyFile *file, - const char *keyfile_dir, - const char *uuid, - NMSetting *setting, - const char *key, - const GValue *value) +cert_writer (NMConnection *connection, + GKeyFile *file, + NMKeyfileWriteTypeDataCert *cert_data, + WriteInfo *info, + GError **error) { - const char *setting_name = nm_setting_get_name (setting); + const char *setting_name = nm_setting_get_name (NM_SETTING (cert_data->setting)); NMSetting8021xCKScheme scheme; NMSetting8021xCKFormat format; const char *path = NULL, *ext = "pem"; - const ObjectType *objtype = NULL; - int i; - for (i = 0; i < G_N_ELEMENTS (objtypes) && objtypes[i].key; i++) { - if (g_strcmp0 (objtypes[i].key, key) == 0) { - objtype = &objtypes[i]; - break; - } - } - if (!objtype) { - g_return_if_fail (objtype); - return; - } - - scheme = objtype->scheme_func (NM_SETTING_802_1X (setting)); + scheme = cert_data->scheme_func (cert_data->setting); if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) { - path = objtype->path_func (NM_SETTING_802_1X (setting)); + path = cert_data->path_func (cert_data->setting); g_assert (path); /* If the path is rooted in the keyfile directory, just use a * relative path instead of an absolute one. */ - if (g_str_has_prefix (path, keyfile_dir)) { - path += strlen (keyfile_dir); - while (*path == '/') - path++; + if (g_str_has_prefix (path, info->keyfile_dir)) { + const char *p = path + strlen (info->keyfile_dir); + + if (*p == '/') { + while (*p == '/') + p++; + if (p[0]) + path = p; + } } - nm_keyfile_plugin_kf_set_string (file, setting_name, key, path); + nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->property_name, path); } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) { GBytes *blob; const guint8 *blob_data; gsize blob_len; gboolean success; - GError *error = NULL; + GError *local = NULL; char *new_path; - blob = objtype->blob_func (NM_SETTING_802_1X (setting)); + blob = cert_data->blob_func (cert_data->setting); g_assert (blob); blob_data = g_bytes_get_data (blob, &blob_len); - if (objtype->format_func) { + if (cert_data->format_func) { /* Get the extension for a private key */ - format = objtype->format_func (NM_SETTING_802_1X (setting)); + format = cert_data->format_func (cert_data->setting); if (format == NM_SETTING_802_1X_CK_FORMAT_PKCS12) ext = "p12"; } else { @@ -549,17 +160,17 @@ cert_writer (GKeyFile *file, /* Write the raw data out to the standard file so that we can use paths * from now on instead of pushing around the certificate data. */ - new_path = g_strdup_printf ("%s/%s-%s.%s", keyfile_dir, uuid, objtype->suffix, ext); - g_assert (new_path); + new_path = g_strdup_printf ("%s/%s-%s.%s", info->keyfile_dir, nm_connection_get_uuid (connection), + cert_data->suffix, ext); - success = write_cert_key_file (new_path, blob_data, blob_len, &error); + success = write_cert_key_file (new_path, blob_data, blob_len, &local); if (success) { /* Write the path value to the keyfile */ - nm_keyfile_plugin_kf_set_string (file, setting_name, key, new_path); + nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->property_name, new_path); } else { - nm_log_warn (LOGD_SETTINGS, "Failed to write certificate/key %s: %s", - new_path, error->message); - g_error_free (error); + nm_log_warn (LOGD_SETTINGS, "keyfile: %s.%s: failed to write certificate to file %s: %s", + setting_name, cert_data->property_name, new_path, local->message); + g_error_free (local); } g_free (new_path); } else { @@ -573,209 +184,21 @@ cert_writer (GKeyFile *file, } } -typedef struct { - const char *setting_name; - const char *key; - void (*writer) (GKeyFile *keyfile, - const char *keyfile_dir, - const char *uuid, - NMSetting *setting, - const char *key, - const GValue *value); -} KeyWriter; - -/* A table of keys that require further parsing/conversion because they are - * stored in a format that can't be automatically read using the key's type. - * i.e. IPv4 addresses, which are stored in NetworkManager as guint32, but are - * stored in keyfiles as strings, eg "10.1.1.2" or IPv6 addresses stored - * in struct in6_addr internally, but as string in keyfiles. - */ -static KeyWriter key_writers[] = { - { NM_SETTING_CONNECTION_SETTING_NAME, - NM_SETTING_CONNECTION_TYPE, - setting_alias_writer }, - { NM_SETTING_IP4_CONFIG_SETTING_NAME, - NM_SETTING_IP_CONFIG_ADDRESSES, - addr_writer }, - { NM_SETTING_IP4_CONFIG_SETTING_NAME, - "address-labels", - ip4_addr_label_writer }, - { NM_SETTING_IP6_CONFIG_SETTING_NAME, - NM_SETTING_IP_CONFIG_ADDRESSES, - addr_writer }, - { NM_SETTING_IP4_CONFIG_SETTING_NAME, - NM_SETTING_IP_CONFIG_GATEWAY, - gateway_writer }, - { NM_SETTING_IP6_CONFIG_SETTING_NAME, - NM_SETTING_IP_CONFIG_GATEWAY, - gateway_writer }, - { NM_SETTING_IP4_CONFIG_SETTING_NAME, - NM_SETTING_IP_CONFIG_ROUTES, - route_writer }, - { NM_SETTING_IP6_CONFIG_SETTING_NAME, - NM_SETTING_IP_CONFIG_ROUTES, - route_writer }, - { NM_SETTING_IP4_CONFIG_SETTING_NAME, - NM_SETTING_IP_CONFIG_DNS, - dns_writer }, - { NM_SETTING_IP6_CONFIG_SETTING_NAME, - NM_SETTING_IP_CONFIG_DNS, - dns_writer }, - { NM_SETTING_WIRELESS_SETTING_NAME, - NM_SETTING_WIRELESS_SSID, - ssid_writer }, - { NM_SETTING_802_1X_SETTING_NAME, - NM_SETTING_802_1X_PASSWORD_RAW, - password_raw_writer }, - { NM_SETTING_802_1X_SETTING_NAME, - NM_SETTING_802_1X_CA_CERT, - cert_writer }, - { NM_SETTING_802_1X_SETTING_NAME, - NM_SETTING_802_1X_CLIENT_CERT, - cert_writer }, - { NM_SETTING_802_1X_SETTING_NAME, - NM_SETTING_802_1X_PRIVATE_KEY, - cert_writer }, - { NM_SETTING_802_1X_SETTING_NAME, - NM_SETTING_802_1X_PHASE2_CA_CERT, - cert_writer }, - { NM_SETTING_802_1X_SETTING_NAME, - NM_SETTING_802_1X_PHASE2_CLIENT_CERT, - cert_writer }, - { NM_SETTING_802_1X_SETTING_NAME, - NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, - cert_writer }, - { NULL, NULL, NULL } -}; - -typedef struct { - GKeyFile *keyfile; - const char *keyfile_dir; - const char *uuid; -} WriteInfo; - -static void -write_setting_value (NMSetting *setting, - const char *key, - const GValue *value, - GParamFlags flag, - gpointer user_data) +static gboolean +_handler_write (NMConnection *connection, + GKeyFile *keyfile, + NMKeyfileWriteType type, + void *type_data, + void *user_data, + GError **error) { - WriteInfo *info = user_data; - const char *setting_name; - GType type = G_VALUE_TYPE (value); - KeyWriter *writer = &key_writers[0]; - GParamSpec *pspec; - - /* Setting name gets picked up from the keyfile's section name instead */ - if (!strcmp (key, NM_SETTING_NAME)) - return; - - /* Don't write the NMSettingConnection object's 'read-only' property */ - if ( NM_IS_SETTING_CONNECTION (setting) - && !strcmp (key, NM_SETTING_CONNECTION_READ_ONLY)) - return; - - setting_name = nm_setting_get_name (setting); - - /* If the value is the default value, remove the item from the keyfile */ - pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (setting), key); - if (pspec) { - if (g_param_value_defaults (pspec, (GValue *) value)) { - g_key_file_remove_key (info->keyfile, setting_name, key, NULL); - return; - } - } - - /* Don't write secrets that are owned by user secret agents or aren't - * supposed to be saved. VPN secrets are handled specially though since - * the secret flags there are in a third-level hash in the 'secrets' - * property. - */ - if (pspec && (pspec->flags & NM_SETTING_PARAM_SECRET) && !NM_IS_SETTING_VPN (setting)) { - NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE; - - if (!nm_setting_get_secret_flags (setting, key, &secret_flags, NULL)) - g_assert_not_reached (); - if (secret_flags != NM_SETTING_SECRET_FLAG_NONE) - return; - } - - /* Look through the list of handlers for non-standard format key values */ - while (writer->setting_name) { - if (!strcmp (writer->setting_name, setting_name) && !strcmp (writer->key, key)) { - (*writer->writer) (info->keyfile, info->keyfile_dir, info->uuid, setting, key, value); - return; - } - writer++; - } - - if (type == G_TYPE_STRING) { - const char *str; - - str = g_value_get_string (value); - if (str) - nm_keyfile_plugin_kf_set_string (info->keyfile, setting_name, key, str); - } else if (type == G_TYPE_UINT) - nm_keyfile_plugin_kf_set_integer (info->keyfile, setting_name, key, (int) g_value_get_uint (value)); - else if (type == G_TYPE_INT) - nm_keyfile_plugin_kf_set_integer (info->keyfile, setting_name, key, g_value_get_int (value)); - else if (type == G_TYPE_UINT64) { - char *numstr; - - numstr = g_strdup_printf ("%" G_GUINT64_FORMAT, g_value_get_uint64 (value)); - nm_keyfile_plugin_kf_set_value (info->keyfile, setting_name, key, numstr); - g_free (numstr); - } else if (type == G_TYPE_INT64) { - char *numstr; - - numstr = g_strdup_printf ("%" G_GINT64_FORMAT, g_value_get_int64 (value)); - nm_keyfile_plugin_kf_set_value (info->keyfile, setting_name, key, numstr); - g_free (numstr); - } else if (type == G_TYPE_BOOLEAN) { - nm_keyfile_plugin_kf_set_boolean (info->keyfile, setting_name, key, g_value_get_boolean (value)); - } else if (type == G_TYPE_CHAR) { - nm_keyfile_plugin_kf_set_integer (info->keyfile, setting_name, key, (int) g_value_get_schar (value)); - } else if (type == G_TYPE_BYTES) { - GBytes *bytes; - const guint8 *data; - gsize len = 0; - - bytes = g_value_get_boxed (value); - data = bytes ? g_bytes_get_data (bytes, &len) : NULL; - - if (data != NULL && len > 0) { - int *tmp_array; - int i; - - tmp_array = g_new (gint, len); - for (i = 0; i < len; i++) - tmp_array[i] = (int) data[i]; - - nm_keyfile_plugin_kf_set_integer_list (info->keyfile, setting_name, key, tmp_array, len); - g_free (tmp_array); - } - } else if (type == G_TYPE_STRV) { - char **array; - - array = (char **) g_value_get_boxed (value); - nm_keyfile_plugin_kf_set_string_list (info->keyfile, setting_name, key, (const gchar **const) array, g_strv_length (array)); - } else if (type == G_TYPE_HASH_TABLE) { - write_hash_of_string (info->keyfile, setting, key, value); - } else if (type == G_TYPE_ARRAY) { - if (!write_array_of_uint (info->keyfile, setting, key, value)) { - nm_log_warn (LOGD_SETTINGS, "Unhandled setting property type (write) '%s/%s' : '%s'", - setting_name, key, g_type_name (type)); - } - } else if (G_VALUE_HOLDS_FLAGS (value)) { - /* Flags are guint but GKeyFile has no uint reader, just uint64 */ - nm_keyfile_plugin_kf_set_uint64 (info->keyfile, setting_name, key, (guint64) g_value_get_flags (value)); - } else if (G_VALUE_HOLDS_ENUM (value)) - nm_keyfile_plugin_kf_set_integer (info->keyfile, setting_name, key, (gint) g_value_get_enum (value)); - else { - nm_log_warn (LOGD_SETTINGS, "Unhandled setting property type (write) '%s/%s' : '%s'", - setting_name, key, g_type_name (type)); + if (type == NM_KEYFILE_WRITE_TYPE_CERT) { + cert_writer (connection, keyfile, + (NMKeyfileWriteTypeDataCert *) type_data, + (WriteInfo *) user_data, error); + return TRUE; } + return FALSE; } static gboolean @@ -793,10 +216,11 @@ _internal_write_connection (NMConnection *connection, gboolean success = FALSE; char *path; const char *id; - WriteInfo info; + WriteInfo info = { 0 }; GError *local_err = NULL; g_return_val_if_fail (!out_path || !*out_path, FALSE); + g_return_val_if_fail (keyfile_dir && keyfile_dir[0] == '/', FALSE); if (!nm_connection_verify (connection, error)) g_return_val_if_reached (FALSE); @@ -804,14 +228,15 @@ _internal_write_connection (NMConnection *connection, id = nm_connection_get_id (connection); g_assert (id && *id); - info.keyfile = key_file = g_key_file_new (); info.keyfile_dir = keyfile_dir; - info.uuid = nm_connection_get_uuid (connection); - g_assert (info.uuid); - nm_connection_for_each_setting_value (connection, write_setting_value, &info); + + key_file = nm_keyfile_write (connection, _handler_write, &info, error); + if (!key_file) + return FALSE; data = g_key_file_to_data (key_file, &len, error); + g_key_file_unref (key_file); if (!data) - goto out; + return FALSE; /* If we have existing file path, use it. Else generate one from * connection's ID. @@ -910,7 +335,6 @@ _internal_write_connection (NMConnection *connection, out: g_free (data); - g_key_file_free (key_file); return success; } diff --git a/src/settings/plugins/keyfile/writer.h b/src/settings/plugins/keyfile/writer.h index a602f2f4a3..8b08812d86 100644 --- a/src/settings/plugins/keyfile/writer.h +++ b/src/settings/plugins/keyfile/writer.h @@ -22,7 +22,6 @@ #ifndef _KEYFILE_PLUGIN_WRITER_H #define _KEYFILE_PLUGIN_WRITER_H -#include #include #include From a49680dacd73549169bfad1ba3bd94e1a7bb1ac1 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 24 Feb 2015 22:22:02 +0100 Subject: [PATCH 21/23] libnm: add define for cert scheme prefix file:// for NMSetting8021x --- clients/cli/settings.c | 11 +++++------ libnm-core/nm-keyfile-internal.h | 3 +++ libnm-core/nm-keyfile-reader.c | 10 ++++------ libnm-core/nm-setting-8021x.c | 26 ++++++++++++-------------- libnm-core/nm-setting-8021x.h | 2 ++ libnm-core/tests/test-setting-8021x.c | 6 ++---- 6 files changed, 28 insertions(+), 30 deletions(-) diff --git a/clients/cli/settings.c b/clients/cli/settings.c index 47c7321756..7da47d43a8 100644 --- a/clients/cli/settings.c +++ b/clients/cli/settings.c @@ -29,6 +29,7 @@ #include "common.h" #include "settings.h" #include "nm-glib-compat.h" +#include "nm-utils-internal.h" /* Forward declarations */ static char *wep_key_type_to_string (NMWepKeyType type); @@ -2691,13 +2692,12 @@ nmc_property_connection_describe_secondaries (NMSetting *setting, const char *pr static gboolean \ def_func (NMSetting *setting, const char *prop, const char *val, GError **error) \ { \ - const char *SCHEME_PATH = "file://"; \ char *val_strip = g_strstrip (g_strdup (val)); \ char *p = val_strip; \ gboolean success; \ \ - if (strncmp (val_strip, SCHEME_PATH, strlen (SCHEME_PATH)) == 0) \ - p += strlen (SCHEME_PATH); \ + if (strncmp (val_strip, NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH, STRLEN (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH)) == 0) \ + p += STRLEN (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH); \ \ success = set_func (NM_SETTING_802_1X (setting), \ p, \ @@ -2713,14 +2713,13 @@ nmc_property_connection_describe_secondaries (NMSetting *setting, const char *pr def_func (NMSetting *setting, const char *prop, const char *val, GError **error) \ { \ char **strv = NULL; \ - const char *SCHEME_PATH = "file://"; \ char *val_strip = g_strstrip (g_strdup (val)); \ char *p = val_strip; \ const char *path, *password; \ gboolean success; \ \ - if (strncmp (val_strip, SCHEME_PATH, strlen (SCHEME_PATH)) == 0) \ - p += strlen (SCHEME_PATH); \ + if (strncmp (val_strip, NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH, STRLEN (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH)) == 0) \ + p += STRLEN (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH); \ \ strv = nmc_strsplit_set (p, " \t,", 2); \ path = strv[0]; \ diff --git a/libnm-core/nm-keyfile-internal.h b/libnm-core/nm-keyfile-internal.h index c5c17fe292..995391747c 100644 --- a/libnm-core/nm-keyfile-internal.h +++ b/libnm-core/nm-keyfile-internal.h @@ -31,6 +31,9 @@ /*********************************************************/ +#define NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB "data:;base64," +#define NM_KEYFILE_CERT_SCHEME_PREFIX_PATH "file://" + typedef enum { NM_KEYFILE_READ_TYPE_WARN = 1, } NMKeyfileReadType; diff --git a/libnm-core/nm-keyfile-reader.c b/libnm-core/nm-keyfile-reader.c index 264acc8ba1..aa54b7bd77 100644 --- a/libnm-core/nm-keyfile-reader.c +++ b/libnm-core/nm-keyfile-reader.c @@ -823,8 +823,6 @@ get_cert_path (const char *base_dir, const guint8 *cert_path, gsize cert_path_le return tmp; } -#define SCHEME_PATH "file://" - static const char *certext[] = { ".pem", ".cert", ".crt", ".cer", ".p12", ".der", ".key" }; static gboolean @@ -848,8 +846,8 @@ handle_as_scheme (GBytes *bytes, NMSetting *setting, const char *key) data = g_bytes_get_data (bytes, &data_len); /* It's the PATH scheme, can just set plain data */ - if ( (data_len > strlen (SCHEME_PATH)) - && g_str_has_prefix ((const char *) data, SCHEME_PATH) + if ( (data_len > strlen (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)) + && g_str_has_prefix ((const char *) data, NM_KEYFILE_CERT_SCHEME_PREFIX_PATH) && (data[data_len - 1] == '\0')) { g_object_set (setting, key, bytes, NULL); return TRUE; @@ -899,8 +897,8 @@ handle_as_path (KeyfileReaderInfo *info, GBytes *val; /* Construct the proper value as required for the PATH scheme */ - tmp = g_byte_array_sized_new (strlen (SCHEME_PATH) + strlen (path) + 1); - g_byte_array_append (tmp, (const guint8 *) SCHEME_PATH, strlen (SCHEME_PATH)); + tmp = g_byte_array_sized_new (strlen (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH) + strlen (path) + 1); + g_byte_array_append (tmp, (const guint8 *) NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, strlen (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)); g_byte_array_append (tmp, (const guint8 *) path, strlen (path)); g_byte_array_append (tmp, (const guint8 *) "\0", 1); val = g_byte_array_free_to_bytes (tmp); diff --git a/libnm-core/nm-setting-8021x.c b/libnm-core/nm-setting-8021x.c index 6a54b575c9..f355c18e70 100644 --- a/libnm-core/nm-setting-8021x.c +++ b/libnm-core/nm-setting-8021x.c @@ -62,8 +62,6 @@ * ISBN: 978-1587051548 **/ -#define SCHEME_PATH "file://" - G_DEFINE_TYPE_WITH_CODE (NMSetting8021x, nm_setting_802_1x, NM_TYPE_SETTING, _nm_register_setting (802_1X, 2)) NM_SETTING_REGISTER_TYPE (NM_TYPE_SETTING_802_1X) @@ -450,8 +448,8 @@ nm_setting_802_1x_check_cert_scheme (gconstpointer pdata, gsize length, GError * } /* interpret the blob as PATH if it starts with "file://". */ - if ( length >= STRLEN (SCHEME_PATH) - && !memcmp (data, SCHEME_PATH, STRLEN (SCHEME_PATH))) { + if ( length >= STRLEN (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH) + && !memcmp (data, NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH, STRLEN (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH))) { /* But it must also be NUL terminated, contain at least * one non-NUL character, and contain only one trailing NUL * chracter. @@ -467,7 +465,7 @@ nm_setting_802_1x_check_cert_scheme (gconstpointer pdata, gsize length, GError * } length--; - if (length <= STRLEN (SCHEME_PATH)) { + if (length <= STRLEN (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH)) { g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, @@ -475,7 +473,7 @@ nm_setting_802_1x_check_cert_scheme (gconstpointer pdata, gsize length, GError * return NM_SETTING_802_1X_CK_SCHEME_UNKNOWN; } - if (!g_utf8_validate (data + STRLEN (SCHEME_PATH), length - STRLEN (SCHEME_PATH), NULL)) { + if (!g_utf8_validate (data + STRLEN (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH), length - STRLEN (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH), NULL)) { g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, @@ -587,7 +585,7 @@ nm_setting_802_1x_get_ca_cert_path (NMSetting8021x *setting) g_return_val_if_fail (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH, NULL); data = g_bytes_get_data (NM_SETTING_802_1X_GET_PRIVATE (setting)->ca_cert, NULL); - return (const char *)data + strlen (SCHEME_PATH); + return (const char *)data + strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH); } static GBytes * @@ -601,8 +599,8 @@ path_to_scheme_value (const char *path) len = strlen (path); /* Add the path scheme tag to the front, then the filename */ - array = g_byte_array_sized_new (len + strlen (SCHEME_PATH) + 1); - g_byte_array_append (array, (const guint8 *) SCHEME_PATH, strlen (SCHEME_PATH)); + array = g_byte_array_sized_new (len + strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH) + 1); + g_byte_array_append (array, (const guint8 *) NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH, strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH)); g_byte_array_append (array, (const guint8 *) path, len); g_byte_array_append (array, (const guint8 *) "\0", 1); @@ -916,7 +914,7 @@ nm_setting_802_1x_get_client_cert_path (NMSetting8021x *setting) g_return_val_if_fail (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH, NULL); data = g_bytes_get_data (NM_SETTING_802_1X_GET_PRIVATE (setting)->client_cert, NULL); - return (const char *)data + strlen (SCHEME_PATH); + return (const char *)data + strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH); } /** @@ -1185,7 +1183,7 @@ nm_setting_802_1x_get_phase2_ca_cert_path (NMSetting8021x *setting) g_return_val_if_fail (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH, NULL); data = g_bytes_get_data (NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_ca_cert, NULL); - return (const char *)data + strlen (SCHEME_PATH); + return (const char *)data + strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH); } /** @@ -1499,7 +1497,7 @@ nm_setting_802_1x_get_phase2_client_cert_path (NMSetting8021x *setting) g_return_val_if_fail (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH, NULL); data = g_bytes_get_data (NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_client_cert, NULL); - return (const char *)data + strlen (SCHEME_PATH); + return (const char *)data + strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH); } /** @@ -1758,7 +1756,7 @@ nm_setting_802_1x_get_private_key_path (NMSetting8021x *setting) g_return_val_if_fail (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH, NULL); data = g_bytes_get_data (NM_SETTING_802_1X_GET_PRIVATE (setting)->private_key, NULL); - return (const char *)data + strlen (SCHEME_PATH); + return (const char *)data + strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH); } static void @@ -2096,7 +2094,7 @@ nm_setting_802_1x_get_phase2_private_key_path (NMSetting8021x *setting) g_return_val_if_fail (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH, NULL); data = g_bytes_get_data (NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_private_key, NULL); - return (const char *)data + strlen (SCHEME_PATH); + return (const char *)data + strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH); } /** diff --git a/libnm-core/nm-setting-8021x.h b/libnm-core/nm-setting-8021x.h index 326165aa59..da86071a63 100644 --- a/libnm-core/nm-setting-8021x.h +++ b/libnm-core/nm-setting-8021x.h @@ -31,6 +31,8 @@ G_BEGIN_DECLS +#define NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH "file://" + /** * NMSetting8021xCKFormat: * @NM_SETTING_802_1X_CK_FORMAT_UNKNOWN: unknown file format diff --git a/libnm-core/tests/test-setting-8021x.c b/libnm-core/tests/test-setting-8021x.c index 992379c7a2..16f6016e23 100644 --- a/libnm-core/tests/test-setting-8021x.c +++ b/libnm-core/tests/test-setting-8021x.c @@ -57,15 +57,13 @@ compare_blob_data (const char *test, g_free (contents); } -#define SCHEME_PATH "file://" - static void check_scheme_path (GBytes *value, const char *path) { const guint8 *p = g_bytes_get_data (value, NULL); - g_assert (memcmp (p, SCHEME_PATH, strlen (SCHEME_PATH)) == 0); - p += strlen (SCHEME_PATH); + g_assert (memcmp (p, NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH, strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH)) == 0); + p += strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH); g_assert (memcmp (p, path, strlen (path)) == 0); p += strlen (path); g_assert (*p == '\0'); From c9a8764ad2805e22e1c556679e09636550374e21 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 24 Feb 2015 22:22:14 +0100 Subject: [PATCH 22/23] keyfile: support writing certificates as blob inside the keyfile keyfile should become our main import/export format. It is desirable, that a keyfile can contain every aspect of a connection. For blob certificates, the writer in core daemon would always write them to a file and convert the scheme to path. This behavior is not great for a (hyptetical) `nmcli connection export` command because it would have to export them somehow outside of keyfile, e.g. by writing them to temporary files. Instead, if the write handler does not handle a certificate, use a default implementation in nm_keyfile_write() which adds the blob inside the keyfile. Interestingly, keyfile reader already supported reading certificate blobs. But this legacy format accepts the blob as arbitrary binary without marking the format and without scheme prefix. Instead of writing the binary data directly, write it with a new uri scheme "data:;base64," and encode it in base64. Also go through some lengths to make sure that whatever path keyfile plugin writes, can be read back again. That is, because keyfile writer preferably writes relative paths without prefix. Add nm_keyfile_detect_unqualified_path_scheme() to encapsulate the detection of pathnames without file:// prefix and use it to check whether the path name must be fully qualified. --- .gitignore | 1 + libnm-core/nm-keyfile-internal.h | 7 + libnm-core/nm-keyfile-reader.c | 263 ++++++--- libnm-core/nm-keyfile-writer.c | 109 +++- libnm-core/tests/Makefile.am | 3 + libnm-core/tests/certs/test-ca-cert.pem | 27 + libnm-core/tests/certs/test-key-and-cert.pem | 118 ++++ libnm-core/tests/test-keyfile.c | 505 ++++++++++++++++++ src/settings/plugins/keyfile/reader.c | 2 + .../tests/keyfiles/Test_Wired_TLS_Blob | 4 +- .../keyfile/tests/keyfiles/Test_Wired_TLS_Old | 6 +- .../plugins/keyfile/tests/test-keyfile.c | 20 +- src/settings/plugins/keyfile/writer.c | 40 +- 13 files changed, 999 insertions(+), 106 deletions(-) create mode 100644 libnm-core/tests/certs/test-ca-cert.pem create mode 100644 libnm-core/tests/certs/test-key-and-cert.pem create mode 100644 libnm-core/tests/test-keyfile.c diff --git a/.gitignore b/.gitignore index 40448f92bc..397524e3f0 100644 --- a/.gitignore +++ b/.gitignore @@ -143,6 +143,7 @@ valgrind-*.log /libnm-core/tests/test-crypto /libnm-core/tests/test-settings-defaults /libnm-core/tests/test-general +/libnm-core/tests/test-keyfile /libnm-core/tests/test-need-secrets /libnm-core/tests/test-secrets /libnm-core/tests/test-setting-8021x diff --git a/libnm-core/nm-keyfile-internal.h b/libnm-core/nm-keyfile-internal.h index 995391747c..90af562cdf 100644 --- a/libnm-core/nm-keyfile-internal.h +++ b/libnm-core/nm-keyfile-internal.h @@ -34,6 +34,12 @@ #define NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB "data:;base64," #define NM_KEYFILE_CERT_SCHEME_PREFIX_PATH "file://" +char *nm_keyfile_detect_unqualified_path_scheme (const char *base_dir, + gconstpointer pdata, + gsize data_len, + gboolean consider_exists, + gboolean *out_exists); + typedef enum { NM_KEYFILE_READ_TYPE_WARN = 1, } NMKeyfileReadType; @@ -58,6 +64,7 @@ typedef gboolean (*NMKeyfileReadHandler) (GKeyFile *keyfile, typedef enum { NM_KEYFILE_WARN_SEVERITY_DEBUG = 1000, NM_KEYFILE_WARN_SEVERITY_INFO = 2000, + NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE = 2901, NM_KEYFILE_WARN_SEVERITY_WARN = 3000, } NMKeyfileWarnSeverity; diff --git a/libnm-core/nm-keyfile-reader.c b/libnm-core/nm-keyfile-reader.c index aa54b7bd77..87c1e14e8c 100644 --- a/libnm-core/nm-keyfile-reader.c +++ b/libnm-core/nm-keyfile-reader.c @@ -31,6 +31,7 @@ #include #include "nm-core-internal.h" +#include "nm-utils-internal.h" #include "gsystem-local-alloc.h" #include "nm-glib-compat.h" #include "nm-keyfile-internal.h" @@ -838,23 +839,179 @@ has_cert_ext (const char *path) } static gboolean -handle_as_scheme (GBytes *bytes, NMSetting *setting, const char *key) +handle_as_scheme (KeyfileReaderInfo *info, GBytes *bytes, NMSetting *setting, const char *key) { - const guint8 *data; - gsize data_len; + const char *data; + gsize data_len, bin_len; data = g_bytes_get_data (bytes, &data_len); - /* It's the PATH scheme, can just set plain data */ - if ( (data_len > strlen (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)) - && g_str_has_prefix ((const char *) data, NM_KEYFILE_CERT_SCHEME_PREFIX_PATH) - && (data[data_len - 1] == '\0')) { - g_object_set (setting, key, bytes, NULL); + g_return_val_if_fail (data && data_len > 0, FALSE); + + /* to be a scheme, @data must be a zero terminated string, which is counted by @data_len */ + if (data[data_len - 1] != '\0') + return FALSE; + data_len--; + + /* It's the PATH scheme, can just set plain data. + * In this case, @data_len includes */ + if ( data_len >= STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH) + && g_str_has_prefix (data, NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)) { + if (nm_setting_802_1x_check_cert_scheme (data, data_len + 1, NULL) == NM_SETTING_802_1X_CK_SCHEME_PATH) { + const char *path = &data[STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)]; + gs_free char *path_free = NULL; + + if (path[0] != '/') { + /* we want to read absolute paths because we use keyfile as exchange + * between different processes which might not have the same cwd. */ + path = path_free = get_cert_path (info->base_dir, (const guint8 *) path, + data_len - STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)); + } + + g_object_set (setting, key, bytes, NULL); + if (!g_file_test (path, G_FILE_TEST_EXISTS)) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE, + _("certificate or key file '%s' does not exist"), + path); + } + } else { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid key/cert value path \"%s\""), data); + } + return TRUE; + } + if ( data_len > STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB) + && g_str_has_prefix (data, NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB)) { + const char *cdata = data + STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB); + guchar *bin; + GBytes *bytes2; + gsize i; + gboolean valid_base64; + + data_len -= STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB); + + /* Let's be strict here. We expect valid base64, no funny stuff!! + * We didn't write such invalid data ourselfes and refuse to read it as blob. */ + if ((valid_base64 = (data_len % 4 == 0))) { + for (i = 0; i < data_len; i++) { + char c = cdata[i]; + + if (!( (c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') + || (c == '+' || c == '/'))) { + if (c != '=' || i < data_len - 2) + valid_base64 = FALSE; + else { + for (; i < data_len; i++) { + if (cdata[i] != '=') + valid_base64 = FALSE; + } + } + break; + } + } + } + if (!valid_base64) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid key/cert value data:;base64, is not base64")); + return TRUE; + } + + bin = g_base64_decode (cdata, &bin_len); + + g_return_val_if_fail (bin_len > 0, FALSE); + if (nm_setting_802_1x_check_cert_scheme (bin, bin_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_BLOB) { + /* The blob probably starts with "file://". Setting the cert data will confuse NMSetting8021x. + * In fact this is a limitation of NMSetting8021x which does not support setting blobs that start + * with file://. Just warn and return TRUE to signal that we ~handled~ the setting. */ + g_free (bin); + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid key/cert value data:;base64,file://")); + } else { + bytes2 = g_bytes_new_take (bin, bin_len); + g_object_set (setting, key, bytes2, NULL); + g_bytes_unref (bytes2); + } return TRUE; } return FALSE; } +char * +nm_keyfile_detect_unqualified_path_scheme (const char *base_dir, + gconstpointer pdata, + gsize data_len, + gboolean consider_exists, + gboolean *out_exists) +{ + const char *data = pdata; + gboolean exists = FALSE; + gboolean success = FALSE; + gsize validate_len; + char *path; + GByteArray *tmp; + + g_return_val_if_fail (base_dir && base_dir[0] == '/', NULL); + + if (!pdata) + return NULL; + if (data_len == -1) + data_len = strlen (data); + if (data_len > 500 || data_len < 1) + return NULL; + + /* If there's a trailing zero tell g_utf8_validate() to validate until the zero */ + if (data[data_len - 1] == '\0') { + /* setting it to -1, would mean we accept data to contain NUL characters before the + * end. Don't accept any NUL in [0 .. data_len-1[ . */ + validate_len = data_len - 1; + } else + validate_len = data_len; + if ( validate_len == 0 + || g_utf8_validate ((const char *) data, validate_len, NULL) == FALSE) + return NULL; + + /* Might be a bare path without the file:// prefix; in that case + * if it's an absolute path, use that, otherwise treat it as a + * relative path to the current directory. + */ + + path = get_cert_path (base_dir, (const guint8 *) data, data_len); + if ( !memchr (data, '/', data_len) + && !has_cert_ext (path)) { + if (!consider_exists) + goto out; + exists = g_file_test (path, G_FILE_TEST_EXISTS); + if (!exists) + goto out; + } else if (out_exists) + exists = g_file_test (path, G_FILE_TEST_EXISTS); + + /* Construct the proper value as required for the PATH scheme */ + tmp = g_byte_array_sized_new (strlen (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH) + strlen (path) + 1); + g_byte_array_append (tmp, (const guint8 *) NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, strlen (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)); + g_byte_array_append (tmp, (const guint8 *) path, strlen (path) + 1); + if (nm_setting_802_1x_check_cert_scheme (tmp->data, tmp->len, NULL) == NM_SETTING_802_1X_CK_SCHEME_PATH) { + g_free (path); + path = (char *) g_byte_array_free (tmp, FALSE); + /* when returning TRUE, we must also be sure that @data_len does not look like + * the deprecated format of list of integers. With this implementation that is the + * case, as long as @consider_exists is FALSE. */ + success = TRUE; + } else + g_byte_array_unref (tmp); + +out: + if (!success) { + g_free (path); + return NULL; + } + if (out_exists) + *out_exists = exists; + return path; +} + static gboolean handle_as_path (KeyfileReaderInfo *info, GBytes *bytes, @@ -863,89 +1020,67 @@ handle_as_path (KeyfileReaderInfo *info, { const guint8 *data; gsize data_len; - gsize validate_len; char *path; - gboolean exists, success = FALSE; + gboolean exists = FALSE; + GBytes *val; data = g_bytes_get_data (bytes, &data_len); - if (data_len > 500 || data_len < 1) + + path = nm_keyfile_detect_unqualified_path_scheme (info->base_dir, data, data_len, TRUE, &exists); + if (!path) return FALSE; - /* If there's a trailing zero tell g_utf8_validate() to validate until the zero */ - if (data[data_len - 1] == '\0') { - /* setting it to -1, would mean we accept data to contain NUL characters before the - * end. Don't accept any NUL in [0 .. data_len-1[ . */ - validate_len = data_len - 1; - } else - validate_len = data_len; + /* Construct the proper value as required for the PATH scheme */ + val = g_bytes_new_take (path, strlen (path) + 1); + g_object_set (setting, key, val, NULL); - if ( validate_len == 0 - || g_utf8_validate ((const char *) data, validate_len, NULL) == FALSE) - return FALSE; - - /* Might be a bare path without the file:// prefix; in that case - * if it's an absolute path, use that, otherwise treat it as a - * relative path to the current directory. - */ - - path = get_cert_path (info->base_dir, data, data_len); - exists = g_file_test (path, G_FILE_TEST_EXISTS); - if ( exists - || memchr (data, '/', data_len) - || has_cert_ext (path)) { - GByteArray *tmp; - GBytes *val; - - /* Construct the proper value as required for the PATH scheme */ - tmp = g_byte_array_sized_new (strlen (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH) + strlen (path) + 1); - g_byte_array_append (tmp, (const guint8 *) NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, strlen (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)); - g_byte_array_append (tmp, (const guint8 *) path, strlen (path)); - g_byte_array_append (tmp, (const guint8 *) "\0", 1); - val = g_byte_array_free_to_bytes (tmp); - g_object_set (setting, key, val, NULL); - g_bytes_unref (val); - success = TRUE; - - /* Warn if the certificate didn't exist */ - if (exists == FALSE) - handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, - _("certificate or key '%s' does not exist"), - path); + /* Warn if the certificate didn't exist */ + if (!exists) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE, + _("certificate or key file '%s' does not exist"), + path); } - g_free (path); + g_bytes_unref (val); - return success; + return TRUE; } static void cert_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) { const char *setting_name = nm_setting_get_name (setting); - GBytes *bytes; - gboolean success = FALSE; + gs_unref_bytes GBytes *bytes = NULL; + gsize bin_len; + const char *bin; bytes = get_bytes (info, setting_name, key, TRUE, FALSE); if (bytes) { /* Try as a path + scheme (ie, starts with "file://") */ - success = handle_as_scheme (bytes, setting, key); + if (handle_as_scheme (info, bytes, setting, key)) + return; + if (info->error) + return; /* If not, it might be a plain path */ - if (success == FALSE) - success = handle_as_path (info, bytes, setting, key); + if (handle_as_path (info, bytes, setting, key)) + return; if (info->error) - goto out_error; + return; - /* If neither of those two, assume blob with certificate data */ - if (success == FALSE) + bin = g_bytes_get_data (bytes, &bin_len); + if (nm_setting_802_1x_check_cert_scheme (bin, bin_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_BLOB) { + /* The blob probably starts with "file://" but contains invalid characters for a path. + * Setting the cert data will confuse NMSetting8021x. + * In fact, NMSetting8021x does not support setting such binary data, so just warn and + * continue. */ + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid key/cert value is not a valid blob")); + } else g_object_set (setting, key, bytes, NULL); } else if (!info->error) { handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("invalid key/cert value")); } - -out_error: - if (bytes) - g_bytes_unref (bytes); } static void diff --git a/libnm-core/nm-keyfile-writer.c b/libnm-core/nm-keyfile-writer.c index f5b4ef234f..ff433b264a 100644 --- a/libnm-core/nm-keyfile-writer.c +++ b/libnm-core/nm-keyfile-writer.c @@ -42,6 +42,7 @@ #include "nm-setting-8021x.h" #include "nm-utils.h" +#include "gsystem-local-alloc.h" #include "nm-glib-compat.h" #include "nm-keyfile-internal.h" #include "nm-keyfile-utils.h" @@ -410,6 +411,76 @@ static const ObjectType objtypes[10] = { { NULL }, }; +/**************************************************************************/ + +static void +cert_writer_default (NMConnection *connection, + GKeyFile *file, + NMKeyfileWriteTypeDataCert *cert_data) +{ + const char *setting_name = nm_setting_get_name (NM_SETTING (cert_data->setting)); + NMSetting8021xCKScheme scheme; + + scheme = cert_data->scheme_func (cert_data->setting); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) { + const char *path; + char *path_free = NULL, *tmp; + gs_free char *base_dir = NULL; + + path = cert_data->path_func (cert_data->setting); + g_assert (path); + + /* If the path is relative, make it an absolute path. + * Relative paths make a keyfile not easily usable in another + * context. */ + if (path[0] && path[0] != '/') { + base_dir = g_get_current_dir (); + path = path_free = g_strconcat (base_dir, "/", path, NULL); + } else + base_dir = g_path_get_dirname (path); + + /* path cannot start with "file://" or "data:;base64,", because it is an absolute path. + * Still, make sure that a prefix-less path will be recognized. This can happen + * for example if the path is longer then 500 chars. */ + tmp = nm_keyfile_detect_unqualified_path_scheme (base_dir, path, -1, FALSE, NULL); + if (tmp) + g_clear_pointer (&tmp, g_free); + else + path = tmp = g_strconcat (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, path, NULL); + + /* Path contains at least a '/', hence it cannot be recognized as the old + * binary format consisting of a list of integers. */ + + nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->property_name, path); + g_free (tmp); + g_free (path_free); + } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) { + GBytes *blob; + const guint8 *blob_data; + gsize blob_len; + char *blob_base64, *val; + + blob = cert_data->blob_func (cert_data->setting); + g_assert (blob); + blob_data = g_bytes_get_data (blob, &blob_len); + + blob_base64 = g_base64_encode (blob_data, blob_len); + val = g_strconcat (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB, blob_base64, NULL); + + nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->property_name, val); + g_free (val); + g_free (blob_base64); + } else { + /* scheme_func() returns UNKNOWN in all other cases. The only valid case + * where a scheme is allowed to be UNKNOWN, is unsetting the value. In this + * case, we don't expect the writer to be called, because the default value + * will not be serialized. + * The only other reason for the scheme to be UNKNOWN is an invalid cert. + * But our connection verifies, so that cannot happen either. */ + g_return_if_reached (); + } +} + static void cert_writer (KeyfileWriterInfo *info, NMSetting *setting, @@ -429,9 +500,6 @@ cert_writer (KeyfileWriterInfo *info, if (!objtype) g_return_if_reached (); - if (!info->handler) - goto out_unhandled; - type_data.setting = NM_SETTING_802_1X (setting); type_data.property_name = key; type_data.suffix = objtype->suffix; @@ -440,30 +508,23 @@ cert_writer (KeyfileWriterInfo *info, type_data.path_func = objtype->path_func; type_data.blob_func = objtype->blob_func; - if (info->handler (info->connection, - info->keyfile, - NM_KEYFILE_WRITE_TYPE_CERT, - &type_data, - info->user_data, - &info->error)) - return; - -out_unhandled: - - /* scheme_func() would not return UNKNOWN, because UNKNOWN happens only - * if the cert is unset (1) or if the cert is invalid (2). - * (1) cannot happen, because we only reach cert_writer() for non-default - * properties. (2) cannot happen, because we verified the connection. - * - * Hence, at this point we do have a certifiacte, but no default implementation - * to write it. The handler *must* do something with these certifications. */ - if (!info->error) { - g_set_error (&info->error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, - _("Failed to write unhandled certificate property %s.%s"), - nm_setting_get_name (setting), key); + if (info->handler) { + if (info->handler (info->connection, + info->keyfile, + NM_KEYFILE_WRITE_TYPE_CERT, + &type_data, + info->user_data, + &info->error)) + return; + if (info->error) + return; } + + cert_writer_default (info->connection, info->keyfile, &type_data); } +/**************************************************************************/ + typedef struct { const char *setting_name; const char *key; diff --git a/libnm-core/tests/Makefile.am b/libnm-core/tests/Makefile.am index 457864c440..daa5825b50 100644 --- a/libnm-core/tests/Makefile.am +++ b/libnm-core/tests/Makefile.am @@ -15,6 +15,7 @@ noinst_PROGRAMS = \ test-compare \ test-crypto \ test-general \ + test-keyfile \ test-secrets \ test-setting-8021x \ test-setting-dcb \ @@ -49,8 +50,10 @@ EXTRA_DIST = \ certs/test-aes-key.pem \ certs/test_ca_cert.der \ certs/test_ca_cert.pem \ + certs/test-ca-cert.pem \ certs/test-cert.p12 \ certs/test_key_and_cert.pem \ + certs/test-key-and-cert.pem \ certs/test-key-only-decrypted.der \ certs/test-key-only-decrypted.pem \ certs/test-key-only.pem diff --git a/libnm-core/tests/certs/test-ca-cert.pem b/libnm-core/tests/certs/test-ca-cert.pem new file mode 100644 index 0000000000..ef1be20d2b --- /dev/null +++ b/libnm-core/tests/certs/test-ca-cert.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEjzCCA3egAwIBAgIJAOvnZPt59yIZMA0GCSqGSIb3DQEBBQUAMIGLMQswCQYD +VQQGEwJVUzESMBAGA1UECBMJQmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcw +FQYDVQQKEw5NeSBDb21wYW55IEx0ZDEQMA4GA1UECxMHVGVzdGluZzENMAsGA1UE +AxMEdGVzdDEcMBoGCSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTAeFw0wOTAzMTAx +NTEyMTRaFw0xOTAzMDgxNTEyMTRaMIGLMQswCQYDVQQGEwJVUzESMBAGA1UECBMJ +QmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcwFQYDVQQKEw5NeSBDb21wYW55 +IEx0ZDEQMA4GA1UECxMHVGVzdGluZzENMAsGA1UEAxMEdGVzdDEcMBoGCSqGSIb3 +DQEJARYNdGVzdEB0ZXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAKot9j+/+CX1/gZLgJHIXCRgCItKLGnf7qGbgqB9T2ACBqR0jllKWwDKrcWU +xjXNIc+GF9Wnv+lX6G0Okn4Zt3/uRNobL+2b/yOF7M3Td3/9W873zdkQQX930YZc +Rr8uxdRPP5bxiCgtcw632y21sSEbG9mjccAUnV/0jdvfmMNj0i8gN6E0fMBiJ9S3 +FkxX/KFvt9JWE9CtoyL7ki7UIDq+6vj7Gd5N0B3dOa1y+rRHZzKlJPcSXQSEYUS4 +HmKDwiKSVahft8c4tDn7KPi0vex91hlgZVd3usL2E/Vq7o5D9FAZ5kZY0AdFXwdm +J4lO4Mj7ac7GE4vNERNcXVIX59sCAwEAAaOB8zCB8DAdBgNVHQ4EFgQUuDU3Mr7P +T3n1e3Sy8hBauoDFahAwgcAGA1UdIwSBuDCBtYAUuDU3Mr7PT3n1e3Sy8hBauoDF +ahChgZGkgY4wgYsxCzAJBgNVBAYTAlVTMRIwEAYDVQQIEwlCZXJrc2hpcmUxEDAO +BgNVBAcTB05ld2J1cnkxFzAVBgNVBAoTDk15IENvbXBhbnkgTHRkMRAwDgYDVQQL +EwdUZXN0aW5nMQ0wCwYDVQQDEwR0ZXN0MRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRl +c3QuY29tggkA6+dk+3n3IhkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOC +AQEAVRG4aALIvCXCiKfe7K+iJxjBVRDFPEf7JWA9LGgbFOn6pNvbxonrR+0BETdc +JV1ET4ct2xsE7QNFIkp9GKRC+6J32zCo8qtLCD5+v436r8TUG2/t2JRMkb9I2XVT +p7RJoot6M0Ltf8KNQUPYh756xmKZ4USfQUwc58MOSDGY8VWEXJOYij9Pf0e0c52t +qiCEjXH7uXiS8Pgq9TYm7AkWSOrglYhSa83x0f8mtT8Q15nBESIHZ6o8FAS2bBgn +B0BkrKRjtBUkuJG3vTox+bYINh2Gxi1JZHWSV1tN5z3hd4VFcKqanW5OgQwToBqp +3nniskIjbH0xjgZf/nVMyLnjxg== +-----END CERTIFICATE----- diff --git a/libnm-core/tests/certs/test-key-and-cert.pem b/libnm-core/tests/certs/test-key-and-cert.pem new file mode 100644 index 0000000000..dec9aa1b8f --- /dev/null +++ b/libnm-core/tests/certs/test-key-and-cert.pem @@ -0,0 +1,118 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,4DE0615F23D82107 + +QPNCO5Dobvz9dDhN32KkZRoEifW+HDm2PCbRQhKDiscGwB6LgypvVjHNsZiFKwzz +L4R51UqgQeJx7GSGJqE626e9z9J+UNBhop02aOO2X0eSPdvBzr/uJ6Umiyr1xqD7 +zWf7u9l5kXElDJRhK+87GMBewp4Ie9NeXDjhF8hzC5Kiulen4AH3AYnfH3S7DimU +h8GFMg8inrudrTbcjBhCdPeHG2jCygOxw3InRFz7uaN6LIhOaPQvmvpP4Cc1WRnW +ZPq9o+eU3fPWPD5t+Op/VzYLvKwgBy/yK1rQXUm6ZMO7MhhRJ94ZCsJv+nVWpJlv +QyBlxDKxwfkfYbDELdnnDQdHdMbKatLqa0KhSkgpp8LywBtanPz731tyT0r7b3na +eLdra59lRU7ZQLPEdS3lPZd2O/KQvWf8wbg7MjXS9LxQ7R5HOPu6DNJlwXVZBmmo +cAfu2q8ubU2IePvWLD1GOrBi6hE9TiGvFJkw+wBK+t72sz3njv9Xm/zlxruaEk5m +RW/kybU3FP4PtjriBbskz3/VZaaxuRN7OoOYTkmyHmG1ADgcRUV6fea19qqsBlN8 +xb+SRtoH28oT/JVWU5neE2dbNzk5LeVO+w70NNdR5s5xqkBhbGGaJxvXwNP4ltFr +T06SMh8znOLKwWB00aRtwfU7jOwR3mOleQO4ugIHmau3zp1TqzAHW8XtpuV7qVeI +ESZOZuf0vW43BtNzgLXt1+r+bmsMsRwhnyomL9M0TUyyBdVYY9GkzTG9pOESheRo +RSvAZ8qKGUliTpgBcbt2v1+NqkszcHa6FxuvS8YU4uo5/GqsgTxHTNIB232hIrrZ +EIm6QL9TC5oFXMjy6UNqoCm5Nb8DBJ6aErt7pt7aoktqUW3O3QIzQT3IbZ4nAcTt +lVF4d7j29I9t7bcC8GOVU1neilguZUss4ghJg9x4zI5UZdR7hZ8fbFT47TyxB+j5 +r0YdmjbjVTaSyaN2JGh1wvb4TzawGNVx/U2EJE16HigOtPfsfQRJ3x+FROKBdVa4 +aIFYXkRBeIPxX6n9pcw0lBCsnXo6/5iTjQSk2VqO3rHO/wyWiEjNczhL33dY2A8W +GG5ECMO5SqXZHQQzpABqK94dxe3UC8aEESO5NhEqDuV7qQGol0qPKrUA3wb0jb2e +DrejJ9HS2m1SUDmjpvvmEGy6GN7CRibbKt5rNZdJNNvWArOF5d0F6wkixQLl73oE +lq5gLQQk9n7ClleKLhlQpBCorxilBbzmSUekkJLi0eaZiBBFWBX9udqnUZloXTgO +8qwuO8K/GPR9Jy1/UH2Vh1H+wivaqKTVgEb0NotzgzECgTEFKJafl7rUNs1OZRZ3 +VBjevi6+iDpxVFgF71kXfdUC4ph0E1XDl0ja2rrKQGivMkUhWJ57+4EV5+hBkAnt +G0RV45NwHXLrK2bd8F9PlRk2XHW6mIcFRXsW1DjeBhk/sQjvlO9R01GRSgcXtekJ +tmX17FWrMrzXHpvy1IC3fk4RVnSjpzQ8O+17YE8/la9wVaeZZzHyYFmMT7VXjIhW +QozJQ0vJ2jxJRh5GYn3tpJzdaeRfvTBik0pChNdUTnWP+BJ35xoCTs8iwJbmgVZ1 +-----END RSA PRIVATE KEY----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=US, ST=Berkshire, L=Newbury, O=My Company Ltd, OU=Testing, CN=test/emailAddress=test@test.com + Validity + Not Before: Mar 10 15:13:16 2009 GMT + Not After : Mar 8 15:13:16 2019 GMT + Subject: C=US, ST=Berkshire, O=My Company Ltd, OU=Testing, CN=test1/emailAddress=test@test.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:cd:34:b1:2e:b0:04:c6:f4:2b:a2:c0:a0:39:7a: + 82:ed:96:c4:f7:19:83:91:5c:b4:e7:9c:de:ec:48: + ec:2d:e4:51:08:26:42:ac:d3:98:26:7a:72:f7:49: + c2:9e:66:05:c6:47:29:fe:3b:ac:6b:af:6f:5e:a8: + 03:5a:73:33:ba:19:03:00:35:f5:00:bc:a8:be:14: + ce:46:69:e3:6d:ed:34:37:85:55:87:62:b3:b7:c9: + c0:cc:9a:aa:61:05:5b:cd:a2:17:42:d3:e5:6f:1c: + 60:8d:c2:15:41:46:f8:12:54:d0:38:57:e1:fd:8d: + 44:c8:fb:56:b3:b9:6c:e9:f8:9e:21:11:57:1b:8b: + f9:cf:e3:17:e7:d8:fd:ac:d1:01:c6:92:30:f3:2d: + c9:d6:c1:f0:3d:fd:ca:30:dd:75:74:e7:d1:6b:75: + d8:c5:4d:43:61:fe:f6:ad:7e:4c:63:7c:03:17:a2: + 06:8f:d0:8b:69:d3:7a:07:0f:0b:a2:cf:0c:70:38: + ba:cc:55:35:60:84:58:d8:d2:be:1f:ef:76:a9:ba: + ae:6a:dc:08:97:80:de:42:00:b7:d4:ce:9a:b0:36: + 2a:c7:6f:45:04:7c:ea:41:19:d8:b9:19:04:1f:11: + a9:22:80:bd:69:08:15:0d:3c:de:cd:7e:88:6c:0f: + a3:43 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + CE:03:7E:EF:E7:DE:C9:87:BF:DE:56:F4:C8:A3:40:F6:C8:6F:05:8C + X509v3 Authority Key Identifier: + keyid:B8:35:37:32:BE:CF:4F:79:F5:7B:74:B2:F2:10:5A:BA:80:C5:6A:10 + DirName:/C=US/ST=Berkshire/L=Newbury/O=My Company Ltd/OU=Testing/CN=test/emailAddress=test@test.com + serial:EB:E7:64:FB:79:F7:22:19 + + Signature Algorithm: md5WithRSAEncryption + 7a:20:93:63:40:73:7d:33:01:2e:c0:13:52:a4:a7:e1:4d:82: + f4:fb:b2:7b:d0:2b:5a:3f:0e:3c:28:61:71:ab:01:4d:fe:89: + b5:cd:2f:97:59:93:53:9d:51:86:48:dd:b9:e4:73:5e:22:0b: + 12:0d:25:39:76:16:44:06:0c:40:45:21:6b:a6:b1:e0:bf:76: + 1b:36:f3:1e:41:82:57:d9:59:b7:60:40:43:1c:1d:79:f6:48: + 32:5c:4e:e2:06:89:96:41:d2:54:1f:4a:6f:f6:78:a5:3c:02: + 85:21:e2:65:e1:8a:6d:24:19:95:f8:c0:35:ab:bd:ff:3d:f1: + fb:50:2d:30:1e:67:a6:7c:50:f9:d5:77:66:77:5a:14:0f:5c: + cd:21:09:9b:a3:92:57:19:dd:01:a4:18:c5:f9:70:e4:17:43: + 8d:b1:e6:61:e9:50:89:83:4f:ce:a4:57:68:58:40:70:ae:71: + 1c:47:66:d2:30:54:50:ea:3a:87:32:64:3b:18:42:fe:5a:19: + 07:64:f7:f1:b1:10:07:fd:a7:d2:a7:a8:05:79:5b:25:ba:69: + 7b:1a:3e:b1:3e:e4:17:17:01:ba:eb:54:ae:83:00:ed:66:62: + 8d:c0:3e:8a:b4:27:5f:e9:01:ce:20:c3:34:a9:28:c0:6f:c7: + 3b:65:fe:f9 +-----BEGIN CERTIFICATE----- +MIIEojCCA4qgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBizELMAkGA1UEBhMCVVMx +EjAQBgNVBAgTCUJlcmtzaGlyZTEQMA4GA1UEBxMHTmV3YnVyeTEXMBUGA1UEChMO +TXkgQ29tcGFueSBMdGQxEDAOBgNVBAsTB1Rlc3RpbmcxDTALBgNVBAMTBHRlc3Qx +HDAaBgkqhkiG9w0BCQEWDXRlc3RAdGVzdC5jb20wHhcNMDkwMzEwMTUxMzE2WhcN +MTkwMzA4MTUxMzE2WjB6MQswCQYDVQQGEwJVUzESMBAGA1UECBMJQmVya3NoaXJl +MRcwFQYDVQQKEw5NeSBDb21wYW55IEx0ZDEQMA4GA1UECxMHVGVzdGluZzEOMAwG +A1UEAxMFdGVzdDExHDAaBgkqhkiG9w0BCQEWDXRlc3RAdGVzdC5jb20wggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNNLEusATG9CuiwKA5eoLtlsT3GYOR +XLTnnN7sSOwt5FEIJkKs05gmenL3ScKeZgXGRyn+O6xrr29eqANaczO6GQMANfUA +vKi+FM5GaeNt7TQ3hVWHYrO3ycDMmqphBVvNohdC0+VvHGCNwhVBRvgSVNA4V+H9 +jUTI+1azuWzp+J4hEVcbi/nP4xfn2P2s0QHGkjDzLcnWwfA9/cow3XV059FrddjF +TUNh/vatfkxjfAMXogaP0Itp03oHDwuizwxwOLrMVTVghFjY0r4f73apuq5q3AiX +gN5CALfUzpqwNirHb0UEfOpBGdi5GQQfEakigL1pCBUNPN7NfohsD6NDAgMBAAGj +ggEfMIIBGzAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVy +YXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUzgN+7+feyYe/3lb0yKNA9shvBYww +gcAGA1UdIwSBuDCBtYAUuDU3Mr7PT3n1e3Sy8hBauoDFahChgZGkgY4wgYsxCzAJ +BgNVBAYTAlVTMRIwEAYDVQQIEwlCZXJrc2hpcmUxEDAOBgNVBAcTB05ld2J1cnkx +FzAVBgNVBAoTDk15IENvbXBhbnkgTHRkMRAwDgYDVQQLEwdUZXN0aW5nMQ0wCwYD +VQQDEwR0ZXN0MRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRlc3QuY29tggkA6+dk+3n3 +IhkwDQYJKoZIhvcNAQEEBQADggEBAHogk2NAc30zAS7AE1Kkp+FNgvT7snvQK1o/ +DjwoYXGrAU3+ibXNL5dZk1OdUYZI3bnkc14iCxINJTl2FkQGDEBFIWumseC/dhs2 +8x5BglfZWbdgQEMcHXn2SDJcTuIGiZZB0lQfSm/2eKU8AoUh4mXhim0kGZX4wDWr +vf898ftQLTAeZ6Z8UPnVd2Z3WhQPXM0hCZujklcZ3QGkGMX5cOQXQ42x5mHpUImD +T86kV2hYQHCucRxHZtIwVFDqOocyZDsYQv5aGQdk9/GxEAf9p9KnqAV5WyW6aXsa +PrE+5BcXAbrrVK6DAO1mYo3APoq0J1/pAc4gwzSpKMBvxztl/vk= +-----END CERTIFICATE----- diff --git a/libnm-core/tests/test-keyfile.c b/libnm-core/tests/test-keyfile.c new file mode 100644 index 0000000000..a0f590e7cd --- /dev/null +++ b/libnm-core/tests/test-keyfile.c @@ -0,0 +1,505 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2015 Red Hat, Inc. + * + */ + +#include "config.h" + +#include "nm-utils-internal.h" +#include "nm-keyfile-utils.h" +#include "nm-keyfile-internal.h" + +#include "nm-simple-connection.h" +#include "nm-setting-connection.h" +#include "nm-setting-wired.h" +#include "nm-setting-8021x.h" + +#include "nm-test-utils.h" + + +#define TEST_WIRED_TLS_CA_CERT TEST_CERT_DIR"/test-ca-cert.pem" +#define TEST_WIRED_TLS_PRIVKEY TEST_CERT_DIR"/test-key-and-cert.pem" + + +/******************************************************************************/ + +#define CLEAR(con, keyfile) \ + G_STMT_START { \ + NMConnection **_con = (con); \ + GKeyFile **_keyfile = (keyfile); \ + \ + g_clear_object (_con); \ + g_clear_pointer (_keyfile, g_key_file_unref); \ + } G_STMT_END + +static void +_assert_gbytes (GBytes *bytes, gconstpointer data, gssize len) +{ + g_assert ((data && len > 0) || !len || (data && len == -1)); + + if (len == -1) + len = strlen (data); + + if (!len) + g_assert (!bytes); + else { + g_assert_cmpint (g_bytes_get_size (bytes), ==, len); + g_assert (memcmp (g_bytes_get_data (bytes, NULL), data, len) == 0); + } +} + +static GKeyFile * +_keyfile_load_from_data (const char *str) +{ + GError *error = NULL; + gboolean success; + GKeyFile *keyfile; + + g_assert (str); + + keyfile = g_key_file_new (); + success = g_key_file_load_from_data (keyfile, str, strlen (str), G_KEY_FILE_NONE, &error); + g_assert_no_error (error); + g_assert (success); + + return keyfile; +} + +static gboolean +_keyfile_a_contains_all_in_b (GKeyFile *kf_a, GKeyFile *kf_b) +{ + gs_strfreev char **groups = NULL; + guint i, j; + + if (kf_a == kf_b) + return TRUE; + + groups = g_key_file_get_groups (kf_a, NULL); + for (i = 0; groups && groups[i]; i++) { + gs_strfreev char **keys = NULL; + + keys = g_key_file_get_keys (kf_a, groups[i], NULL, NULL); + if (keys) { + for (j = 0; keys[j]; j++) { + gs_free char *key_a = g_key_file_get_value (kf_a, groups[i], keys[j], NULL); + gs_free char *key_b = g_key_file_get_value (kf_b, groups[i], keys[j], NULL); + + if (g_strcmp0 (key_a, key_b) != 0) + return FALSE; + } + } + } + return TRUE; +} + +static gboolean +_keyfile_equals (GKeyFile *kf_a, GKeyFile *kf_b) +{ + return _keyfile_a_contains_all_in_b (kf_a, kf_b) && _keyfile_a_contains_all_in_b (kf_b, kf_a); +} + +static void +_keyfile_convert (NMConnection **con, + GKeyFile **keyfile, + const char *keyfile_name, + const char *base_dir, + NMKeyfileReadHandler read_handler, + void *read_data, + NMKeyfileWriteHandler write_handler, + void *write_data, + gboolean needs_normalization) +{ + NMConnection *c, *c2; + GKeyFile *k, *k2; + GError *error = NULL; + NMSetting8021x *s1, *s2; + + /* convert from @con to @keyfile and check that we can make + * full round trips and obtaining the same result. */ + + g_assert (con); + g_assert (keyfile); + g_assert (*con || *keyfile); + + if (!*keyfile) { + k = nm_keyfile_write (*con, write_handler, read_data, &error); + g_assert_no_error (error); + g_assert (k); + *keyfile = k; + } else + k = *keyfile; + if (!*con) { + c = nm_keyfile_read (*keyfile, keyfile_name, base_dir, read_handler, read_data, &error); + g_assert_no_error (error); + g_assert (c); + if (needs_normalization) + nmtst_assert_connection_verifies_after_normalization (c, 0, 0); + else + nmtst_assert_connection_verifies_without_normalization (c); + *con = c; + } else + c = *con; + + k2 = nm_keyfile_write (c, write_handler, read_data, &error); + g_assert_no_error (error); + g_assert (k2); + + c2 = nm_keyfile_read (k, keyfile_name, base_dir, read_handler, read_data, &error); + g_assert_no_error (error); + g_assert (c2); + if (needs_normalization) + nmtst_assert_connection_verifies_after_normalization (c2, 0, 0); + else + nmtst_assert_connection_verifies_without_normalization (c2); + + s1 = nm_connection_get_setting_802_1x (*con); + s2 = nm_connection_get_setting_802_1x (c2); + if (s1 || s2) { + g_assert_cmpint (nm_setting_802_1x_get_ca_cert_scheme (s1), ==, nm_setting_802_1x_get_ca_cert_scheme (s2)); + switch (nm_setting_802_1x_get_ca_cert_scheme (s1)) { + case NM_SETTING_802_1X_CK_SCHEME_PATH: + nmtst_assert_resolve_relative_path_equals (nm_setting_802_1x_get_ca_cert_path (s1), nm_setting_802_1x_get_ca_cert_path (s2)); + break; + case NM_SETTING_802_1X_CK_SCHEME_BLOB: { + GBytes *b1, *b2; + + b1 = nm_setting_802_1x_get_ca_cert_blob (s1); + b2 = nm_setting_802_1x_get_ca_cert_blob (s2); + g_assert_cmpint (g_bytes_get_size (b1), ==, g_bytes_get_size (b2)); + g_assert (memcmp (g_bytes_get_data (b1, NULL), g_bytes_get_data (b2, NULL), g_bytes_get_size (b1)) == 0); + break; + } + default: + break; + } + } + + nmtst_assert_connection_equals (c2, FALSE, *con, FALSE); + _keyfile_equals (k2, *keyfile); + + g_object_unref (c2); + g_key_file_unref (k2); +} + +/******************************************************************************/ + +static void +_test_8021x_cert_check (NMConnection *con, + NMSetting8021xCKScheme expected_scheme, + const void *value, + gssize val_len) +{ + GKeyFile *keyfile = NULL; + NMSetting8021x *s_8021x; + gs_free char *kval = NULL; + + _keyfile_convert (&con, &keyfile, NULL, NULL, NULL, NULL, NULL, NULL, FALSE); + + s_8021x = nm_connection_get_setting_802_1x (con); + + g_assert (nm_setting_802_1x_get_ca_cert_scheme (s_8021x) == expected_scheme); + + if (expected_scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) { + const char *path = nm_setting_802_1x_get_ca_cert_path (s_8021x); + + g_assert_cmpstr (path, ==, value); + g_assert (val_len == -1 || strlen (path) == val_len); + + kval = g_key_file_get_string (keyfile, "802-1x", "ca-cert", NULL); + g_assert (kval); + g_assert_cmpstr (kval, ==, value); + } else if (expected_scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) { + GBytes *blob = nm_setting_802_1x_get_ca_cert_blob (s_8021x); + gs_free char *file_blob = NULL; + + if (val_len == -1) { + gsize l; + gboolean success; + + success = g_file_get_contents (value, &file_blob, &l, NULL); + g_assert (success); + + value = file_blob; + val_len = l; + } + + g_assert (blob); + g_assert_cmpint (g_bytes_get_size (blob), ==, val_len); + g_assert (!memcmp (g_bytes_get_data (blob, NULL), value, val_len)); + + kval = g_key_file_get_string (keyfile, "802-1x", "ca-cert", NULL); + g_assert (kval); + g_assert (g_str_has_prefix (kval, NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB)); + } + + g_key_file_unref (keyfile); +} + +static void +_test_8021x_cert_check_blob_full (NMConnection *con, const void *data, gsize len) +{ + GBytes *bytes; + NMSetting8021x *s_8021x = nm_connection_get_setting_802_1x (con); + + bytes = g_bytes_new (data, len); + g_object_set (s_8021x, + NM_SETTING_802_1X_CA_CERT, + bytes, + NULL); + _test_8021x_cert_check (con, NM_SETTING_802_1X_CK_SCHEME_BLOB, g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes)); + g_bytes_unref (bytes); +} +#define _test_8021x_cert_check_blob(con, data) _test_8021x_cert_check_blob_full(con, data, STRLEN (data)) + +static void +test_8021x_cert (void) +{ + NMSetting8021x *s_8021x; + gs_unref_object NMConnection *con = nmtst_create_minimal_connection ("test-cert", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + GError *error = NULL; + gboolean success; + NMSetting8021xCKScheme scheme = NM_SETTING_802_1X_CK_SCHEME_PATH; + gs_free char *full_TEST_WIRED_TLS_CA_CERT = nmtst_file_resolve_relative_path (TEST_WIRED_TLS_CA_CERT, NULL); + gs_free char *full_TEST_WIRED_TLS_PRIVKEY = nmtst_file_resolve_relative_path (TEST_WIRED_TLS_PRIVKEY, NULL); + + /* test writing/reading of certificates of NMSetting8021x */ + + /* create a valid connection with NMSetting8021x */ + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new (); + nm_setting_802_1x_add_eap_method (s_8021x, "tls"); + g_object_set (s_8021x, NM_SETTING_802_1X_IDENTITY, "Bill Smith", NULL); + success = nm_setting_802_1x_set_ca_cert (s_8021x, + full_TEST_WIRED_TLS_CA_CERT, + scheme, + NULL, + &error); + g_assert_no_error (error); + g_assert (success); + success = nm_setting_802_1x_set_client_cert (s_8021x, + full_TEST_WIRED_TLS_CA_CERT, + scheme, + NULL, + &error); + g_assert_no_error (error); + g_assert (success); + success = nm_setting_802_1x_set_private_key (s_8021x, + full_TEST_WIRED_TLS_PRIVKEY, + "test1", + scheme, + NULL, + &error); + g_assert_no_error (error); + g_assert (success); + + + /* test reseting ca-cert to different values and see whether we can write/read. */ + + nm_connection_add_setting (con, NM_SETTING (s_8021x)); + nmtst_assert_connection_verifies_and_normalizable (con); + + + _test_8021x_cert_check (con, scheme, full_TEST_WIRED_TLS_CA_CERT, -1); + + scheme = NM_SETTING_802_1X_CK_SCHEME_BLOB; + success = nm_setting_802_1x_set_ca_cert (s_8021x, + full_TEST_WIRED_TLS_CA_CERT, + scheme, + NULL, + &error); + g_assert_no_error (error); + g_assert (success); + _test_8021x_cert_check (con, scheme, full_TEST_WIRED_TLS_CA_CERT, -1); + + _test_8021x_cert_check_blob (con, "a"); + _test_8021x_cert_check_blob (con, "\0"); + _test_8021x_cert_check_blob (con, "10"); + _test_8021x_cert_check_blob (con, "data:;base64,a"); + _test_8021x_cert_check_blob_full (con, "data:;base64,a", STRLEN ("data:;base64,a") + 1); + _test_8021x_cert_check_blob (con, "data:;base64,file://a"); + _test_8021x_cert_check_blob (con, "123"); + +} + +/******************************************************************************/ + +static void +test_8021x_cert_read (void) +{ + GKeyFile *keyfile = NULL; + gs_unref_object NMConnection *con = NULL; + NMSetting8021x *s_8021x; + + keyfile = _keyfile_load_from_data ( + "[connection]\n" + "type=ethernet" + ); + _keyfile_convert (&con, &keyfile, "/test_8021x_cert_read/test1", NULL, NULL, NULL, NULL, NULL, TRUE); + CLEAR (&con, &keyfile); + + keyfile = _keyfile_load_from_data ( + "[connection]\n" + "type=802-3-ethernet\n" + + "[802-1x]\n" + "eap=tls;\n" + "identity=Bill Smith\n" + "ca-cert=48;130;2;52;48;130;1;161;2;16;2;173;102;126;78;69;254;94;87;111;60;152;25;94;221;192;48;13;6;9;42;134;72;134;247;13;1;1;2;5;0;48;95;49;11;48;9;6;3;85;4;6;19;2;85;83;49;32;48;30;6;3;85;4;10;19;23;82;83;65;32;68;97;116;97;32;83;101;99;117;114;105;116;121;44;32;73;110;99;46;49;46;48;44;6;3;85;4;11;19;37;83;101;99;117;114;101;32;83;101;114;118;101;114;32;67;101;114;116;105;102;105;99;97;116;105;111;110;32;65;117;116;104;111;114;105;116;121;48;30;23;13;57;52;49;49;48;57;48;48;48;48;48;48;90;23;13;49;48;48;49;48;55;50;51;53;57;53;57;90;48;95;49;11;48;9;6;3;85;4;6;19;2;85;83;49;32;48;30;6;3;85;4;10;19;23;82;83;65;32;68;97;116;97;32;83;101;99;117;114;105;116;121;44;32;73;110;99;46;49;46;48;44;6;3;85;4;11;19;37;83;101;99;117;114;101;32;83;101;114;118;101;114;32;67;101;114;116;105;102;105;99;97;116;105;111;110;32;65;117;116;104;111;114;105;116;121;48;129;155;48;13;6;9;42;134;72;134;247;13;1;1;1;5;0;3;129;137;0;48;129;133;2;126;0;146;206;122;193;174;131;62;90;170;137;131;87;172;37;1;118;12;173;174;142;44;55;206;235;53;120;100;84;3;229;132;64;81;201;191;143;8;226;138;130;8;210;22;134;55;85;233;177;33;2;173;118;104;129;154;5;162;75;201;75;37;102;34;86;108;136;7;143;247;129;89;109;132;7;101;112;19;113;118;62;155;119;76;227;80;137;86;152;72;185;29;167;41;26;19;46;74;17;89;156;30;21;213;73;84;44;115;58;105;130;177;151;57;156;109;112;103;72;229;221;45;214;200;30;123;2;3;1;0;1;48;13;6;9;42;134;72;134;247;13;1;1;2;5;0;3;126;0;101;221;126;225;178;236;176;226;58;224;236;113;70;154;25;17;184;211;199;160;180;3;64;38;2;62;9;156;225;18;179;209;90;246;55;165;183;97;3;182;91;22;105;59;198;68;8;12;136;83;12;107;151;73;199;62;53;220;108;185;187;170;223;92;187;58;47;147;96;182;169;75;77;242;32;247;205;95;127;100;123;142;220;0;92;215;250;119;202;57;22;89;111;14;234;211;181;131;127;77;77;66;86;118;180;201;95;4;248;56;248;235;210;95;117;95;205;123;252;229;142;128;124;252;80;\n" + "client-cert=102;105;108;101;58;47;47;47;104;111;109;101;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0;\n" + "private-key=102;105;108;101;58;47;47;47;104;111;109;101;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0;\n" + "private-key-password=12345testing\n" + ); + _keyfile_convert (&con, &keyfile, "/test_8021x_cert_read/test2", NULL, NULL, NULL, NULL, NULL, TRUE); + CLEAR (&con, &keyfile); + + + keyfile = _keyfile_load_from_data ( + "[connection]\n" + "type=802-3-ethernet\n" + + "[802-1x]\n" + "eap=tls;\n" + "identity=Bill Smith\n" + /* unqualified strings are only recognized as path up to 500 chars*/ + "ca-cert=" "/111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" + "/111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" + "/111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" + "/111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" + "/11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111\n" + "client-cert=/222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222221" + "/222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222221" + "/222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222221" + "/222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222221" + "/222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222\n" + "private-key=file://" + "/333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333331" + "/333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333331" + "/333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333331" + "/333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333331" + "/33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333111111\n" + "private-key-password=12345testing\n" + ); + _keyfile_convert (&con, &keyfile, "/test_8021x_cert_read/test2", NULL, NULL, NULL, NULL, NULL, TRUE); + s_8021x = nm_connection_get_setting_802_1x (con); + + g_assert (nm_setting_802_1x_get_ca_cert_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH); + g_assert (g_str_has_prefix (nm_setting_802_1x_get_ca_cert_path (s_8021x), "/111111111111")); + g_assert_cmpint (strlen (nm_setting_802_1x_get_ca_cert_path (s_8021x)), ==, 499); + + g_assert (nm_setting_802_1x_get_client_cert_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_BLOB); + g_assert (g_str_has_prefix (g_bytes_get_data (nm_setting_802_1x_get_client_cert_blob (s_8021x), NULL), "/2222222222")); + g_assert_cmpint (g_bytes_get_size (nm_setting_802_1x_get_client_cert_blob (s_8021x)), ==, 500 + 1 /* keyfile reader adds a trailing NUL */); + + g_assert (nm_setting_802_1x_get_private_key_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH); + g_assert (g_str_has_prefix (nm_setting_802_1x_get_private_key_path (s_8021x), "/333333333")); + g_assert_cmpint (strlen (nm_setting_802_1x_get_private_key_path (s_8021x)), ==, 505); + CLEAR (&con, &keyfile); + + + keyfile = _keyfile_load_from_data ( + "[connection]\n" + "type=802-3-ethernet\n" + + "[802-1x]\n" + "eap=tls;\n" + "identity=Bill Smith\n" + "ca-cert=/\n" + "client-cert=a.pem\n" + "private-key=data:;base64,aGFsbG8=\n" // hallo + "private-key-password=12345testing\n" + ); + _keyfile_convert (&con, &keyfile, "/test_8021x_cert_read/test2", NULL, NULL, NULL, NULL, NULL, TRUE); + s_8021x = nm_connection_get_setting_802_1x (con); + + g_assert (nm_setting_802_1x_get_ca_cert_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH); + g_assert_cmpstr (nm_setting_802_1x_get_ca_cert_path (s_8021x), ==, "/"); + + g_assert (nm_setting_802_1x_get_client_cert_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH); + g_assert_cmpstr (nm_setting_802_1x_get_client_cert_path (s_8021x), ==, "/test_8021x_cert_read/a.pem"); + + g_assert (nm_setting_802_1x_get_private_key_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_BLOB); + _assert_gbytes (nm_setting_802_1x_get_private_key_blob (s_8021x), "hallo", -1); + CLEAR (&con, &keyfile); + + + keyfile = _keyfile_load_from_data ( + "[connection]\n" + "type=802-3-ethernet\n" + + "[802-1x]\n" + "eap=tls;\n" + "identity=Bill Smith\n" + "ca-cert=file://data:;base64,x\n" + "client-cert=abc.der\n" + "private-key=abc.deR\n" + "private-key-password=12345testing\n" + ); + _keyfile_convert (&con, &keyfile, "/test_8021x_cert_read/test2", NULL, NULL, NULL, NULL, NULL, TRUE); + s_8021x = nm_connection_get_setting_802_1x (con); + + g_assert (nm_setting_802_1x_get_ca_cert_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH); + g_assert_cmpstr (nm_setting_802_1x_get_ca_cert_path (s_8021x), ==, "data:;base64,x"); + + g_assert (nm_setting_802_1x_get_client_cert_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH); + g_assert_cmpstr (nm_setting_802_1x_get_client_cert_path (s_8021x), ==, "/test_8021x_cert_read/abc.der"); + + g_assert (nm_setting_802_1x_get_private_key_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_BLOB); + _assert_gbytes (nm_setting_802_1x_get_private_key_blob (s_8021x), "abc.deR\0", 8); + CLEAR (&con, &keyfile); + + + keyfile = _keyfile_load_from_data ( + "[connection]\n" + "type=802-3-ethernet\n" + + "[802-1x]\n" + "eap=tls;\n" + "identity=Bill Smith\n" + "ca-cert=104;97;108;108;111;\n" /* "hallo" without trailing NUL */ + "client-cert=104;097;108;108;111;0;\n" + "private-key=hallo\n" + "private-key-password=12345testing\n" + ); + _keyfile_convert (&con, &keyfile, "/test_8021x_cert_read/test2", NULL, NULL, NULL, NULL, NULL, TRUE); + s_8021x = nm_connection_get_setting_802_1x (con); + + g_assert (nm_setting_802_1x_get_ca_cert_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_BLOB); + _assert_gbytes (nm_setting_802_1x_get_ca_cert_blob (s_8021x), "hallo", 5); + + g_assert (nm_setting_802_1x_get_client_cert_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_BLOB); + _assert_gbytes (nm_setting_802_1x_get_client_cert_blob (s_8021x), "hallo\0", 6); + + g_assert (nm_setting_802_1x_get_private_key_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_BLOB); + _assert_gbytes (nm_setting_802_1x_get_private_key_blob (s_8021x), "hallo\0", 6); + CLEAR (&con, &keyfile); +} + +/******************************************************************************/ + +NMTST_DEFINE (); + +int main (int argc, char **argv) +{ + nmtst_init (&argc, &argv, TRUE); + + g_test_add_func ("/core/keyfile/test_8021x_cert", test_8021x_cert); + g_test_add_func ("/core/keyfile/test_8021x_cert_read", test_8021x_cert_read); + + return g_test_run (); +} + diff --git a/src/settings/plugins/keyfile/reader.c b/src/settings/plugins/keyfile/reader.c index 3e4cfed8ed..a149e06d93 100644 --- a/src/settings/plugins/keyfile/reader.c +++ b/src/settings/plugins/keyfile/reader.c @@ -70,6 +70,8 @@ _handler_read (GKeyFile *keyfile, level = LOGL_ERR; else if (warn_data->severity >= NM_KEYFILE_WARN_SEVERITY_WARN) level = LOGL_WARN; + else if (warn_data->severity == NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE) + level = LOGL_WARN; else level = LOGL_INFO; diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob b/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob index 9f4ef62fd4..62e6ae31fd 100644 --- a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob +++ b/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob @@ -8,8 +8,8 @@ type=802-3-ethernet eap=tls; identity=Bill Smith ca-cert=48;130;2;52;48;130;1;161;2;16;2;173;102;126;78;69;254;94;87;111;60;152;25;94;221;192;48;13;6;9;42;134;72;134;247;13;1;1;2;5;0;48;95;49;11;48;9;6;3;85;4;6;19;2;85;83;49;32;48;30;6;3;85;4;10;19;23;82;83;65;32;68;97;116;97;32;83;101;99;117;114;105;116;121;44;32;73;110;99;46;49;46;48;44;6;3;85;4;11;19;37;83;101;99;117;114;101;32;83;101;114;118;101;114;32;67;101;114;116;105;102;105;99;97;116;105;111;110;32;65;117;116;104;111;114;105;116;121;48;30;23;13;57;52;49;49;48;57;48;48;48;48;48;48;90;23;13;49;48;48;49;48;55;50;51;53;57;53;57;90;48;95;49;11;48;9;6;3;85;4;6;19;2;85;83;49;32;48;30;6;3;85;4;10;19;23;82;83;65;32;68;97;116;97;32;83;101;99;117;114;105;116;121;44;32;73;110;99;46;49;46;48;44;6;3;85;4;11;19;37;83;101;99;117;114;101;32;83;101;114;118;101;114;32;67;101;114;116;105;102;105;99;97;116;105;111;110;32;65;117;116;104;111;114;105;116;121;48;129;155;48;13;6;9;42;134;72;134;247;13;1;1;1;5;0;3;129;137;0;48;129;133;2;126;0;146;206;122;193;174;131;62;90;170;137;131;87;172;37;1;118;12;173;174;142;44;55;206;235;53;120;100;84;3;229;132;64;81;201;191;143;8;226;138;130;8;210;22;134;55;85;233;177;33;2;173;118;104;129;154;5;162;75;201;75;37;102;34;86;108;136;7;143;247;129;89;109;132;7;101;112;19;113;118;62;155;119;76;227;80;137;86;152;72;185;29;167;41;26;19;46;74;17;89;156;30;21;213;73;84;44;115;58;105;130;177;151;57;156;109;112;103;72;229;221;45;214;200;30;123;2;3;1;0;1;48;13;6;9;42;134;72;134;247;13;1;1;2;5;0;3;126;0;101;221;126;225;178;236;176;226;58;224;236;113;70;154;25;17;184;211;199;160;180;3;64;38;2;62;9;156;225;18;179;209;90;246;55;165;183;97;3;182;91;22;105;59;198;68;8;12;136;83;12;107;151;73;199;62;53;220;108;185;187;170;223;92;187;58;47;147;96;182;169;75;77;242;32;247;205;95;127;100;123;142;220;0;92;215;250;119;202;57;22;89;111;14;234;211;181;131;127;77;77;66;86;118;180;201;95;4;248;56;248;235;210;95;117;95;205;123;252;229;142;128;124;252;80; -client-cert=102;105;108;101;58;47;47;47;104;111;109;101;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0; -private-key=102;105;108;101;58;47;47;47;104;111;109;101;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0; +client-cert=102;105;108;101;58;47;47;47;67;65;83;65;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0; +private-key=102;105;108;101;58;47;47;47;67;65;83;65;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0; private-key-password=12345testing [ipv4] diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old b/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old index 61afdd91c8..d3da598c81 100644 --- a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old +++ b/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old @@ -7,9 +7,9 @@ type=802-3-ethernet [802-1x] eap=tls; identity=Bill Smith -ca-cert=102;105;108;101;58;47;47;47;104;111;109;101;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;67;65;47;101;97;112;116;101;115;116;95;99;97;95;99;101;114;116;46;112;101;109;0; -client-cert=102;105;108;101;58;47;47;47;104;111;109;101;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0; -private-key=102;105;108;101;58;47;47;47;104;111;109;101;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0; +ca-cert=102;105;108;101;58;47;47;47;67;65;83;65;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;67;65;47;101;97;112;116;101;115;116;95;99;97;95;99;101;114;116;46;112;101;109;0; +client-cert=102;105;108;101;58;47;47;47;67;65;83;65;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0; +private-key=102;105;108;101;58;47;47;47;67;65;83;65;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0; private-key-password=12345testing [ipv4] diff --git a/src/settings/plugins/keyfile/tests/test-keyfile.c b/src/settings/plugins/keyfile/tests/test-keyfile.c index 1e90824261..a86bfed6c2 100644 --- a/src/settings/plugins/keyfile/tests/test-keyfile.c +++ b/src/settings/plugins/keyfile/tests/test-keyfile.c @@ -2127,6 +2127,10 @@ test_read_wired_8021x_tls_blob_connection (void) gboolean success; GBytes *blob; + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, + "* keyfile: 802-1x.client-cert: certificate or key file '/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, + "* keyfile: 802-1x.private-key: certificate or key file '/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*"); connection = nm_keyfile_plugin_connection_from_file (TEST_WIRED_TLS_BLOB_FILE, &error); if (connection == NULL) { g_assert (error); @@ -2174,10 +2178,10 @@ test_read_wired_8021x_tls_blob_connection (void) g_assert_cmpint (g_bytes_get_size (blob), ==, 568); tmp = nm_setting_802_1x_get_client_cert_path (s_8021x); - g_assert_cmpstr (tmp, ==, "/home/dcbw/Desktop/certinfra/client.pem"); + g_assert_cmpstr (tmp, ==, "/CASA/dcbw/Desktop/certinfra/client.pem"); tmp = nm_setting_802_1x_get_private_key_path (s_8021x); - g_assert_cmpstr (tmp, ==, "/home/dcbw/Desktop/certinfra/client.pem"); + g_assert_cmpstr (tmp, ==, "/CASA/dcbw/Desktop/certinfra/client.pem"); g_object_unref (connection); } @@ -2259,6 +2263,12 @@ test_read_wired_8021x_tls_old_connection (void) const char *tmp; gboolean success; + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, + "* keyfile: 802-1x.ca-cert: certificate or key file '/CASA/dcbw/Desktop/certinfra/CA/eaptest_ca_cert.pem' does not exist*"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, + "* keyfile: 802-1x.client-cert: certificate or key file '/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, + "* keyfile: 802-1x.private-key: certificate or key file '/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*"); connection = nm_keyfile_plugin_connection_from_file (TEST_WIRED_TLS_OLD_FILE, &error); if (connection == NULL) { g_assert (error); @@ -2292,13 +2302,13 @@ test_read_wired_8021x_tls_old_connection (void) g_assert (g_strcmp0 (tmp, "12345testing") == 0); tmp = nm_setting_802_1x_get_ca_cert_path (s_8021x); - g_assert (g_strcmp0 (tmp, "/home/dcbw/Desktop/certinfra/CA/eaptest_ca_cert.pem") == 0); + g_assert (g_strcmp0 (tmp, "/CASA/dcbw/Desktop/certinfra/CA/eaptest_ca_cert.pem") == 0); tmp = nm_setting_802_1x_get_client_cert_path (s_8021x); - g_assert (g_strcmp0 (tmp, "/home/dcbw/Desktop/certinfra/client.pem") == 0); + g_assert (g_strcmp0 (tmp, "/CASA/dcbw/Desktop/certinfra/client.pem") == 0); tmp = nm_setting_802_1x_get_private_key_path (s_8021x); - g_assert (g_strcmp0 (tmp, "/home/dcbw/Desktop/certinfra/client.pem") == 0); + g_assert (g_strcmp0 (tmp, "/CASA/dcbw/Desktop/certinfra/client.pem") == 0); g_object_unref (connection); } diff --git a/src/settings/plugins/keyfile/writer.c b/src/settings/plugins/keyfile/writer.c index 6b4a35b1b0..9cf119c0c8 100644 --- a/src/settings/plugins/keyfile/writer.c +++ b/src/settings/plugins/keyfile/writer.c @@ -116,24 +116,47 @@ cert_writer (NMConnection *connection, scheme = cert_data->scheme_func (cert_data->setting); if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) { + char *tmp = NULL; + const char *accepted_path = NULL; + path = cert_data->path_func (cert_data->setting); g_assert (path); - /* If the path is rooted in the keyfile directory, just use a - * relative path instead of an absolute one. - */ if (g_str_has_prefix (path, info->keyfile_dir)) { const char *p = path + strlen (info->keyfile_dir); + /* If the path is rooted in the keyfile directory, just use a + * relative path instead of an absolute one. + */ if (*p == '/') { while (*p == '/') p++; - if (p[0]) - path = p; + if (p[0]) { + /* If @p looks like an integer list, the following detection will fail too and + * we will file:// qualify the path below. We thus avoid writing a path string + * that would be interpreted as legacy binary format by reader. */ + tmp = nm_keyfile_detect_unqualified_path_scheme (info->keyfile_dir, p, -1, FALSE, NULL); + if (tmp) { + g_clear_pointer (&tmp, g_free); + accepted_path = p; + } + } + } + } + if (!accepted_path) { + /* What we are about to write, must also be understood by the reader. + * Otherwise, add a file:// prefix */ + tmp = nm_keyfile_detect_unqualified_path_scheme (info->keyfile_dir, path, -1, FALSE, NULL); + if (tmp) { + g_clear_pointer (&tmp, g_free); + accepted_path = path; } } - nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->property_name, path); + if (!accepted_path) + accepted_path = tmp = g_strconcat (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, path, NULL); + nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->property_name, accepted_path); + g_free (tmp); } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) { GBytes *blob; const guint8 *blob_data; @@ -165,8 +188,9 @@ cert_writer (NMConnection *connection, success = write_cert_key_file (new_path, blob_data, blob_len, &local); if (success) { - /* Write the path value to the keyfile */ - nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->property_name, new_path); + /* Write the path value to the keyfile. + * We know, that basename(new_path) starts with a UUID, hence no conflict with "data:;base64," */ + nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->property_name, strrchr (new_path, '/') + 1); } else { nm_log_warn (LOGD_SETTINGS, "keyfile: %s.%s: failed to write certificate to file %s: %s", setting_name, cert_data->property_name, new_path, local->message); From 997fc07ca5b3bcafcdaa69dc7c7f6d8671efa4b4 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Fri, 27 Feb 2015 10:24:09 +0100 Subject: [PATCH 23/23] test: add nmtst_create_connection_from_keyfile() --- include/nm-test-utils.h | 30 ++++++++++++++++++++++++++++++ libnm-core/tests/test-keyfile.c | 7 +++++++ 2 files changed, 37 insertions(+) diff --git a/include/nm-test-utils.h b/include/nm-test-utils.h index 33b8fa51a7..187a0f1931 100644 --- a/include/nm-test-utils.h +++ b/include/nm-test-utils.h @@ -1061,6 +1061,36 @@ nmtst_assert_hwaddr_equals (gconstpointer hwaddr1, gssize hwaddr1_len, const cha nmtst_assert_hwaddr_equals (hwaddr1, hwaddr1_len, expected, G_STRLOC) #endif +#if defined(__NM_SIMPLE_CONNECTION_H__) && defined(__NM_SETTING_CONNECTION_H__) && defined(__NM_KEYFILE_INTERNAL_H__) + +inline static NMConnection * +nmtst_create_connection_from_keyfile (const char *keyfile_str, const char *keyfile_name, const char *base_dir) +{ + GKeyFile *keyfile; + GError *error = NULL; + gboolean success; + NMConnection *con; + + g_assert (keyfile_str); + + keyfile = g_key_file_new (); + success = g_key_file_load_from_data (keyfile, keyfile_str, strlen (keyfile_str), G_KEY_FILE_NONE, &error); + g_assert_no_error (error); + g_assert (success); + + con = nm_keyfile_read (keyfile, keyfile_name, base_dir, NULL, NULL, &error); + g_assert_no_error (error); + g_assert (NM_IS_CONNECTION (con)); + + g_key_file_unref (keyfile); + + nmtst_connection_normalize (con); + + return con; +} + +#endif + #ifdef __NM_CONNECTION_H__ typedef enum { diff --git a/libnm-core/tests/test-keyfile.c b/libnm-core/tests/test-keyfile.c index a0f590e7cd..43958cb6bb 100644 --- a/libnm-core/tests/test-keyfile.c +++ b/libnm-core/tests/test-keyfile.c @@ -345,6 +345,13 @@ test_8021x_cert_read (void) gs_unref_object NMConnection *con = NULL; NMSetting8021x *s_8021x; + con = nmtst_create_connection_from_keyfile ( + "[connection]\n" + "type=ethernet", + "/test_8021x_cert_read/test0", NULL); + CLEAR (&con, &keyfile); + + keyfile = _keyfile_load_from_data ( "[connection]\n" "type=ethernet"