mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2025-12-28 13:20:08 +01:00
551 lines
16 KiB
C
551 lines
16 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
|
|
/* NetworkManager system settings service
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Copyright 2014 Red Hat, Inc.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/inotify.h>
|
|
#include <errno.h>
|
|
#include <sys/ioctl.h>
|
|
#include <unistd.h>
|
|
|
|
#include <glib.h>
|
|
#include <glib/gi18n.h>
|
|
|
|
#include "nm-core-internal.h"
|
|
#include "nm-platform.h"
|
|
#include "NetworkManagerUtils.h"
|
|
#include "nm-logging.h"
|
|
|
|
#include "reader.h"
|
|
|
|
#define PARSE_WARNING(msg...) nm_log_warn (LOGD_SETTINGS, " " msg)
|
|
|
|
/* Removes trailing whitespace and whitespace before and immediately after the '=' */
|
|
static char *
|
|
remove_most_whitespace (const char *src)
|
|
{
|
|
char *s_new, *s2;
|
|
const char *svalue;
|
|
|
|
while (*src && g_ascii_isspace (*src))
|
|
src++;
|
|
|
|
svalue = strchr (src, '=');
|
|
if (!svalue || svalue == src)
|
|
return NULL;
|
|
|
|
s_new = g_new (char, strlen (src) + 1);
|
|
|
|
memcpy (s_new, src, svalue - src);
|
|
s_new[svalue - src] = '\0';
|
|
g_strchomp (s_new);
|
|
|
|
svalue++;
|
|
while (*svalue && g_ascii_isspace (*svalue))
|
|
svalue++;
|
|
|
|
s2 = strchr (s_new, '\0');
|
|
s2[0] = '=';
|
|
strcpy (++s2, svalue);
|
|
g_strchomp (s2);
|
|
|
|
return s_new;
|
|
}
|
|
|
|
#define TAG_BEGIN "# BEGIN RECORD"
|
|
#define TAG_END "# END RECORD"
|
|
|
|
/**
|
|
* read_ibft_blocks:
|
|
* @iscsiadm_path: path to iscsiadm program
|
|
* @out_blocks: on return if successful, a #GSList of #GPtrArray, or %NULL on
|
|
* failure
|
|
* @error: location for an error on failure
|
|
*
|
|
* Parses iscsiadm output and returns a #GSList of #GPtrArray in the @out_blocks
|
|
* argument on success, otherwise @out_blocks is set to %NULL. Each #GPtrArray
|
|
* in @out_blocks contains the lines from an iscsiadm interface block.
|
|
*
|
|
* Returns: %TRUE on success, %FALSE on errors
|
|
*/
|
|
gboolean
|
|
read_ibft_blocks (const char *iscsiadm_path,
|
|
GSList **out_blocks,
|
|
GError **error)
|
|
{
|
|
const char *argv[4] = { iscsiadm_path, "-m", "fw", NULL };
|
|
const char *envp[1] = { NULL };
|
|
GSList *blocks = NULL;
|
|
char *out = NULL, *err = NULL;
|
|
gint status = 0;
|
|
char **lines = NULL, **iter;
|
|
GPtrArray *block_lines = NULL;
|
|
gboolean success = FALSE;
|
|
|
|
g_return_val_if_fail (iscsiadm_path != NULL, FALSE);
|
|
g_return_val_if_fail (out_blocks != NULL && *out_blocks == NULL, FALSE);
|
|
|
|
if (!g_spawn_sync ("/", (char **) argv, (char **) envp, 0,
|
|
NULL, NULL, &out, &err, &status, error))
|
|
goto done;
|
|
|
|
if (!WIFEXITED (status)) {
|
|
g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
|
|
"iBFT: %s exited abnormally.", iscsiadm_path);
|
|
goto done;
|
|
}
|
|
|
|
if (WEXITSTATUS (status) != 0) {
|
|
if (err) {
|
|
char *nl;
|
|
|
|
/* the error message contains newlines. concatenate the lines with whitespace */
|
|
for (nl = err; *nl; nl++) {
|
|
if (*nl == '\n')
|
|
*nl = ' ';
|
|
}
|
|
}
|
|
g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
|
|
"iBFT: %s exited with error %d. Message: '%s'",
|
|
iscsiadm_path, WEXITSTATUS (status), err ? err : "(none)");
|
|
goto done;
|
|
}
|
|
|
|
nm_log_dbg (LOGD_SETTINGS, "iBFT records:\n%s", out);
|
|
|
|
lines = g_strsplit_set (out, "\n\r", -1);
|
|
for (iter = lines; iter && *iter; iter++) {
|
|
if (!*iter[0])
|
|
continue;
|
|
|
|
if (!g_ascii_strncasecmp (*iter, TAG_BEGIN, STRLEN (TAG_BEGIN))) {
|
|
if (block_lines) {
|
|
PARSE_WARNING ("malformed iscsiadm record: missing END RECORD.");
|
|
g_ptr_array_unref (block_lines);
|
|
}
|
|
/* Start new record */
|
|
block_lines = g_ptr_array_new_full (15, g_free);
|
|
} else if (!g_ascii_strncasecmp (*iter, TAG_END, STRLEN (TAG_END))) {
|
|
if (block_lines) {
|
|
if (block_lines->len)
|
|
blocks = g_slist_prepend (blocks, block_lines);
|
|
else
|
|
g_ptr_array_unref (block_lines);
|
|
block_lines = NULL;
|
|
}
|
|
} else if (block_lines) {
|
|
char *s = remove_most_whitespace (*iter);
|
|
|
|
if (s)
|
|
g_ptr_array_add (block_lines, s);
|
|
else {
|
|
PARSE_WARNING ("malformed iscsiadm record: no = in '%s'.", *iter);
|
|
g_clear_pointer (&block_lines, g_ptr_array_unref);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (block_lines) {
|
|
PARSE_WARNING ("malformed iscsiadm record: missing # END RECORD.");
|
|
g_clear_pointer (&block_lines, g_ptr_array_unref);
|
|
}
|
|
success = TRUE;
|
|
|
|
done:
|
|
if (lines)
|
|
g_strfreev (lines);
|
|
g_free (out);
|
|
g_free (err);
|
|
if (success)
|
|
*out_blocks = blocks;
|
|
else
|
|
g_slist_free_full (blocks, (GDestroyNotify) g_ptr_array_unref);
|
|
return success;
|
|
}
|
|
|
|
#define ISCSI_HWADDR_TAG "iface.hwaddress"
|
|
#define ISCSI_BOOTPROTO_TAG "iface.bootproto"
|
|
#define ISCSI_IPADDR_TAG "iface.ipaddress"
|
|
#define ISCSI_SUBNET_TAG "iface.subnet_mask"
|
|
#define ISCSI_GATEWAY_TAG "iface.gateway"
|
|
#define ISCSI_DNS1_TAG "iface.primary_dns"
|
|
#define ISCSI_DNS2_TAG "iface.secondary_dns"
|
|
#define ISCSI_VLAN_ID_TAG "iface.vlan_id"
|
|
#define ISCSI_IFACE_TAG "iface.net_ifacename"
|
|
|
|
static const char *
|
|
match_iscsiadm_tag (const char *line, const char *tag)
|
|
{
|
|
gsize taglen = strlen (tag);
|
|
|
|
if (g_ascii_strncasecmp (line, tag, taglen) != 0)
|
|
return NULL;
|
|
if (line[taglen] != '=')
|
|
return NULL;
|
|
return line + taglen + 1;
|
|
}
|
|
|
|
/**
|
|
* parse_ibft_config:
|
|
* @data: an array of iscsiadm interface block lines
|
|
* @error: return location for errors
|
|
* @...: pairs of key (const char *) : location (const char **) indicating the
|
|
* key to look for and the location to store the retrieved value in
|
|
*
|
|
* Parses an iscsiadm interface block into variables requested by the caller.
|
|
* Callers should verify the returned data is complete and valid. Returned
|
|
* strings are owned by @data and should not be used after @data is freed.
|
|
*
|
|
* Returns: %TRUE if at least , %FALSE on failure
|
|
*/
|
|
gboolean
|
|
parse_ibft_config (const GPtrArray *data, GError **error, ...)
|
|
{
|
|
gboolean success = FALSE;
|
|
const char **out_value, *p;
|
|
va_list ap;
|
|
const char *key;
|
|
guint i;
|
|
|
|
g_return_val_if_fail (data != NULL, FALSE);
|
|
g_return_val_if_fail (data->len > 0, FALSE);
|
|
|
|
/* Find requested keys and populate return values */
|
|
va_start (ap, error);
|
|
while ((key = va_arg (ap, const char *))) {
|
|
out_value = va_arg (ap, const char **);
|
|
*out_value = NULL;
|
|
for (i = 0; i < data->len; i++) {
|
|
p = match_iscsiadm_tag (g_ptr_array_index (data, i), key);
|
|
if (p) {
|
|
*out_value = p;
|
|
success = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
va_end (ap);
|
|
|
|
if (!success) {
|
|
g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
|
|
"iBFT: failed to match at least one iscsiadm block field");
|
|
}
|
|
return success;
|
|
}
|
|
|
|
static gboolean
|
|
ip4_setting_add_from_block (const GPtrArray *block,
|
|
NMConnection *connection,
|
|
GError **error)
|
|
{
|
|
NMSettingIPConfig *s_ip4 = NULL;
|
|
NMIPAddress *addr;
|
|
const char *s_method = NULL;
|
|
const char *s_ipaddr = NULL;
|
|
const char *s_gateway = NULL;
|
|
const char *s_dns1 = NULL;
|
|
const char *s_dns2 = NULL;
|
|
const char *s_netmask = NULL;
|
|
guint32 netmask = 0;
|
|
guint32 prefix;
|
|
|
|
g_assert (block);
|
|
|
|
if (!parse_ibft_config (block, error,
|
|
ISCSI_BOOTPROTO_TAG, &s_method,
|
|
ISCSI_IPADDR_TAG, &s_ipaddr,
|
|
ISCSI_SUBNET_TAG, &s_netmask,
|
|
ISCSI_GATEWAY_TAG, &s_gateway,
|
|
ISCSI_DNS1_TAG, &s_dns1,
|
|
ISCSI_DNS2_TAG, &s_dns2,
|
|
NULL))
|
|
goto error;
|
|
|
|
if (!s_method) {
|
|
g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
|
|
"iBFT: malformed iscsiadm record: missing " ISCSI_BOOTPROTO_TAG);
|
|
goto error;
|
|
}
|
|
|
|
s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new ();
|
|
|
|
if (!g_ascii_strcasecmp (s_method, "dhcp")) {
|
|
g_object_set (s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
|
|
goto success;
|
|
} else if (g_ascii_strcasecmp (s_method, "static") != 0) {
|
|
g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
|
|
"iBFT: malformed iscsiadm record: unknown " ISCSI_BOOTPROTO_TAG " '%s'.",
|
|
s_method);
|
|
goto error;
|
|
}
|
|
|
|
/* Static configuration stuff */
|
|
g_object_set (s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL, NULL);
|
|
|
|
/* IP address */
|
|
if (!s_ipaddr || !nm_utils_ipaddr_valid (AF_INET, s_ipaddr)) {
|
|
g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
|
|
"iBFT: malformed iscsiadm record: invalid IP address '%s'.",
|
|
s_ipaddr);
|
|
goto error;
|
|
}
|
|
|
|
/* Subnet/prefix */
|
|
if (!s_netmask || inet_pton (AF_INET, s_netmask, &netmask) != 1) {
|
|
g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
|
|
"iBFT: malformed iscsiadm record: invalid subnet mask '%s'.",
|
|
s_netmask);
|
|
goto error;
|
|
}
|
|
prefix = nm_utils_ip4_netmask_to_prefix (netmask);
|
|
|
|
if (s_gateway && !nm_utils_ipaddr_valid (AF_INET, s_gateway)) {
|
|
g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
|
|
"iBFT: malformed iscsiadm record: invalid IP gateway '%s'.",
|
|
s_gateway);
|
|
goto error;
|
|
}
|
|
|
|
if (s_dns1 && !nm_utils_ipaddr_valid (AF_INET, s_dns1)) {
|
|
g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
|
|
"iBFT: malformed iscsiadm record: invalid DNS1 address '%s'.",
|
|
s_dns1);
|
|
goto error;
|
|
}
|
|
|
|
if (s_dns2 && !nm_utils_ipaddr_valid (AF_INET, s_dns2)) {
|
|
g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
|
|
"iBFT: malformed iscsiadm record: invalid DNS2 address '%s'.",
|
|
s_dns2);
|
|
goto error;
|
|
}
|
|
|
|
addr = nm_ip_address_new (AF_INET, s_ipaddr, prefix, error);
|
|
if (!addr) {
|
|
g_prefix_error (error, "iBFT: malformed iscsiadm record: ");
|
|
goto error;
|
|
}
|
|
|
|
nm_setting_ip_config_add_address (s_ip4, addr);
|
|
nm_ip_address_unref (addr);
|
|
|
|
g_object_set (s_ip4, NM_SETTING_IP_CONFIG_GATEWAY, s_gateway, NULL);
|
|
|
|
if (s_dns1)
|
|
nm_setting_ip_config_add_dns (s_ip4, s_dns1);
|
|
if (s_dns2)
|
|
nm_setting_ip_config_add_dns (s_ip4, s_dns2);
|
|
|
|
success:
|
|
nm_connection_add_setting (connection, NM_SETTING (s_ip4));
|
|
return TRUE;
|
|
|
|
error:
|
|
g_clear_object (&s_ip4);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
connection_setting_add (const GPtrArray *block,
|
|
NMConnection *connection,
|
|
const char *type,
|
|
const char *prefix,
|
|
const char *iface,
|
|
GError **error)
|
|
{
|
|
NMSetting *s_con;
|
|
char *id, *uuid;
|
|
const char *s_hwaddr = NULL, *s_ip4addr = NULL, *s_vlanid;
|
|
|
|
if (!parse_ibft_config (block, error,
|
|
ISCSI_VLAN_ID_TAG, &s_vlanid,
|
|
ISCSI_HWADDR_TAG, &s_hwaddr,
|
|
ISCSI_IPADDR_TAG, &s_ip4addr,
|
|
NULL))
|
|
return FALSE;
|
|
if (!s_hwaddr) {
|
|
g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
|
|
"iBFT: malformed iscsiadm record: missing " ISCSI_HWADDR_TAG);
|
|
return FALSE;
|
|
}
|
|
|
|
id = g_strdup_printf ("iBFT%s%s %s",
|
|
prefix ? " " : "",
|
|
prefix ? prefix : "",
|
|
iface);
|
|
|
|
uuid = _nm_utils_uuid_generate_from_strings ("ibft",
|
|
s_hwaddr,
|
|
s_vlanid ? "V" : "v",
|
|
s_vlanid ? s_vlanid : "",
|
|
s_ip4addr ? "A" : "DHCP",
|
|
s_ip4addr ? s_ip4addr : "",
|
|
NULL);
|
|
|
|
s_con = nm_setting_connection_new ();
|
|
g_object_set (s_con,
|
|
NM_SETTING_CONNECTION_TYPE, type,
|
|
NM_SETTING_CONNECTION_UUID, uuid,
|
|
NM_SETTING_CONNECTION_ID, id,
|
|
NM_SETTING_CONNECTION_READ_ONLY, TRUE,
|
|
NULL);
|
|
|
|
g_free (uuid);
|
|
g_free (id);
|
|
|
|
nm_connection_add_setting (connection, NM_SETTING (s_con));
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
is_ibft_vlan_device (const GPtrArray *block)
|
|
{
|
|
char *s_vlan_id = NULL;
|
|
|
|
if (parse_ibft_config (block, NULL, ISCSI_VLAN_ID_TAG, &s_vlan_id, NULL)) {
|
|
g_assert (s_vlan_id);
|
|
|
|
/* VLAN 0 is normally a valid VLAN ID, but in the iBFT case it
|
|
* means "no VLAN".
|
|
*/
|
|
if (_nm_utils_ascii_str_to_int64 (s_vlan_id, 10, 1, 4095, -1) != -1)
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
vlan_setting_add_from_block (const GPtrArray *block,
|
|
NMConnection *connection,
|
|
GError **error)
|
|
{
|
|
NMSetting *s_vlan = NULL;
|
|
const char *vlan_id_str = NULL;
|
|
gint64 vlan_id = -1;
|
|
gboolean success;
|
|
|
|
g_assert (block);
|
|
g_assert (connection);
|
|
|
|
/* This won't fail since this function shouldn't be called unless the
|
|
* iBFT VLAN ID exists and is > 0.
|
|
*/
|
|
success = parse_ibft_config (block, NULL, ISCSI_VLAN_ID_TAG, &vlan_id_str, NULL);
|
|
g_assert (success);
|
|
g_assert (vlan_id_str);
|
|
|
|
/* VLAN 0 is normally a valid VLAN ID, but in the iBFT case it means "no VLAN" */
|
|
vlan_id = _nm_utils_ascii_str_to_int64 (vlan_id_str, 10, 1, 4095, -1);
|
|
if (vlan_id == -1) {
|
|
g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
|
|
"Invalid VLAN_ID '%s'", vlan_id_str);
|
|
return FALSE;
|
|
}
|
|
|
|
s_vlan = nm_setting_vlan_new ();
|
|
g_object_set (s_vlan, NM_SETTING_VLAN_ID, (guint32) vlan_id, NULL);
|
|
nm_connection_add_setting (connection, NM_SETTING (s_vlan));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
wired_setting_add_from_block (const GPtrArray *block,
|
|
NMConnection *connection,
|
|
GError **error)
|
|
{
|
|
NMSetting *s_wired = NULL;
|
|
const char *hwaddr = NULL;
|
|
|
|
g_assert (block);
|
|
g_assert (connection);
|
|
|
|
if (!parse_ibft_config (block, NULL, ISCSI_HWADDR_TAG, &hwaddr, NULL)) {
|
|
g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
|
|
"iBFT: malformed iscsiadm record: missing " ISCSI_HWADDR_TAG);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!nm_utils_hwaddr_valid (hwaddr, ETH_ALEN)) {
|
|
g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
|
|
"iBFT: malformed iscsiadm record: invalid " ISCSI_HWADDR_TAG " '%s'.",
|
|
hwaddr);
|
|
return FALSE;
|
|
}
|
|
|
|
s_wired = nm_setting_wired_new ();
|
|
g_object_set (s_wired, NM_SETTING_WIRED_MAC_ADDRESS, hwaddr, NULL);
|
|
|
|
nm_connection_add_setting (connection, s_wired);
|
|
return TRUE;
|
|
}
|
|
|
|
NMConnection *
|
|
connection_from_block (const GPtrArray *block, GError **error)
|
|
{
|
|
NMConnection *connection = NULL;
|
|
gboolean is_vlan = FALSE;
|
|
const char *iface = NULL;
|
|
|
|
g_assert (block);
|
|
|
|
if (!parse_ibft_config (block, error, ISCSI_IFACE_TAG, &iface, NULL)) {
|
|
g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
|
|
"iBFT: malformed iscsiadm record: missing " ISCSI_IFACE_TAG);
|
|
return NULL;
|
|
}
|
|
|
|
connection = nm_simple_connection_new ();
|
|
|
|
is_vlan = is_ibft_vlan_device (block);
|
|
if (is_vlan && !vlan_setting_add_from_block (block, connection, error))
|
|
goto error;
|
|
|
|
/* Always have a wired setting; for VLAN it defines the parent */
|
|
if (!wired_setting_add_from_block (block, connection, error))
|
|
goto error;
|
|
|
|
if (!ip4_setting_add_from_block (block, connection, error))
|
|
goto error;
|
|
|
|
if (!connection_setting_add (block,
|
|
connection,
|
|
is_vlan ? NM_SETTING_VLAN_SETTING_NAME : NM_SETTING_WIRED_SETTING_NAME,
|
|
is_vlan ? "VLAN" : NULL,
|
|
iface,
|
|
error))
|
|
goto error;
|
|
|
|
if (!nm_connection_normalize (connection, NULL, NULL, error))
|
|
goto error;
|
|
|
|
return connection;
|
|
|
|
error:
|
|
g_object_unref (connection);
|
|
return NULL;
|
|
}
|
|
|