From f2b397acf220c0453155c8adc31d78fb7cae1968 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 10 Mar 2015 17:16:34 +0100 Subject: [PATCH 01/21] build/trivial: cleanup Makefile.am by sorting EXTRA_DIST entries (cherry picked from commit ad4e6f7fad77d4e877f87c9763771889b410f395) --- 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 db2fa9455d32550ce263320d68dbd8cebe2be0ec Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 10 Mar 2015 14:32:16 +0100 Subject: [PATCH 02/21] test: add nmtst_assert_resolve_relative_path_equals() function (cherry picked from commit cbfa00219e77a0cbe9afa7aae548905f188bf6cf) --- 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 67b54f3a9e..b33dcedae8 100644 --- a/include/nm-test-utils.h +++ b/include/nm-test-utils.h @@ -707,6 +707,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 36f5c1b2787f7591a0d478954d1fb5e5255d4561 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 23 Feb 2015 15:55:28 +0100 Subject: [PATCH 03/21] keyfile: add code comment to cert_writer() and downgrade assertion to g_critical() (cherry picked from commit 1e4612e476c48a4620a347948b5c1bf698dc1f43) --- 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 7642a6c771946e7519d68660e0bd58c5a0b8d999 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 23 Feb 2015 15:34:24 +0100 Subject: [PATCH 04/21] 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). (cherry picked from commit e59e68c5286c59b8583d23651f2aea4440cfb147) --- 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..a83c24c30c 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-macros-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..f5ab1bf9c1 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-macros-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 fa5a6caf2b70ac0071a09646d7ddc5f8e6df5e6a Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 26 Feb 2015 01:48:50 +0100 Subject: [PATCH 05/21] 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(). (cherry picked from commit cda7b158e2b87fc4a397334c50448ecb9d2457c2) --- 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 a83c24c30c..259eea7cfc 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-macros-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 f5ab1bf9c1..ea141c65d7 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 ef3cd489e1d974a6337a27de0d57326decc7f097 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 26 Feb 2015 02:28:52 +0100 Subject: [PATCH 06/21] 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. (cherry picked from commit 88a79625a667a562831925d4636bf819e0aa0f6b) --- 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 259eea7cfc..8b8950b222 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 40b640f9bcb4c88f4f7931648a5c7e4887cebd53 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 26 Feb 2015 02:27:54 +0100 Subject: [PATCH 07/21] libnm: only call strlen() once in NMSetting8021x:path_to_scheme_value() Also assert that path is not empty. (cherry picked from commit 11371b5470a890512b697d0926a242d9aefe9d1d) --- 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 8b8950b222..078a59eb4d 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 ea141c65d7..678d70ab71 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 7c6da95f14ab902d00eae440432259b83d7b8fe0 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 26 Feb 2015 02:38:14 +0100 Subject: [PATCH 08/21] trivial: add FIXME code comment about potential race (cherry picked from commit 0f1fe6942253bfdfa35c1108049baeb2be048bb7) --- 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 078a59eb4d..3306228316 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 21849d73ee292018cb06245c0c9d357e72589e21 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 26 Feb 2015 10:12:11 +0100 Subject: [PATCH 09/21] 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. While backporting, hide public API from 1.2. (cherry picked from commit 15926e9eb359d84a4bc039e4a5e3c20604a3d4f7) --- libnm-core/nm-setting-8021x.c | 30 +++++++++++++++++++++++++----- libnm-core/nm-setting-private.h | 2 ++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/libnm-core/nm-setting-8021x.c b/libnm-core/nm-setting-8021x.c index 3306228316..2c201b3933 100644 --- a/libnm-core/nm-setting-8021x.c +++ b/libnm-core/nm-setting-8021x.c @@ -416,7 +416,30 @@ 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. + **/ +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 +507,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-private.h b/libnm-core/nm-setting-private.h index 2d34d509a5..955608fae5 100644 --- a/libnm-core/nm-setting-private.h +++ b/libnm-core/nm-setting-private.h @@ -155,4 +155,6 @@ gboolean _nm_setting_use_legacy_property (NMSetting *setting, GPtrArray *_nm_setting_need_secrets (NMSetting *setting); +NMSetting8021xCKScheme nm_setting_802_1x_check_cert_scheme (gconstpointer pdata, gsize length, GError **error); + #endif /* NM_SETTING_PRIVATE_H */ From d655b80dbe983c472ecc181bc4eeddb6940ba3e0 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 25 Feb 2015 21:52:00 +0100 Subject: [PATCH 10/21] 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). (cherry picked from commit 7b6759b76427868723f480fc3516d964df237542) --- 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 4dad608f11..6460ef63e6 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 57c2c844f16d9246e40860416ff1415fcd889cf1 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 24 Feb 2015 23:27:36 +0100 Subject: [PATCH 11/21] 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. (cherry picked from commit f430774ca0f5a4542b5d1faa59240a952c5e19ce) --- 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 6460ef63e6..8f50b4bf4f 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 0bc93e62f76b9b6c4064c940aed1e6fd1bfb2a20 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Fri, 27 Feb 2015 00:27:13 +0100 Subject: [PATCH 12/21] keyfile: remove unused struct member ObjectType.privkey_pw_prop in writer.c (cherry picked from commit b66deb67faa09597a8b6e52bbf07c39622220f38) --- 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 e0777a33b6f01e449a4c9010980cec94040211a1 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Sun, 22 Feb 2015 20:15:40 +0100 Subject: [PATCH 13/21] trivial: rename nm_utils_uuid_generate_from_strings() to _nm_utils_uuid_generate_from_strings() (cherry picked from commit 67510e323a69a6724c9f454fa040b993e1d9b7f4) --- 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 a870f51751..a4760cbdfd 100644 --- a/src/NetworkManagerUtils.c +++ b/src/NetworkManagerUtils.c @@ -1996,7 +1996,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. @@ -2009,7 +2009,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 c1512176fc..8bc3a49350 100644 --- a/src/NetworkManagerUtils.h +++ b/src/NetworkManagerUtils.h @@ -157,7 +157,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 7617637086eed38772f108e632a7c0e6de71916a Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Sun, 22 Feb 2015 20:15:52 +0100 Subject: [PATCH 14/21] libnm: move _nm_utils_uuid_generate_from_strings() from src/ to libnm-core/ (cherry picked from commit e82293ebf68a68fe59adc138bcbecfb978d7e135) --- 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 2f42ab52ac..ab147b9f7a 100644 --- a/libnm-core/nm-core-internal.h +++ b/libnm-core/nm-core-internal.h @@ -128,6 +128,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 abd5a56e5b..16561175da 100644 --- a/libnm-core/nm-utils.c +++ b/libnm-core/nm-utils.c @@ -2042,6 +2042,8 @@ nm_utils_ip_routes_from_variant (GVariant *value, return routes; } +/**********************************************************************************************/ + /** * nm_utils_uuid_generate: * @@ -2114,6 +2116,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 5ffe5c5660..bd144f384b 100644 --- a/libnm-core/tests/test-general.c +++ b/libnm-core/tests/test-general.c @@ -4042,6 +4042,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 @@ -4498,6 +4545,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); g_test_add_func ("/core/general/nm_utils_is_power_of_two", test_nm_utils_is_power_of_two); diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c index a4760cbdfd..355e3c9897 100644 --- a/src/NetworkManagerUtils.c +++ b/src/NetworkManagerUtils.c @@ -1995,48 +1995,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 8bc3a49350..19d2b0783b 100644 --- a/src/NetworkManagerUtils.h +++ b/src/NetworkManagerUtils.h @@ -155,10 +155,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 d086b5e928eccbc84d676179a622438360f1d1d3 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 18 Feb 2015 19:59:28 +0100 Subject: [PATCH 15/21] 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 (cherry picked from commit 1fc9bc401e1de676bac3a3220af1bc094bed6e22) --- 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 45aba0fe73d85b7176cdb259e63137be17a8beba Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 18 Feb 2015 18:59:35 +0100 Subject: [PATCH 16/21] libnm: add keyfile support to libnm-core https://bugzilla.gnome.org/show_bug.cgi?id=744699 (cherry picked from commit 04df4edf48e55478d0f360ea566f5f398aa76268) --- 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 176e4a3f54706b4836d1afb5e1bc0c89ddff5d92 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 23 Feb 2015 11:41:22 +0100 Subject: [PATCH 17/21] 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. (cherry picked from commit 5e5afcffce1ab8ee189721678b1760f329f83cce) --- 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 a18e6c81af0d41c67c4e7819d4ca98f4778b5ea1 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 23 Feb 2015 11:56:04 +0100 Subject: [PATCH 18/21] keyfile: refactor to use reading and writing of keyfile from libnm-core (cherry picked from commit 57a432fa8a225dc5849251467d24c9927d210f37) --- 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 3af5c798f5..f491dbd561 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 46ebed64f43baaa82642e0ea1f0b6c3960157383 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 24 Feb 2015 22:22:02 +0100 Subject: [PATCH 19/21] libnm: add define for cert scheme prefix file:// for NMSetting8021x (cherry picked from commit a49680dacd73549169bfad1ba3bd94e1a7bb1ac1) --- clients/cli/settings.c | 10 ++++------ 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, 27 insertions(+), 30 deletions(-) diff --git a/clients/cli/settings.c b/clients/cli/settings.c index 297e173dbc..66e6f62245 100644 --- a/clients/cli/settings.c +++ b/clients/cli/settings.c @@ -2705,13 +2705,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, \ @@ -2727,14 +2726,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 2c201b3933..02f9c9039d 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) @@ -448,8 +446,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. @@ -465,7 +463,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, @@ -473,7 +471,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, @@ -585,7 +583,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 * @@ -599,8 +597,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); @@ -914,7 +912,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); } /** @@ -1183,7 +1181,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); } /** @@ -1497,7 +1495,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); } /** @@ -1756,7 +1754,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 @@ -2094,7 +2092,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 43885daaa3..3099fbd1d7 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 a1abe76aec80e48a9c626dbe8134e37993d93acc Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 24 Feb 2015 22:22:14 +0100 Subject: [PATCH 20/21] 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. (cherry picked from commit c9a8764ad2805e22e1c556679e09636550374e21) --- .gitignore | 1 + libnm-core/nm-keyfile-internal.h | 7 + libnm-core/nm-keyfile-reader.c | 264 ++++++--- 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, 1000 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 2e1bf5febc..9868ec3eaf 100644 --- a/.gitignore +++ b/.gitignore @@ -150,6 +150,7 @@ test-*.trs /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..c1d5ce7559 100644 --- a/libnm-core/nm-keyfile-reader.c +++ b/libnm-core/nm-keyfile-reader.c @@ -31,10 +31,12 @@ #include #include "nm-core-internal.h" +#include "nm-macros-internal.h" #include "gsystem-local-alloc.h" #include "nm-glib-compat.h" #include "nm-keyfile-internal.h" #include "nm-keyfile-utils.h" +#include "nm-setting-private.h" typedef struct { @@ -838,23 +840,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 +1021,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..2de114da77 --- /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-macros-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 f491dbd561..fcb1da60f6 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 e977e55e94bbe2fbeb5a84760e175442b68a9a65 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Fri, 27 Feb 2015 10:24:09 +0100 Subject: [PATCH 21/21] test: add nmtst_create_connection_from_keyfile() (cherry picked from commit 997fc07ca5b3bcafcdaa69dc7c7f6d8671efa4b4) --- 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 b33dcedae8..0e6fee642e 100644 --- a/include/nm-test-utils.h +++ b/include/nm-test-utils.h @@ -1217,6 +1217,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 2de114da77..79d8ba2451 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"