NetworkManager/libnm-core/nm-keyfile-reader.c
Thomas Haller 32f78ae6c3 libnm: expose nm_utils_is_json_object() utility function
Since we possibly already link against libjansson, we can also expose some
helper utils which allows nmcli to do basic validation of JSON without
requiring to duplicate the effort of using libjansson.

Also, tighten up the cecks to ensure that we have a JSON object at hand.
We are really interested in that and not of arrays or literals.
2016-09-27 10:56:42 +02:00

1736 lines
51 KiB
C

/* -*- 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 "nm-default.h"
#include "nm-keyfile-internal.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-common-macros.h"
#include "nm-core-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 address: %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 (&current, &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 (&current, &error, DIGITS, DELIMITERS);
/* get gateway field */
gateway_str = read_field (&current, &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 (&current, &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
ip6_addr_gen_mode_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
{
NMSettingIP6ConfigAddrGenMode addr_gen_mode;
const char *setting_name = nm_setting_get_name (setting);
gs_free char *s = NULL;
s = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
if (s) {
if (!nm_utils_enum_from_str (nm_setting_ip6_config_addr_gen_mode_get_type (), s,
(int *) &addr_gen_mode, NULL)) {
handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
_("invalid option '%s', use one of [%s]"),
s, "eui64,stable-privacy");
return;
}
} else
addr_gen_mode = NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64;
g_object_set (G_OBJECT (setting), key, (gint) addr_gen_mode, NULL);
}
static void
mac_address_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key, gsize enforce_length, gboolean cloned_mac_addr)
{
const char *setting_name = nm_setting_get_name (setting);
gs_free char *tmp_string = NULL;
const char *p, *mac_str;
gs_free guint8 *buf_arr = NULL;
guint buf_len = 0;
gsize length;
tmp_string = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
if ( cloned_mac_addr
&& NM_CLONED_MAC_IS_SPECIAL (tmp_string)) {
mac_str = tmp_string;
goto out;
}
if (tmp_string && tmp_string[0]) {
/* Look for enough ':' characters to signify a MAC address */
guint i = 0;
p = tmp_string;
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 */
buf_len = i + 1;
buf_arr = g_new (guint8, buf_len);
if (!nm_utils_hwaddr_aton (tmp_string, buf_arr, buf_len))
g_clear_pointer (&buf_arr, g_free);
}
}
g_clear_pointer (&tmp_string, g_free);
if (!buf_arr) {
gs_free int *tmp_list = 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;
buf_len = length;
buf_arr = g_new (guint8, buf_len);
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);
return;
}
buf_arr[i] = v;
}
}
}
if (!buf_arr) {
handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
_("ignoring invalid MAC address"));
return;
}
tmp_string = nm_utils_hwaddr_ntoa (buf_arr, buf_len);
mac_str = tmp_string;
out:
g_object_set (setting, key, mac_str, NULL);
}
static void
mac_address_parser_ETHER (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
{
mac_address_parser (info, setting, key, ETH_ALEN, FALSE);
}
static void
mac_address_parser_ETHER_cloned (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
{
mac_address_parser (info, setting, key, ETH_ALEN, TRUE);
}
static void
mac_address_parser_INFINIBAND (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
{
mac_address_parser (info, setting, key, INFINIBAND_ALEN, FALSE);
}
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 >= NM_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[NM_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 - NM_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 > NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB)
&& g_str_has_prefix (data, NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB)) {
const char *cdata = data + NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB);
guchar *bin;
GBytes *bytes2;
gsize i;
gboolean valid_base64;
data_len -= NM_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);
}
static void
team_config_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
{
const char *setting_name = nm_setting_get_name (setting);
gs_free char *conf = NULL;
gs_free_error GError *error = NULL;
conf = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
if (conf && conf[0] && !nm_utils_is_json_object (conf, &error)) {
handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
_("ignoring invalid team configuration: %s"),
error->message);
g_clear_pointer (&conf, g_free);
}
g_object_set (G_OBJECT (setting), key, conf, 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_IP6_CONFIG_SETTING_NAME,
NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE,
FALSE,
ip6_addr_gen_mode_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_cloned },
{ 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_cloned },
{ 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 },
{ NM_SETTING_TEAM_SETTING_NAME,
NM_SETTING_TEAM_CONFIG,
TRUE,
team_config_parser },
{ NM_SETTING_TEAM_PORT_SETTING_NAME,
NM_SETTING_TEAM_CONFIG,
TRUE,
team_config_parser },
{ NULL, NULL, FALSE }
};
static void
set_default_for_missing_key (NMSetting *setting, const char *property)
{
/* Set a value different from the default value of the property's spec */
if (NM_IS_SETTING_WIRELESS (setting)) {
if (!strcmp (property, NM_SETTING_WIRELESS_MAC_ADDRESS_RANDOMIZATION))
g_object_set (setting, property, (NMSettingMacRandomization) NM_SETTING_MAC_RANDOMIZATION_NEVER, NULL);
}
}
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;
}
/* Allow default values different than in property spec */
set_default_for_missing_key (setting, key);
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' (%llu)"),
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, NM_KEYFILE_GROUP_VPN_SECRETS, NULL, NULL);
for (iter = keys; *iter; iter++) {
char *secret;
secret = nm_keyfile_plugin_kf_get_string (info->keyfile, NM_KEYFILE_GROUP_VPN_SECRETS, *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);
if (!groups)
length = 0;
for (i = 0; i < length; i++) {
/* Only read out secrets when needed */
if (!strcmp (groups[i], NM_KEYFILE_GROUP_VPN_SECRETS)) {
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;
}