/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2008 - 2009 Novell, Inc. * Copyright (C) 2008 - 2017 Red Hat, Inc. */ #include "libnm-core-impl/nm-default-libnm-core.h" #include "libnm-core-intern/nm-keyfile-internal.h" #include #include #include #include #include #include #include #include #include #include "libnm-glib-aux/nm-uuid.h" #include "libnm-glib-aux/nm-str-buf.h" #include "libnm-glib-aux/nm-secret-utils.h" #include "libnm-systemd-shared/nm-sd-utils-shared.h" #include "libnm-core-aux-intern/nm-common-macros.h" #include "libnm-core-aux-intern/nm-libnm-core-utils.h" #include "libnm-core-intern/nm-core-internal.h" #include "nm-keyfile.h" #include "nm-setting-private.h" #include "nm-setting-user.h" #include "nm-setting-ovs-external-ids.h" #include "libnm-core-intern/nm-keyfile-utils.h" #define ETHERNET_S390_OPTIONS_GROUP_NAME "ethernet-s390-options" #define OVS_EXTERNAL_IDS_DATA_PREFIX "data." /*****************************************************************************/ typedef struct _ParseInfoProperty ParseInfoProperty; typedef struct { NMConnection * connection; GKeyFile * keyfile; const char * base_dir; NMKeyfileReadHandler read_handler; void * user_data; GError * error; const char * group; NMSetting * setting; } KeyfileReaderInfo; typedef struct { NMConnection * connection; GKeyFile * keyfile; GError * error; NMKeyfileWriteHandler write_handler; void * user_data; } KeyfileWriterInfo; /*****************************************************************************/ static void _key_file_handler_data_init(NMKeyfileHandlerData *handler_data, NMKeyfileHandlerType handler_type, const char * kf_group_name, const char * kf_key, NMSetting * cur_setting, const char * cur_property, GError ** p_error) { nm_assert(handler_data); nm_assert(p_error && !*p_error); handler_data->type = handler_type; handler_data->p_error = p_error; handler_data->kf_group_name = kf_group_name; handler_data->kf_key = kf_key; handler_data->cur_setting = cur_setting; handler_data->cur_property = cur_property; } static void _key_file_handler_data_init_read(NMKeyfileHandlerData *handler_data, NMKeyfileHandlerType handler_type, KeyfileReaderInfo * info, const char * kf_key, const char * cur_property) { _key_file_handler_data_init(handler_data, handler_type, info->group, kf_key, info->setting, cur_property, &info->error); } static void _key_file_handler_data_init_write(NMKeyfileHandlerData *handler_data, NMKeyfileHandlerType handler_type, KeyfileWriterInfo * info, const char * kf_group, const char * kf_key, NMSetting * cur_setting, const char * cur_property) { _key_file_handler_data_init(handler_data, handler_type, kf_group, kf_key, cur_setting, cur_property, &info->error); } _nm_printf(5, 6) static void _handle_warn(KeyfileReaderInfo * info, const char * kf_key, const char * cur_property, NMKeyfileWarnSeverity severity, const char * fmt, ...) { NMKeyfileHandlerData handler_data; _key_file_handler_data_init_read(&handler_data, NM_KEYFILE_HANDLER_TYPE_WARN, info, kf_key, cur_property); handler_data.warn = (NMKeyfileHandlerDataWarn){ .severity = severity, .message = NULL, .fmt = fmt, }; va_start(handler_data.warn.ap, fmt); info->read_handler(info->keyfile, info->connection, NM_KEYFILE_HANDLER_TYPE_WARN, &handler_data, info->user_data); va_end(handler_data.warn.ap); g_free(handler_data.warn.message); } #define handle_warn(arg_info, arg_kf_key, arg_property_name, arg_severity, ...) \ ({ \ KeyfileReaderInfo *_info = (arg_info); \ \ nm_assert(!_info->error); \ \ if (_info->read_handler) { \ _handle_warn(_info, (arg_kf_key), (arg_property_name), (arg_severity), __VA_ARGS__); \ } \ _info->error == NULL; \ }) /*****************************************************************************/ static gboolean _secret_flags_persist_secret(NMSettingSecretFlags flags) { return flags == NM_SETTING_SECRET_FLAG_NONE; } /*****************************************************************************/ /* 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); const char * key_setting_name; gs_free char *s = NULL; s = nm_keyfile_plugin_kf_get_string(info->keyfile, setting_name, key, NULL); if (!s) return; key_setting_name = nm_keyfile_plugin_get_setting_name_for_alias(s); g_object_set(G_OBJECT(setting), key, key_setting_name ?: s, NULL); } static void sriov_vfs_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key) { const char * setting_name = nm_setting_get_name(setting); gs_unref_ptrarray GPtrArray *vfs = NULL; gs_strfreev char ** keys = NULL; gsize n_keys = 0; int i; keys = nm_keyfile_plugin_kf_get_keys(info->keyfile, setting_name, &n_keys, NULL); if (n_keys == 0) return; vfs = g_ptr_array_new_with_free_func((GDestroyNotify) nm_sriov_vf_unref); for (i = 0; i < n_keys; i++) { gs_free char *value = NULL; NMSriovVF * vf; const char * rest; if (!g_str_has_prefix(keys[i], "vf.")) continue; rest = &keys[i][3]; if (!NM_STRCHAR_ALL(rest, ch, g_ascii_isdigit(ch))) continue; value = nm_keyfile_plugin_kf_get_string(info->keyfile, setting_name, keys[i], NULL); vf = _nm_utils_sriov_vf_from_strparts(rest, value, TRUE, NULL); if (vf) g_ptr_array_add(vfs, vf); } g_object_set(G_OBJECT(setting), key, vfs, NULL); } static void read_array_of_uint(GKeyFile *file, NMSetting *setting, const char *key) { gs_unref_array GArray *array = NULL; gs_free_error GError *error = NULL; gs_free guint *tmp = NULL; gsize length; tmp = nm_keyfile_plugin_kf_get_integer_list_uint(file, nm_setting_get_name(setting), key, &length, &error); if (error) return; array = g_array_sized_new(FALSE, FALSE, sizeof(guint), length); g_array_append_vals(array, tmp, length); g_object_set(setting, key, array, NULL); } static gboolean get_one_int(KeyfileReaderInfo *info, const char * kf_key, const char * property_name, const char * str, guint32 max_val, guint32 * out) { gint64 tmp; nm_assert((!info) == (!property_name)); nm_assert((!info) == (!kf_key)); if (!str || !str[0]) { if (info) { handle_warn(info, kf_key, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, _("ignoring missing number")); } return FALSE; } tmp = _nm_utils_ascii_str_to_int64(str, 10, 0, max_val, -1); if (tmp == -1) { if (info) { handle_warn(info, kf_key, 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, const char * kf_key, const char * property_name, 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) { handle_warn(info, kf_key, 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 * kf_key, const char * property_name, int family, const char * dest_str, guint32 plen, const char * gateway_str, const char * metric_str) { NMIPRoute *route; guint32 u32; gint64 metric = -1; GError * error = NULL; g_return_val_if_fail(dest_str, NULL); /* Next hop */ if (gateway_str && gateway_str[0]) { if (!nm_utils_ipaddr_is_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, NULL, gateway_str, G_MAXUINT32, &u32)) { metric = u32; gateway_str = NULL; } else { handle_warn(info, kf_key, 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 -1 */ if (metric_str) { if (!get_one_int(info, kf_key, property_name, metric_str, G_MAXUINT32, &u32)) return NULL; metric = u32; } route = nm_ip_route_new(family, dest_str, plen, gateway_str, metric, &error); if (!route) { handle_warn(info, kf_key, 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 const char * read_field(char **current, const char **out_err_str, const char *characters, const char *delimiters) { const char *start; nm_assert(current); nm_assert(out_err_str); nm_assert(characters); nm_assert(delimiters); *out_err_str = 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 */ *out_err_str = *current; *current = (char *) start; return NULL; } else { /* success, end of input */ *current = NULL; return start; } } /*****************************************************************************/ #define NM_DBUS_SERVICE_OPENCONNECT "org.freedesktop.NetworkManager.openconnect" #define NM_OPENCONNECT_KEY_GATEWAY "gateway" #define NM_OPENCONNECT_KEY_COOKIE "cookie" #define NM_OPENCONNECT_KEY_GWCERT "gwcert" #define NM_OPENCONNECT_KEY_XMLCONFIG "xmlconfig" #define NM_OPENCONNECT_KEY_LASTHOST "lasthost" #define NM_OPENCONNECT_KEY_AUTOCONNECT "autoconnect" #define NM_OPENCONNECT_KEY_CERTSIGS "certsigs" static void openconnect_fix_secret_flags(NMSetting *setting) { NMSettingVpn * s_vpn; NMSettingSecretFlags flags; /* Huge hack. There were some openconnect changes that needed to happen * pretty late, too late to get into distros. Migration has already * happened for many people, and their secret flags are wrong. But we * don't want to require re-migration, so we have to fix it up here. Ugh. */ if (!NM_IS_SETTING_VPN(setting)) return; s_vpn = NM_SETTING_VPN(setting); if (!nm_streq0(nm_setting_vpn_get_service_type(s_vpn), NM_DBUS_SERVICE_OPENCONNECT)) return; /* These are different for every login session, and should not be stored */ flags = NM_SETTING_SECRET_FLAG_NOT_SAVED; nm_setting_set_secret_flags(NM_SETTING(s_vpn), NM_OPENCONNECT_KEY_GATEWAY, flags, NULL); nm_setting_set_secret_flags(NM_SETTING(s_vpn), NM_OPENCONNECT_KEY_COOKIE, flags, NULL); nm_setting_set_secret_flags(NM_SETTING(s_vpn), NM_OPENCONNECT_KEY_GWCERT, flags, NULL); /* These are purely internal data for the auth-dialog, and should be stored */ flags = 0; nm_setting_set_secret_flags(NM_SETTING(s_vpn), NM_OPENCONNECT_KEY_XMLCONFIG, flags, NULL); nm_setting_set_secret_flags(NM_SETTING(s_vpn), NM_OPENCONNECT_KEY_LASTHOST, flags, NULL); nm_setting_set_secret_flags(NM_SETTING(s_vpn), NM_OPENCONNECT_KEY_AUTOCONNECT, flags, NULL); nm_setting_set_secret_flags(NM_SETTING(s_vpn), NM_OPENCONNECT_KEY_CERTSIGS, flags, NULL); } /*****************************************************************************/ #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 interchangeable. 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 * kf_key, gboolean ipv6, gboolean route, char ** out_gateway, NMSetting * setting) { guint plen; gpointer result; const char * address_str; const char * plen_str; const char * gateway_str; const char * metric_str; const char * err_str = NULL; char * current; gs_free char *value = NULL; gs_free char *value_orig = NULL; #define VALUE_ORIG() \ (value_orig \ ?: (value_orig = \ nm_keyfile_plugin_kf_get_string(info->keyfile, setting_name, kf_key, NULL))) value = nm_keyfile_plugin_kf_get_string(info->keyfile, setting_name, kf_key, NULL); if (!value) return NULL; current = value; /* get address field */ address_str = read_field(¤t, &err_str, IP_ADDRESS_CHARS, DELIMITERS); if (err_str) { handle_warn(info, kf_key, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, _("unexpected character '%c' for address %s: '%s' (position %td)"), *err_str, kf_key, VALUE_ORIG(), err_str - current); return NULL; } /* get prefix length field (skippable) */ plen_str = read_field(¤t, &err_str, DIGITS, DELIMITERS); /* get gateway field */ gateway_str = read_field(¤t, &err_str, IP_ADDRESS_CHARS, DELIMITERS); if (err_str) { handle_warn(info, kf_key, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, _("unexpected character '%c' for %s: '%s' (position %td)"), *err_str, kf_key, VALUE_ORIG(), err_str - current); return NULL; } /* for routes, get metric */ if (route) { metric_str = read_field(¤t, &err_str, DIGITS, DELIMITERS); if (err_str) { handle_warn(info, kf_key, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, _("unexpected character '%c' in prefix length for %s: '%s' (position %td)"), *err_str, kf_key, VALUE_ORIG(), err_str - current); return NULL; } } else metric_str = NULL; if (current) { /* there is still some data */ if (*current) { /* another field follows */ handle_warn(info, kf_key, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, _("garbage at the end of value %s: '%s'"), kf_key, VALUE_ORIG()); return NULL; } else { /* semicolon at the end of input */ if (!handle_warn(info, kf_key, property_name, NM_KEYFILE_WARN_SEVERITY_INFO, _("deprecated semicolon at the end of value %s: '%s'"), kf_key, 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, kf_key, property_name, plen_str, ipv6 ? 128 : 32, &plen)) { plen = DEFAULT_PREFIX(route, ipv6); if (info->error || !handle_warn(info, kf_key, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, _("invalid prefix length for %s '%s', defaulting to %d"), kf_key, VALUE_ORIG(), plen)) return NULL; } } else { plen = DEFAULT_PREFIX(route, ipv6); if (!handle_warn(info, kf_key, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, _("missing prefix length for %s '%s', defaulting to %d"), kf_key, VALUE_ORIG(), plen)) return NULL; } /* build the appropriate data structure for NetworkManager settings */ if (route) { result = build_route(info, kf_key, property_name, ipv6 ? AF_INET6 : AF_INET, address_str, plen, gateway_str, metric_str); } else { result = build_address(info, kf_key, property_name, ipv6 ? AF_INET6 : AF_INET, address_str, plen); if (!result) return NULL; if (gateway_str) NM_SET_OUT(out_gateway, g_strdup(gateway_str)); } #undef VALUE_ORIG return result; } static void fill_route_attributes(GKeyFile * kf, NMIPRoute * route, const char *setting, const char *key, int family) { gs_free char * value = NULL; gs_unref_hashtable GHashTable *hash = NULL; GHashTableIter iter; char * name; GVariant * variant; value = nm_keyfile_plugin_kf_get_string(kf, setting, key, NULL); if (!value || !value[0]) return; hash = nm_utils_parse_variant_attributes(value, ',', '=', TRUE, nm_ip_route_get_variant_attribute_spec(), NULL); if (hash) { g_hash_table_iter_init(&iter, hash); while (g_hash_table_iter_next(&iter, (gpointer *) &name, (gpointer *) &variant)) { if (nm_ip_route_attribute_validate(name, variant, family, NULL, NULL)) nm_ip_route_set_attribute(route, name, g_variant_ref(variant)); } } } typedef struct { const char *s_key; gint32 key_idx; gint8 key_type; } BuildListData; typedef enum { BUILD_LIST_TYPE_ADDRESSES, BUILD_LIST_TYPE_ROUTES, BUILD_LIST_TYPE_ROUTING_RULES, } BuildListType; static int _build_list_data_cmp(gconstpointer p_a, gconstpointer p_b, gpointer user_data) { const BuildListData *a = p_a; const BuildListData *b = p_b; NM_CMP_FIELD(a, b, key_idx); NM_CMP_FIELD(a, b, key_type); NM_CMP_FIELD_STR(a, b, s_key); return 0; } static gboolean _build_list_data_is_shadowed(const BuildListData *build_list, gsize build_list_len, gsize idx) { /* the keyfile contains duplicate keys, which are both returned * by g_key_file_get_keys() (WHY??). * * Skip the earlier one. */ return idx + 1 < build_list_len && build_list[idx].key_idx == build_list[idx + 1].key_idx && build_list[idx].key_type == build_list[idx + 1].key_type && nm_streq(build_list[idx].s_key, build_list[idx + 1].s_key); } static gboolean _build_list_match_key_w_name_impl(const char *key, const char *base_name, gsize base_name_l, gint32 * out_key_idx) { gint64 v; /* some very strict parsing. */ /* the key must start with base_name. */ if (strncmp(key, base_name, base_name_l) != 0) return FALSE; key += base_name_l; if (key[0] == '\0') { /* if key is identical to base_name, that's good. */ NM_SET_OUT(out_key_idx, -1); return TRUE; } /* if base_name is followed by a zero, then it must be * only a zero, nothing else. */ if (key[0] == '0') { if (key[1] != '\0') return FALSE; NM_SET_OUT(out_key_idx, 0); return TRUE; } /* otherwise, it can only be followed by a non-zero decimal. */ if (!(key[0] >= '1' && key[0] <= '9')) return FALSE; /* and all remaining chars must be decimals too. */ if (!NM_STRCHAR_ALL(&key[1], ch, g_ascii_isdigit(ch))) return FALSE; /* and it must be convertible to a (positive) int. */ v = _nm_utils_ascii_str_to_int64(key, 10, 0, G_MAXINT32, -1); if (v < 0) return FALSE; /* good */ NM_SET_OUT(out_key_idx, v); return TRUE; } #define _build_list_match_key_w_name(key, base_name, out_key_idx) \ _build_list_match_key_w_name_impl(key, base_name, NM_STRLEN(base_name), out_key_idx) static BuildListData * _build_list_create(GKeyFile * keyfile, const char * group_name, BuildListType build_list_type, gsize * out_build_list_len, char *** out_keys_strv) { gs_strfreev char **keys = NULL; gsize i_keys, n_keys; gs_free BuildListData *build_list = NULL; gsize build_list_len = 0; nm_assert(out_build_list_len && *out_build_list_len == 0); nm_assert(out_keys_strv && !*out_keys_strv); keys = nm_keyfile_plugin_kf_get_keys(keyfile, group_name, &n_keys, NULL); if (n_keys == 0) return NULL; for (i_keys = 0; i_keys < n_keys; i_keys++) { const char *s_key = keys[i_keys]; gint32 key_idx; gint8 key_type = 0; switch (build_list_type) { case BUILD_LIST_TYPE_ROUTES: if (_build_list_match_key_w_name(s_key, "route", &key_idx)) key_type = 0; else if (_build_list_match_key_w_name(s_key, "routes", &key_idx)) key_type = 1; else continue; break; case BUILD_LIST_TYPE_ADDRESSES: if (_build_list_match_key_w_name(s_key, "address", &key_idx)) key_type = 0; else if (_build_list_match_key_w_name(s_key, "addresses", &key_idx)) key_type = 1; else continue; break; case BUILD_LIST_TYPE_ROUTING_RULES: if (_build_list_match_key_w_name(s_key, "routing-rule", &key_idx)) key_type = 0; else continue; break; default: nm_assert_not_reached(); break; } if (G_UNLIKELY(!build_list)) build_list = g_new(BuildListData, n_keys - i_keys); build_list[build_list_len++] = (BuildListData){ .s_key = s_key, .key_idx = key_idx, .key_type = key_type, }; } if (build_list_len == 0) return NULL; if (build_list_len > 1) { g_qsort_with_data(build_list, build_list_len, sizeof(BuildListData), _build_list_data_cmp, NULL); } *out_build_list_len = build_list_len; *out_keys_strv = g_steal_pointer(&keys); return g_steal_pointer(&build_list); } static void ip_address_or_route_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *setting_key) { const char * setting_name = nm_setting_get_name(setting); gboolean is_ipv6 = nm_streq(setting_name, "ipv6"); gboolean is_routes = nm_streq(setting_key, "routes"); gs_free char * gateway = NULL; gs_unref_ptrarray GPtrArray *list = NULL; gs_strfreev char ** keys = NULL; gs_free BuildListData *build_list = NULL; gsize i_build_list, build_list_len = 0; build_list = _build_list_create(info->keyfile, setting_name, is_routes ? BUILD_LIST_TYPE_ROUTES : BUILD_LIST_TYPE_ADDRESSES, &build_list_len, &keys); if (!build_list) return; list = g_ptr_array_new_with_free_func(is_routes ? (GDestroyNotify) nm_ip_route_unref : (GDestroyNotify) nm_ip_address_unref); for (i_build_list = 0; i_build_list < build_list_len; i_build_list++) { const char *s_key; gpointer item; if (_build_list_data_is_shadowed(build_list, build_list_len, i_build_list)) continue; s_key = build_list[i_build_list].s_key; item = read_one_ip_address_or_route(info, setting_key, setting_name, s_key, is_ipv6, is_routes, gateway ? NULL : &gateway, setting); if (item && is_routes) { char options_key[128]; nm_sprintf_buf(options_key, "%s_options", s_key); fill_route_attributes(info->keyfile, item, setting_name, options_key, is_ipv6 ? AF_INET6 : AF_INET); } if (info->error) return; if (item) g_ptr_array_add(list, item); } if (list->len >= 1) g_object_set(setting, setting_key, list, NULL); if (gateway) g_object_set(setting, "gateway", gateway, NULL); } static void ip_routing_rule_parser_full(KeyfileReaderInfo * info, const NMMetaSettingInfo * setting_info, const NMSettInfoProperty *property_info, const ParseInfoProperty * pip, NMSetting * setting) { const char * setting_name = nm_setting_get_name(setting); gboolean is_ipv6 = nm_streq(setting_name, "ipv6"); gs_strfreev char **keys = NULL; gs_free BuildListData *build_list = NULL; gsize i_build_list, build_list_len = 0; build_list = _build_list_create(info->keyfile, setting_name, BUILD_LIST_TYPE_ROUTING_RULES, &build_list_len, &keys); if (!build_list) return; for (i_build_list = 0; i_build_list < build_list_len; i_build_list++) { nm_auto_unref_ip_routing_rule NMIPRoutingRule *rule = NULL; gs_free char * value = NULL; gs_free_error GError *local = NULL; if (_build_list_data_is_shadowed(build_list, build_list_len, i_build_list)) continue; value = nm_keyfile_plugin_kf_get_string(info->keyfile, setting_name, build_list[i_build_list].s_key, NULL); if (!value) continue; rule = nm_ip_routing_rule_from_string( value, (NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE | (is_ipv6 ? NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6 : NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET)), NULL, &local); if (!rule) { if (!handle_warn(info, build_list[i_build_list].s_key, property_info->name, NM_KEYFILE_WARN_SEVERITY_WARN, _("invalid value for \"%s\": %s"), build_list[i_build_list].s_key, local->message)) return; continue; } nm_setting_ip_config_add_routing_rule(NM_SETTING_IP_CONFIG(setting), rule); } } static void _parser_full_ovs_external_ids_data(KeyfileReaderInfo * info, const NMMetaSettingInfo * setting_info, const NMSettInfoProperty *property_info, const ParseInfoProperty * pip, NMSetting * setting) { const char * setting_name = NM_SETTING_OVS_EXTERNAL_IDS_SETTING_NAME; gs_strfreev char **keys = NULL; gsize n_keys; gsize i; nm_assert(NM_IS_SETTING_OVS_EXTERNAL_IDS(setting)); nm_assert(nm_streq(property_info->name, NM_SETTING_OVS_EXTERNAL_IDS_DATA)); nm_assert(nm_streq(setting_name, setting_info->setting_name)); nm_assert(nm_streq(setting_name, nm_setting_get_name(setting))); keys = nm_keyfile_plugin_kf_get_keys(info->keyfile, setting_name, &n_keys, NULL); for (i = 0; i < n_keys; i++) { const char * key = keys[i]; gs_free char *name_to_free = NULL; gs_free char *value = NULL; const char * name; if (!NM_STR_HAS_PREFIX(key, OVS_EXTERNAL_IDS_DATA_PREFIX)) continue; value = nm_keyfile_plugin_kf_get_string(info->keyfile, setting_name, key, NULL); if (!value) continue; name = &key[NM_STRLEN(OVS_EXTERNAL_IDS_DATA_PREFIX)]; name = nm_keyfile_key_decode(name, &name_to_free); nm_setting_ovs_external_ids_set_data(NM_SETTING_OVS_EXTERNAL_IDS(setting), name, value); } } static void ip_dns_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key) { int addr_family; gs_strfreev char **list = NULL; gsize i, n, length; nm_assert(NM_IS_SETTING_IP4_CONFIG(setting) || NM_IS_SETTING_IP6_CONFIG(setting)); list = nm_keyfile_plugin_kf_get_string_list(info->keyfile, nm_setting_get_name(setting), key, &length, NULL); nm_assert(length == NM_PTRARRAY_LEN(list)); if (length == 0) return; addr_family = NM_IS_SETTING_IP4_CONFIG(setting) ? AF_INET : AF_INET6; n = 0; for (i = 0; i < length; i++) { NMIPAddr addr; if (inet_pton(addr_family, list[i], &addr) <= 0) { if (!handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("ignoring invalid DNS server IPv%c address '%s'"), nm_utils_addr_family_to_char(addr_family), list[i])) { do { nm_clear_g_free(&list[i]); } while (++i < length); return; } nm_clear_g_free(&list[i]); continue; } if (n != i) list[n] = g_steal_pointer(&list[i]); n++; } g_object_set(setting, key, list, NULL); } 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, 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, (int) addr_gen_mode, NULL); } static void mac_address_parser(KeyfileReaderInfo *info, NMSetting * setting, const char * key, gsize addr_len, gboolean cloned_mac_addr) { const char * setting_name = nm_setting_get_name(setting); char addr_str[NM_UTILS_HWADDR_LEN_MAX * 3]; guint8 addr_bin[NM_UTILS_HWADDR_LEN_MAX]; gs_free char *tmp_string = NULL; gs_free guint *int_list = NULL; const char * mac_str; gsize int_list_len; gsize i; nm_assert(addr_len > 0); nm_assert(addr_len <= NM_UTILS_HWADDR_LEN_MAX); 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 && nm_utils_hwaddr_aton(tmp_string, addr_bin, addr_len)) goto good_addr_bin; /* Old format; list of ints */ int_list = nm_keyfile_plugin_kf_get_integer_list_uint(info->keyfile, setting_name, key, &int_list_len, NULL); if (int_list_len == addr_len) { for (i = 0; i < addr_len; i++) { const guint val = int_list[i]; if (val > 255) break; addr_bin[i] = (guint8) val; } if (i == addr_len) goto good_addr_bin; } handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("ignoring invalid MAC address")); return; good_addr_bin: nm_utils_bin2hexstr_full(addr_bin, addr_len, ':', TRUE, addr_str); mac_str = addr_str; 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(KeyfileReaderInfo *info, GKeyFile * file, NMSetting * setting, const char * kf_group) { gs_strfreev char **keys = NULL; const char *const *iter; const char * setting_name = nm_setting_get_name(setting); gboolean is_vpn; gsize n_keys; nm_assert((NM_IS_SETTING_VPN(setting) && nm_streq(kf_group, NM_SETTING_VPN_DATA)) || (NM_IS_SETTING_VPN(setting) && nm_streq(kf_group, NM_SETTING_VPN_SECRETS)) || (NM_IS_SETTING_BOND(setting) && nm_streq(kf_group, NM_SETTING_BOND_OPTIONS)) || (NM_IS_SETTING_USER(setting) && nm_streq(kf_group, NM_SETTING_USER_DATA))); keys = nm_keyfile_plugin_kf_get_keys(file, setting_name, &n_keys, NULL); if (n_keys == 0) return; if ((is_vpn = NM_IS_SETTING_VPN(setting)) || NM_IS_SETTING_BOND(setting)) { for (iter = (const char *const *) keys; *iter; iter++) { const char * kf_key = *iter; gs_free char *to_free = NULL; gs_free char *value = NULL; const char * name; value = nm_keyfile_plugin_kf_get_string(file, setting_name, kf_key, NULL); if (!value) continue; name = nm_keyfile_key_decode(kf_key, &to_free); if (is_vpn) { /* Add any item that's not a class property to the data hash */ if (!g_object_class_find_property(G_OBJECT_GET_CLASS(setting), name)) nm_setting_vpn_add_data_item(NM_SETTING_VPN(setting), name, value); } else { if (!nm_streq(name, "interface-name")) { gs_free_error GError *error = NULL; if (!_nm_setting_bond_validate_option(name, value, &error)) { if (!handle_warn(info, kf_key, name, NM_KEYFILE_WARN_SEVERITY_WARN, _("ignoring invalid bond option %s%s%s = %s%s%s: %s"), NM_PRINT_FMT_QUOTE_STRING(name), NM_PRINT_FMT_QUOTE_STRING(value), error->message)) return; } else nm_setting_bond_add_option(NM_SETTING_BOND(setting), name, value); } } } openconnect_fix_secret_flags(setting); return; } if (NM_IS_SETTING_USER(setting)) { gs_unref_hashtable GHashTable *data = NULL; data = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free); for (iter = (const char *const *) keys; *iter; iter++) { gs_free char *to_free = NULL; char * value = NULL; const char * name; value = nm_keyfile_plugin_kf_get_string(file, setting_name, *iter, NULL); if (!value) continue; name = nm_keyfile_key_decode(*iter, &to_free); g_hash_table_insert(data, g_steal_pointer(&to_free) ?: g_strdup(name), value); } g_object_set(setting, NM_SETTING_USER_DATA, data, NULL); return; } nm_assert_not_reached(); } static gsize unescape_semicolons(char *str) { gsize i, j; for (i = 0, j = 0; str[i];) { if (str[i] == '\\' && str[i + 1] == ';') i++; str[j++] = str[i++]; } nm_explicit_bzero(&str[j], i - j); return j; } static GBytes * get_bytes(KeyfileReaderInfo *info, const char * setting_name, const char * key, gboolean zero_terminate, gboolean unescape_semicolon) { nm_auto_free_secret char *tmp_string = NULL; gboolean may_be_int_list = TRUE; gsize length; GBytes * result; /* 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) return NULL; /* if the string is empty, we return an empty GBytes array. * Note that for NM_SETTING_802_1X_PASSWORD_RAW both %NULL and * an empty GBytes are valid, and shall be destinguished. */ if (!tmp_string[0]) { /* note that even if @zero_terminate is TRUE, we return an empty * byte-array. The reason is that zero_terminate is there to terminate * *valid* strings. It's not there to terminated invalid (empty) strings. */ return g_bytes_new_static("", 0); } for (length = 0; tmp_string[length]; length++) { const char ch = tmp_string[length]; if (!g_ascii_isspace(ch) && !g_ascii_isdigit(ch) && ch != ';') { may_be_int_list = FALSE; length += strlen(&tmp_string[length]); break; } } /* Try to parse the string as a integer list. */ if (may_be_int_list && length > 0) { nm_auto_free_secret_buf NMSecretBuf *bin = NULL; const char *const s = tmp_string; gsize i, d; bin = nm_secret_buf_new(length / 2 + 3); #define DIGIT(c) ((c) - '0') i = 0; d = 0; while (TRUE) { int n; /* leading whitespace */ while (g_ascii_isspace(s[i])) i++; if (s[i] == '\0') break; /* then expect 1 to 3 digits */ if (!g_ascii_isdigit(s[i])) { d = 0; break; } n = DIGIT(s[i]); i++; if (g_ascii_isdigit(s[i])) { n = 10 * n + DIGIT(s[i]); i++; if (g_ascii_isdigit(s[i])) { n = 10 * n + DIGIT(s[i]); i++; } } if (n > 255) { d = 0; break; } nm_assert(d < bin->len); bin->bin[d++] = n; /* allow whitespace after the digit. */ while (g_ascii_isspace(s[i])) i++; /* need a semicolon as separator. */ if (s[i] != ';') { d = 0; break; } i++; } #undef DIGIT /* Old format; list of ints. We already did a strict validation of the * string format before. We expect that this conversion cannot fail. */ if (d > 0) { /* note that @zero_terminate does not add a terminating '\0' to * binary data as an integer list. If the bytes are expressed as * an integer list, all potential NUL characters are supposed to * be included there explicitly. * * However, in the spirit of defensive programming, we do append a * NUL character to the buffer, although this character is hidden * and only a mitigation for bugs. */ if (d + 10 < bin->len) { /* hm, too much unused memory. Copy the memory to a suitable * sized buffer. */ return nm_secret_copy_to_gbytes(bin->bin, d); } nm_assert(d < bin->len); bin->bin[d] = '\0'; return nm_secret_buf_to_gbytes_take(g_steal_pointer(&bin), d); } } /* Handle as a simple string (ie, new format) */ if (unescape_semicolon) length = unescape_semicolons(tmp_string); if (zero_terminate) length++; if (length == 0) return NULL; result = g_bytes_new_with_free_func(tmp_string, length, (GDestroyNotify) nm_free_secret, tmp_string); tmp_string = NULL; return result; } static void ssid_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key) { const char * setting_name = nm_setting_get_name(setting); gs_unref_bytes GBytes *bytes = NULL; bytes = get_bytes(info, setting_name, key, FALSE, TRUE); if (!bytes) { handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("ignoring invalid SSID")); return; } g_object_set(setting, key, bytes, NULL); } static void password_raw_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key) { const char * setting_name = nm_setting_get_name(setting); gs_unref_bytes GBytes *bytes = NULL; bytes = get_bytes(info, setting_name, key, FALSE, TRUE); if (!bytes) { handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("ignoring invalid raw password")); return; } g_object_set(setting, key, bytes, NULL); } 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); path = g_strndup((char *) cert_path, cert_path_len); if (path[0] == '/') return path; base = 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; } 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; gsize validate_len; gsize path_len, pathuri_len; gs_free char *path = NULL; gs_free char *pathuri = NULL; 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); /* FIXME(keyfile-parse-in-memory): it is wrong that keyfile reader makes decisions based on * the file systems content. The serialization/parsing should be entirely in-memory. */ if (!memchr(data, '/', data_len) && !has_cert_ext(path)) { if (!consider_exists) return NULL; exists = g_file_test(path, G_FILE_TEST_EXISTS); if (!exists) return NULL; } else if (out_exists) exists = g_file_test(path, G_FILE_TEST_EXISTS); /* Construct the proper value as required for the PATH scheme. * * 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. */ path_len = strlen(path); pathuri_len = (NM_STRLEN(NM_KEYFILE_CERT_SCHEME_PREFIX_PATH) + 1) + path_len; pathuri = g_new(char, pathuri_len); memcpy(pathuri, NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, NM_STRLEN(NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)); memcpy(&pathuri[NM_STRLEN(NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)], path, path_len + 1); if (nm_setting_802_1x_check_cert_scheme(pathuri, pathuri_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_PATH) return NULL; NM_SET_OUT(out_exists, exists); return g_steal_pointer(&pathuri); } #define HAS_SCHEME_PREFIX(bin, bin_len, scheme) \ ({ \ const char *const _bin = (bin); \ const gsize _bin_len = (bin_len); \ \ nm_assert(_bin &&_bin_len > 0); \ \ (_bin_len > NM_STRLEN(scheme) + 1 && _bin[_bin_len - 1] == '\0' \ && memcmp(_bin, scheme, NM_STRLEN(scheme)) == 0); \ }) 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; const char * bin = NULL; gsize bin_len = 0; char * path; gboolean path_exists; bytes = get_bytes(info, setting_name, key, TRUE, FALSE); if (bytes) bin = g_bytes_get_data(bytes, &bin_len); if (bin_len == 0) { if (!info->error) { handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("invalid key/cert value")); } return; } if (HAS_SCHEME_PREFIX(bin, bin_len, NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)) { const char * path2 = &bin[NM_STRLEN(NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)]; gs_free char *path2_free = NULL; if (nm_setting_802_1x_check_cert_scheme(bin, bin_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_PATH) { handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("invalid key/cert value path \"%s\""), bin); return; } g_object_set(setting, key, bytes, NULL); if (path2[0] != '/') { /* we want to read absolute paths because we use keyfile as exchange * between different processes which might not have the same cwd. */ path2_free = get_cert_path(info->base_dir, (const guint8 *) path2, bin_len - NM_STRLEN(NM_KEYFILE_CERT_SCHEME_PREFIX_PATH) - 1); path2 = path2_free; } /* FIXME(keyfile-parse-in-memory): keyfile reader must not access the file system and * (in a first step) only operate in memory-only. If the presence of files should be checked, * then by invoking a callback (and possibly keyfile settings plugin would * collect the file names to be checked and check them later). */ if (!g_file_test(path2, G_FILE_TEST_EXISTS)) { handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE, _("certificate or key file '%s' does not exist"), path2); } return; } if (HAS_SCHEME_PREFIX(bin, bin_len, NM_KEYFILE_CERT_SCHEME_PREFIX_PKCS11)) { if (nm_setting_802_1x_check_cert_scheme(bin, bin_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_PKCS11) { handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("invalid PKCS#11 URI \"%s\""), bin); return; } g_object_set(setting, key, bytes, NULL); return; } if (HAS_SCHEME_PREFIX(bin, bin_len, NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB)) { const char *cdata = bin + NM_STRLEN(NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB); gsize cdata_len = bin_len - NM_STRLEN(NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB) - 1; gs_free guchar *bin_decoded = NULL; gsize bin_decoded_len = 0; gsize i; gboolean valid_base64; gs_unref_bytes GBytes *val = NULL; /* 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 = (cdata_len % 4 == 0))) { for (i = 0; i < cdata_len; i++) { char c = cdata[i]; if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '+' || c == '/'))) { if (c != '=' || i < cdata_len - 2) valid_base64 = FALSE; else { for (; i < cdata_len; i++) { if (cdata[i] != '=') valid_base64 = FALSE; } } break; } } } if (valid_base64) bin_decoded = g_base64_decode(cdata, &bin_decoded_len); if (bin_decoded_len == 0) { handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("invalid key/cert value data:;base64, is not base64")); return; } if (nm_setting_802_1x_check_cert_scheme(bin_decoded, bin_decoded_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. */ handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("invalid key/cert value data:;base64,file://")); return; } val = g_bytes_new_take(g_steal_pointer(&bin_decoded), bin_decoded_len); g_object_set(setting, key, val, NULL); return; } /* If not, it might be a plain path */ path = nm_keyfile_detect_unqualified_path_scheme(info->base_dir, bin, bin_len, TRUE, &path_exists); if (path) { gs_unref_bytes GBytes *val = NULL; /* 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 (!path_exists) { handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE, _("certificate or key file '%s' does not exist"), path); } return; } 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, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("invalid key/cert value is not a valid blob")); return; } g_object_set(setting, key, bytes, NULL); } static int _parity_from_char(int ch) { #if NM_MORE_ASSERTS > 5 { static char check = 0; if (check == 0) { nm_auto_unref_gtypeclass GEnumClass *klass = g_type_class_ref(NM_TYPE_SETTING_SERIAL_PARITY); guint i; check = 1; /* In older versions, parity was G_TYPE_CHAR/gint8, and the character * value was stored as integer. * For example parity=69 equals parity=E, meaning NM_SETTING_SERIAL_PARITY_EVEN. * * That means, certain values are reserved. Assert that these numbers * are not reused when we extend NMSettingSerialParity enum. * Actually, since NM_SETTING_SERIAL_PARITY is g_param_spec_enum(), * we anyway cannot extend the enum without breaking API... * * [1] commit "a91e60902e libnm-core: make NMSettingSerial:parity an enum" * [2] https://cgit.freedesktop.org/NetworkManager/NetworkManager/commit/?id=a91e60902eabae1de93d61323dae6ac894b5d40f */ g_assert(G_IS_ENUM_CLASS(klass)); for (i = 0; i < klass->n_values; i++) { const GEnumValue *v = &klass->values[i]; int num = v->value; g_assert(_parity_from_char(num) == -1); g_assert(!NM_IN_SET(num, 'e', 'E', 'o', 'O', 'n', 'N')); } } } #endif switch (ch) { case 'E': case 'e': return NM_SETTING_SERIAL_PARITY_EVEN; case 'O': case 'o': return NM_SETTING_SERIAL_PARITY_ODD; case 'N': case 'n': return NM_SETTING_SERIAL_PARITY_NONE; } return -1; } static void parity_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key) { const char * setting_name = nm_setting_get_name(setting); gs_free_error GError *err = NULL; int parity; gs_free char * tmp_str = NULL; gint64 i64; /* 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). */ tmp_str = nm_keyfile_plugin_kf_get_value(info->keyfile, setting_name, key, &err); if (err) goto out_err; if (tmp_str && tmp_str[0] != '\0' && tmp_str[1] == '\0') { /* the ASCII characters like 'E' are taken directly... */ parity = _parity_from_char(tmp_str[0]); if (parity >= 0) goto parity_good; } i64 = _nm_utils_ascii_str_to_int64(tmp_str, 0, G_MININT, G_MAXINT, G_MININT64); if (i64 != G_MININT64 && errno == 0) { if ((parity = _parity_from_char(i64)) >= 0) { /* another oddity: the string is a valid number. However, if the numeric values * is one of the supported ASCII codes, accept it (like 69 for 'E'). */ goto parity_good; } /* Finally, take the numeric value as is. */ parity = i64; goto parity_good; } handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("invalid parity value '%s'"), tmp_str ?: ""); return; parity_good: nm_g_object_set_property_enum(G_OBJECT(setting), key, NM_TYPE_SETTING_SERIAL_PARITY, parity, &err); out_err: if (!err) return; if (nm_keyfile_error_is_not_found(err)) { /* ignore such errors. The key is not present. */ return; } handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("invalid setting: %s"), err->message); } 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); g_object_set(G_OBJECT(setting), key, conf, NULL); if (conf && !nm_setting_verify(setting, NULL, &error)) { handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("ignoring invalid team configuration: %s"), error->message); g_object_set(G_OBJECT(setting), key, NULL, NULL); } } static void bridge_vlan_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key) { gs_unref_ptrarray GPtrArray *vlans = NULL; gs_free char * value = NULL; gs_free const char ** strv = NULL; const char *const * iter; GError * local = NULL; NMBridgeVlan * vlan; value = nm_keyfile_plugin_kf_get_string(info->keyfile, nm_setting_get_name(setting), key, NULL); if (!value || !value[0]) return; vlans = g_ptr_array_new_with_free_func((GDestroyNotify) nm_bridge_vlan_unref); strv = nm_utils_escaped_tokens_split(value, ","); if (strv) { for (iter = strv; *iter; iter++) { vlan = nm_bridge_vlan_from_str(*iter, &local); if (!vlan) { handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, "invalid bridge VLAN: %s", local->message); g_clear_error(&local); continue; } g_ptr_array_add(vlans, vlan); } } if (vlans->len > 0) g_object_set(setting, key, vlans, NULL); } static void qdisc_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key) { const char * setting_name = nm_setting_get_name(setting); gs_unref_ptrarray GPtrArray *qdiscs = NULL; gs_strfreev char ** keys = NULL; gsize n_keys = 0; int i; keys = nm_keyfile_plugin_kf_get_keys(info->keyfile, setting_name, &n_keys, NULL); if (n_keys == 0) return; qdiscs = g_ptr_array_new_with_free_func((GDestroyNotify) nm_tc_qdisc_unref); for (i = 0; i < n_keys; i++) { NMTCQdisc * qdisc; const char * qdisc_parent; gs_free char *qdisc_rest = NULL; gs_free char *qdisc_str = NULL; gs_free_error GError *err = NULL; if (!g_str_has_prefix(keys[i], "qdisc.")) continue; qdisc_parent = keys[i] + sizeof("qdisc.") - 1; qdisc_rest = nm_keyfile_plugin_kf_get_string(info->keyfile, setting_name, keys[i], NULL); qdisc_str = g_strdup_printf( "%s%s %s", _nm_utils_parse_tc_handle(qdisc_parent, NULL) != TC_H_UNSPEC ? "parent " : "", qdisc_parent, qdisc_rest); qdisc = nm_utils_tc_qdisc_from_str(qdisc_str, &err); if (!qdisc) { handle_warn(info, keys[i], key, NM_KEYFILE_WARN_SEVERITY_WARN, _("invalid qdisc: %s"), err->message); } else { g_ptr_array_add(qdiscs, qdisc); } } if (qdiscs->len >= 1) g_object_set(setting, key, qdiscs, NULL); } static void tfilter_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key) { const char * setting_name = nm_setting_get_name(setting); gs_unref_ptrarray GPtrArray *tfilters = NULL; gs_strfreev char ** keys = NULL; gsize n_keys = 0; int i; keys = nm_keyfile_plugin_kf_get_keys(info->keyfile, setting_name, &n_keys, NULL); if (n_keys == 0) return; tfilters = g_ptr_array_new_with_free_func((GDestroyNotify) nm_tc_tfilter_unref); for (i = 0; i < n_keys; i++) { NMTCTfilter * tfilter; const char * tfilter_parent; gs_free char *tfilter_rest = NULL; gs_free char *tfilter_str = NULL; gs_free_error GError *err = NULL; if (!g_str_has_prefix(keys[i], "tfilter.")) continue; tfilter_parent = keys[i] + sizeof("tfilter.") - 1; tfilter_rest = nm_keyfile_plugin_kf_get_string(info->keyfile, setting_name, keys[i], NULL); tfilter_str = g_strdup_printf( "%s%s %s", _nm_utils_parse_tc_handle(tfilter_parent, NULL) != TC_H_UNSPEC ? "parent " : "", tfilter_parent, tfilter_rest); tfilter = nm_utils_tc_tfilter_from_str(tfilter_str, &err); if (!tfilter) { handle_warn(info, keys[i], key, NM_KEYFILE_WARN_SEVERITY_WARN, _("invalid tfilter: %s"), err->message); } else { g_ptr_array_add(tfilters, tfilter); } } if (tfilters->len >= 1) g_object_set(setting, key, tfilters, NULL); } /*****************************************************************************/ /* 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 * encryption. 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 ?: str); } static void sriov_vfs_writer(KeyfileWriterInfo *info, NMSetting *setting, const char *key, const GValue *value) { GPtrArray *vfs; guint i; vfs = g_value_get_boxed(value); if (!vfs) return; for (i = 0; i < vfs->len; i++) { const NMSriovVF *vf = vfs->pdata[i]; gs_free char * kf_value = NULL; char kf_key[32]; kf_value = nm_utils_sriov_vf_to_str(vf, TRUE, NULL); if (!kf_value) continue; nm_sprintf_buf(kf_key, "vf.%u", nm_sriov_vf_get_index(vf)); nm_keyfile_plugin_kf_set_string(info->keyfile, nm_setting_get_name(setting), kf_key, kf_value); } } static void write_array_of_uint(GKeyFile *file, NMSetting *setting, const char *key, const GValue *value) { GArray *array; array = g_value_get_boxed(value); nm_assert(!array || g_array_get_element_size(array) == sizeof(guint)); if (!array || !array->len) return; nm_keyfile_plugin_kf_set_integer_list_uint(file, nm_setting_get_name(setting), key, (const guint *) array->data, array->len); } 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 ip6_addr_gen_mode_writer(KeyfileWriterInfo *info, NMSetting * setting, const char * key, const GValue * value) { NMSettingIP6ConfigAddrGenMode addr_gen_mode; gs_free char * str = NULL; addr_gen_mode = (NMSettingIP6ConfigAddrGenMode) g_value_get_int(value); str = nm_utils_enum_to_str(nm_setting_ip6_config_addr_gen_mode_get_type(), addr_gen_mode); nm_keyfile_plugin_kf_set_string(info->keyfile, nm_setting_get_name(setting), key, str); } static void write_ip_values(GKeyFile * file, const char *setting_name, GPtrArray * array, const char *gateway, gboolean is_route) { if (array->len > 0) { nm_auto_str_buf NMStrBuf output = NM_STR_BUF_INIT(2 * INET_ADDRSTRLEN + 10, FALSE); int addr_family; guint i; const char * addr; const char * gw; guint32 plen; char key_name[64]; char * key_name_idx; addr_family = nm_streq(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); for (i = 0; i < array->len; i++) { gint64 metric = -1; 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 = 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; } nm_str_buf_set_size(&output, 0, FALSE, FALSE); nm_str_buf_append_printf(&output, "%s/%u", addr, plen); if (metric != -1 || 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 (addr_family == AF_INET) gw = "0.0.0.0"; else gw = "::"; } nm_str_buf_append_c(&output, ','); nm_str_buf_append(&output, gw); if (is_route && metric != -1) nm_str_buf_append_printf(&output, ",%lu", (unsigned long) metric); } sprintf(key_name_idx, "%u", i + 1); nm_keyfile_plugin_kf_set_string(file, setting_name, key_name, nm_str_buf_get_str(&output)); if (is_route) { gs_free char *attributes = NULL; attributes = nm_utils_format_variant_attributes(_nm_ip_route_get_attributes(array->pdata[i]), ',', '='); if (attributes) { g_strlcat(key_name, "_options", sizeof(key_name)); nm_keyfile_plugin_kf_set_string(file, setting_name, key_name, attributes); } } } } } 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 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 bridge_vlan_writer(KeyfileWriterInfo *info, NMSetting * setting, const char * key, const GValue * value) { GPtrArray *vlans; vlans = g_value_get_boxed(value); if (vlans && vlans->len > 0) { const guint string_initial_size = vlans->len * 10u; nm_auto_str_buf NMStrBuf string = NM_STR_BUF_INIT(string_initial_size, FALSE); guint i; for (i = 0; i < vlans->len; i++) { gs_free char *vlan_str = NULL; vlan_str = nm_bridge_vlan_to_str(vlans->pdata[i], NULL); if (i > 0) nm_str_buf_append_c(&string, ','); nm_utils_escaped_tokens_escape_strbuf_assert(vlan_str, ",", &string); } nm_keyfile_plugin_kf_set_string(info->keyfile, nm_setting_get_name(setting), "vlans", nm_str_buf_get_str(&string)); } } static void wired_s390_options_parser_full(KeyfileReaderInfo * info, const NMMetaSettingInfo * setting_info, const NMSettInfoProperty *property_info, const ParseInfoProperty * pip, NMSetting * setting) { NMSettingWired * s_wired = NM_SETTING_WIRED(setting); gs_strfreev char **keys = NULL; gsize n_keys; gsize i; keys = nm_keyfile_plugin_kf_get_keys(info->keyfile, ETHERNET_S390_OPTIONS_GROUP_NAME, &n_keys, NULL); for (i = 0; i < n_keys; i++) { gs_free char *value = NULL; gs_free char *key_to_free = NULL; value = nm_keyfile_plugin_kf_get_string(info->keyfile, ETHERNET_S390_OPTIONS_GROUP_NAME, keys[i], NULL); if (!value) continue; /* Here we don't verify the key/value further. If the file contains invalid keys, * we will later reject the connection as invalid. */ nm_setting_wired_add_s390_option(s_wired, nm_keyfile_key_decode(keys[i], &key_to_free), value); } } static void wired_s390_options_writer_full(KeyfileWriterInfo * info, const NMMetaSettingInfo * setting_info, const NMSettInfoProperty *property_info, const ParseInfoProperty * pip, NMSetting * setting) { NMSettingWired *s_wired = NM_SETTING_WIRED(setting); guint i, n; n = nm_setting_wired_get_num_s390_options(s_wired); for (i = 0; i < n; i++) { gs_free char *key_to_free = NULL; const char * opt_key; const char * opt_val; nm_setting_wired_get_s390_option(s_wired, i, &opt_key, &opt_val); nm_keyfile_plugin_kf_set_string(info->keyfile, ETHERNET_S390_OPTIONS_GROUP_NAME, nm_keyfile_key_encode(opt_key, &key_to_free), opt_val); } } static void ip_routing_rule_writer_full(KeyfileWriterInfo * info, const NMMetaSettingInfo * setting_info, const NMSettInfoProperty *property_info, const ParseInfoProperty * pip, NMSetting * setting) { const char * setting_name = nm_setting_get_name(setting); NMSettingIPConfig *s_ip = NM_SETTING_IP_CONFIG(setting); guint i, j, n; char key_name_full[100] = "routing-rule"; char * key_name_num = &key_name_full[NM_STRLEN("routing-rule")]; n = nm_setting_ip_config_get_num_routing_rules(s_ip); j = 0; for (i = 0; i < n; i++) { NMIPRoutingRule *rule = nm_setting_ip_config_get_routing_rule(s_ip, i); gs_free char * str = NULL; str = nm_ip_routing_rule_to_string(rule, NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE, NULL, NULL); if (!str) continue; sprintf(key_name_num, "%u", ++j); nm_keyfile_plugin_kf_set_string(info->keyfile, setting_name, key_name_full, str); } } static void qdisc_writer(KeyfileWriterInfo *info, NMSetting *setting, const char *key, const GValue *value) { nm_auto_free_gstring GString *key_name = NULL; nm_auto_free_gstring GString *value_str = NULL; GPtrArray * array; guint i; array = g_value_get_boxed(value); if (!array || !array->len) return; for (i = 0; i < array->len; i++) { NMTCQdisc *qdisc = array->pdata[i]; nm_gstring_prepare(&key_name); nm_gstring_prepare(&value_str); g_string_append(key_name, "qdisc."); _nm_utils_string_append_tc_parent(key_name, NULL, nm_tc_qdisc_get_parent(qdisc)); _nm_utils_string_append_tc_qdisc_rest(value_str, qdisc); nm_keyfile_plugin_kf_set_string(info->keyfile, NM_SETTING_TC_CONFIG_SETTING_NAME, key_name->str, value_str->str); } } static void tfilter_writer(KeyfileWriterInfo *info, NMSetting *setting, const char *key, const GValue *value) { nm_auto_free_gstring GString *key_name = NULL; nm_auto_free_gstring GString *value_str = NULL; GPtrArray * array; guint i; array = g_value_get_boxed(value); if (!array || !array->len) return; for (i = 0; i < array->len; i++) { NMTCTfilter *tfilter = array->pdata[i]; nm_gstring_prepare(&key_name); nm_gstring_prepare(&value_str); g_string_append(key_name, "tfilter."); _nm_utils_string_append_tc_parent(key_name, NULL, nm_tc_tfilter_get_parent(tfilter)); _nm_utils_string_append_tc_tfilter_rest(value_str, tfilter, NULL); nm_keyfile_plugin_kf_set_string(info->keyfile, NM_SETTING_TC_CONFIG_SETTING_NAME, key_name->str, value_str->str); } } static void _writer_full_ovs_external_ids_data(KeyfileWriterInfo * info, const NMMetaSettingInfo * setting_info, const NMSettInfoProperty *property_info, const ParseInfoProperty * pip, NMSetting * setting) { GHashTable * hash; NMUtilsNamedValue data_static[300u / sizeof(NMUtilsNamedValue)]; gs_free NMUtilsNamedValue *data_free = NULL; const NMUtilsNamedValue * data; guint data_len; char full_key_static[NM_STRLEN(OVS_EXTERNAL_IDS_DATA_PREFIX) + 300u]; guint i; nm_assert(NM_IS_SETTING_OVS_EXTERNAL_IDS(setting)); nm_assert(nm_streq(property_info->name, NM_SETTING_OVS_EXTERNAL_IDS_DATA)); hash = _nm_setting_ovs_external_ids_get_data(NM_SETTING_OVS_EXTERNAL_IDS(setting)); if (!hash) return; data = nm_utils_named_values_from_strdict(hash, &data_len, data_static, &data_free); if (data_len == 0) return; memcpy(full_key_static, OVS_EXTERNAL_IDS_DATA_PREFIX, NM_STRLEN(OVS_EXTERNAL_IDS_DATA_PREFIX)); for (i = 0; i < data_len; i++) { const char * key = data[i].name; const char * val = data[i].value_str; gs_free char *escaped_key_to_free = NULL; const char * escaped_key; gsize len; gs_free char *full_key_free = NULL; char * full_key = full_key_static; escaped_key = nm_keyfile_key_encode(key, &escaped_key_to_free); len = strlen(escaped_key) + 1u; if (len >= G_N_ELEMENTS(full_key_static) - NM_STRLEN(OVS_EXTERNAL_IDS_DATA_PREFIX)) { full_key_free = g_new(char, NM_STRLEN(OVS_EXTERNAL_IDS_DATA_PREFIX) + len); full_key = full_key_free; memcpy(full_key, OVS_EXTERNAL_IDS_DATA_PREFIX, NM_STRLEN(OVS_EXTERNAL_IDS_DATA_PREFIX)); } memcpy(&full_key[NM_STRLEN(OVS_EXTERNAL_IDS_DATA_PREFIX)], escaped_key, len); nm_keyfile_plugin_kf_set_string(info->keyfile, NM_SETTING_OVS_EXTERNAL_IDS_SETTING_NAME, full_key, val); } } static void write_hash_of_string(GKeyFile *file, NMSetting *setting, const char *key, const GValue *value) { GHashTable * hash; const char * group_name = nm_setting_get_name(setting); gboolean vpn_secrets = FALSE; gs_free const char **keys = NULL; guint i, l; nm_assert((NM_IS_SETTING_VPN(setting) && nm_streq(key, NM_SETTING_VPN_DATA)) || (NM_IS_SETTING_VPN(setting) && nm_streq(key, NM_SETTING_VPN_SECRETS)) || (NM_IS_SETTING_BOND(setting) && nm_streq(key, NM_SETTING_BOND_OPTIONS)) || (NM_IS_SETTING_USER(setting) && nm_streq(key, NM_SETTING_USER_DATA))); /* Write VPN secrets out to a different group to keep them separate */ if (NM_IS_SETTING_VPN(setting) && nm_streq(key, NM_SETTING_VPN_SECRETS)) { group_name = NM_KEYFILE_GROUP_VPN_SECRETS; vpn_secrets = TRUE; } hash = g_value_get_boxed(value); keys = nm_strdict_get_keys(hash, TRUE, &l); for (i = 0; i < l; i++) { gs_free char *to_free = NULL; const char * property, *data; property = keys[i]; /* 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; if (!nm_setting_get_secret_flags(setting, property, &secret_flags, NULL)) nm_assert_not_reached(); if (!_secret_flags_persist_secret(secret_flags)) continue; } data = g_hash_table_lookup(hash, property); nm_keyfile_plugin_kf_set_string(file, group_name, nm_keyfile_key_encode(property, &to_free), 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; gsize semicolons = 0; gsize i; 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_data || !ssid_len) { nm_keyfile_plugin_kf_set_string(info->keyfile, setting_name, key, ""); 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++) { const char c = ssid_data[i]; if (!g_ascii_isprint(c)) { new_format = FALSE; break; } if (c == ';') semicolons++; } if (new_format) { gs_free char *ssid = NULL; if (semicolons == 0) ssid = g_strndup((char *) ssid_data, ssid_len); else { /* Escape semicolons with backslashes to make strings * containing ';', such as '16;17;' unambiguous */ gsize j = 0; ssid = g_malloc(ssid_len + semicolons + 1); for (i = 0; i < ssid_len; i++) { if (ssid_data[i] == ';') ssid[j++] = '\\'; ssid[j++] = ssid_data[i]; } ssid[j] = '\0'; } nm_keyfile_plugin_kf_set_string(info->keyfile, setting_name, key, ssid); } else nm_keyfile_plugin_kf_set_integer_list_uint8(info->keyfile, setting_name, key, ssid_data, ssid_len); } 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; gsize len; const guint8 *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 = 0; nm_keyfile_plugin_kf_set_integer_list_uint8(info->keyfile, setting_name, key, data, len); } /*****************************************************************************/ static void cert_writer_default(NMConnection * connection, GKeyFile * file, NMSetting8021x * setting, const char * setting_name, const NMSetting8021xSchemeVtable *vtable) { NMSetting8021xCKScheme scheme; scheme = vtable->scheme_func(setting); if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) { gs_free char *path_free = NULL; gs_free char *base_dir = NULL; gs_free char *tmp = NULL; const char * path; path = vtable->path_func(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_free = g_strconcat(base_dir, "/", path, NULL); path = path_free; } 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) nm_clear_g_free(&tmp); else { tmp = g_strconcat(NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, path, NULL); path = tmp; } /* 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, vtable->setting_key, path); } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) { GBytes * blob; const guint8 *blob_data; gsize blob_len; gs_free char *blob_base64 = NULL; gs_free char *val = NULL; blob = vtable->blob_func(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, vtable->setting_key, val); } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11) { nm_keyfile_plugin_kf_set_string(file, setting_name, vtable->setting_key, vtable->uri_func(setting)); } 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 NMSetting8021xSchemeVtable *vtable = NULL; const char * setting_name; guint i; for (i = 0; nm_setting_8021x_scheme_vtable[i].setting_key; i++) { if (nm_streq0(nm_setting_8021x_scheme_vtable[i].setting_key, key)) { vtable = &nm_setting_8021x_scheme_vtable[i]; break; } } if (!vtable) g_return_if_reached(); setting_name = nm_setting_get_name(NM_SETTING(setting)); if (info->write_handler) { NMKeyfileHandlerData handler_data; _key_file_handler_data_init_write(&handler_data, NM_KEYFILE_HANDLER_TYPE_WRITE_CERT, info, setting_name, vtable->setting_key, setting, key); handler_data.write_cert = (NMKeyfileHandlerDataWriteCert){ .vtable = vtable, }; if (info->write_handler(info->connection, info->keyfile, NM_KEYFILE_HANDLER_TYPE_WRITE_CERT, &handler_data, info->user_data)) return; if (info->error) return; } cert_writer_default(info->connection, info->keyfile, NM_SETTING_802_1X(setting), setting_name, vtable); } /*****************************************************************************/ struct _ParseInfoProperty { const char *property_name; union { void (*parser)(KeyfileReaderInfo *info, NMSetting *setting, const char *key); void (*parser_full)(KeyfileReaderInfo * info, const NMMetaSettingInfo * setting_info, const NMSettInfoProperty *property_info, const ParseInfoProperty * pip, NMSetting * setting); }; union { void (*writer)(KeyfileWriterInfo *info, NMSetting * setting, const char * key, const GValue * value); void (*writer_full)(KeyfileWriterInfo * info, const NMMetaSettingInfo * setting_info, const NMSettInfoProperty *property_info, const ParseInfoProperty * pip, NMSetting * setting); }; bool parser_skip; bool parser_no_check_key : 1; bool writer_skip : 1; bool has_writer_full : 1; bool has_parser_full : 1; /* usually, we skip to write values that have their * default value. By setting this flag to TRUE, also * default values are written. */ bool writer_persist_default : 1; }; #define PARSE_INFO_PROPERTY(_property_name, ...) \ (&((const ParseInfoProperty){.property_name = _property_name, __VA_ARGS__})) #define PARSE_INFO_PROPERTIES(...) \ .properties = ((const ParseInfoProperty *const[]){ \ __VA_ARGS__ NULL, \ }) typedef struct { const ParseInfoProperty *const *properties; } ParseInfoSetting; #define PARSE_INFO_SETTING(setting_type, ...) \ [setting_type] = (&((const ParseInfoSetting){__VA_ARGS__})) static const ParseInfoSetting *const parse_infos[_NM_META_SETTING_TYPE_NUM] = { PARSE_INFO_SETTING( NM_META_SETTING_TYPE_WIRELESS, PARSE_INFO_PROPERTIES( PARSE_INFO_PROPERTY(NM_SETTING_WIRELESS_BSSID, .parser = mac_address_parser_ETHER, ), PARSE_INFO_PROPERTY(NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, .parser = mac_address_parser_ETHER_cloned, ), PARSE_INFO_PROPERTY(NM_SETTING_WIRELESS_MAC_ADDRESS, .parser = mac_address_parser_ETHER, ), PARSE_INFO_PROPERTY(NM_SETTING_WIRELESS_SEEN_BSSIDS, .parser_skip = TRUE, .writer_skip = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_WIRELESS_SSID, .parser = ssid_parser, .writer = ssid_writer, ), ), ), PARSE_INFO_SETTING( NM_META_SETTING_TYPE_802_1X, PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_802_1X_CA_CERT, .parser = cert_parser, .writer = cert_writer, ), PARSE_INFO_PROPERTY(NM_SETTING_802_1X_CLIENT_CERT, .parser = cert_parser, .writer = cert_writer, ), PARSE_INFO_PROPERTY(NM_SETTING_802_1X_PASSWORD_RAW, .parser = password_raw_parser, .writer = password_raw_writer, ), PARSE_INFO_PROPERTY(NM_SETTING_802_1X_PHASE2_CA_CERT, .parser = cert_parser, .writer = cert_writer, ), PARSE_INFO_PROPERTY(NM_SETTING_802_1X_PHASE2_CLIENT_CERT, .parser = cert_parser, .writer = cert_writer, ), PARSE_INFO_PROPERTY(NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, .parser = cert_parser, .writer = cert_writer, ), PARSE_INFO_PROPERTY(NM_SETTING_802_1X_PRIVATE_KEY, .parser = cert_parser, .writer = cert_writer, ), ), ), PARSE_INFO_SETTING( NM_META_SETTING_TYPE_WIRED, PARSE_INFO_PROPERTIES( PARSE_INFO_PROPERTY(NM_SETTING_WIRED_CLONED_MAC_ADDRESS, .parser = mac_address_parser_ETHER_cloned, ), PARSE_INFO_PROPERTY(NM_SETTING_WIRED_MAC_ADDRESS, .parser = mac_address_parser_ETHER, ), PARSE_INFO_PROPERTY(NM_SETTING_WIRED_S390_OPTIONS, .parser_no_check_key = TRUE, .parser_full = wired_s390_options_parser_full, .writer_full = wired_s390_options_writer_full, .has_parser_full = TRUE, .has_writer_full = TRUE, ), ), ), PARSE_INFO_SETTING( NM_META_SETTING_TYPE_BLUETOOTH, PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_BLUETOOTH_BDADDR, .parser = mac_address_parser_ETHER, ), ), ), PARSE_INFO_SETTING( NM_META_SETTING_TYPE_BOND, PARSE_INFO_PROPERTIES( PARSE_INFO_PROPERTY(NM_SETTING_BOND_OPTIONS, .parser_no_check_key = TRUE, ), ), ), PARSE_INFO_SETTING( NM_META_SETTING_TYPE_BRIDGE, PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_BRIDGE_MAC_ADDRESS, .parser = mac_address_parser_ETHER, ), PARSE_INFO_PROPERTY(NM_SETTING_BRIDGE_VLANS, .parser_no_check_key = TRUE, .parser = bridge_vlan_parser, .writer = bridge_vlan_writer, ), ), ), PARSE_INFO_SETTING( NM_META_SETTING_TYPE_BRIDGE_PORT, PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_BRIDGE_PORT_VLANS, .parser_no_check_key = TRUE, .parser = bridge_vlan_parser, .writer = bridge_vlan_writer, ), ), ), PARSE_INFO_SETTING( NM_META_SETTING_TYPE_CONNECTION, PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_CONNECTION_READ_ONLY, .parser_skip = TRUE, .writer_skip = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_CONNECTION_TYPE, .parser = setting_alias_parser, .writer = setting_alias_writer, ), ), ), PARSE_INFO_SETTING( NM_META_SETTING_TYPE_INFINIBAND, PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_INFINIBAND_MAC_ADDRESS, .parser = mac_address_parser_INFINIBAND, ), ), ), PARSE_INFO_SETTING(NM_META_SETTING_TYPE_IP4_CONFIG, PARSE_INFO_PROPERTIES( PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_ADDRESSES, .parser_no_check_key = TRUE, .parser = ip_address_or_route_parser, .writer = addr_writer, ), PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_DNS, .parser_no_check_key = TRUE, .parser = ip_dns_parser, .writer = dns_writer, ), PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_GATEWAY, .writer_skip = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_ROUTES, .parser_no_check_key = TRUE, .parser = ip_address_or_route_parser, .writer = route_writer, ), PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_ROUTING_RULES, .parser_no_check_key = TRUE, .parser_full = ip_routing_rule_parser_full, .writer_full = ip_routing_rule_writer_full, .has_parser_full = TRUE, .has_writer_full = TRUE, ), ), ), PARSE_INFO_SETTING(NM_META_SETTING_TYPE_IP6_CONFIG, PARSE_INFO_PROPERTIES( PARSE_INFO_PROPERTY(NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE, .parser_no_check_key = TRUE, .parser = ip6_addr_gen_mode_parser, .writer = ip6_addr_gen_mode_writer, .writer_persist_default = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_ADDRESSES, .parser_no_check_key = TRUE, .parser = ip_address_or_route_parser, .writer = addr_writer, ), PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_DNS, .parser_no_check_key = TRUE, .parser = ip_dns_parser, .writer = dns_writer, ), PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_GATEWAY, .writer_skip = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_ROUTES, .parser_no_check_key = TRUE, .parser = ip_address_or_route_parser, .writer = route_writer, ), PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_ROUTING_RULES, .parser_no_check_key = TRUE, .parser_full = ip_routing_rule_parser_full, .writer_full = ip_routing_rule_writer_full, .has_parser_full = TRUE, .has_writer_full = TRUE, ), ), ), PARSE_INFO_SETTING( NM_META_SETTING_TYPE_OVS_EXTERNAL_IDS, PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_OVS_EXTERNAL_IDS_DATA, .parser_no_check_key = TRUE, .parser_full = _parser_full_ovs_external_ids_data, .writer_full = _writer_full_ovs_external_ids_data, .has_parser_full = TRUE, .has_writer_full = TRUE, ), ), ), PARSE_INFO_SETTING(NM_META_SETTING_TYPE_SERIAL, PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_SERIAL_PARITY, .parser = parity_parser, ), ), ), PARSE_INFO_SETTING( NM_META_SETTING_TYPE_SRIOV, PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_SRIOV_VFS, .parser_no_check_key = TRUE, .parser = sriov_vfs_parser, .writer = sriov_vfs_writer, ), ), ), PARSE_INFO_SETTING(NM_META_SETTING_TYPE_TC_CONFIG, PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_TC_CONFIG_QDISCS, .parser_no_check_key = TRUE, .parser = qdisc_parser, .writer = qdisc_writer, ), PARSE_INFO_PROPERTY(NM_SETTING_TC_CONFIG_TFILTERS, .parser_no_check_key = TRUE, .parser = tfilter_parser, .writer = tfilter_writer, ), ), ), PARSE_INFO_SETTING( NM_META_SETTING_TYPE_TEAM, PARSE_INFO_PROPERTIES( PARSE_INFO_PROPERTY(NM_SETTING_TEAM_CONFIG, .parser = team_config_parser, ), PARSE_INFO_PROPERTY(NM_SETTING_TEAM_LINK_WATCHERS, .parser_skip = TRUE, .writer_skip = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_TEAM_MCAST_REJOIN_COUNT, .parser_skip = TRUE, .writer_skip = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_TEAM_MCAST_REJOIN_INTERVAL, .parser_skip = TRUE, .writer_skip = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_TEAM_NOTIFY_PEERS_COUNT, .parser_skip = TRUE, .writer_skip = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_TEAM_NOTIFY_PEERS_INTERVAL, .parser_skip = TRUE, .writer_skip = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_TEAM_RUNNER, .parser_skip = TRUE, .writer_skip = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_TEAM_RUNNER_ACTIVE, .parser_skip = TRUE, .writer_skip = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY, .parser_skip = TRUE, .writer_skip = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_TEAM_RUNNER_FAST_RATE, .parser_skip = TRUE, .writer_skip = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_TEAM_RUNNER_HWADDR_POLICY, .parser_skip = TRUE, .writer_skip = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_TEAM_RUNNER_MIN_PORTS, .parser_skip = TRUE, .writer_skip = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_TEAM_RUNNER_SYS_PRIO, .parser_skip = TRUE, .writer_skip = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_TEAM_RUNNER_TX_BALANCER, .parser_skip = TRUE, .writer_skip = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL, .parser_skip = TRUE, .writer_skip = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_TEAM_RUNNER_TX_HASH, .parser_skip = TRUE, .writer_skip = TRUE, ), ), ), PARSE_INFO_SETTING(NM_META_SETTING_TYPE_TEAM_PORT, PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_TEAM_CONFIG, .parser = team_config_parser, ), PARSE_INFO_PROPERTY(NM_SETTING_TEAM_PORT_LACP_KEY, .parser_skip = TRUE, .writer_skip = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_TEAM_PORT_LACP_PRIO, .parser_skip = TRUE, .writer_skip = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_TEAM_PORT_LINK_WATCHERS, .parser_skip = TRUE, .writer_skip = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_TEAM_PORT_PRIO, .parser_skip = TRUE, .writer_skip = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_TEAM_PORT_QUEUE_ID, .parser_skip = TRUE, .writer_skip = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_TEAM_PORT_STICKY, .parser_skip = TRUE, .writer_skip = TRUE, ), ), ), PARSE_INFO_SETTING( NM_META_SETTING_TYPE_USER, PARSE_INFO_PROPERTIES( PARSE_INFO_PROPERTY(NM_SETTING_USER_DATA, .parser_no_check_key = TRUE, ), ), ), PARSE_INFO_SETTING( NM_META_SETTING_TYPE_VLAN, PARSE_INFO_PROPERTIES( PARSE_INFO_PROPERTY(NM_SETTING_VLAN_FLAGS, .writer_persist_default = TRUE, ), ), ), PARSE_INFO_SETTING( NM_META_SETTING_TYPE_VPN, PARSE_INFO_PROPERTIES( PARSE_INFO_PROPERTY(NM_SETTING_VPN_DATA, .parser_no_check_key = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_VPN_PERSISTENT, .parser_no_check_key = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_VPN_SECRETS, .parser_no_check_key = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_VPN_SERVICE_TYPE, .parser_no_check_key = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_VPN_TIMEOUT, .parser_no_check_key = TRUE, ), PARSE_INFO_PROPERTY(NM_SETTING_VPN_USER_NAME, .parser_no_check_key = TRUE, ), ), ), PARSE_INFO_SETTING( NM_META_SETTING_TYPE_WIMAX, PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_WIMAX_MAC_ADDRESS, .parser = mac_address_parser_ETHER, ), ), ), }; static void _parse_info_find(NMSetting * setting, const char * property_name, const NMMetaSettingInfo **out_setting_info, const ParseInfoSetting ** out_parse_info_setting, const ParseInfoProperty **out_parse_info_property) { const NMMetaSettingInfo *setting_info; const ParseInfoSetting * pis; const ParseInfoProperty *pip; #if NM_MORE_ASSERTS > 10 { guint i, j; static int asserted = FALSE; if (!asserted) { for (i = 0; i < G_N_ELEMENTS(parse_infos); i++) { pis = parse_infos[i]; if (!pis) continue; if (!pis->properties) continue; g_assert(pis->properties[0]); for (j = 0; pis->properties[j]; j++) { const ParseInfoProperty *pip0; const ParseInfoProperty *pipj = pis->properties[j]; g_assert(pipj->property_name); if (j > 0 && (pip0 = pis->properties[j - 1]) && strcmp(pip0->property_name, pipj->property_name) >= 0) { g_error("Wrong order at index #%d.%d: \"%s.%s\" before \"%s.%s\"", i, j - 1, nm_meta_setting_infos[i].setting_name, pip0->property_name, nm_meta_setting_infos[i].setting_name, pipj->property_name); } } } asserted = TRUE; } } #endif if (!NM_IS_SETTING(setting) || !(setting_info = NM_SETTING_GET_CLASS(setting)->setting_info)) { /* handle invalid setting objects gracefully. */ NM_SET_OUT(out_setting_info, NULL); NM_SET_OUT(out_parse_info_setting, NULL); NM_SET_OUT(out_parse_info_property, NULL); return; } nm_assert(setting_info->setting_name); nm_assert(_NM_INT_NOT_NEGATIVE(setting_info->meta_type)); nm_assert(setting_info->meta_type < G_N_ELEMENTS(parse_infos)); pis = parse_infos[setting_info->meta_type]; pip = NULL; if (pis && property_name) { gssize idx; G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(ParseInfoProperty, property_name) == 0); idx = nm_utils_ptrarray_find_binary_search((gconstpointer *) pis->properties, NM_PTRARRAY_LEN(pis->properties), &property_name, nm_strcmp_p_with_data, NULL); if (idx >= 0) pip = pis->properties[idx]; } NM_SET_OUT(out_setting_info, setting_info); NM_SET_OUT(out_parse_info_setting, pis); NM_SET_OUT(out_parse_info_property, pip); } /*****************************************************************************/ static void read_one_setting_value(KeyfileReaderInfo * info, NMSetting * setting, const NMSettInfoProperty *property_info) { GKeyFile * keyfile = info->keyfile; gs_free_error GError * err = NULL; const NMMetaSettingInfo *setting_info; const ParseInfoProperty *pip; gs_free char * tmp_str = NULL; const char * key; GType type; guint64 u64; gint64 i64; nm_assert(!info->error); nm_assert(!property_info->param_spec || nm_streq(property_info->param_spec->name, property_info->name)); key = property_info->name; _parse_info_find(setting, key, &setting_info, NULL, &pip); nm_assert(setting_info); if (!pip) { if (nm_streq(key, NM_SETTING_NAME)) return; if (!property_info->param_spec) return; if ((property_info->param_spec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) != G_PARAM_WRITABLE) return; } else { if (pip->parser_skip) return; if (pip->has_parser_full) { pip->parser_full(info, setting_info, property_info, pip, setting); return; } } nm_assert(property_info->param_spec); nm_assert((property_info->param_spec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) == G_PARAM_WRITABLE); /* 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 ((!pip || !pip->parser_no_check_key) && !nm_keyfile_plugin_kf_has_key(keyfile, setting_info->setting_name, key, &err)) { /* Key doesn't exist or an error occurred, thus nothing to do. */ if (err) { if (!handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("error loading setting value: %s"), err->message)) return; } return; } if (pip && pip->parser) { pip->parser(info, setting, key); return; } type = G_PARAM_SPEC_VALUE_TYPE(property_info->param_spec); if (type == G_TYPE_STRING) { gs_free char *str_val = NULL; str_val = nm_keyfile_plugin_kf_get_string(keyfile, setting_info->setting_name, key, &err); if (!err) nm_g_object_set_property_string_take(G_OBJECT(setting), key, g_steal_pointer(&str_val), &err); } else if (type == G_TYPE_UINT) { tmp_str = nm_keyfile_plugin_kf_get_value(keyfile, setting_info->setting_name, key, &err); if (!err) { u64 = _nm_utils_ascii_str_to_uint64(tmp_str, 0, 0, G_MAXUINT, G_MAXUINT64); if (u64 == G_MAXUINT64 && errno != 0) { g_set_error_literal(&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, _("value cannot be interpreted as integer")); } else nm_g_object_set_property_uint(G_OBJECT(setting), key, u64, &err); } } else if (type == G_TYPE_INT) { tmp_str = nm_keyfile_plugin_kf_get_value(keyfile, setting_info->setting_name, key, &err); if (!err) { i64 = _nm_utils_ascii_str_to_int64(tmp_str, 0, G_MININT, G_MAXINT, G_MININT64); if (i64 == G_MININT64 && errno != 0) { g_set_error_literal(&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, _("value cannot be interpreted as integer")); } else nm_g_object_set_property_int(G_OBJECT(setting), key, i64, &err); } } else if (type == G_TYPE_BOOLEAN) { gboolean bool_val; bool_val = nm_keyfile_plugin_kf_get_boolean(keyfile, setting_info->setting_name, key, &err); if (!err) nm_g_object_set_property_boolean(G_OBJECT(setting), key, bool_val, &err); } else if (type == G_TYPE_CHAR) { tmp_str = nm_keyfile_plugin_kf_get_value(keyfile, setting_info->setting_name, key, &err); if (!err) { /* As documented by glib, G_TYPE_CHAR is really a (signed!) gint8. */ i64 = _nm_utils_ascii_str_to_int64(tmp_str, 0, G_MININT8, G_MAXINT8, G_MININT64); if (i64 == G_MININT64 && errno != 0) { g_set_error_literal(&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, _("value cannot be interpreted as integer")); } else nm_g_object_set_property_char(G_OBJECT(setting), key, i64, &err); } } else if (type == G_TYPE_UINT64) { tmp_str = nm_keyfile_plugin_kf_get_value(keyfile, setting_info->setting_name, key, &err); if (!err) { u64 = _nm_utils_ascii_str_to_uint64(tmp_str, 0, 0, G_MAXUINT64, G_MAXUINT64); if (u64 == G_MAXUINT64 && errno != 0) { g_set_error_literal(&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, _("value cannot be interpreted as integer")); } else nm_g_object_set_property_uint64(G_OBJECT(setting), key, u64, &err); } } else if (type == G_TYPE_INT64) { tmp_str = nm_keyfile_plugin_kf_get_value(keyfile, setting_info->setting_name, key, &err); if (!err) { i64 = _nm_utils_ascii_str_to_int64(tmp_str, 0, G_MININT64, G_MAXINT64, G_MAXINT64); if (i64 == G_MAXINT64 && errno != 0) { g_set_error_literal(&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, _("value cannot be interpreted as integer")); } else nm_g_object_set_property_int64(G_OBJECT(setting), key, i64, &err); } } else if (type == G_TYPE_BYTES) { nm_auto_unref_bytearray GByteArray *array = NULL; gs_unref_bytes GBytes *bytes = NULL; gs_free guint *tmp = NULL; gsize length; int i; gboolean already_warned = FALSE; tmp = nm_keyfile_plugin_kf_get_integer_list_uint(keyfile, setting_info->setting_name, key, &length, NULL); array = g_byte_array_sized_new(length); for (i = 0; i < length; i++) { const guint val = tmp[i]; unsigned char v = (unsigned char) (val & 0xFF); if (val > 255u) { if (!already_warned && !handle_warn( info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("ignoring invalid byte element '%u' (not between 0 and 255 inclusive)"), val)) return; already_warned = TRUE; } else g_byte_array_append(array, (const unsigned char *) &v, sizeof(v)); } bytes = g_byte_array_free_to_bytes(g_steal_pointer(&array)); g_object_set(setting, key, bytes, NULL); } else if (type == G_TYPE_STRV) { gs_strfreev char **sa = NULL; gsize length; sa = nm_keyfile_plugin_kf_get_string_list(keyfile, setting_info->setting_name, key, &length, NULL); g_object_set(setting, key, sa, NULL); } else if (type == G_TYPE_HASH_TABLE) { read_hash_of_string(info, keyfile, setting, key); } else if (type == G_TYPE_ARRAY) { read_array_of_uint(keyfile, setting, key); } else if (G_TYPE_IS_FLAGS(type)) { tmp_str = nm_keyfile_plugin_kf_get_value(keyfile, setting_info->setting_name, key, &err); if (!err) { u64 = _nm_utils_ascii_str_to_uint64(tmp_str, 0, 0, G_MAXUINT, G_MAXUINT64); if (u64 == G_MAXUINT64 && errno != 0) { g_set_error_literal(&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, _("value cannot be interpreted as integer")); } else nm_g_object_set_property_flags(G_OBJECT(setting), key, type, u64, &err); } } else if (G_TYPE_IS_ENUM(type)) { tmp_str = nm_keyfile_plugin_kf_get_value(keyfile, setting_info->setting_name, key, &err); if (!err) { i64 = _nm_utils_ascii_str_to_int64(tmp_str, 0, G_MININT, G_MAXINT, G_MAXINT64); if (i64 == G_MAXINT64 && errno != 0) { g_set_error_literal(&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, _("value cannot be interpreted as integer")); } else nm_g_object_set_property_enum(G_OBJECT(setting), key, type, i64, &err); } } else g_return_if_reached(); if (err) { if (nm_keyfile_error_is_not_found(err)) { /* ignore such errors. The key is not present. */ } else { handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("invalid setting: %s"), err->message); } } } static void _read_setting(KeyfileReaderInfo *info) { const NMSettInfoSetting *sett_info; gs_unref_object NMSetting *setting = NULL; const char * alias; GType type; guint16 i; alias = nm_keyfile_plugin_get_setting_name_for_alias(info->group); if (!alias) alias = info->group; type = nm_setting_lookup_type(alias); if (!type) { handle_warn(info, NULL, NULL, NM_KEYFILE_WARN_SEVERITY_WARN, _("invalid setting name '%s'"), info->group); return; } setting = g_object_new(type, NULL); info->setting = setting; sett_info = _nm_setting_class_get_sett_info(NM_SETTING_GET_CLASS(setting)); if (sett_info->detail.gendata_info) { gs_free char **keys = NULL; gsize k, n_keys; keys = g_key_file_get_keys(info->keyfile, info->group, &n_keys, NULL); if (!keys) n_keys = 0; if (n_keys > 0) { GHashTable *h = _nm_setting_option_hash(setting, TRUE); nm_strv_sort(keys, n_keys); for (k = 0; k < n_keys; k++) { gs_free char *key = keys[k]; gs_free_error GError *local = NULL; const GVariantType * variant_type; GVariant * variant; /* a GKeyFile can return duplicate keys, there is just no API to make sense * of them. Skip them. */ if (k + 1 < n_keys && nm_streq(key, keys[k + 1])) continue; /* currently, the API is very simple. The setting class just returns * the desired variant type, and keyfile reader will try to parse * it accordingly. Note, that this does currently not allow, that * a particular key can contain different variant types, nor is it * very flexible in general. * * We add flexibility when we need it. Keep it simple for now. */ variant_type = sett_info->detail.gendata_info->get_variant_type(sett_info, key, &local); if (!variant_type) { if (!handle_warn(info, key, NULL, NM_KEYFILE_WARN_SEVERITY_WARN, _("invalid key '%s.%s'"), info->group, key)) break; continue; } if (g_variant_type_equal(variant_type, G_VARIANT_TYPE_BOOLEAN)) { gboolean v; v = g_key_file_get_boolean(info->keyfile, info->group, key, &local); if (local) { if (!handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("key '%s.%s' is not boolean"), info->group, key)) break; continue; } variant = g_variant_new_boolean(v); } else if (g_variant_type_equal(variant_type, G_VARIANT_TYPE_UINT32)) { guint64 v; v = g_key_file_get_uint64(info->keyfile, info->group, key, &local); if (local) { if (!handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("key '%s.%s' is not a uint32"), info->group, key)) break; continue; } variant = g_variant_new_uint32((guint32) v); } else { nm_assert_not_reached(); continue; } g_hash_table_insert(h, g_steal_pointer(&key), g_variant_take_ref(variant)); } for (; k < n_keys; k++) g_free(keys[k]); } } for (i = 0; i < sett_info->property_infos_len; i++) { read_one_setting_value(info, setting, &sett_info->property_infos[i]); if (info->error) goto out; } out: info->setting = NULL; if (!info->error) nm_connection_add_setting(info->connection, g_steal_pointer(&setting)); } static void _read_setting_wireguard_peer(KeyfileReaderInfo *info) { gs_unref_object NMSettingWireGuard *s_wg_new = NULL; nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL; gs_free_error GError *error = NULL; NMSettingWireGuard * s_wg; gs_free char * str = NULL; const char * cstr = NULL; const char * key; gint64 i64; gs_strfreev char ** sa = NULL; gsize n_sa; peer = nm_wireguard_peer_new(); nm_assert(g_str_has_prefix(info->group, NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER)); cstr = &info->group[NM_STRLEN(NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER)]; if (!nm_utils_base64secret_normalize(cstr, NM_WIREGUARD_PUBLIC_KEY_LEN, &str) || !nm_streq0(str, cstr)) { /* the group name must be identical to the normalized(!) key, so that it * is uniquely identified. */ handle_warn(info, NULL, NM_SETTING_WIREGUARD_PEERS, NM_KEYFILE_WARN_SEVERITY_WARN, _("invalid peer public key in section '%s'"), info->group); return; } nm_wireguard_peer_set_public_key(peer, cstr, TRUE); nm_clear_g_free(&str); key = NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY; str = nm_keyfile_plugin_kf_get_string(info->keyfile, info->group, key, NULL); if (str) { if (!nm_wireguard_peer_set_preshared_key(peer, str, FALSE)) { if (!handle_warn(info, key, NM_SETTING_WIREGUARD_PEERS, NM_KEYFILE_WARN_SEVERITY_WARN, _("key '%s.%s' is not a valid 256 bit key in base64 encoding"), info->group, key)) return; } nm_clear_g_free(&str); } key = NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS; i64 = nm_keyfile_plugin_kf_get_int64(info->keyfile, info->group, key, 0, 0, NM_SETTING_SECRET_FLAG_ALL, -1, NULL); if (errno != ENODATA) { if (i64 == -1 || !_nm_setting_secret_flags_valid(i64)) { if (!handle_warn(info, key, NM_SETTING_WIREGUARD_PEERS, NM_KEYFILE_WARN_SEVERITY_WARN, _("key '%s.%s' is not a valid secret flag"), info->group, key)) return; } else nm_wireguard_peer_set_preshared_key_flags(peer, i64); } key = NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE; i64 = nm_keyfile_plugin_kf_get_int64(info->keyfile, info->group, key, 0, 0, G_MAXUINT32, -1, NULL); if (errno != ENODATA) { if (i64 == -1) { if (!handle_warn(info, key, NM_SETTING_WIREGUARD_PEERS, NM_KEYFILE_WARN_SEVERITY_WARN, _("key '%s.%s' is not a integer in range 0 to 2^32"), info->group, key)) return; } else nm_wireguard_peer_set_persistent_keepalive(peer, i64); } key = NM_WIREGUARD_PEER_ATTR_ENDPOINT; str = nm_keyfile_plugin_kf_get_string(info->keyfile, info->group, key, NULL); if (str && str[0]) { if (!nm_wireguard_peer_set_endpoint(peer, str, FALSE)) { if (!handle_warn(info, key, NM_SETTING_WIREGUARD_PEERS, NM_KEYFILE_WARN_SEVERITY_WARN, _("key '%s.%s' is not a valid endpoint"), info->group, key)) return; } } nm_clear_g_free(&str); key = NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS; sa = nm_keyfile_plugin_kf_get_string_list(info->keyfile, info->group, key, &n_sa, NULL); if (n_sa > 0) { gboolean has_error = FALSE; gsize i; for (i = 0; i < n_sa; i++) { if (!nm_utils_parse_inaddr_prefix_bin(AF_UNSPEC, sa[i], NULL, NULL, NULL)) { has_error = TRUE; continue; } nm_wireguard_peer_append_allowed_ip(peer, sa[i], TRUE); } if (has_error) { if (!handle_warn(info, key, NM_SETTING_WIREGUARD_PEERS, NM_KEYFILE_WARN_SEVERITY_WARN, _("key '%s.%s' has invalid allowed-ips"), info->group, key)) return; } } if (info->error) return; if (!nm_wireguard_peer_is_valid(peer, TRUE, TRUE, &error)) { handle_warn(info, NULL, NM_SETTING_WIREGUARD_PEERS, NM_KEYFILE_WARN_SEVERITY_WARN, _("peer '%s' is invalid: %s"), info->group, error->message); return; } s_wg = NM_SETTING_WIREGUARD( nm_connection_get_setting(info->connection, NM_TYPE_SETTING_WIREGUARD)); if (!s_wg) { s_wg_new = NM_SETTING_WIREGUARD(nm_setting_wireguard_new()); s_wg = s_wg_new; } nm_setting_wireguard_append_peer(s_wg, peer); if (s_wg_new) { nm_connection_add_setting(info->connection, NM_SETTING(g_steal_pointer(&s_wg_new))); } } static void _read_setting_vpn_secrets(KeyfileReaderInfo *info) { gs_strfreev char **keys = NULL; gsize i, n_keys; NMSettingVpn * s_vpn; s_vpn = nm_connection_get_setting_vpn(info->connection); if (!s_vpn) { /* if we don't also have a [vpn] section (which must be parsed earlier), * we don't do anything. */ nm_assert(!g_key_file_has_group(info->keyfile, "vpn")); return; } keys = nm_keyfile_plugin_kf_get_keys(info->keyfile, NM_KEYFILE_GROUP_VPN_SECRETS, &n_keys, NULL); for (i = 0; i < n_keys; i++) { gs_free char *secret = NULL; secret = nm_keyfile_plugin_kf_get_string(info->keyfile, NM_KEYFILE_GROUP_VPN_SECRETS, keys[i], NULL); if (secret) nm_setting_vpn_add_secret(s_vpn, keys[i], secret); } } gboolean nm_keyfile_read_ensure_id(NMConnection *connection, const char *fallback_id) { NMSettingConnection *s_con; g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE); g_return_val_if_fail(fallback_id, FALSE); s_con = nm_connection_get_setting_connection(connection); g_return_val_if_fail(NM_IS_SETTING_CONNECTION(s_con), FALSE); if (nm_setting_connection_get_id(s_con)) return FALSE; g_object_set(s_con, NM_SETTING_CONNECTION_ID, fallback_id, NULL); return TRUE; } gboolean nm_keyfile_read_ensure_uuid(NMConnection *connection, const char *fallback_uuid_seed) { NMSettingConnection *s_con; gs_free char * hashed_uuid = NULL; g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE); g_return_val_if_fail(fallback_uuid_seed, FALSE); s_con = nm_connection_get_setting_connection(connection); g_return_val_if_fail(NM_IS_SETTING_CONNECTION(s_con), FALSE); if (nm_setting_connection_get_uuid(s_con)) return FALSE; hashed_uuid = nm_uuid_generate_from_strings("keyfile", fallback_uuid_seed, NULL); g_object_set(s_con, NM_SETTING_CONNECTION_UUID, hashed_uuid, NULL); return TRUE; } /** * nm_keyfile_read: * @keyfile: the keyfile from which to create the connection * @base_dir: when reading certificates from files with relative name, * the relative path is made absolute using @base_dir. This must * be an absolute path. * @handler_flags: the #NMKeyfileHandlerFlags. * @handler: (allow-none) (scope call): read handler * @user_data: user data for read handler * @error: (allow-none) (out): 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. * * Since: 1.30 */ NMConnection * nm_keyfile_read(GKeyFile * keyfile, const char * base_dir, NMKeyfileHandlerFlags handler_flags, NMKeyfileReadHandler handler, void * user_data, GError ** error) { gs_unref_object NMConnection *connection = NULL; NMSettingConnection * s_con; gs_strfreev char ** groups = NULL; gsize n_groups; gsize i; gboolean vpn_secrets = FALSE; KeyfileReaderInfo info; g_return_val_if_fail(keyfile, NULL); g_return_val_if_fail(!error || !*error, NULL); g_return_val_if_fail(base_dir && base_dir[0] == '/', NULL); g_return_val_if_fail(handler_flags == NM_KEYFILE_HANDLER_FLAGS_NONE, NULL); connection = nm_simple_connection_new(); info = (KeyfileReaderInfo){ .connection = connection, .keyfile = keyfile, .base_dir = base_dir, .read_handler = handler, .user_data = user_data, }; groups = g_key_file_get_groups(keyfile, &n_groups); if (!groups) n_groups = 0; for (i = 0; i < n_groups; i++) { info.group = groups[i]; if (nm_streq(groups[i], NM_KEYFILE_GROUP_VPN_SECRETS)) { /* Only read out secrets when needed */ vpn_secrets = TRUE; } else if (NM_STR_HAS_PREFIX(groups[i], NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER)) _read_setting_wireguard_peer(&info); else if (NM_IN_STRSET(groups[i], NM_KEYFILE_GROUP_NMMETA, ETHERNET_S390_OPTIONS_GROUP_NAME)) { /* pass */ } else _read_setting(&info); info.group = NULL; if (info.error) goto out_with_info_error; } s_con = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_CONNECTION); /* 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)) { gs_free char *interface_name = NULL; 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); } if (vpn_secrets) { info.group = NM_KEYFILE_GROUP_VPN_SECRETS; _read_setting_vpn_secrets(&info); info.group = NULL; if (info.error) goto out_with_info_error; } return g_steal_pointer(&connection); out_with_info_error: g_propagate_error(error, info.error); return NULL; } /*****************************************************************************/ static void write_setting_value(KeyfileWriterInfo * info, NMSetting * setting, const NMSettInfoProperty *property_info) { const NMMetaSettingInfo *setting_info; const ParseInfoProperty *pip; const char * key; char numstr[64]; GValue value; GType type; nm_assert(!info->error); nm_assert(!property_info->param_spec || nm_streq(property_info->param_spec->name, property_info->name)); key = property_info->name; _parse_info_find(setting, key, &setting_info, NULL, &pip); if (!pip) { if (!setting_info) { /* the setting type is unknown. That is highly unexpected * (and as this is currently only called from NetworkManager * daemon, not possible). * * Still, handle it gracefully, because later keyfile writer will become * public API of libnm, where @setting is (untrusted) user input. * * Gracefully here just means: ignore the setting. */ return; } if (!property_info->param_spec) return; if (nm_streq(key, NM_SETTING_NAME)) return; } else { if (pip->has_writer_full) { pip->writer_full(info, setting_info, property_info, pip, setting); return; } if (pip->writer_skip) return; } nm_assert(property_info->param_spec); /* 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 ((property_info->param_spec->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_return_if_reached(); if (!_secret_flags_persist_secret(secret_flags)) return; } value = (GValue){0}; g_value_init(&value, G_PARAM_SPEC_VALUE_TYPE(property_info->param_spec)); g_object_get_property(G_OBJECT(setting), property_info->param_spec->name, &value); if ((!pip || !pip->writer_persist_default) && g_param_value_defaults(property_info->param_spec, &value)) { nm_assert(!g_key_file_has_key(info->keyfile, setting_info->setting_name, key, NULL)); goto out_unset_value; } if (pip && pip->writer) { pip->writer(info, setting, key, &value); goto out_unset_value; } type = G_VALUE_TYPE(&value); 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_info->setting_name, key, str); } else if (type == G_TYPE_UINT) { nm_sprintf_buf(numstr, "%u", g_value_get_uint(&value)); nm_keyfile_plugin_kf_set_value(info->keyfile, setting_info->setting_name, key, numstr); } else if (type == G_TYPE_INT) { nm_sprintf_buf(numstr, "%d", g_value_get_int(&value)); nm_keyfile_plugin_kf_set_value(info->keyfile, setting_info->setting_name, key, numstr); } else if (type == G_TYPE_UINT64) { nm_sprintf_buf(numstr, "%" G_GUINT64_FORMAT, g_value_get_uint64(&value)); nm_keyfile_plugin_kf_set_value(info->keyfile, setting_info->setting_name, key, numstr); } else if (type == G_TYPE_INT64) { nm_sprintf_buf(numstr, "%" G_GINT64_FORMAT, g_value_get_int64(&value)); nm_keyfile_plugin_kf_set_value(info->keyfile, setting_info->setting_name, key, numstr); } else if (type == G_TYPE_BOOLEAN) { nm_keyfile_plugin_kf_set_value(info->keyfile, setting_info->setting_name, key, g_value_get_boolean(&value) ? "true" : "false"); } else if (type == G_TYPE_CHAR) { nm_sprintf_buf(numstr, "%d", (int) g_value_get_schar(&value)); nm_keyfile_plugin_kf_set_value(info->keyfile, setting_info->setting_name, key, numstr); } 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) nm_keyfile_plugin_kf_set_integer_list_uint8(info->keyfile, setting_info->setting_name, key, data, len); } 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_info->setting_name, key, (const char **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)) { nm_sprintf_buf(numstr, "%u", g_value_get_flags(&value)); nm_keyfile_plugin_kf_set_value(info->keyfile, setting_info->setting_name, key, numstr); } else if (G_VALUE_HOLDS_ENUM(&value)) { nm_sprintf_buf(numstr, "%d", g_value_get_enum(&value)); nm_keyfile_plugin_kf_set_value(info->keyfile, setting_info->setting_name, key, numstr); } else g_return_if_reached(); out_unset_value: g_value_unset(&value); } static void _write_setting_wireguard(NMSetting *setting, KeyfileWriterInfo *info) { NMSettingWireGuard *s_wg; guint i_peer, n_peers; s_wg = NM_SETTING_WIREGUARD(setting); n_peers = nm_setting_wireguard_get_peers_len(s_wg); for (i_peer = 0; i_peer < n_peers; i_peer++) { NMWireGuardPeer * peer = nm_setting_wireguard_get_peer(s_wg, i_peer); const char * public_key; char group[NM_STRLEN(NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER) + 200]; NMSettingSecretFlags secret_flags; gboolean any_key = FALSE; guint i_aip, n_aip; const char * cstr; guint32 u32; public_key = nm_wireguard_peer_get_public_key(peer); if (!public_key || !public_key[0] || !NM_STRCHAR_ALL(public_key, ch, nm_sd_utils_unbase64char(ch, TRUE) >= 0)) { /* invalid peer. Skip it */ continue; } if (g_snprintf(group, sizeof(group), "%s%s", NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER, nm_wireguard_peer_get_public_key(peer)) >= sizeof(group)) { /* Too long. Not a valid public key. Skip the peer. */ continue; } cstr = nm_wireguard_peer_get_endpoint(peer); if (cstr) { g_key_file_set_string(info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ENDPOINT, cstr); any_key = TRUE; } secret_flags = nm_wireguard_peer_get_preshared_key_flags(peer); if (_secret_flags_persist_secret(secret_flags)) { cstr = nm_wireguard_peer_get_preshared_key(peer); if (cstr) { g_key_file_set_string(info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, cstr); any_key = TRUE; } } /* usually, we don't persist the secret-flags 0 (because they are the default). * For WireGuard peers, the default secret-flags for preshared-key are 4 (not-required). * So, in this case behave differently: a missing preshared-key-flag setting means * "not-required". */ if (secret_flags != NM_SETTING_SECRET_FLAG_NOT_REQUIRED) { g_key_file_set_int64(info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS, secret_flags); any_key = TRUE; } u32 = nm_wireguard_peer_get_persistent_keepalive(peer); if (u32) { g_key_file_set_uint64(info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE, u32); any_key = TRUE; } n_aip = nm_wireguard_peer_get_allowed_ips_len(peer); if (n_aip > 0) { gs_free const char **strv = NULL; strv = g_new(const char *, ((gsize) n_aip) + 1); for (i_aip = 0; i_aip < n_aip; i_aip++) strv[i_aip] = nm_wireguard_peer_get_allowed_ip(peer, i_aip, NULL); strv[n_aip] = NULL; g_key_file_set_string_list(info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS, strv, n_aip); any_key = TRUE; } if (!any_key) { /* we cannot omit all keys. At an empty endpoint. */ g_key_file_set_string(info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ENDPOINT, ""); } } } /** * nm_keyfile_write: * @connection: the #NMConnection to persist to keyfile. * @handler_flags: the #NMKeyfileHandlerFlags. * @handler: (allow-none) (scope call): optional handler for events and * to override the default behavior. * @user_data: argument for @handler. * @error: the #GError in case writing fails. * * @connection must verify as a valid profile according to * nm_connection_verify(). * * Returns: (transfer full): a new #GKeyFile or %NULL on error. * * Since: 1.30 */ GKeyFile * nm_keyfile_write(NMConnection * connection, NMKeyfileHandlerFlags handler_flags, NMKeyfileWriteHandler handler, void * user_data, GError ** error) { nm_auto_unref_keyfile GKeyFile *keyfile = NULL; GError * local = NULL; KeyfileWriterInfo info; NMSetting ** settings; int i; guint16 j; g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL); g_return_val_if_fail(!error || !*error, NULL); g_return_val_if_fail(handler_flags == NM_KEYFILE_HANDLER_FLAGS_NONE, NULL); /* Technically, we might not require that a profile is valid in * order to serialize it. Like also nm_keyfile_read() does not * ensure that the read profile validates. * * However, if the profile does not validate, then there might be * unexpected edge cases when we try to serialize it. Edge cases * that might result in dangerous crash. * * So, for now we require valid profiles. */ if (!nm_connection_verify(connection, error ? &local : NULL)) { if (error) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED, _("the profile is not valid: %s"), local->message); g_error_free(local); } else nm_assert(!local); return NULL; } keyfile = g_key_file_new(); info = (KeyfileWriterInfo){ .connection = connection, .keyfile = keyfile, .error = NULL, .write_handler = handler, .user_data = user_data, }; settings = _nm_connection_get_settings_arr(connection); for (i = 0; i < (int) _NM_META_SETTING_TYPE_NUM; i++) { NMSetting * setting = settings[nm_meta_setting_types_by_priority[i]]; const NMSettInfoSetting *sett_info; const char * setting_name; const char * setting_alias; if (!setting) continue; sett_info = _nm_setting_class_get_sett_info(NM_SETTING_GET_CLASS(setting)); setting_name = sett_info->setting_class->setting_info->setting_name; if (sett_info->detail.gendata_info) { guint k, n_keys; const char *const *keys; nm_assert(!nm_keyfile_plugin_get_alias_for_setting_name( sett_info->setting_class->setting_info->setting_name)); n_keys = _nm_setting_option_get_all(setting, &keys, NULL); if (n_keys > 0) { GHashTable *h = _nm_setting_option_hash(setting, FALSE); for (k = 0; k < n_keys; k++) { const char *key = keys[k]; GVariant * v; v = g_hash_table_lookup(h, key); if (g_variant_is_of_type(v, G_VARIANT_TYPE_BOOLEAN)) { g_key_file_set_boolean(info.keyfile, setting_name, key, g_variant_get_boolean(v)); } else if (g_variant_is_of_type(v, G_VARIANT_TYPE_UINT32)) { g_key_file_set_uint64(info.keyfile, setting_name, key, (guint64) g_variant_get_uint32(v)); } else { /* BUG: The variant type is not implemented. Since the connection * verifies, this can only mean we either wrongly didn't reject * the connection as invalid, or we didn't properly implement the * variant type. */ nm_assert_not_reached(); continue; } } } } for (j = 0; j < sett_info->property_infos_len; j++) { const NMSettInfoProperty *property_info = _nm_sett_info_property_info_get_sorted(sett_info, j); write_setting_value(&info, setting, property_info); if (info.error) goto out_with_info_error; } setting_alias = nm_keyfile_plugin_get_alias_for_setting_name(setting_name); if ((setting_alias && g_key_file_has_group(info.keyfile, setting_alias)) || g_key_file_has_group(info.keyfile, setting_name)) { /* we have a section for the setting. Nothing to do. */ } else { /* ensure the group is present. There is no API for that, so add and remove * a dummy key. */ g_key_file_set_value(info.keyfile, setting_alias ?: setting_name, ".X", "1"); g_key_file_remove_key(info.keyfile, setting_alias ?: setting_name, ".X", NULL); } if (NM_IS_SETTING_WIREGUARD(setting)) { _write_setting_wireguard(setting, &info); if (info.error) goto out_with_info_error; } nm_assert(!info.error); } nm_assert(!info.error); return g_steal_pointer(&keyfile); out_with_info_error: g_propagate_error(error, info.error); return NULL; } /*****************************************************************************/ static const char temp_letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; /* * Check '.[a-zA-Z0-9]{6}' file suffix used for temporary files by g_file_set_contents() (mkstemp()). */ static gboolean check_mkstemp_suffix(const char *path) { const char *ptr; nm_assert(path); /* Matches *.[a-zA-Z0-9]{6} suffix of mkstemp()'s temporary files */ ptr = strrchr(path, '.'); if (ptr && strspn(&ptr[1], temp_letters) == 6 && ptr[7] == '\0') return TRUE; return FALSE; } #define SWP_TAG ".swp" #define SWPX_TAG ".swpx" #define PEM_TAG ".pem" #define DER_TAG ".der" gboolean nm_keyfile_utils_ignore_filename(const char *filename, gboolean require_extension) { const char *base; gsize l; /* ignore_filename() must mirror nm_keyfile_utils_create_filename() */ g_return_val_if_fail(filename, TRUE); base = strrchr(filename, '/'); if (base) base++; else base = filename; if (!base[0]) { /* this check above with strrchr() also rejects "/some/path/with/trailing/slash/", * but that is fine, because such a path would name a directory, and we are not * interested in directories. */ return TRUE; } if (base[0] == '.') { /* don't allow hidden files */ return TRUE; } if (require_extension) { if (!NM_STR_HAS_SUFFIX_WITH_MORE(base, NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)) return TRUE; return FALSE; } l = strlen(base); /* Ignore backup files */ if (base[l - 1] == '~') return TRUE; /* Ignore temporary files * * This check is also important to ignore .nmload files (see * %NM_KEYFILE_PATH_SUFFIX_NMMETA). */ if (check_mkstemp_suffix(base)) return TRUE; /* Ignore 802.1x certificates and keys */ if (NM_STR_HAS_SUFFIX_ASCII_CASE_WITH_MORE(base, PEM_TAG) || NM_STR_HAS_SUFFIX_ASCII_CASE_WITH_MORE(base, DER_TAG)) return TRUE; return FALSE; } char * nm_keyfile_utils_create_filename(const char *name, gboolean with_extension) { /* keyfile used to escape with '*', do not change that behavior. * * But for newly added escapings, use '_' instead. * Also, @with_extension is new-style. */ const char ESCAPE_CHAR = with_extension ? '_' : '*'; const char ESCAPE_CHAR2 = '_'; NMStrBuf str; char * p; gsize len; gsize i; g_return_val_if_fail(name && name[0], NULL); nm_str_buf_init(&str, 0, FALSE); len = strlen(name); p = nm_str_buf_append_len0(&str, name, len); /* Convert '/' to ESCAPE_CHAR */ for (i = 0; i < len; i++) { if (p[i] == '/') p[i] = ESCAPE_CHAR; } /* nm_keyfile_utils_create_filename() must avoid anything that ignore_filename() would reject. * We can escape here more aggressively then what we would read back. */ if (p[0] == '.') p[0] = ESCAPE_CHAR2; if (p[str.len - 1] == '~') p[str.len - 1] = ESCAPE_CHAR2; if (check_mkstemp_suffix(p) || NM_STR_HAS_SUFFIX_ASCII_CASE_WITH_MORE(p, PEM_TAG) || NM_STR_HAS_SUFFIX_ASCII_CASE_WITH_MORE(p, DER_TAG)) nm_str_buf_append_c(&str, ESCAPE_CHAR2); if (with_extension) nm_str_buf_append(&str, NM_KEYFILE_PATH_SUFFIX_NMCONNECTION); p = nm_str_buf_finalize(&str, NULL); /* nm_keyfile_utils_create_filename() must mirror ignore_filename() */ nm_assert(!strchr(p, '/')); nm_assert(!nm_keyfile_utils_ignore_filename(p, with_extension)); return p; } /*****************************************************************************/ /** * nm_keyfile_handler_data_fail_with_error: * @handler_data: the #NMKeyfileHandlerData * @src: (transfer full): error to move into the return location * * Set the error for the handler. This lets the operation fail * with the provided error. You may only set the error once. * * @src must be non-%NULL. * * Note that @src is no longer valid after this call. If you want * to keep using the same GError*, you need to set it to %NULL * after calling this function on it. * * Since: 1.30 */ void nm_keyfile_handler_data_fail_with_error(NMKeyfileHandlerData *handler_data, GError *src) { g_return_if_fail(handler_data); g_return_if_fail(handler_data->p_error && !*handler_data->p_error); g_return_if_fail(src); *handler_data->p_error = src; } /** * nm_keyfile_handler_data_get_context: * @handler_data: the #NMKeyfileHandlerData for any event. * @out_kf_group_name: (out) (allow-none) (transfer none): if the event is in the * context of a keyfile group, the group name. * @out_kf_key_name: (out) (allow-none) (transfer none): if the event is in the * context of a keyfile value, the key name. * @out_cur_setting: (out) (allow-none) (transfer none): if the event happens while * handling a particular #NMSetting instance. * @out_cur_property_name: (out) (allow-none) (transfer none): the property name if applicable. * * Get context information of the current event. This function can be called * on all events, but the context information may be unset. * * Since: 1.30 */ void nm_keyfile_handler_data_get_context(const NMKeyfileHandlerData *handler_data, const char ** out_kf_group_name, const char ** out_kf_key_name, NMSetting ** out_cur_setting, const char ** out_cur_property_name) { g_return_if_fail(handler_data); NM_SET_OUT(out_kf_group_name, handler_data->kf_group_name); NM_SET_OUT(out_kf_key_name, handler_data->kf_key); NM_SET_OUT(out_cur_setting, handler_data->cur_setting); NM_SET_OUT(out_cur_property_name, handler_data->cur_property); } const char * _nm_keyfile_handler_data_warn_get_message(const NMKeyfileHandlerData *handler_data) { nm_assert(handler_data); nm_assert(handler_data->type == NM_KEYFILE_HANDLER_TYPE_WARN); if (!handler_data->warn.message) { /* we cast the const away. @handler_data is const w.r.t. visible mutations * from POV of the user. Internally, we construct the message in * a lazy manner. It's like a mutable field in C++. */ NM_PRAGMA_WARNING_DISABLE("-Wformat-nonliteral") ((NMKeyfileHandlerData *) handler_data)->warn.message = g_strdup_vprintf(handler_data->warn.fmt, ((NMKeyfileHandlerData *) handler_data)->warn.ap); NM_PRAGMA_WARNING_REENABLE } return handler_data->warn.message; } /** * nm_keyfile_handler_data_warn_get: * @handler_data: the #NMKeyfileHandlerData for a %NM_KEYFILE_HANDLER_TYPE_WARN * event. * @out_message: (out) (allow-none) (transfer none): the warning message. * @out_severity: (out) (allow-none): the #NMKeyfileWarnSeverity warning severity. * * Since: 1.30 */ void nm_keyfile_handler_data_warn_get(const NMKeyfileHandlerData *handler_data, const char ** out_message, NMKeyfileWarnSeverity * out_severity) { g_return_if_fail(handler_data); g_return_if_fail(handler_data->type == NM_KEYFILE_HANDLER_TYPE_WARN); NM_SET_OUT(out_message, _nm_keyfile_handler_data_warn_get_message(handler_data)); NM_SET_OUT(out_severity, handler_data->warn.severity); }