/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Alexander Sack * Copyright (C) 2008 Canonical Ltd. */ #include "src/core/nm-default-daemon.h" #include "nms-ifupdown-parser.h" #include #include #include #include "libnm-glib-aux/nm-uuid.h" #include "libnm-core-intern/nm-core-internal.h" #include "settings/nm-settings-plugin.h" #include "nms-ifupdown-plugin.h" #include "nms-ifupdown-parser.h" /*****************************************************************************/ #define _NMLOG_PREFIX_NAME "ifupdown" #define _NMLOG_DOMAIN LOGD_SETTINGS #define _NMLOG(level, ...) \ nm_log((level), \ _NMLOG_DOMAIN, \ NULL, \ NULL, \ "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ _NMLOG_PREFIX_NAME ": " _NM_UTILS_MACRO_REST(__VA_ARGS__)) /*****************************************************************************/ static const char * _ifupdownplugin_guess_connection_type(if_block *block) { const char *ret_type = NULL; if (nm_streq0(ifparser_getkey(block, "inet"), "ppp")) ret_type = NM_SETTING_PPP_SETTING_NAME; else { if_data *ifb; c_list_for_each_entry (ifb, &block->data_lst_head, data_lst) { if (NM_STR_HAS_PREFIX(ifb->key, "wireless-") || NM_STR_HAS_PREFIX(ifb->key, "wpa-")) { ret_type = NM_SETTING_WIRELESS_SETTING_NAME; break; } } if (!ret_type) ret_type = NM_SETTING_WIRED_SETTING_NAME; } _LOGI("guessed connection type (%s) = %s", block->name, ret_type); return ret_type; } struct _Mapping { const char *domain; const gpointer target; }; static gpointer map_by_mapping(struct _Mapping *mapping, const char *key) { struct _Mapping *curr = mapping; while (curr->domain) { if (nm_streq(curr->domain, key)) return curr->target; curr++; } return NULL; } static void update_wireless_setting_from_if_block(NMConnection *connection, if_block *block) { if_data *curr; const char *value = ifparser_getkey(block, "inet"); struct _Mapping mapping[] = {{"ssid", "ssid"}, {"essid", "ssid"}, {"mode", "mode"}, {NULL, NULL}}; NMSettingWireless *wireless_setting = NULL; if (nm_streq0(value, "ppp")) return; _LOGI("update wireless settings (%s).", block->name); wireless_setting = NM_SETTING_WIRELESS(nm_setting_wireless_new()); c_list_for_each_entry (curr, &block->data_lst_head, data_lst) { if (NM_STR_HAS_PREFIX_WITH_MORE(curr->key, "wireless-")) { const char *newkey = map_by_mapping(mapping, curr->key + NM_STRLEN("wireless-")); _LOGI("wireless setting key: %s='%s'", newkey, curr->data); if (nm_streq0(newkey, "ssid")) { GBytes *ssid; int len = strlen(curr->data); ssid = g_bytes_new(curr->data, len); g_object_set(wireless_setting, NM_SETTING_WIRELESS_SSID, ssid, NULL); g_bytes_unref(ssid); _LOGI("setting wireless ssid = %d", len); } else if (nm_streq0(newkey, "mode")) { if (!g_ascii_strcasecmp(curr->data, "Managed") || !g_ascii_strcasecmp(curr->data, "Auto")) g_object_set(wireless_setting, NM_SETTING_WIRELESS_MODE, NM_SETTING_WIRELESS_MODE_INFRA, NULL); else if (!g_ascii_strcasecmp(curr->data, "Ad-Hoc")) g_object_set(wireless_setting, NM_SETTING_WIRELESS_MODE, NM_SETTING_WIRELESS_MODE_ADHOC, NULL); else if (!g_ascii_strcasecmp(curr->data, "Master")) g_object_set(wireless_setting, NM_SETTING_WIRELESS_MODE, NM_SETTING_WIRELESS_MODE_AP, NULL); else _LOGW("Invalid mode '%s' (not 'Ad-Hoc', 'Ap', 'Managed', or 'Auto')", curr->data); } else { g_object_set(wireless_setting, newkey, curr->data, NULL); } } else if (NM_STR_HAS_PREFIX_WITH_MORE(curr->key, "wpa-")) { const char *newkey = map_by_mapping(mapping, curr->key + NM_STRLEN("wpa-")); if (nm_streq0(newkey, "ssid")) { GBytes *ssid; int len = strlen(curr->data); ssid = g_bytes_new(curr->data, len); g_object_set(wireless_setting, NM_SETTING_WIRELESS_SSID, ssid, NULL); g_bytes_unref(ssid); _LOGI("setting wpa ssid = %d", len); } else if (newkey) { g_object_set(wireless_setting, newkey, curr->data, NULL); _LOGI("setting wpa newkey(%s)=data(%s)", newkey, curr->data); } } } nm_connection_add_setting(connection, (NMSetting *) wireless_setting); } typedef char *(*IfupdownStrDupeFunc)(gconstpointer value, gpointer data); typedef gpointer (*IfupdownStrToTypeFunc)(const char *value); static char * normalize_dupe_wireless_key(gpointer value, gpointer data) { char *valuec = value; char *endc = valuec + strlen(valuec); char *delim = valuec; char *next = delim; char *result = malloc(strlen(valuec) + 1); char *result_cur = result; while (*delim && (next = strchr(delim, '-')) != NULL) { if (next == delim) { delim++; continue; } strncpy(result_cur, delim, next - delim); result_cur += next - delim; delim = next + 1; } if (*delim && strlen(valuec) > GPOINTER_TO_UINT(delim - valuec)) { strncpy(result_cur, delim, endc - delim); result_cur += endc - delim; } *result_cur = '\0'; return result; } static char * normalize_dupe(gpointer value, gpointer data) { return g_strdup(value); } static char * normalize_tolower(gpointer value, gpointer data) { return g_ascii_strdown(value, -1); } static char * normalize_psk(gpointer value, gpointer data) { if (strlen(value) >= 8 && strlen(value) <= 64) return g_strdup(value); return NULL; } static gpointer string_to_gpointerint(const char *data) { int result = (int) strtol(data, NULL, 10); return GINT_TO_POINTER(result); } static gpointer string_to_glist_of_strings(const char *data) { GSList *ret = NULL; char *string = (char *) data; while (string) { char *next = NULL; if ((next = strchr(string, ' ')) || (next = strchr(string, '\t')) || (next = strchr(string, '\0'))) { char *part = g_strndup(string, (next - string)); ret = g_slist_append(ret, part); if (*next) string = next + 1; else string = NULL; } else { string = NULL; } } return ret; } static void slist_free_all(gpointer slist) { g_slist_free_full((GSList *) slist, g_free); } static void update_wireless_security_setting_from_if_block(NMConnection *connection, if_block *block) { if_data *curr; const char *value = ifparser_getkey(block, "inet"); struct _Mapping mapping[] = {{"psk", "psk"}, {"identity", "leap-username"}, {"password", "leap-password"}, {"key", "wep-key0"}, {"key-mgmt", "key-mgmt"}, {"group", "group"}, {"pairwise", "pairwise"}, {"proto", "proto"}, {"pin", "pin"}, {"wep-key0", "wep-key0"}, {"wep-key1", "wep-key1"}, {"wep-key2", "wep-key2"}, {"wep-key3", "wep-key3"}, {"wep-tx-keyidx", "wep-tx-keyidx"}, {NULL, NULL}}; struct _Mapping dupe_mapping[] = {{"psk", normalize_psk}, {"identity", normalize_dupe}, {"password", normalize_dupe}, {"key", normalize_dupe_wireless_key}, {"key-mgmt", normalize_tolower}, {"group", normalize_tolower}, {"pairwise", normalize_tolower}, {"proto", normalize_tolower}, {"pin", normalize_dupe}, {"wep-key0", normalize_dupe_wireless_key}, {"wep-key1", normalize_dupe_wireless_key}, {"wep-key2", normalize_dupe_wireless_key}, {"wep-key3", normalize_dupe_wireless_key}, {"wep-tx-keyidx", normalize_dupe}, {NULL, NULL}}; struct _Mapping type_mapping[] = {{"group", string_to_glist_of_strings}, {"pairwise", string_to_glist_of_strings}, {"proto", string_to_glist_of_strings}, {"wep-tx-keyidx", string_to_gpointerint}, {NULL, NULL}}; struct _Mapping free_type_mapping[] = {{"group", slist_free_all}, {"pairwise", slist_free_all}, {"proto", slist_free_all}, {NULL, NULL}}; NMSettingWirelessSecurity *wireless_security_setting; NMSettingWireless *s_wireless; gboolean security = FALSE; if (nm_streq0(value, "ppp")) return; s_wireless = nm_connection_get_setting_wireless(connection); g_return_if_fail(s_wireless); _LOGI("update wireless security settings (%s).", block->name); wireless_security_setting = NM_SETTING_WIRELESS_SECURITY(nm_setting_wireless_security_new()); c_list_for_each_entry (curr, &block->data_lst_head, data_lst) { if (NM_STR_HAS_PREFIX_WITH_MORE(curr->key, "wireless-")) { const char *key = curr->key + NM_STRLEN("wireless-"); char *property_value = NULL; gpointer typed_property_value = NULL; const char *newkey = map_by_mapping(mapping, key); IfupdownStrDupeFunc dupe_func = map_by_mapping(dupe_mapping, key); IfupdownStrToTypeFunc type_map_func = map_by_mapping(type_mapping, key); GFreeFunc free_func = map_by_mapping(free_type_mapping, key); if (!newkey || !dupe_func) goto next; property_value = (*dupe_func)(curr->data, connection); _LOGI("setting wireless security key: %s=%s", newkey, property_value); if (type_map_func) { errno = 0; typed_property_value = (*type_map_func)(property_value); if (errno) goto wireless_next; } g_object_set(wireless_security_setting, newkey, typed_property_value ?: property_value, NULL); security = TRUE; wireless_next: g_free(property_value); if (typed_property_value && free_func) (*free_func)(typed_property_value); } else if (NM_STR_HAS_PREFIX_WITH_MORE(curr->key, "wpa-")) { const char *key = curr->key + NM_STRLEN("wpa-"); char *property_value = NULL; gpointer typed_property_value = NULL; const char *newkey = map_by_mapping(mapping, key); IfupdownStrDupeFunc dupe_func = map_by_mapping(dupe_mapping, key); IfupdownStrToTypeFunc type_map_func = map_by_mapping(type_mapping, key); GFreeFunc free_func = map_by_mapping(free_type_mapping, key); if (!newkey || !dupe_func) goto next; property_value = (*dupe_func)(curr->data, connection); _LOGI("setting wpa security key: %s=%s", newkey, NM_IN_STRSET(newkey, "key", "leap-password", "pin", "psk", "wep-key0", "wep-key1", "wep-key2", "wep-key3") ? "" : property_value); if (type_map_func) { errno = 0; typed_property_value = (*type_map_func)(property_value); if (errno) goto wpa_next; } g_object_set(wireless_security_setting, newkey, typed_property_value ?: property_value, NULL); security = TRUE; wpa_next: g_free(property_value); if (free_func && typed_property_value) (*free_func)(typed_property_value); } next:; } if (security) nm_connection_add_setting(connection, NM_SETTING(wireless_security_setting)); } static void update_wired_setting_from_if_block(NMConnection *connection, if_block *block) { NMSettingWired *s_wired = NULL; s_wired = NM_SETTING_WIRED(nm_setting_wired_new()); nm_connection_add_setting(connection, NM_SETTING(s_wired)); } static void ifupdown_ip4_add_dns(NMSettingIPConfig *s_ip4, const char *dns) { gs_free const char **list = NULL; const char **iter; guint32 addr; if (dns == NULL) return; list = nm_strsplit_set(dns, " \t"); for (iter = list; iter && *iter; iter++) { if (!inet_pton(AF_INET, *iter, &addr)) { _LOGW(" ignoring invalid nameserver '%s'", *iter); continue; } if (!nm_setting_ip_config_add_dns(s_ip4, *iter)) _LOGW(" duplicate DNS domain '%s'", *iter); } } static gboolean update_ip4_setting_from_if_block(NMConnection *connection, if_block *block, GError **error) { gs_unref_object NMSettingIPConfig *s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new()); const char *type = ifparser_getkey(block, "inet"); if (!nm_streq0(type, "static")) { g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); } else { guint32 tmp_mask; NMIPAddress *addr; const char *address_v; const char *netmask_v; const char *gateway_v; const char *nameserver_v; const char *nameservers_v; const char *search_v; guint32 netmask_int = 32; /* Address */ address_v = ifparser_getkey(block, "address"); if (!address_v) { g_set_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, "Missing IPv4 address"); return FALSE; } /* mask/prefix */ netmask_v = ifparser_getkey(block, "netmask"); if (netmask_v) { if (strlen(netmask_v) < 7) { netmask_int = atoi(netmask_v); } else if (!inet_pton(AF_INET, netmask_v, &tmp_mask)) { g_set_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, "Invalid IPv4 netmask '%s'", netmask_v); return FALSE; } else { netmask_int = nm_ip4_addr_netmask_to_prefix(tmp_mask); } } /* Add the new address to the setting */ addr = nm_ip_address_new(AF_INET, address_v, netmask_int, error); if (!addr) return FALSE; if (nm_setting_ip_config_add_address(s_ip4, addr)) { _LOGI("addresses count: %d", nm_setting_ip_config_get_num_addresses(s_ip4)); } else { _LOGI("ignoring duplicate IP4 address"); } nm_ip_address_unref(addr); /* gateway */ gateway_v = ifparser_getkey(block, "gateway"); if (gateway_v) { if (!nm_inet_is_valid(AF_INET, gateway_v)) { g_set_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, "Invalid IPv4 gateway '%s'", gateway_v); return FALSE; } if (!nm_setting_ip_config_get_gateway(s_ip4)) g_object_set(s_ip4, NM_SETTING_IP_CONFIG_GATEWAY, gateway_v, NULL); } nameserver_v = ifparser_getkey(block, "dns-nameserver"); ifupdown_ip4_add_dns(s_ip4, nameserver_v); nameservers_v = ifparser_getkey(block, "dns-nameservers"); ifupdown_ip4_add_dns(s_ip4, nameservers_v); if (!nm_setting_ip_config_get_num_dns(s_ip4)) _LOGI("No dns-nameserver configured in /etc/network/interfaces"); /* DNS searches */ search_v = ifparser_getkey(block, "dns-search"); if (search_v) { gs_free const char **list = NULL; const char **iter; list = nm_strsplit_set(search_v, " \t"); for (iter = list; iter && *iter; iter++) { if (!nm_setting_ip_config_add_dns_search(s_ip4, *iter)) _LOGW(" duplicate DNS domain '%s'", *iter); } } g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL, NULL); } nm_connection_add_setting(connection, NM_SETTING(g_steal_pointer(&s_ip4))); return TRUE; } static void ifupdown_ip6_add_dns(NMSettingIPConfig *s_ip6, const char *dns) { gs_free const char **list = NULL; const char **iter; struct in6_addr addr; if (dns == NULL) return; list = nm_strsplit_set(dns, " \t"); for (iter = list; iter && *iter; iter++) { if (!inet_pton(AF_INET6, *iter, &addr)) { _LOGW(" ignoring invalid nameserver '%s'", *iter); continue; } if (!nm_setting_ip_config_add_dns(s_ip6, *iter)) _LOGW(" duplicate DNS domain '%s'", *iter); } } static gboolean update_ip6_setting_from_if_block(NMConnection *connection, if_block *block, GError **error) { gs_unref_object NMSettingIPConfig *s_ip6 = NM_SETTING_IP_CONFIG(nm_setting_ip6_config_new()); const char *type = ifparser_getkey(block, "inet6"); if (!NM_IN_STRSET(type, "static", "v4tunnel")) { g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL); } else { NMIPAddress *addr; const char *address_v; const char *prefix_v; const char *gateway_v; const char *nameserver_v; const char *nameservers_v; const char *search_v; guint prefix_int; address_v = ifparser_getkey(block, "address"); if (!address_v) { g_set_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, "Missing IPv6 address"); return FALSE; } prefix_v = ifparser_getkey(block, "netmask"); if (prefix_v) prefix_int = _nm_utils_ascii_str_to_int64(prefix_v, 10, 0, 128, G_MAXINT); else prefix_int = 128; addr = nm_ip_address_new(AF_INET6, address_v, prefix_int, error); if (!addr) return FALSE; if (nm_setting_ip_config_add_address(s_ip6, addr)) { _LOGI("addresses count: %d", nm_setting_ip_config_get_num_addresses(s_ip6)); } else { _LOGI("ignoring duplicate IP6 address"); } nm_ip_address_unref(addr); gateway_v = ifparser_getkey(block, "gateway"); if (gateway_v) { if (!nm_inet_is_valid(AF_INET6, gateway_v)) { g_set_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, "Invalid IPv6 gateway '%s'", gateway_v); return FALSE; } if (!nm_setting_ip_config_get_gateway(s_ip6)) g_object_set(s_ip6, NM_SETTING_IP_CONFIG_GATEWAY, gateway_v, NULL); } nameserver_v = ifparser_getkey(block, "dns-nameserver"); ifupdown_ip6_add_dns(s_ip6, nameserver_v); nameservers_v = ifparser_getkey(block, "dns-nameservers"); ifupdown_ip6_add_dns(s_ip6, nameservers_v); if (!nm_setting_ip_config_get_num_dns(s_ip6)) _LOGI("No dns-nameserver configured in /etc/network/interfaces"); search_v = ifparser_getkey(block, "dns-search"); if (search_v) { gs_free const char **list = NULL; const char **iter; list = nm_strsplit_set(search_v, " \t"); for (iter = list; iter && *iter; iter++) { if (!nm_setting_ip_config_add_dns_search(s_ip6, *iter)) _LOGW(" duplicate DNS domain '%s'", *iter); } } g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_MANUAL, NULL); } nm_connection_add_setting(connection, NM_SETTING(g_steal_pointer(&s_ip6))); return TRUE; } NMConnection * ifupdown_new_connection_from_if_block(if_block *block, gboolean autoconnect, GError **error) { gs_unref_object NMConnection *connection = NULL; const char *type; gs_free char *idstr = NULL; gs_free char *uuid = NULL; NMSettingConnection *s_con; connection = nm_simple_connection_new(); s_con = NM_SETTING_CONNECTION(nm_setting_connection_new()); nm_connection_add_setting(connection, NM_SETTING(s_con)); type = _ifupdownplugin_guess_connection_type(block); idstr = g_strconcat("Ifupdown (", block->name, ")", NULL); uuid = nm_uuid_generate_from_string_str(idstr, -1, NM_UUID_TYPE_LEGACY, NULL); g_object_set(s_con, NM_SETTING_CONNECTION_TYPE, type, NM_SETTING_CONNECTION_INTERFACE_NAME, block->name, NM_SETTING_CONNECTION_ID, idstr, NM_SETTING_CONNECTION_UUID, uuid, NM_SETTING_CONNECTION_AUTOCONNECT, (gboolean) (!!autoconnect), NULL); _LOGD("update_connection_setting_from_if_block: name:%s, type:%s, id:%s, uuid: %s", block->name, type, idstr, nm_setting_connection_get_uuid(s_con)); if (nm_streq(type, NM_SETTING_WIRED_SETTING_NAME)) update_wired_setting_from_if_block(connection, block); else if (nm_streq(type, NM_SETTING_WIRELESS_SETTING_NAME)) { update_wireless_setting_from_if_block(connection, block); update_wireless_security_setting_from_if_block(connection, block); } if (ifparser_haskey(block, "inet6")) { if (!update_ip6_setting_from_if_block(connection, block, error)) return FALSE; } else { if (!update_ip4_setting_from_if_block(connection, block, error)) return FALSE; } if (!nm_connection_normalize(connection, NULL, NULL, error)) return NULL; return g_steal_pointer(&connection); }