// SPDX-License-Identifier: GPL-2.0+
/*
* Alexander Sack <asac@ubuntu.com>
* Copyright (C) 2008 Canonical Ltd.
*/
#include "nm-default.h"
#include "nms-ifupdown-parser.h"
#include <arpa/inet.h>
#include <stdlib.h>
#include <ctype.h>
#include "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__))
/*****************************************************************************/
#define _str_has_prefix(val, prefix, require_suffix) \
({ \
const char *_val = (val); \
\
(strncmp (_val, ""prefix"", NM_STRLEN (prefix)) == 0) \
&& ( !(require_suffix) \
|| _val[NM_STRLEN (prefix)] != '\0'); \
})
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 ( _str_has_prefix (ifb->key, "wireless-", FALSE)
|| _str_has_prefix (ifb->key, "wpa-", FALSE)) {
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 (_str_has_prefix (curr->key, "wireless-", TRUE)) {
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 (_str_has_prefix (curr->key, "wpa-", TRUE)) {
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 (_str_has_prefix (curr->key, "wireless-", TRUE)) {
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 (_str_has_prefix (curr->key, "wpa-", TRUE)) {
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")
? "<omitted>"
: 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_utils_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_utils_ip4_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_utils_ipaddr_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_utils_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_utils_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_utils_ipaddr_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_utils_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_utils_uuid_generate_from_string (idstr, -1, NM_UTILS_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);
}