summary refs log tree commit diff
path: root/src/settings/plugins/ibft/nms-ibft-reader.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/settings/plugins/ibft/nms-ibft-reader.c')
-rw-r--r--src/settings/plugins/ibft/nms-ibft-reader.c549
1 files changed, 549 insertions, 0 deletions
diff --git a/src/settings/plugins/ibft/nms-ibft-reader.c b/src/settings/plugins/ibft/nms-ibft-reader.c
new file mode 100644
index 00000000..d26c8334
--- /dev/null
+++ b/src/settings/plugins/ibft/nms-ibft-reader.c
@@ -0,0 +1,549 @@
+/* -*- 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 "nm-default.h"
+
+#include "nms-ibft-reader.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 "nm-core-internal.h"
+#include "platform/nm-platform.h"
+#include "NetworkManagerUtils.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"
+
+/**
+ * nms_ibft_reader_load_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
+nms_ibft_reader_load_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, NM_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, NM_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;
+}
+
+/**
+ * nms_ibft_reader_parse_block:
+ * @block: 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 @block and should not be used after @block is freed.
+ *
+ * Returns: %TRUE if at least , %FALSE on failure
+ */
+gboolean
+nms_ibft_reader_parse_block (const GPtrArray *block, GError **error, ...)
+{
+	gboolean success = FALSE;
+	const char **out_value, *p;
+	va_list ap;
+	const char *key;
+	guint i;
+
+	g_return_val_if_fail (block != NULL, FALSE);
+	g_return_val_if_fail (block->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 < block->len; i++) {
+			p = match_iscsiadm_tag (g_ptr_array_index (block, 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 (!nms_ibft_reader_parse_block (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 (!nms_ibft_reader_parse_block (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 (nms_ibft_reader_parse_block (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 = nms_ibft_reader_parse_block (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 (!nms_ibft_reader_parse_block (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 *
+nms_ibft_reader_get_connection_from_block (const GPtrArray *block, GError **error)
+{
+	NMConnection *connection = NULL;
+	gboolean is_vlan = FALSE;
+	const char *iface = NULL;
+
+	g_assert (block);
+
+	if (!nms_ibft_reader_parse_block (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;
+}
+