diff options
author | Thomas Haller <thaller@redhat.com> | 2015-03-12 18:17:21 +0100 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2015-03-12 18:22:12 +0100 |
commit | 59eb5312a5d64fd4dd91b2348ab035506bfb12a4 (patch) | |
tree | 8ed96d29452bece679082017eb4f0f07ecdb1950 | |
parent | 0429ed85addddaf96e231183efe2a2b9f5f653b3 (diff) | |
parent | 997fc07ca5b3bcafcdaa69dc7c7f6d8671efa4b4 (diff) | |
download | NetworkManager-59eb5312a5d64fd4dd91b2348ab035506bfb12a4.tar.gz |
keyfile: merge branch 'th/libnm-keyfile-bgo744699'
Move basic keyfile functionality from settings plugin to libnm-core.
This is a first step to have a semi-standard way to stringify
connections back and forth, which is also available to libnm users.
Still the new functions are internal API (nm-keyfile-internal.h).
Let's decide later how the public API should really look like.
https://bugzilla.gnome.org/show_bug.cgi?id=744699
41 files changed, 4336 insertions, 2620 deletions
diff --git a/.gitignore b/.gitignore index 40448f92bc..397524e3f0 100644 --- a/.gitignore +++ b/.gitignore @@ -143,6 +143,7 @@ valgrind-*.log /libnm-core/tests/test-crypto /libnm-core/tests/test-settings-defaults /libnm-core/tests/test-general +/libnm-core/tests/test-keyfile /libnm-core/tests/test-need-secrets /libnm-core/tests/test-secrets /libnm-core/tests/test-setting-8021x diff --git a/clients/cli/settings.c b/clients/cli/settings.c index 47c7321756..7da47d43a8 100644 --- a/clients/cli/settings.c +++ b/clients/cli/settings.c @@ -29,6 +29,7 @@ #include "common.h" #include "settings.h" #include "nm-glib-compat.h" +#include "nm-utils-internal.h" /* Forward declarations */ static char *wep_key_type_to_string (NMWepKeyType type); @@ -2691,13 +2692,12 @@ nmc_property_connection_describe_secondaries (NMSetting *setting, const char *pr static gboolean \ def_func (NMSetting *setting, const char *prop, const char *val, GError **error) \ { \ - const char *SCHEME_PATH = "file://"; \ char *val_strip = g_strstrip (g_strdup (val)); \ char *p = val_strip; \ gboolean success; \ \ - if (strncmp (val_strip, SCHEME_PATH, strlen (SCHEME_PATH)) == 0) \ - p += strlen (SCHEME_PATH); \ + if (strncmp (val_strip, NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH, STRLEN (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH)) == 0) \ + p += STRLEN (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH); \ \ success = set_func (NM_SETTING_802_1X (setting), \ p, \ @@ -2713,14 +2713,13 @@ nmc_property_connection_describe_secondaries (NMSetting *setting, const char *pr def_func (NMSetting *setting, const char *prop, const char *val, GError **error) \ { \ char **strv = NULL; \ - const char *SCHEME_PATH = "file://"; \ char *val_strip = g_strstrip (g_strdup (val)); \ char *p = val_strip; \ const char *path, *password; \ gboolean success; \ \ - if (strncmp (val_strip, SCHEME_PATH, strlen (SCHEME_PATH)) == 0) \ - p += strlen (SCHEME_PATH); \ + if (strncmp (val_strip, NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH, STRLEN (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH)) == 0) \ + p += STRLEN (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH); \ \ strv = nmc_strsplit_set (p, " \t,", 2); \ path = strv[0]; \ diff --git a/include/nm-test-utils.h b/include/nm-test-utils.h index 7961b03891..187a0f1931 100644 --- a/include/nm-test-utils.h +++ b/include/nm-test-utils.h @@ -576,6 +576,39 @@ __nmtst_spawn_sync (const char *working_directory, char **standard_out, char **s /*******************************************************************************/ +inline static char * +nmtst_file_resolve_relative_path (const char *rel, const char *cwd) +{ + gs_free char *cwd_free = NULL; + + g_assert (rel && *rel); + + if (g_path_is_absolute (rel)) + return g_strdup (rel); + + if (!cwd) + cwd = cwd_free = g_get_current_dir (); + return g_build_filename (cwd, rel, NULL); +} + +inline static void +_nmtst_assert_resolve_relative_path_equals (const char *f1, const char *f2, const char *file, int line) +{ + gs_free char *p1 = NULL, *p2 = NULL; + + p1 = nmtst_file_resolve_relative_path (f1, NULL); + p2 = nmtst_file_resolve_relative_path (f2, NULL); + g_assert (p1 && *p1); + + /* Fixme: later we might need to coalesce repeated '/', "./", and "../". + * For now, it's good enough. */ + if (g_strcmp0 (p1, p2) != 0) + g_error ("%s:%d : filenames don't match \"%s\" vs. \"%s\" // \"%s\" - \"%s\"", file, line, f1, f2, p1, p2); +} +#define nmtst_assert_resolve_relative_path_equals(f1, f2) _nmtst_assert_resolve_relative_path_equals (f1, f2, __FILE__, __LINE__); + +/*******************************************************************************/ + #ifdef __NETWORKMANAGER_PLATFORM_H__ inline static NMPlatformIP6Address * @@ -1028,6 +1061,36 @@ nmtst_assert_hwaddr_equals (gconstpointer hwaddr1, gssize hwaddr1_len, const cha nmtst_assert_hwaddr_equals (hwaddr1, hwaddr1_len, expected, G_STRLOC) #endif +#if defined(__NM_SIMPLE_CONNECTION_H__) && defined(__NM_SETTING_CONNECTION_H__) && defined(__NM_KEYFILE_INTERNAL_H__) + +inline static NMConnection * +nmtst_create_connection_from_keyfile (const char *keyfile_str, const char *keyfile_name, const char *base_dir) +{ + GKeyFile *keyfile; + GError *error = NULL; + gboolean success; + NMConnection *con; + + g_assert (keyfile_str); + + keyfile = g_key_file_new (); + success = g_key_file_load_from_data (keyfile, keyfile_str, strlen (keyfile_str), G_KEY_FILE_NONE, &error); + g_assert_no_error (error); + g_assert (success); + + con = nm_keyfile_read (keyfile, keyfile_name, base_dir, NULL, NULL, &error); + g_assert_no_error (error); + g_assert (NM_IS_CONNECTION (con)); + + g_key_file_unref (keyfile); + + nmtst_connection_normalize (con); + + return con; +} + +#endif + #ifdef __NM_CONNECTION_H__ typedef enum { diff --git a/libnm-core/Makefile.libnm-core b/libnm-core/Makefile.libnm-core index fc6d779b5e..09a80c714b 100644 --- a/libnm-core/Makefile.libnm-core +++ b/libnm-core/Makefile.libnm-core @@ -48,6 +48,8 @@ libnm_core_private_headers = \ $(core)/crypto.h \ $(core)/nm-connection-private.h \ $(core)/nm-core-internal.h \ + $(core)/nm-keyfile-internal.h \ + $(core)/nm-keyfile-utils.h \ $(core)/nm-property-compare.h \ $(core)/nm-setting-private.h \ $(core)/nm-utils-private.h @@ -57,6 +59,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-core-internal.h b/libnm-core/nm-core-internal.h index 5af90267a5..86a301bd1b 100644 --- a/libnm-core/nm-core-internal.h +++ b/libnm-core/nm-core-internal.h @@ -122,6 +122,10 @@ char ** _nm_utils_strsplit_set (const char *str, char *nm_utils_uuid_generate_from_string (const char *s, gssize slen, int uuid_type, gpointer type_args); +#define NM_UTILS_UUID_NS "b425e9fb-7598-44b4-9e3b-5a2e3aaa4905" + +char *_nm_utils_uuid_generate_from_strings (const char *string1, ...) G_GNUC_NULL_TERMINATED; + void _nm_dbus_errors_init (void); extern gboolean _nm_utils_is_manager_process; @@ -132,4 +136,6 @@ GByteArray *nm_utils_rsa_key_encrypt (const guint8 *data, char **out_password, GError **error); +gint64 _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max, gint64 fallback); + #endif diff --git a/libnm-core/nm-keyfile-internal.h b/libnm-core/nm-keyfile-internal.h new file mode 100644 index 0000000000..90af562cdf --- /dev/null +++ b/libnm-core/nm-keyfile-internal.h @@ -0,0 +1,166 @@ +/* -*- 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_INTERNAL_H__ +#define __NM_KEYFILE_INTERNAL_H__ + +#include <glib.h> +#include <sys/types.h> + +#include "nm-connection.h" +#include "nm-setting-8021x.h" + + +/*********************************************************/ + +#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; + +/** + * 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_INFO_MISSING_FILE = 2901, + 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; + +/** + * 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); + +/*********************************************************/ + +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-reader.c b/libnm-core/nm-keyfile-reader.c new file mode 100644 index 0000000000..87c1e14e8c --- /dev/null +++ b/libnm-core/nm-keyfile-reader.c @@ -0,0 +1,1656 @@ +/* -*- 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 - 2015 Red Hat, Inc. + */ + +#include "config.h" + +#include <errno.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <unistd.h> +#include <sys/types.h> +#include <arpa/inet.h> +#include <string.h> +#include <glib/gi18n-lib.h> + +#include "nm-core-internal.h" +#include "nm-utils-internal.h" +#include "gsystem-local-alloc.h" +#include "nm-glib-compat.h" +#include "nm-keyfile-internal.h" +#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 + * 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 (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 (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), + key, key_setting_name ? key_setting_name : s, + NULL); + g_free (s); + } +} + +static void +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); + + for (i = 0; i < length; i++) + g_array_append_val (array, tmp[i]); + + g_object_set (setting, key, array, NULL); + g_array_unref (array); +} + +static gboolean +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 (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 (property_name) + handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid number '%s'"), + str); + return FALSE; + } + + *out = (guint32) tmp; + return TRUE; +} + +static gpointer +build_address (KeyfileReaderInfo *info, int family, const char *address_str, guint32 plen, const char *property_name) +{ + 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) { + 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); + } + + return addr; +} + +static gpointer +build_route (KeyfileReaderInfo *info, + const char *property_name, + int family, + const char *dest_str, guint32 plen, + const char *gateway_str, const char *metric_str) +{ + 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 (NULL, NULL, gateway_str, G_MAXUINT32, &metric)) + gateway_str = NULL; + else { + 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; + } + } + } else + gateway_str = NULL; + + /* parse metric, default to 0 */ + if (metric_str) { + if (!get_one_int (info, property_name, metric_str, G_MAXUINT32, &metric)) + return NULL; + } + + route = nm_ip_route_new (family, dest_str, plen, gateway_str, + metric ? (gint64) metric : -1, + &error); + if (!route) { + 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); + } + + 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 (KeyfileReaderInfo *info, + const char *property_name, + const char *setting_name, + const char *key_name, + gboolean ipv6, + gboolean route, + char **out_gateway, + NMSetting *setting) +{ + guint32 plen = G_MAXUINT32; + gpointer result; + char *address_str, *plen_str, *gateway_str, *metric_str, *current, *error; + gs_free char *value = NULL, *value_orig = 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) { + 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) { + 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) { + 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; + if (current) { + /* there is still some data */ + if (*current) { + /* another field follows */ + 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 */ + 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; + } + } + +#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 (info, property_name, plen_str, ipv6 ? 128 : 32, &plen) + || (route && plen == 0)) { + plen = DEFAULT_PREFIX (route, ipv6); + 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); + 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 (info, property_name, + ipv6 ? AF_INET6 : AF_INET, + address_str, plen, gateway_str, metric_str); + } else { + 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); + } + +#undef VALUE_ORIG + + return result; +} + +static void +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"); + 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 (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); + + } + } + + 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 (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + 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 (info->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) { + 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; + } + + 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 (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + 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 (info->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) { + 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; + } + + 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 (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; + gint *tmp_list; + GByteArray *array = NULL; + gsize length; + + 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; + + 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 (info->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) { + 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); + g_free (tmp_list); + return; + } + g_byte_array_append (array, &v, 1); + } + } + g_free (tmp_list); + } + + if (!array) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid MAC address")); + 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 (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + mac_address_parser (info, setting, key, ETH_ALEN); +} + +static void +mac_address_parser_INFINIBAND (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + mac_address_parser (info, setting, key, 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 (KeyfileReaderInfo *info, + 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 (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 (info->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) { + gboolean already_warned = FALSE; + + /* Old format; list of ints */ + tmp_list = nm_keyfile_plugin_kf_get_integer_list (info->keyfile, setting_name, key, &length, NULL); + if (!tmp_list) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid binary property")); + 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) { + 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)); + } + 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 (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char *setting_name = nm_setting_get_name (setting); + GBytes *bytes; + + bytes = get_bytes (info, setting_name, key, FALSE, TRUE); + if (bytes) { + g_object_set (setting, key, bytes, NULL); + g_bytes_unref (bytes); + } else if (!info->error) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid SSID")); + } +} + +static void +password_raw_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char *setting_name = nm_setting_get_name (setting); + GBytes *bytes; + + bytes = get_bytes (info, setting_name, key, FALSE, TRUE); + if (bytes) { + g_object_set (setting, key, bytes, NULL); + g_bytes_unref (bytes); + } else if (!info->error) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid raw password")); + } +} + +static char * +get_cert_path (const char *base_dir, const guint8 *cert_path, gsize cert_path_len) +{ + const char *base; + char *p = NULL, *path, *tmp; + + 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); + memcpy (path, cert_path, cert_path_len); + + if (path[0] == '/') + return path; + + p = strrchr (path, '/'); + if (p) + base = p + 1; + + tmp = g_build_path ("/", base_dir, base, NULL); + g_free (path); + return tmp; +} + +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 (KeyfileReaderInfo *info, GBytes *bytes, NMSetting *setting, const char *key) +{ + const char *data; + gsize data_len, bin_len; + + data = g_bytes_get_data (bytes, &data_len); + + 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, + NMSetting *setting, + const char *key) +{ + const guint8 *data; + gsize data_len; + char *path; + gboolean exists = FALSE; + GBytes *val; + + data = g_bytes_get_data (bytes, &data_len); + + path = nm_keyfile_detect_unqualified_path_scheme (info->base_dir, data, data_len, TRUE, &exists); + if (!path) + return FALSE; + + /* 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); + + /* 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_bytes_unref (val); + + return TRUE; +} + +static void +cert_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char *setting_name = nm_setting_get_name (setting); + 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://") */ + if (handle_as_scheme (info, bytes, setting, key)) + return; + if (info->error) + return; + + /* If not, it might be a plain path */ + if (handle_as_path (info, bytes, setting, key)) + return; + if (info->error) + return; + + 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")); + } +} + +static void +parity_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char *setting_name = nm_setting_get_name (setting); + NMSettingSerialParity parity; + int int_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 (info->keyfile, setting_name, key, NULL); + if (!int_val) { + 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]; + else { + /* This will hit the warning below */ + int_val = 'X'; + } + } + } + + 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: + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid parity value '%s'"), + str_val ? str_val : ""); + return; + } + + g_object_set (setting, key, parity, NULL); +} + +typedef struct { + const char *setting_name; + const char *key; + gboolean check_for_key; + void (*parser) (KeyfileReaderInfo *info, NMSetting *setting, const char *key); +} 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 } +}; + +static void +read_one_setting_value (NMSetting *setting, + const char *key, + const GValue *value, + GParamFlags flags, + gpointer user_data) +{ + KeyfileReaderInfo *info = user_data; + GKeyFile *keyfile = info->keyfile; + const char *setting_name; + int errsv; + GType type; + 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; + + /* 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 (keyfile, setting_name, key, &err)) { + /* Key doesn't exist or an error ocurred, thus nothing to do. */ + if (err) { + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("error loading setting value: %s"), + err->message)) + goto out_error; + } + return; + } + + /* If there's a custom parser for this key, handle that before the generic + * parsers below. + */ + if (parser->setting_name) { + (*parser->parser) (info, setting, key); + return; + } + + type = G_VALUE_TYPE (value); + + if (type == G_TYPE_STRING) { + char *str_val; + + 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 (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 (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 (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 (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 (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) { + gs_free char *tmp_str = NULL; + gint64 int_val; + + 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) { + 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); + } 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 (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) { + 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)); + } + + 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 (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 (keyfile, setting, key); + } else if (type == G_TYPE_ARRAY) { + 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 (keyfile, setting_name, key, &err); + if (!err) { + if (uint_val <= G_MAXUINT) + g_object_set (setting, key, (guint) uint_val, NULL); + else { + 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; + } + } + } else if (G_VALUE_HOLDS_ENUM (value)) { + gint int_val; + + int_val = nm_keyfile_plugin_kf_get_integer (keyfile, setting_name, key, &err); + if (!err) + g_object_set (setting, key, (gint) int_val, NULL); + } else { + 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 (KeyfileReaderInfo *info) +{ + const char *alias; + GType type; + + alias = nm_keyfile_plugin_get_setting_name_for_alias (info->group); + if (!alias) + alias = info->group; + + type = nm_setting_lookup_type (alias); + if (type) { + NMSetting *setting = g_object_new (type, NULL); + + 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 (KeyfileReaderInfo *info, NMSettingVpn *s_vpn) +{ + char **keys, **iter; + + 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 (info->keyfile, VPN_SECRETS_GROUP, *iter, NULL); + if (secret) { + nm_setting_vpn_add_secret (s_vpn, *iter, secret); + g_free (secret); + } + } + 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_read (GKeyFile *keyfile, + const char *keyfile_name, + const char *base_dir, + NMKeyfileReadHandler handler, + void *user_data, + GError **error) +{ + NMConnection *connection = NULL; + NMSettingConnection *s_con; + NMSetting *setting; + gchar **groups; + gsize length; + int i; + gboolean vpn_secrets = FALSE; + KeyfileReaderInfo info = { 0 }; + gs_free char *base_dir_free = NULL; + + g_return_val_if_fail (keyfile, NULL); + g_return_val_if_fail (!error || !*error, NULL); + + 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 (); + + 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)) { + vpn_secrets = TRUE; + continue; + } + + 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) { + 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 ( keyfile_name + && !nm_setting_connection_get_id (s_con)) { + char *base_name; + + 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 ( keyfile_name + && !nm_setting_connection_get_uuid (s_con)) { + char *hashed_uuid; + + 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); + } + + /* 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 (keyfile, + 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 (&info, s_vpn); + if (info.error) + goto out_error; + } + } + + return connection; +out_error: + g_propagate_error (error, info.error); + g_free (connection); + return NULL; +} diff --git a/libnm-core/nm-keyfile-utils.c b/libnm-core/nm-keyfile-utils.c new file mode 100644 index 0000000000..61b30ab9c8 --- /dev/null +++ b/libnm-core/nm-keyfile-utils.c @@ -0,0 +1,207 @@ +/* -*- 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 <glib.h> +#include <stdlib.h> +#include <string.h> + +#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" + + +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..fd4334d112 --- /dev/null +++ b/libnm-core/nm-keyfile-utils.h @@ -0,0 +1,79 @@ +/* -*- 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-2015 Red Hat, Inc. + */ + +#ifndef __NM_KEYFILE_UTILS_H__ +#define __NM_KEYFILE_UTILS_H__ + +#include <glib.h> + +#define VPN_SECRETS_GROUP "vpn-secrets" + +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 /* __NM_KEYFILE_UTILS_H__ */ + diff --git a/libnm-core/nm-keyfile-writer.c b/libnm-core/nm-keyfile-writer.c new file mode 100644 index 0000000000..ff433b264a --- /dev/null +++ b/libnm-core/nm-keyfile-writer.c @@ -0,0 +1,751 @@ +/* -*- 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 - 2015 Red Hat, Inc. + */ + +#include "config.h" + +#include <stdlib.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdio.h> +#include <errno.h> +#include <arpa/inet.h> +#include <string.h> +#include <glib/gi18n-lib.h> + +#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 "gsystem-local-alloc.h" +#include "nm-glib-compat.h" +#include "nm-keyfile-internal.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 + * 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 (KeyfileWriterInfo *info, + 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 (info->keyfile, + nm_setting_get_name (setting), + key, + alias ? alias : str); +} + +static void +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; + + 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); +} + +static void +dns_writer (KeyfileWriterInfo *info, + 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 (info->keyfile, 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 (KeyfileWriterInfo *info, + 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 (info->keyfile, setting_name, array, gateway, FALSE); +} + +static void +ip4_addr_label_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + /* skip */ +} + +static void +gateway_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + /* skip */ +} + +static void +route_writer (KeyfileWriterInfo *info, + 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 (info->keyfile, 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 (KeyfileWriterInfo *info, + 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 (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 (info->keyfile, setting_name, key, tmp_array, ssid_len); + g_free (tmp_array); + } +} + +static void +password_raw_writer (KeyfileWriterInfo *info, + 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 (info->keyfile, 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 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, + const char *key, + const GValue *value) +{ + const ObjectType *objtype = NULL; + 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) { + objtype = &objtypes[i]; + break; + } + } + if (!objtype) + g_return_if_reached (); + + 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) { + 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; + void (*writer) (KeyfileWriterInfo *info, + 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 } +}; + +static void +write_setting_value (NMSetting *setting, + const char *key, + const GValue *value, + GParamFlags flag, + gpointer 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; + + /* 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, 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) { + 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 + g_warn_if_reached (); +} + +GKeyFile * +nm_keyfile_write (NMConnection *connection, + NMKeyfileWriteHandler handler, + void *user_data, + GError **error) +{ + KeyfileWriterInfo info = { 0 }; + + g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL); + g_return_val_if_fail (!error || !*error, NULL); + + if (!nm_connection_verify (connection, error)) + return NULL; + + 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); + + if (info.error) { + g_propagate_error (error, info.error); + g_key_file_unref (info.keyfile); + return NULL; + } + return info.keyfile; +} + diff --git a/libnm-core/nm-setting-8021x.c b/libnm-core/nm-setting-8021x.c index 41559e0c8a..f355c18e70 100644 --- a/libnm-core/nm-setting-8021x.c +++ b/libnm-core/nm-setting-8021x.c @@ -31,6 +31,8 @@ #include "nm-utils-private.h" #include "nm-setting-private.h" #include "nm-core-enum-types.h" +#include "nm-utils-internal.h" +#include "gsystem-local-alloc.h" /** * SECTION:nm-setting-8021x @@ -60,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) @@ -400,25 +400,122 @@ 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) + return nm_setting_802_1x_check_cert_scheme (data, length, error); +} + +/** + * nm_setting_802_1x_check_cert_scheme: + * @pdata: (allow-none): the data pointer + * @length: the length of the data + * @error: (allow-none): (out): validation reason + * + * Determines and verifies the blob type. + * When setting certificate properties of NMSetting8021x + * the blob must be not UNKNOWN (or NULL). + * + * Returns: the scheme of the blob or %NM_SETTING_802_1X_CK_SCHEME_UNKNOWN. + * For NULL it also returns NM_SETTING_802_1X_CK_SCHEME_UNKNOWN. + * + * Since: 1.2 + **/ +NMSetting8021xCKScheme +nm_setting_802_1x_check_cert_scheme (gconstpointer pdata, gsize length, GError **error) +{ + const char *data = pdata; + + g_return_val_if_fail (!length || data, NM_SETTING_802_1X_CK_SCHEME_UNKNOWN); + + if (!length || !data) { + g_set_error_literal (error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("binary data missing")); return NM_SETTING_802_1X_CK_SCHEME_UNKNOWN; + } + + /* interpret the blob as PATH if it starts with "file://". */ + 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. + * 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 (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_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 (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, + _("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; } +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. */ + 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; + } + + if (out_file_format) + *out_file_format = format; + return array; +} + /** * nm_setting_802_1x_get_ca_cert_scheme: * @setting: the #NMSetting8021x @@ -434,7 +531,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); } /** @@ -488,20 +585,23 @@ 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 * 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_byte_array_append (array, (const guint8 *) SCHEME_PATH, strlen (SCHEME_PATH)); - g_byte_array_append (array, (const guint8 *) path, strlen (path)); + len = strlen (path); + + /* Add the path scheme tag to the front, then the filename */ + 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); return g_byte_array_free_to_bytes (array); @@ -558,7 +658,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) { @@ -766,7 +866,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); } /** @@ -814,7 +914,7 @@ nm_setting_802_1x_get_client_cert_path (NMSetting8021x *setting) g_return_val_if_fail (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH, NULL); data = g_bytes_get_data (NM_SETTING_802_1X_GET_PRIVATE (setting)->client_cert, NULL); - return (const char *)data + strlen (SCHEME_PATH); + return (const char *)data + strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH); } /** @@ -872,7 +972,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; @@ -1029,7 +1129,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); } /** @@ -1083,7 +1183,7 @@ nm_setting_802_1x_get_phase2_ca_cert_path (NMSetting8021x *setting) g_return_val_if_fail (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH, NULL); data = g_bytes_get_data (NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_ca_cert, NULL); - return (const char *)data + strlen (SCHEME_PATH); + return (const char *)data + strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH); } /** @@ -1137,7 +1237,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) { @@ -1349,7 +1449,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); } /** @@ -1397,7 +1497,7 @@ nm_setting_802_1x_get_phase2_client_cert_path (NMSetting8021x *setting) g_return_val_if_fail (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH, NULL); data = g_bytes_get_data (NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_client_cert, NULL); - return (const char *)data + strlen (SCHEME_PATH); + return (const char *)data + strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH); } /** @@ -1455,7 +1555,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; @@ -1604,7 +1704,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); } /** @@ -1656,7 +1756,7 @@ nm_setting_802_1x_get_private_key_path (NMSetting8021x *setting) g_return_val_if_fail (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH, NULL); data = g_bytes_get_data (NM_SETTING_802_1X_GET_PRIVATE (setting)->private_key, NULL); - return (const char *)data + strlen (SCHEME_PATH); + return (const char *)data + strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH); } static void @@ -1678,7 +1778,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); } @@ -1790,7 +1890,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) @@ -1941,7 +2042,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); } /** @@ -1993,7 +2094,7 @@ nm_setting_802_1x_get_phase2_private_key_path (NMSetting8021x *setting) g_return_val_if_fail (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH, NULL); data = g_bytes_get_data (NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_private_key, NULL); - return (const char *)data + strlen (SCHEME_PATH); + return (const char *)data + strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH); } /** @@ -2101,7 +2202,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) @@ -2575,35 +2677,18 @@ need_secrets (NMSetting *setting) static gboolean verify_cert (GBytes *bytes, const char *prop_name, GError **error) { - gconstpointer data; - gsize length; - - if (!bytes) - return TRUE; + GError *local = NULL; - switch (get_cert_scheme (bytes)) { - case NM_SETTING_802_1X_CK_SCHEME_BLOB: + if ( !bytes + || get_cert_scheme (bytes, &local) != NM_SETTING_802_1X_CK_SCHEME_UNKNOWN) 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-core/nm-setting-8021x.h b/libnm-core/nm-setting-8021x.h index 43885daaa3..da86071a63 100644 --- a/libnm-core/nm-setting-8021x.h +++ b/libnm-core/nm-setting-8021x.h @@ -31,6 +31,8 @@ G_BEGIN_DECLS +#define NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH "file://" + /** * NMSetting8021xCKFormat: * @NM_SETTING_802_1X_CK_FORMAT_UNKNOWN: unknown file format @@ -147,6 +149,9 @@ GType nm_setting_802_1x_get_type (void); NMSetting *nm_setting_802_1x_new (void); +NM_AVAILABLE_IN_1_2 +NMSetting8021xCKScheme nm_setting_802_1x_check_cert_scheme (gconstpointer pdata, gsize length, GError **error); + guint32 nm_setting_802_1x_get_num_eap_methods (NMSetting8021x *setting); const char * nm_setting_802_1x_get_eap_method (NMSetting8021x *setting, guint32 i); gboolean nm_setting_802_1x_add_eap_method (NMSetting8021x *setting, const char *eap); diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c index 321c03f18c..7e0c6d9217 100644 --- a/libnm-core/nm-utils.c +++ b/libnm-core/nm-utils.c @@ -22,6 +22,7 @@ #include "config.h" #include <string.h> +#include <errno.h> #include <stdlib.h> #include <netinet/ether.h> #include <arpa/inet.h> @@ -1939,6 +1940,8 @@ nm_utils_ip_routes_from_variant (GVariant *value, return routes; } +/**********************************************************************************************/ + /** * nm_utils_uuid_generate: * @@ -2012,6 +2015,50 @@ nm_utils_uuid_generate_from_string (const char *s, gssize slen, int uuid_type, g } /** + * _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 * @len: length of @data @@ -3278,3 +3325,81 @@ nm_utils_bond_mode_string_to_int (const char *mode) } return -1; } + +/**********************************************************************************************/ + +/* _nm_utils_ascii_str_to_int64: + * + * A wrapper for g_ascii_strtoll, that checks whether the whole string + * can be successfully converted to a number and is within a given + * range. On any error, @fallback will be returned and %errno will be set + * to a non-zero value. On success, %errno will be set to zero, check %errno + * for errors. Any trailing or leading (ascii) white space is ignored and the + * functions is locale independent. + * + * The function is guaranteed to return a value between @min and @max + * (inclusive) or @fallback. Also, the parsing is rather strict, it does + * not allow for any unrecognized characters, except leading and trailing + * white space. + **/ +gint64 +_nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max, gint64 fallback) +{ + gint64 v; + size_t len; + char buf[64], *s, *str_free = NULL; + + if (str) { + while (g_ascii_isspace (str[0])) + str++; + } + if (!str || !str[0]) { + errno = EINVAL; + return fallback; + } + + len = strlen (str); + if (g_ascii_isspace (str[--len])) { + /* backward search the first non-ws character. + * We already know that str[0] is non-ws. */ + while (g_ascii_isspace (str[--len])) + ; + + /* str[len] is now the last non-ws character... */ + len++; + + if (len >= sizeof (buf)) + s = str_free = g_malloc (len + 1); + else + s = buf; + + memcpy (s, str, len); + s[len] = 0; + + /* + g_assert (len > 0 && len < strlen (str) && len == strlen (s)); + g_assert (!g_ascii_isspace (str[len-1]) && g_ascii_isspace (str[len])); + g_assert (strncmp (str, s, len) == 0); + */ + + str = s; + } + + errno = 0; + v = g_ascii_strtoll (str, &s, base); + + if (errno != 0) + v = fallback; + else if (s[0] != 0) { + errno = EINVAL; + v = fallback; + } else if (v > max || v < min) { + errno = ERANGE; + v = fallback; + } + + if (G_UNLIKELY (str_free)) + g_free (str_free); + return v; +} + diff --git a/libnm-core/tests/Makefile.am b/libnm-core/tests/Makefile.am index 79aa73dbf9..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 \ @@ -38,20 +39,22 @@ 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-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-general.c b/libnm-core/tests/test-general.c index 7df4e57aa5..a417d3fe31 100644 --- a/libnm-core/tests/test-general.c +++ b/libnm-core/tests/test-general.c @@ -4038,6 +4038,184 @@ 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 +test_nm_utils_ascii_str_to_int64_check (const char *str, guint base, gint64 min, + gint64 max, gint64 fallback, int exp_errno, + gint64 exp_val) +{ + gint64 v; + + errno = 1; + v = _nm_utils_ascii_str_to_int64 (str, base, min, max, fallback); + g_assert_cmpint (errno, ==, exp_errno); + g_assert_cmpint (v, ==, exp_val); +} + +static void +test_nm_utils_ascii_str_to_int64_do (const char *str, guint base, gint64 min, + gint64 max, gint64 fallback, int exp_errno, + gint64 exp_val) +{ + const char *sign = ""; + const char *val; + static const char *whitespaces[] = { + "", + " ", + "\r\n\t", + " \r\n\t ", + " \r\n\t \t\r\n\t", + NULL, + }; + static const char *nulls[] = { + "", + "0", + "00", + "0000", + "0000000000000000", + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + NULL, + }; + const char **ws_pre, **ws_post, **null; + guint i; + + if (str == NULL || exp_errno != 0) { + test_nm_utils_ascii_str_to_int64_check (str, base, min, max, fallback, exp_errno, exp_val); + return; + } + + if (strncmp (str, "-", 1) == 0) + sign = "-"; + + val = str + strlen (sign); + + for (ws_pre = whitespaces; *ws_pre; ws_pre++) { + for (ws_post = whitespaces; *ws_post; ws_post++) { + for (null = nulls; *null; null++) { + for (i = 0; ; i++) { + char *s; + const char *str_base = ""; + + if (base == 16) { + if (i == 1) + str_base = "0x"; + else if (i > 1) + break; + } else if (base == 8) { + if (i == 1) + str_base = "0"; + else if (i > 1) + break; + } else if (base == 0) { + if (i > 0) + break; + /* with base==0, a leading zero would be interpreted as octal. Only test without *null */ + if ((*null)[0]) + break; + } else { + if (i > 0) + break; + } + + s = g_strdup_printf ("%s%s%s%s%s%s", *ws_pre, sign, str_base, *null, val, *ws_post); + + test_nm_utils_ascii_str_to_int64_check (s, base, min, max, fallback, exp_errno, exp_val); + g_free (s); + } + } + } + } +} + +static void +test_nm_utils_ascii_str_to_int64 (void) +{ + test_nm_utils_ascii_str_to_int64_do (NULL, 10, 0, 10000, -1, EINVAL, -1); + test_nm_utils_ascii_str_to_int64_do ("", 10, 0, 10000, -1, EINVAL, -1); + test_nm_utils_ascii_str_to_int64_do ("1x", 10, 0, 10000, -1, EINVAL, -1); + test_nm_utils_ascii_str_to_int64_do ("4711", 10, 0, 10000, -1, 0, 4711); + test_nm_utils_ascii_str_to_int64_do ("10000", 10, 0, 10000, -1, 0, 10000); + test_nm_utils_ascii_str_to_int64_do ("10001", 10, 0, 10000, -1, ERANGE, -1); + test_nm_utils_ascii_str_to_int64_do ("FF", 16, 0, 10000, -1, 0, 255); + test_nm_utils_ascii_str_to_int64_do ("FF", 10, 0, 10000, -2, EINVAL, -2); + test_nm_utils_ascii_str_to_int64_do ("9223372036854775807", 10, 0, G_MAXINT64, -2, 0, G_MAXINT64); + test_nm_utils_ascii_str_to_int64_do ("7FFFFFFFFFFFFFFF", 16, 0, G_MAXINT64, -2, 0, G_MAXINT64); + test_nm_utils_ascii_str_to_int64_do ("9223372036854775808", 10, 0, G_MAXINT64, -2, ERANGE, -2); + test_nm_utils_ascii_str_to_int64_do ("-9223372036854775808", 10, G_MININT64, 0, -2, 0, G_MININT64); + test_nm_utils_ascii_str_to_int64_do ("-9223372036854775808", 10, G_MININT64+1, 0, -2, ERANGE, -2); + test_nm_utils_ascii_str_to_int64_do ("-9223372036854775809", 10, G_MININT64, 0, -2, ERANGE, -2); + test_nm_utils_ascii_str_to_int64_do ("1.0", 10, 1, 1, -1, EINVAL, -1); + test_nm_utils_ascii_str_to_int64_do ("1x0", 16, -10, 10, -100, EINVAL, -100); + test_nm_utils_ascii_str_to_int64_do ("0", 16, -10, 10, -100, 0, 0); + test_nm_utils_ascii_str_to_int64_do ("10001111", 2, -1000, 1000, -100000, 0, 0x8F); + test_nm_utils_ascii_str_to_int64_do ("-10001111", 2, -1000, 1000, -100000, 0, -0x8F); + test_nm_utils_ascii_str_to_int64_do ("1111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7F); + test_nm_utils_ascii_str_to_int64_do ("111111111111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFF); + test_nm_utils_ascii_str_to_int64_do ("11111111111111111111111111111111111111111111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFFFFFFFFFF); + test_nm_utils_ascii_str_to_int64_do ("111111111111111111111111111111111111111111111111111111111111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFFFFFFFFFFFFFF); + test_nm_utils_ascii_str_to_int64_do ("100000000000000000000000000000000000000000000000000000000000000", 2, G_MININT64, G_MAXINT64, -1, 0, 0x4000000000000000); + test_nm_utils_ascii_str_to_int64_do ("1000000000000000000000000000000000000000000000000000000000000000", 2, G_MININT64, G_MAXINT64, -1, ERANGE, -1); + test_nm_utils_ascii_str_to_int64_do ("-100000000000000000000000000000000000000000000000000000000000000", 2, G_MININT64, G_MAXINT64, -1, 0, -0x4000000000000000); + test_nm_utils_ascii_str_to_int64_do ("111111111111111111111111111111111111111111111111111111111111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFFFFFFFFFFFFFF); + test_nm_utils_ascii_str_to_int64_do ("-100000000000000000000000000000000000000000000000000000000000000", 2, G_MININT64, G_MAXINT64, -1, 0, -0x4000000000000000); + test_nm_utils_ascii_str_to_int64_do ("0x70", 10, G_MININT64, G_MAXINT64, -1, EINVAL, -1); + test_nm_utils_ascii_str_to_int64_do ("4711", 0, G_MININT64, G_MAXINT64, -1, 0, 4711); + test_nm_utils_ascii_str_to_int64_do ("04711", 0, G_MININT64, G_MAXINT64, -1, 0, 04711); + test_nm_utils_ascii_str_to_int64_do ("0x4711", 0, G_MININT64, G_MAXINT64, -1, 0, 0x4711); + test_nm_utils_ascii_str_to_int64_do ("080", 0, G_MININT64, G_MAXINT64, -1, EINVAL, -1); + test_nm_utils_ascii_str_to_int64_do ("070", 0, G_MININT64, G_MAXINT64, -1, 0, 7*8); + test_nm_utils_ascii_str_to_int64_do ("0x70", 0, G_MININT64, G_MAXINT64, -1, 0, 0x70); +} + /******************************************************************************/ NMTST_DEFINE (); @@ -4137,6 +4315,9 @@ 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); return g_test_run (); } diff --git a/libnm-core/tests/test-keyfile.c b/libnm-core/tests/test-keyfile.c new file mode 100644 index 0000000000..43958cb6bb --- /dev/null +++ b/libnm-core/tests/test-keyfile.c @@ -0,0 +1,512 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2015 Red Hat, Inc. + * + */ + +#include "config.h" + +#include "nm-utils-internal.h" +#include "nm-keyfile-utils.h" +#include "nm-keyfile-internal.h" + +#include "nm-simple-connection.h" +#include "nm-setting-connection.h" +#include "nm-setting-wired.h" +#include "nm-setting-8021x.h" + +#include "nm-test-utils.h" + + +#define TEST_WIRED_TLS_CA_CERT TEST_CERT_DIR"/test-ca-cert.pem" +#define TEST_WIRED_TLS_PRIVKEY TEST_CERT_DIR"/test-key-and-cert.pem" + + +/******************************************************************************/ + +#define CLEAR(con, keyfile) \ + G_STMT_START { \ + NMConnection **_con = (con); \ + GKeyFile **_keyfile = (keyfile); \ + \ + g_clear_object (_con); \ + g_clear_pointer (_keyfile, g_key_file_unref); \ + } G_STMT_END + +static void +_assert_gbytes (GBytes *bytes, gconstpointer data, gssize len) +{ + g_assert ((data && len > 0) || !len || (data && len == -1)); + + if (len == -1) + len = strlen (data); + + if (!len) + g_assert (!bytes); + else { + g_assert_cmpint (g_bytes_get_size (bytes), ==, len); + g_assert (memcmp (g_bytes_get_data (bytes, NULL), data, len) == 0); + } +} + +static GKeyFile * +_keyfile_load_from_data (const char *str) +{ + GError *error = NULL; + gboolean success; + GKeyFile *keyfile; + + g_assert (str); + + keyfile = g_key_file_new (); + success = g_key_file_load_from_data (keyfile, str, strlen (str), G_KEY_FILE_NONE, &error); + g_assert_no_error (error); + g_assert (success); + + return keyfile; +} + +static gboolean +_keyfile_a_contains_all_in_b (GKeyFile *kf_a, GKeyFile *kf_b) +{ + gs_strfreev char **groups = NULL; + guint i, j; + + if (kf_a == kf_b) + return TRUE; + + groups = g_key_file_get_groups (kf_a, NULL); + for (i = 0; groups && groups[i]; i++) { + gs_strfreev char **keys = NULL; + + keys = g_key_file_get_keys (kf_a, groups[i], NULL, NULL); + if (keys) { + for (j = 0; keys[j]; j++) { + gs_free char *key_a = g_key_file_get_value (kf_a, groups[i], keys[j], NULL); + gs_free char *key_b = g_key_file_get_value (kf_b, groups[i], keys[j], NULL); + + if (g_strcmp0 (key_a, key_b) != 0) + return FALSE; + } + } + } + return TRUE; +} + +static gboolean +_keyfile_equals (GKeyFile *kf_a, GKeyFile *kf_b) +{ + return _keyfile_a_contains_all_in_b (kf_a, kf_b) && _keyfile_a_contains_all_in_b (kf_b, kf_a); +} + +static void +_keyfile_convert (NMConnection **con, + GKeyFile **keyfile, + const char *keyfile_name, + const char *base_dir, + NMKeyfileReadHandler read_handler, + void *read_data, + NMKeyfileWriteHandler write_handler, + void *write_data, + gboolean needs_normalization) +{ + NMConnection *c, *c2; + GKeyFile *k, *k2; + GError *error = NULL; + NMSetting8021x *s1, *s2; + + /* convert from @con to @keyfile and check that we can make + * full round trips and obtaining the same result. */ + + g_assert (con); + g_assert (keyfile); + g_assert (*con || *keyfile); + + if (!*keyfile) { + k = nm_keyfile_write (*con, write_handler, read_data, &error); + g_assert_no_error (error); + g_assert (k); + *keyfile = k; + } else + k = *keyfile; + if (!*con) { + c = nm_keyfile_read (*keyfile, keyfile_name, base_dir, read_handler, read_data, &error); + g_assert_no_error (error); + g_assert (c); + if (needs_normalization) + nmtst_assert_connection_verifies_after_normalization (c, 0, 0); + else + nmtst_assert_connection_verifies_without_normalization (c); + *con = c; + } else + c = *con; + + k2 = nm_keyfile_write (c, write_handler, read_data, &error); + g_assert_no_error (error); + g_assert (k2); + + c2 = nm_keyfile_read (k, keyfile_name, base_dir, read_handler, read_data, &error); + g_assert_no_error (error); + g_assert (c2); + if (needs_normalization) + nmtst_assert_connection_verifies_after_normalization (c2, 0, 0); + else + nmtst_assert_connection_verifies_without_normalization (c2); + + s1 = nm_connection_get_setting_802_1x (*con); + s2 = nm_connection_get_setting_802_1x (c2); + if (s1 || s2) { + g_assert_cmpint (nm_setting_802_1x_get_ca_cert_scheme (s1), ==, nm_setting_802_1x_get_ca_cert_scheme (s2)); + switch (nm_setting_802_1x_get_ca_cert_scheme (s1)) { + case NM_SETTING_802_1X_CK_SCHEME_PATH: + nmtst_assert_resolve_relative_path_equals (nm_setting_802_1x_get_ca_cert_path (s1), nm_setting_802_1x_get_ca_cert_path (s2)); + break; + case NM_SETTING_802_1X_CK_SCHEME_BLOB: { + GBytes *b1, *b2; + + b1 = nm_setting_802_1x_get_ca_cert_blob (s1); + b2 = nm_setting_802_1x_get_ca_cert_blob (s2); + g_assert_cmpint (g_bytes_get_size (b1), ==, g_bytes_get_size (b2)); + g_assert (memcmp (g_bytes_get_data (b1, NULL), g_bytes_get_data (b2, NULL), g_bytes_get_size (b1)) == 0); + break; + } + default: + break; + } + } + + nmtst_assert_connection_equals (c2, FALSE, *con, FALSE); + _keyfile_equals (k2, *keyfile); + + g_object_unref (c2); + g_key_file_unref (k2); +} + +/******************************************************************************/ + +static void +_test_8021x_cert_check (NMConnection *con, + NMSetting8021xCKScheme expected_scheme, + const void *value, + gssize val_len) +{ + GKeyFile *keyfile = NULL; + NMSetting8021x *s_8021x; + gs_free char *kval = NULL; + + _keyfile_convert (&con, &keyfile, NULL, NULL, NULL, NULL, NULL, NULL, FALSE); + + s_8021x = nm_connection_get_setting_802_1x (con); + + g_assert (nm_setting_802_1x_get_ca_cert_scheme (s_8021x) == expected_scheme); + + if (expected_scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) { + const char *path = nm_setting_802_1x_get_ca_cert_path (s_8021x); + + g_assert_cmpstr (path, ==, value); + g_assert (val_len == -1 || strlen (path) == val_len); + + kval = g_key_file_get_string (keyfile, "802-1x", "ca-cert", NULL); + g_assert (kval); + g_assert_cmpstr (kval, ==, value); + } else if (expected_scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) { + GBytes *blob = nm_setting_802_1x_get_ca_cert_blob (s_8021x); + gs_free char *file_blob = NULL; + + if (val_len == -1) { + gsize l; + gboolean success; + + success = g_file_get_contents (value, &file_blob, &l, NULL); + g_assert (success); + + value = file_blob; + val_len = l; + } + + g_assert (blob); + g_assert_cmpint (g_bytes_get_size (blob), ==, val_len); + g_assert (!memcmp (g_bytes_get_data (blob, NULL), value, val_len)); + + kval = g_key_file_get_string (keyfile, "802-1x", "ca-cert", NULL); + g_assert (kval); + g_assert (g_str_has_prefix (kval, NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB)); + } + + g_key_file_unref (keyfile); +} + +static void +_test_8021x_cert_check_blob_full (NMConnection *con, const void *data, gsize len) +{ + GBytes *bytes; + NMSetting8021x *s_8021x = nm_connection_get_setting_802_1x (con); + + bytes = g_bytes_new (data, len); + g_object_set (s_8021x, + NM_SETTING_802_1X_CA_CERT, + bytes, + NULL); + _test_8021x_cert_check (con, NM_SETTING_802_1X_CK_SCHEME_BLOB, g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes)); + g_bytes_unref (bytes); +} +#define _test_8021x_cert_check_blob(con, data) _test_8021x_cert_check_blob_full(con, data, STRLEN (data)) + +static void +test_8021x_cert (void) +{ + NMSetting8021x *s_8021x; + gs_unref_object NMConnection *con = nmtst_create_minimal_connection ("test-cert", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + GError *error = NULL; + gboolean success; + NMSetting8021xCKScheme scheme = NM_SETTING_802_1X_CK_SCHEME_PATH; + gs_free char *full_TEST_WIRED_TLS_CA_CERT = nmtst_file_resolve_relative_path (TEST_WIRED_TLS_CA_CERT, NULL); + gs_free char *full_TEST_WIRED_TLS_PRIVKEY = nmtst_file_resolve_relative_path (TEST_WIRED_TLS_PRIVKEY, NULL); + + /* test writing/reading of certificates of NMSetting8021x */ + + /* create a valid connection with NMSetting8021x */ + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new (); + nm_setting_802_1x_add_eap_method (s_8021x, "tls"); + g_object_set (s_8021x, NM_SETTING_802_1X_IDENTITY, "Bill Smith", NULL); + success = nm_setting_802_1x_set_ca_cert (s_8021x, + full_TEST_WIRED_TLS_CA_CERT, + scheme, + NULL, + &error); + g_assert_no_error (error); + g_assert (success); + success = nm_setting_802_1x_set_client_cert (s_8021x, + full_TEST_WIRED_TLS_CA_CERT, + scheme, + NULL, + &error); + g_assert_no_error (error); + g_assert (success); + success = nm_setting_802_1x_set_private_key (s_8021x, + full_TEST_WIRED_TLS_PRIVKEY, + "test1", + scheme, + NULL, + &error); + g_assert_no_error (error); + g_assert (success); + + + /* test reseting ca-cert to different values and see whether we can write/read. */ + + nm_connection_add_setting (con, NM_SETTING (s_8021x)); + nmtst_assert_connection_verifies_and_normalizable (con); + + + _test_8021x_cert_check (con, scheme, full_TEST_WIRED_TLS_CA_CERT, -1); + + scheme = NM_SETTING_802_1X_CK_SCHEME_BLOB; + success = nm_setting_802_1x_set_ca_cert (s_8021x, + full_TEST_WIRED_TLS_CA_CERT, + scheme, + NULL, + &error); + g_assert_no_error (error); + g_assert (success); + _test_8021x_cert_check (con, scheme, full_TEST_WIRED_TLS_CA_CERT, -1); + + _test_8021x_cert_check_blob (con, "a"); + _test_8021x_cert_check_blob (con, "\0"); + _test_8021x_cert_check_blob (con, "10"); + _test_8021x_cert_check_blob (con, "data:;base64,a"); + _test_8021x_cert_check_blob_full (con, "data:;base64,a", STRLEN ("data:;base64,a") + 1); + _test_8021x_cert_check_blob (con, "data:;base64,file://a"); + _test_8021x_cert_check_blob (con, "123"); + +} + +/******************************************************************************/ + +static void +test_8021x_cert_read (void) +{ + GKeyFile *keyfile = NULL; + gs_unref_object NMConnection *con = NULL; + NMSetting8021x *s_8021x; + + 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" + ); + _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/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'); diff --git a/libnm-util/nm-setting-8021x.c b/libnm-util/nm-setting-8021x.c index 6d5268a17b..1dc768c210 100644 --- a/libnm-util/nm-setting-8021x.c +++ b/libnm-util/nm-setting-8021x.c @@ -33,6 +33,7 @@ #include "crypto.h" #include "nm-utils-private.h" #include "nm-setting-private.h" +#include "nm-utils-internal.h" /** * SECTION:nm-setting-8021x @@ -430,13 +431,52 @@ 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; } +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 @@ -511,14 +551,16 @@ static GByteArray * path_to_scheme_value (const char *path) { GByteArray *array; + gsize len; + + g_return_val_if_fail (path != NULL && path[0], NULL); - g_return_val_if_fail (path != NULL, NULL); + len = strlen (path); - /* 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); + /* 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; } @@ -578,7 +620,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) { @@ -894,7 +936,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; @@ -1159,7 +1201,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) { @@ -1479,7 +1521,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; @@ -2604,25 +2646,9 @@ need_secrets (NMSetting *setting) static gboolean verify_cert (GByteArray *array, const char *prop_name, GError **error) { - if (!array) - return TRUE; - - switch (get_cert_scheme (array)) { - case NM_SETTING_802_1X_CK_SCHEME_BLOB: + if ( !array + || get_cert_scheme (array) != NM_SETTING_802_1X_CK_SCHEME_UNKNOWN) 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, diff --git a/libnm/libnm.ver b/libnm/libnm.ver index 258336ae3d..44b83f6ecb 100644 --- a/libnm/libnm.ver +++ b/libnm/libnm.ver @@ -847,6 +847,7 @@ local: libnm_1_2_0 { global: + nm_setting_802_1x_check_cert_scheme; nm_setting_bridge_get_multicast_snooping; nm_setting_wireless_get_powersave; nm_utils_bond_mode_int_to_string; 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 diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c index 67633bccef..57f817649e 100644 --- a/src/NetworkManagerUtils.c +++ b/src/NetworkManagerUtils.c @@ -1798,123 +1798,6 @@ nm_utils_cmp_connection_by_autoconnect_priority (NMConnection **a, NMConnection return 0; } -/* nm_utils_ascii_str_to_int64: - * - * A wrapper for g_ascii_strtoll, that checks whether the whole string - * can be successfully converted to a number and is within a given - * range. On any error, @fallback will be returned and %errno will be set - * to a non-zero value. On success, %errno will be set to zero, check %errno - * for errors. Any trailing or leading (ascii) white space is ignored and the - * functions is locale independent. - * - * The function is guaranteed to return a value between @min and @max - * (inclusive) or @fallback. Also, the parsing is rather strict, it does - * not allow for any unrecognized characters, except leading and trailing - * white space. - **/ -gint64 -nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max, gint64 fallback) -{ - gint64 v; - size_t len; - char buf[64], *s, *str_free = NULL; - - if (str) { - while (g_ascii_isspace (str[0])) - str++; - } - if (!str || !str[0]) { - errno = EINVAL; - return fallback; - } - - len = strlen (str); - if (g_ascii_isspace (str[--len])) { - /* backward search the first non-ws character. - * We already know that str[0] is non-ws. */ - while (g_ascii_isspace (str[--len])) - ; - - /* str[len] is now the last non-ws character... */ - len++; - - if (len >= sizeof (buf)) - s = str_free = g_malloc (len + 1); - else - s = buf; - - memcpy (s, str, len); - s[len] = 0; - - /* - g_assert (len > 0 && len < strlen (str) && len == strlen (s)); - g_assert (!g_ascii_isspace (str[len-1]) && g_ascii_isspace (str[len])); - g_assert (strncmp (str, s, len) == 0); - */ - - str = s; - } - - errno = 0; - v = g_ascii_strtoll (str, &s, base); - - if (errno != 0) - v = fallback; - else if (s[0] != 0) { - errno = EINVAL; - v = fallback; - } else if (v > max || v < min) { - errno = ERANGE; - v = fallback; - } - - if (G_UNLIKELY (str_free)) - g_free (str_free); - return v; -} - -/** - * nm_utils_uuid_generate_from_strings: - * @string1: a variadic list of strings. Must be NULL terminated. - * - * 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 25b3e27a0d..23d8330a34 100644 --- a/src/NetworkManagerUtils.h +++ b/src/NetworkManagerUtils.h @@ -163,12 +163,6 @@ int nm_utils_cmp_connection_by_autoconnect_priority (NMConnection **a, NMConnect void nm_utils_log_connection_diff (NMConnection *connection, NMConnection *diff_base, guint32 level, guint64 domain, const char *name, const char *prefix); -gint64 nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max, gint64 fallback); - -#define NM_UTILS_UUID_NS "b425e9fb-7598-44b4-9e3b-5a2e3aaa4905" - -char *nm_utils_uuid_generate_from_strings (const char *string1, ...) G_GNUC_NULL_TERMINATED; - #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/dhcp-manager/nm-dhcp-listener.c b/src/dhcp-manager/nm-dhcp-listener.c index bb29717a97..ca57ae595a 100644 --- a/src/dhcp-manager/nm-dhcp-listener.c +++ b/src/dhcp-manager/nm-dhcp-listener.c @@ -31,6 +31,7 @@ #include <unistd.h> #include "nm-dhcp-listener.h" +#include "nm-core-internal.h" #include "nm-logging.h" #include "nm-dbus-manager.h" #include "nm-dbus-glib-types.h" @@ -132,7 +133,7 @@ handle_event (DBusGProxy *proxy, } pid_str = get_option (options, "pid"); - pid = nm_utils_ascii_str_to_int64 (pid_str, 10, 0, G_MAXINT32, -1); + pid = _nm_utils_ascii_str_to_int64 (pid_str, 10, 0, G_MAXINT32, -1); if (pid == -1) { nm_log_warn (LOGD_DHCP, "DHCP event: couldn't convert PID '%s' to an integer", pid_str ? pid_str : "(null)"); goto out; diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c index e1887e5a4b..47075f226f 100644 --- a/src/platform/nm-linux-platform.c +++ b/src/platform/nm-linux-platform.c @@ -55,6 +55,7 @@ #endif #include "gsystem-local-alloc.h" +#include "nm-core-internal.h" #include "NetworkManagerUtils.h" #include "nm-linux-platform.h" #include "NetworkManagerUtils.h" @@ -3025,7 +3026,7 @@ tun_get_properties (NMPlatform *platform, int ifindex, NMPlatformTunProperties * val = nm_platform_sysctl_get (path); g_free (path); if (val) { - props->owner = nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1); + props->owner = _nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1); if (errno) success = FALSE; g_free (val); @@ -3036,7 +3037,7 @@ tun_get_properties (NMPlatform *platform, int ifindex, NMPlatformTunProperties * val = nm_platform_sysctl_get (path); g_free (path); if (val) { - props->group = nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1); + props->group = _nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1); if (errno) success = FALSE; g_free (val); @@ -3049,7 +3050,7 @@ tun_get_properties (NMPlatform *platform, int ifindex, NMPlatformTunProperties * if (val) { gint64 flags; - flags = nm_utils_ascii_str_to_int64 (val, 16, 0, G_MAXINT64, 0); + flags = _nm_utils_ascii_str_to_int64 (val, 16, 0, G_MAXINT64, 0); if (!errno) { #ifndef IFF_MULTI_QUEUE const int IFF_MULTI_QUEUE = 0x0100; diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c index cbf2c8cd61..9500a75424 100644 --- a/src/platform/nm-platform.c +++ b/src/platform/nm-platform.c @@ -35,6 +35,7 @@ #include "NetworkManagerUtils.h" #include "nm-logging.h" #include "nm-enum-types.h" +#include "nm-core-internal.h" #define debug(...) nm_log_dbg (LOGD_PLATFORM, __VA_ARGS__) @@ -322,7 +323,7 @@ nm_platform_sysctl_get_int_checked (const char *path, guint base, gint64 min, gi return fallback; } - ret = nm_utils_ascii_str_to_int64 (value, base, min, max, fallback); + ret = _nm_utils_ascii_str_to_int64 (value, base, min, max, fallback); g_free (value); return ret; } diff --git a/src/settings/plugins/ibft/reader.c b/src/settings/plugins/ibft/reader.c index e3a0168a6c..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, @@ -431,7 +431,7 @@ is_ibft_vlan_device (const GPtrArray *block) /* VLAN 0 is normally a valid VLAN ID, but in the iBFT case it * means "no VLAN". */ - if (nm_utils_ascii_str_to_int64 (s_vlan_id, 10, 1, 4095, -1) != -1) + if (_nm_utils_ascii_str_to_int64 (s_vlan_id, 10, 1, 4095, -1) != -1) return TRUE; } return FALSE; @@ -458,7 +458,7 @@ vlan_setting_add_from_block (const GPtrArray *block, g_assert (vlan_id_str); /* VLAN 0 is normally a valid VLAN ID, but in the iBFT case it means "no VLAN" */ - vlan_id = nm_utils_ascii_str_to_int64 (vlan_id_str, 10, 1, 4095, -1); + vlan_id = _nm_utils_ascii_str_to_int64 (vlan_id_str, 10, 1, 4095, -1); if (vlan_id == -1) { g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, "Invalid VLAN_ID '%s'", vlan_id_str); diff --git a/src/settings/plugins/ifcfg-rh/reader.c b/src/settings/plugins/ifcfg-rh/reader.c index c40fecf50b..febbd90ac6 100644 --- a/src/settings/plugins/ifcfg-rh/reader.c +++ b/src/settings/plugins/ifcfg-rh/reader.c @@ -503,7 +503,7 @@ read_one_ip4_route (shvarFile *ifcfg, /* Metric */ value = svGetValue (ifcfg, metric_tag, FALSE); if (value) { - metric = nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT32, -1); + metric = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT32, -1); if (metric < 0) { g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, "Invalid IP4 route metric '%s'", value); @@ -3344,7 +3344,7 @@ make_wireless_setting (shvarFile *ifcfg, value = svGetValue (ifcfg, "CHANNEL", FALSE); if (value) { errno = 0; - chan = nm_utils_ascii_str_to_int64 (value, 10, 1, 196, 0); + chan = _nm_utils_ascii_str_to_int64 (value, 10, 1, 196, 0); if (errno || (chan == 0)) { g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, "Invalid wireless channel '%s'", value); @@ -4889,7 +4889,7 @@ devtimeout_from_file (const char *filename) devtimeout_str = svGetValue (ifcfg, "DEVTIMEOUT", FALSE); if (devtimeout_str) { - devtimeout = nm_utils_ascii_str_to_int64 (devtimeout_str, 10, 0, G_MAXUINT, 0); + devtimeout = _nm_utils_ascii_str_to_int64 (devtimeout_str, 10, 0, G_MAXUINT, 0); g_free (devtimeout_str); } else devtimeout = 0; diff --git a/src/settings/plugins/ifcfg-rh/shvar.c b/src/settings/plugins/ifcfg-rh/shvar.c index 5f3d173b2a..ed12683072 100644 --- a/src/settings/plugins/ifcfg-rh/shvar.c +++ b/src/settings/plugins/ifcfg-rh/shvar.c @@ -36,7 +36,7 @@ #include "shvar.h" -#include "NetworkManagerUtils.h" +#include "nm-core-internal.h" #include "nm-logging.h" #define PARSE_WARNING(msg...) nm_log_warn (LOGD_SETTINGS, " " msg) @@ -321,7 +321,7 @@ svTrueValue (shvarFile *s, const char *key, gboolean def) * @max: the maximum for range-check * @fallback: the fallback value in any error case * - * Reads a value @key and converts it to an integer using nm_utils_ascii_str_to_int64(). + * Reads a value @key and converts it to an integer using _nm_utils_ascii_str_to_int64(). * In case of error, @errno will be set and @fallback returned. */ gint64 svGetValueInt64 (shvarFile *s, const char *key, guint base, gint64 min, gint64 max, gint64 fallback) @@ -336,7 +336,7 @@ svGetValueInt64 (shvarFile *s, const char *key, guint base, gint64 min, gint64 m return fallback; } - result = nm_utils_ascii_str_to_int64 (tmp, base, min, max, fallback); + result = _nm_utils_ascii_str_to_int64 (tmp, base, min, max, fallback); errsv = errno; if (errsv != 0) PARSE_WARNING ("Error reading '%s' value '%s' as integer (%d)", key, tmp, errsv); diff --git a/src/settings/plugins/ifnet/connection_parser.c b/src/settings/plugins/ifnet/connection_parser.c index 41baf31507..888ee589ad 100644 --- a/src/settings/plugins/ifnet/connection_parser.c +++ b/src/settings/plugins/ifnet/connection_parser.c @@ -706,13 +706,13 @@ make_ip4_setting (NMConnection *connection, GError *local = NULL; if ((metric_str = ifnet_get_data (conn_name, "metric")) != NULL) { - metric = nm_utils_ascii_str_to_int64 (metric_str, 10, 0, G_MAXUINT32, -1); + metric = _nm_utils_ascii_str_to_int64 (metric_str, 10, 0, G_MAXUINT32, -1); } else { metric_str = ifnet_get_global_data ("metric"); if (metric_str) { stripped = g_strdup (metric_str); strip_string (stripped, '"'); - metric = nm_utils_ascii_str_to_int64 (metric_str, 10, 0, G_MAXUINT32, -1); + metric = _nm_utils_ascii_str_to_int64 (metric_str, 10, 0, G_MAXUINT32, -1); g_free (stripped); } else metric = -1; @@ -846,13 +846,13 @@ make_ip6_setting (NMConnection *connection, /* metric is not per routes configuration right now * global metric is also supported (metric="x") */ if ((metric_str = ifnet_get_data (conn_name, "metric")) != NULL) - metric = nm_utils_ascii_str_to_int64 (metric_str, 10, 0, G_MAXUINT32, -1); + metric = _nm_utils_ascii_str_to_int64 (metric_str, 10, 0, G_MAXUINT32, -1); else { metric_str = ifnet_get_global_data ("metric"); if (metric_str) { stripped = g_strdup (metric_str); strip_string (stripped, '"'); - metric = nm_utils_ascii_str_to_int64 (metric_str, 10, 0, G_MAXUINT32, -1); + metric = _nm_utils_ascii_str_to_int64 (metric_str, 10, 0, G_MAXUINT32, -1); g_free (stripped); } else metric = 1; diff --git a/src/settings/plugins/keyfile/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 1a1d9663d3..a149e06d93 100644 --- a/src/settings/plugins/keyfile/reader.c +++ b/src/settings/plugins/keyfile/reader.c @@ -15,1239 +15,76 @@ * 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 <errno.h> -#include <stdlib.h> #include <sys/stat.h> -#include <unistd.h> -#include <sys/types.h> -#include <arpa/inet.h> #include <string.h> -#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; -} +#include "nm-logging.h" +#include "nm-keyfile-internal.h" -static void -ip_address_or_route_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path) +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); - 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); + const char *setting_name = setting ? nm_setting_get_name (setting) : NULL; - for (i = -1; i < 1000; i++) { - const char **key_basename; - - for (key_basename = key_names; *key_basename; key_basename++) { - char *key_name; - gpointer item; + if (group) { + char *res; - /* -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; - - /* 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); - 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; + return message; } -#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); +_handler_read (GKeyFile *keyfile, + NMConnection *connection, + NMKeyfileReadType type, + void *type_data, + void *user_data, + GError **error) +{ + if (type == NM_KEYFILE_READ_TYPE_WARN) { + NMKeyfileReadTypeDataWarn *warn_data = type_data; + NMLogLevel level; + char *message_free = NULL; + + 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 if (warn_data->severity == NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE) + 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 NULL tell g_utf8_validate() to to until the NULL */ - if (data[data_len - 1] == '\0') - validate_len = -1; - else - validate_len = data_len; - - if (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) { @@ -1255,12 +92,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)) { @@ -1282,77 +113,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); @@ -1363,3 +131,4 @@ out: g_key_file_free (key_file); return connection; } + 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 b1de5d3ca1..a86bfed6c2 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, @@ -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, + "*<warn> 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, + "*<warn> 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, + "*<warn> 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, + "*<warn> 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, + "*<warn> 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/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 <glib.h> -#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 314ff11608..9cf119c0c8 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,407 +24,20 @@ #include <stdlib.h> #include <sys/stat.h> #include <unistd.h> -#include <stdio.h> #include <errno.h> - -#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 <string.h> -#include <arpa/inet.h> -#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); -} - -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 { + const char *keyfile_dir; +} WriteInfo; -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); - GBytes * (*blob_func) (NMSetting8021x *setting); -} 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, - nm_setting_802_1x_get_ca_cert_blob }, - - { 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, - nm_setting_802_1x_get_phase2_ca_cert_blob }, - - { 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, - nm_setting_802_1x_get_client_cert_blob }, - - { 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, - nm_setting_802_1x_get_phase2_client_cert_blob }, - - { 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, - nm_setting_802_1x_get_private_key_blob }, - - { 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, - nm_setting_802_1x_get_phase2_private_key_blob }, - - { NULL }, -}; static gboolean write_cert_key_file (const char *path, @@ -490,61 +103,75 @@ 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)); + 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, 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 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]) { + /* 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, key, 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; 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 { @@ -556,226 +183,46 @@ 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); + /* 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, "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 - 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 { - 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 +240,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 +252,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 +359,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 <sys/types.h> #include <glib.h> #include <nm-connection.h> diff --git a/src/tests/test-general.c b/src/tests/test-general.c index 21533cc743..dae872c86d 100644 --- a/src/tests/test-general.c +++ b/src/tests/test-general.c @@ -30,135 +30,6 @@ #include "nm-test-utils.h" -static void -test_nm_utils_ascii_str_to_int64_check (const char *str, guint base, gint64 min, - gint64 max, gint64 fallback, int exp_errno, - gint64 exp_val) -{ - gint64 v; - - errno = 1; - v = nm_utils_ascii_str_to_int64 (str, base, min, max, fallback); - g_assert_cmpint (errno, ==, exp_errno); - g_assert_cmpint (v, ==, exp_val); -} - -static void -test_nm_utils_ascii_str_to_int64_do (const char *str, guint base, gint64 min, - gint64 max, gint64 fallback, int exp_errno, - gint64 exp_val) -{ - const char *sign = ""; - const char *val; - static const char *whitespaces[] = { - "", - " ", - "\r\n\t", - " \r\n\t ", - " \r\n\t \t\r\n\t", - NULL, - }; - static const char *nulls[] = { - "", - "0", - "00", - "0000", - "0000000000000000", - "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - NULL, - }; - const char **ws_pre, **ws_post, **null; - guint i; - - if (str == NULL || exp_errno != 0) { - test_nm_utils_ascii_str_to_int64_check (str, base, min, max, fallback, exp_errno, exp_val); - return; - } - - if (strncmp (str, "-", 1) == 0) - sign = "-"; - - val = str + strlen (sign); - - for (ws_pre = whitespaces; *ws_pre; ws_pre++) { - for (ws_post = whitespaces; *ws_post; ws_post++) { - for (null = nulls; *null; null++) { - for (i = 0; ; i++) { - char *s; - const char *str_base = ""; - - if (base == 16) { - if (i == 1) - str_base = "0x"; - else if (i > 1) - break; - } else if (base == 8) { - if (i == 1) - str_base = "0"; - else if (i > 1) - break; - } else if (base == 0) { - if (i > 0) - break; - /* with base==0, a leading zero would be interpreted as octal. Only test without *null */ - if ((*null)[0]) - break; - } else { - if (i > 0) - break; - } - - s = g_strdup_printf ("%s%s%s%s%s%s", *ws_pre, sign, str_base, *null, val, *ws_post); - - test_nm_utils_ascii_str_to_int64_check (s, base, min, max, fallback, exp_errno, exp_val); - g_free (s); - } - } - } - } -} - -static void -test_nm_utils_ascii_str_to_int64 (void) -{ - test_nm_utils_ascii_str_to_int64_do (NULL, 10, 0, 10000, -1, EINVAL, -1); - test_nm_utils_ascii_str_to_int64_do ("", 10, 0, 10000, -1, EINVAL, -1); - test_nm_utils_ascii_str_to_int64_do ("1x", 10, 0, 10000, -1, EINVAL, -1); - test_nm_utils_ascii_str_to_int64_do ("4711", 10, 0, 10000, -1, 0, 4711); - test_nm_utils_ascii_str_to_int64_do ("10000", 10, 0, 10000, -1, 0, 10000); - test_nm_utils_ascii_str_to_int64_do ("10001", 10, 0, 10000, -1, ERANGE, -1); - test_nm_utils_ascii_str_to_int64_do ("FF", 16, 0, 10000, -1, 0, 255); - test_nm_utils_ascii_str_to_int64_do ("FF", 10, 0, 10000, -2, EINVAL, -2); - test_nm_utils_ascii_str_to_int64_do ("9223372036854775807", 10, 0, G_MAXINT64, -2, 0, G_MAXINT64); - test_nm_utils_ascii_str_to_int64_do ("7FFFFFFFFFFFFFFF", 16, 0, G_MAXINT64, -2, 0, G_MAXINT64); - test_nm_utils_ascii_str_to_int64_do ("9223372036854775808", 10, 0, G_MAXINT64, -2, ERANGE, -2); - test_nm_utils_ascii_str_to_int64_do ("-9223372036854775808", 10, G_MININT64, 0, -2, 0, G_MININT64); - test_nm_utils_ascii_str_to_int64_do ("-9223372036854775808", 10, G_MININT64+1, 0, -2, ERANGE, -2); - test_nm_utils_ascii_str_to_int64_do ("-9223372036854775809", 10, G_MININT64, 0, -2, ERANGE, -2); - test_nm_utils_ascii_str_to_int64_do ("1.0", 10, 1, 1, -1, EINVAL, -1); - test_nm_utils_ascii_str_to_int64_do ("1x0", 16, -10, 10, -100, EINVAL, -100); - test_nm_utils_ascii_str_to_int64_do ("0", 16, -10, 10, -100, 0, 0); - test_nm_utils_ascii_str_to_int64_do ("10001111", 2, -1000, 1000, -100000, 0, 0x8F); - test_nm_utils_ascii_str_to_int64_do ("-10001111", 2, -1000, 1000, -100000, 0, -0x8F); - test_nm_utils_ascii_str_to_int64_do ("1111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7F); - test_nm_utils_ascii_str_to_int64_do ("111111111111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFF); - test_nm_utils_ascii_str_to_int64_do ("11111111111111111111111111111111111111111111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFFFFFFFFFF); - test_nm_utils_ascii_str_to_int64_do ("111111111111111111111111111111111111111111111111111111111111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFFFFFFFFFFFFFF); - test_nm_utils_ascii_str_to_int64_do ("100000000000000000000000000000000000000000000000000000000000000", 2, G_MININT64, G_MAXINT64, -1, 0, 0x4000000000000000); - test_nm_utils_ascii_str_to_int64_do ("1000000000000000000000000000000000000000000000000000000000000000", 2, G_MININT64, G_MAXINT64, -1, ERANGE, -1); - test_nm_utils_ascii_str_to_int64_do ("-100000000000000000000000000000000000000000000000000000000000000", 2, G_MININT64, G_MAXINT64, -1, 0, -0x4000000000000000); - test_nm_utils_ascii_str_to_int64_do ("111111111111111111111111111111111111111111111111111111111111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFFFFFFFFFFFFFF); - test_nm_utils_ascii_str_to_int64_do ("-100000000000000000000000000000000000000000000000000000000000000", 2, G_MININT64, G_MAXINT64, -1, 0, -0x4000000000000000); - test_nm_utils_ascii_str_to_int64_do ("0x70", 10, G_MININT64, G_MAXINT64, -1, EINVAL, -1); - test_nm_utils_ascii_str_to_int64_do ("4711", 0, G_MININT64, G_MAXINT64, -1, 0, 4711); - test_nm_utils_ascii_str_to_int64_do ("04711", 0, G_MININT64, G_MAXINT64, -1, 0, 04711); - test_nm_utils_ascii_str_to_int64_do ("0x4711", 0, G_MININT64, G_MAXINT64, -1, 0, 0x4711); - test_nm_utils_ascii_str_to_int64_do ("080", 0, G_MININT64, G_MAXINT64, -1, EINVAL, -1); - test_nm_utils_ascii_str_to_int64_do ("070", 0, G_MININT64, G_MAXINT64, -1, 0, 7*8); - test_nm_utils_ascii_str_to_int64_do ("0x70", 0, G_MININT64, G_MAXINT64, -1, 0, 0x70); -} - /* Reference implementation for nm_utils_ip6_address_clear_host_address. * Taken originally from set_address_masked(), src/rdisc/nm-lndp-rdisc.c **/ @@ -727,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", @@ -893,7 +717,6 @@ main (int argc, char **argv) { nmtst_init_with_logging (&argc, &argv, NULL, "ALL"); - g_test_add_func ("/general/nm_utils_ascii_str_to_int64", test_nm_utils_ascii_str_to_int64); g_test_add_func ("/general/nm_utils_ip6_address_clear_host_address", test_nm_utils_ip6_address_clear_host_address); g_test_add_func ("/general/nm_utils_log_connection_diff", test_nm_utils_log_connection_diff); @@ -908,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 (); |