diff --git a/README.mdv b/README.mdv new file mode 100644 index 0000000..989ca01 --- /dev/null +++ b/README.mdv @@ -0,0 +1,14 @@ +The ifcfg-mdv is a BETA plugin, which adds support for Mandriva-specific +variables in /etc/sysconfig/network-scripts/ifcfg-* files to network +manager. + +This is not a production-ready version, nor it is officially supported as of +now. However, it works fine for me, so - at least in theory - it should work +for some of you out there as well. + +To get the most up-to-date patch against mainstream NetworkManager code, +just run: +# git diff master mdv + +The 'patches' directory contains mostly up-to-date patches to be used with +Mandriva's networkmanager package. diff --git a/configure.ac b/configure.ac index 124f47c..8667cb9 100644 --- a/configure.ac +++ b/configure.ac @@ -461,6 +461,7 @@ system-settings/plugins/ifupdown/Makefile system-settings/plugins/ifcfg-rh/Makefile system-settings/plugins/ifcfg-rh/tests/Makefile system-settings/plugins/ifcfg-rh/tests/network-scripts/Makefile +system-settings/plugins/ifcfg-mdv/Makefile system-settings/plugins/ifcfg-suse/Makefile system-settings/plugins/keyfile/Makefile system-settings/plugins/keyfile/io/Makefile diff --git a/initscript/Mandriva/networkmanager.in b/initscript/Mandriva/networkmanager.in index dac14e7..1214563 100644 --- a/initscript/Mandriva/networkmanager.in +++ b/initscript/Mandriva/networkmanager.in @@ -12,6 +12,7 @@ ### BEGIN INIT INFO # Provides: networkmanager # Required-Start: $network +# Should-Start: $named # Required-Stop: $network # Default-Start: 3 4 5 # Short-Description: Daemon for automatically switching to best network connection. @@ -46,10 +47,6 @@ start() echo $"Setting network parameters... " sysctl -e -p /etc/sysctl.conf >/dev/null 2>&1 - if [ ! -e /var/lock/subsys/named ]; then - service named start >/dev/null 2>&1 - fi - echo -n $"Starting NetworkManager daemon: " daemon --check $servicename $processname --pid-file=$pidfile RETVAL=$? diff --git a/system-settings/plugins/Makefile.am b/system-settings/plugins/Makefile.am index 94f7560..40bc052 100644 --- a/system-settings/plugins/Makefile.am +++ b/system-settings/plugins/Makefile.am @@ -9,6 +9,7 @@ SUBDIRS+=ifcfg-suse endif if TARGET_MANDRIVA +SUBDIRS+=ifcfg-mdv SUBDIRS+=ifcfg-rh endif diff --git a/system-settings/plugins/ifcfg-mdv/Makefile.am b/system-settings/plugins/ifcfg-mdv/Makefile.am new file mode 100644 index 0000000..3646ac7 --- /dev/null +++ b/system-settings/plugins/ifcfg-mdv/Makefile.am @@ -0,0 +1,64 @@ +SUBDIRS=. + +pkglib_LTLIBRARIES = libnm-settings-plugin-ifcfg-mdv.la + +noinst_LTLIBRARIES = libifcfg-mdv-io.la + +libifcfg_mdv_io_la_SOURCES = \ + parse_wpa_supplicant_conf.c \ + parse_wpa_supplicant_conf.h \ + reader.c \ + reader.h \ + writer.c \ + writer.h \ + common.h \ + ../ifcfg-rh/shvar.c \ + ../ifcfg-rh/shvar.h \ + ../ifcfg-rh/errors.c \ + ../ifcfg-rh/utils.c \ + ../ifcfg-rh/utils.h + +INCLUDES = \ + -I$(top_srcdir)/system-settings/plugins/ifcfg-rh \ + -I$(top_srcdir)/src/system-settings \ + -I$(top_srcdir)/include \ + -I$(top_srcdir)/libnm-glib \ + -I$(top_srcdir)/libnm-util \ + -I$(top_builddir)/marshallers + +libifcfg_mdv_io_la_CPPFLAGS = \ + $(GLIB_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(NSS_CFLAGS) \ + -DG_DISABLE_DEPRECATED \ + -DSYSCONFDIR=\"$(sysconfdir)\" \ + -DSBINDIR=\"$(sbindir)\" + +libifcfg_mdv_io_la_LIBADD = \ + $(top_builddir)/libnm-util/libnm-util.la \ + $(GLIB_LIBS) \ + $(NSS_LIBS) + +libnm_settings_plugin_ifcfg_mdv_la_SOURCES = \ + plugin.c \ + plugin.h \ + nm-ifcfg-connection.c \ + nm-ifcfg-connection.h + +libnm_settings_plugin_ifcfg_mdv_la_CPPFLAGS = \ + $(GLIB_CFLAGS) \ + $(GMODULE_CFLAGS) \ + $(DBUS_CFLAGS) \ + -DG_DISABLE_DEPRECATED \ + -DSYSCONFDIR=\"$(sysconfdir)\" + +libnm_settings_plugin_ifcfg_mdv_la_LDFLAGS = -module -avoid-version +libnm_settings_plugin_ifcfg_mdv_la_LIBADD = \ + $(top_builddir)/libnm-util/libnm-util.la \ + $(top_builddir)/libnm-glib/libnm-glib.la \ + $(top_builddir)/marshallers/libmarshallers.la \ + libifcfg-mdv-io.la \ + $(GLIB_LIBS) \ + $(GMODULE_LIBS) \ + $(GIO_LIBS) + diff --git a/system-settings/plugins/ifcfg-mdv/common.h b/system-settings/plugins/ifcfg-mdv/common.h new file mode 100644 index 0000000..edd5e69 --- /dev/null +++ b/system-settings/plugins/ifcfg-mdv/common.h @@ -0,0 +1,49 @@ +/* -*- 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. + * + * (C) Copyright 2008 - 2009 Red Hat, Inc. + */ + +#ifndef __COMMON_H__ +#define __COMMON_H__ + +#include <glib.h> + +#define IFCFG_TAG "ifcfg-" +#define KEYS_TAG "keys-" +#define ROUTE_TAG "route-" +#define ROUTE6_TAG "route6-" + +#define BAK_TAG ".bak" +#define TILDE_TAG "~" +#define ORIG_TAG ".orig" +#define REJ_TAG ".rej" +#define RPMNEW_TAG ".rpmnew" + +#define IFCFG_DIR SYSCONFDIR"/sysconfig/network-scripts" + +#define IFCFG_PLUGIN_NAME "ifcfg-mdv" +#define IFCFG_PLUGIN_INFO "(c) 2009 - 2010 Eugeni Dodonov <eugeni@mandriva.com>." + +#define TYPE_ETHERNET "Ethernet" +#define TYPE_WIRELESS "Wireless" + +GQuark ifcfg_plugin_error_quark (void); + + +#endif /* __COMMON_H__ */ + diff --git a/system-settings/plugins/ifcfg-mdv/nm-ifcfg-connection.c b/system-settings/plugins/ifcfg-mdv/nm-ifcfg-connection.c new file mode 100644 index 0000000..42bf1e6 --- /dev/null +++ b/system-settings/plugins/ifcfg-mdv/nm-ifcfg-connection.c @@ -0,0 +1,374 @@ +/* -*- 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 (C) 2008 - 2009 Red Hat, Inc. + */ + +#include <string.h> +#include <net/ethernet.h> +#include <netinet/ether.h> + +#include <glib/gstdio.h> + +#include <NetworkManager.h> +#include <nm-setting-connection.h> +#include <nm-setting-wired.h> +#include <nm-setting-wireless.h> +#include <nm-setting-gsm.h> +#include <nm-setting-cdma.h> +#include <nm-setting-pppoe.h> +#include <nm-setting-wireless-security.h> +#include <nm-setting-8021x.h> +#include <nm-settings-connection-interface.h> + +#include "common.h" +#include "nm-ifcfg-connection.h" +#include "reader.h" +#include "utils.h" +#include "writer.h" +#include "nm-inotify-helper.h" + +#include "parse_wpa_supplicant_conf.h" + +static NMSettingsConnectionInterface *parent_settings_connection_iface; + +static void settings_connection_interface_init (NMSettingsConnectionInterface *klass); + +G_DEFINE_TYPE_EXTENDED (NMIfcfgConnection, nm_ifcfg_connection, NM_TYPE_SYSCONFIG_CONNECTION, 0, + G_IMPLEMENT_INTERFACE (NM_TYPE_SETTINGS_CONNECTION_INTERFACE, + settings_connection_interface_init)) + +#define NM_IFCFG_CONNECTION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_IFCFG_CONNECTION, NMIfcfgConnectionPrivate)) + +typedef struct { + gulong ih_event_id; + + char *filename; + int file_wd; + + char *keyfile; + int keyfile_wd; + + char *routefile; + int routefile_wd; + + char *route6file; + int route6file_wd; + + char *udi; + char *unmanaged; +} NMIfcfgConnectionPrivate; + +enum { + PROP_0, + PROP_FILENAME, + PROP_UNMANAGED, + PROP_UDI, + + LAST_PROP +}; + +/* Signals */ +enum { + IFCFG_CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +static void +files_changed_cb (NMInotifyHelper *ih, + struct inotify_event *evt, + const char *path, + gpointer user_data) +{ + NMIfcfgConnection *self = NM_IFCFG_CONNECTION (user_data); + NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self); + + if ((evt->wd != priv->file_wd) && (evt->wd != priv->keyfile_wd) && (evt->wd != priv->routefile_wd) && (evt->wd != priv->route6file_wd)) + return; + + /* push the event up to the plugin */ + g_signal_emit (self, signals[IFCFG_CHANGED], 0); +} + +NMIfcfgConnection * +nm_ifcfg_connection_new (const char *filename, + GError **error, + gboolean *ignore_error) +{ + GObject *object; + NMIfcfgConnectionPrivate *priv; + NMConnection *tmp; + char *unmanaged = NULL; + char *keyfile = NULL; + char *routefile = NULL; + char *route6file = NULL; + NMInotifyHelper *ih; + + g_return_val_if_fail (filename != NULL, NULL); + + tmp = connection_from_file (filename, NULL, NULL, NULL, &unmanaged, &keyfile, &routefile, &route6file, error, ignore_error); + if (!tmp) + return NULL; + + object = (GObject *) g_object_new (NM_TYPE_IFCFG_CONNECTION, + NM_IFCFG_CONNECTION_FILENAME, filename, + NM_IFCFG_CONNECTION_UNMANAGED, unmanaged, + NULL); + if (!object) { + g_object_unref (tmp); + return NULL; + } + + /* Update our settings with what was read from the file */ + nm_sysconfig_connection_update (NM_SYSCONFIG_CONNECTION (object), tmp, FALSE, NULL); + g_object_unref (tmp); + + priv = NM_IFCFG_CONNECTION_GET_PRIVATE (object); + + ih = nm_inotify_helper_get (); + priv->ih_event_id = g_signal_connect (ih, "event", G_CALLBACK (files_changed_cb), object); + + priv->file_wd = nm_inotify_helper_add_watch (ih, filename); + + priv->keyfile = keyfile; + priv->keyfile_wd = nm_inotify_helper_add_watch (ih, keyfile); + + priv->routefile = routefile; + priv->routefile_wd = nm_inotify_helper_add_watch (ih, routefile); + + priv->route6file = route6file; + priv->route6file_wd = nm_inotify_helper_add_watch (ih, route6file); + + return NM_IFCFG_CONNECTION (object); +} + +const char * +nm_ifcfg_connection_get_filename (NMIfcfgConnection *self) +{ + g_return_val_if_fail (NM_IS_IFCFG_CONNECTION (self), NULL); + + return NM_IFCFG_CONNECTION_GET_PRIVATE (self)->filename; +} + +const char * +nm_ifcfg_connection_get_unmanaged_spec (NMIfcfgConnection *self) +{ + g_return_val_if_fail (NM_IS_IFCFG_CONNECTION (self), FALSE); + + return NM_IFCFG_CONNECTION_GET_PRIVATE (self)->unmanaged; +} + +static gboolean +update (NMSettingsConnectionInterface *connection, + NMSettingsConnectionInterfaceUpdateFunc callback, + gpointer user_data) +{ + NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (connection); + GError *error = NULL; + + if (!writer_update_connection (NM_CONNECTION (connection), + IFCFG_DIR, + priv->filename, + priv->keyfile, + &error)) { + callback (connection, error, user_data); + g_error_free (error); + return FALSE; + } + + return parent_settings_connection_iface->update (connection, callback, user_data); +} + +static gboolean +do_delete (NMSettingsConnectionInterface *connection, + NMSettingsConnectionInterfaceDeleteFunc callback, + gpointer user_data) +{ + NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (connection); + NMSettingWireless *s_wireless; + WPANetwork *wpan; + const GByteArray *ssid = NULL; + + s_wireless = (NMSettingWireless *)nm_connection_get_setting(NM_CONNECTION(connection), NM_TYPE_SETTING_WIRELESS); + if (s_wireless) + ssid = nm_setting_wireless_get_ssid(s_wireless); + + /* Delete network block from wpa_supplicant.conf */ + if (ssid) { + wpan = ifcfg_mdv_wpa_network_new(NULL); + if (wpan) { + ifcfg_mdv_wpa_network_set_ssid(wpan, ssid); + ifcfg_mdv_wpa_network_set_val(wpan, "__DELETE__", "yes"); + ifcfg_mdv_wpa_network_save(wpan, "/etc/wpa_supplicant.conf", NULL); + ifcfg_mdv_wpa_network_free(wpan); + } + } + + g_unlink (priv->filename); + if (priv->keyfile) + g_unlink (priv->keyfile); + if (priv->routefile) + g_unlink (priv->routefile); + + if (priv->route6file) + g_unlink (priv->route6file); + + return parent_settings_connection_iface->delete (connection, callback, user_data); +} + +/* GObject */ + +static void +settings_connection_interface_init (NMSettingsConnectionInterface *iface) +{ + parent_settings_connection_iface = g_type_interface_peek_parent (iface); + iface->update = update; + iface->delete = do_delete; +} + +static void +nm_ifcfg_connection_init (NMIfcfgConnection *connection) +{ +} + +static void +finalize (GObject *object) +{ + NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (object); + NMInotifyHelper *ih; + + g_free (priv->udi); + + nm_connection_clear_secrets (NM_CONNECTION (object)); + + ih = nm_inotify_helper_get (); + + g_signal_handler_disconnect (ih, priv->ih_event_id); + + g_free (priv->filename); + if (priv->file_wd >= 0) + nm_inotify_helper_remove_watch (ih, priv->file_wd); + + g_free (priv->keyfile); + if (priv->keyfile_wd >= 0) + nm_inotify_helper_remove_watch (ih, priv->keyfile_wd); + + g_free (priv->routefile); + if (priv->routefile_wd >= 0) + nm_inotify_helper_remove_watch (ih, priv->routefile_wd); + + g_free (priv->route6file); + if (priv->route6file_wd >= 0) + nm_inotify_helper_remove_watch (ih, priv->route6file_wd); + + G_OBJECT_CLASS (nm_ifcfg_connection_parent_class)->finalize (object); +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_FILENAME: + /* Construct only */ + priv->filename = g_value_dup_string (value); + break; + case PROP_UNMANAGED: + priv->unmanaged = g_value_dup_string (value); + break; + case PROP_UDI: + /* Construct only */ + priv->udi = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_FILENAME: + g_value_set_string (value, priv->filename); + break; + case PROP_UNMANAGED: + g_value_set_string (value, priv->unmanaged); + break; + case PROP_UDI: + g_value_set_string (value, priv->udi); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nm_ifcfg_connection_class_init (NMIfcfgConnectionClass *ifcfg_connection_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (ifcfg_connection_class); + + g_type_class_add_private (ifcfg_connection_class, sizeof (NMIfcfgConnectionPrivate)); + + /* Virtual methods */ + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->finalize = finalize; + + /* Properties */ + g_object_class_install_property + (object_class, PROP_FILENAME, + g_param_spec_string (NM_IFCFG_CONNECTION_FILENAME, + "FileName", + "File name", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_UNMANAGED, + g_param_spec_string (NM_IFCFG_CONNECTION_UNMANAGED, + "Unmanaged", + "Unmanaged", + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_UDI, + g_param_spec_string (NM_IFCFG_CONNECTION_UDI, + "UDI", + "UDI", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + signals[IFCFG_CHANGED] = + g_signal_new ("ifcfg-changed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + diff --git a/system-settings/plugins/ifcfg-mdv/nm-ifcfg-connection.h b/system-settings/plugins/ifcfg-mdv/nm-ifcfg-connection.h new file mode 100644 index 0000000..5cac5d9 --- /dev/null +++ b/system-settings/plugins/ifcfg-mdv/nm-ifcfg-connection.h @@ -0,0 +1,64 @@ +/* -*- 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 (C) 2008 Red Hat, Inc. + */ + +#ifndef NM_IFCFG_CONNECTION_H +#define NM_IFCFG_CONNECTION_H + +G_BEGIN_DECLS + +#include <NetworkManager.h> +#include <nm-sysconfig-connection.h> + +#define NM_TYPE_IFCFG_CONNECTION (nm_ifcfg_connection_get_type ()) +#define NM_IFCFG_CONNECTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_IFCFG_CONNECTION, NMIfcfgConnection)) +#define NM_IFCFG_CONNECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_IFCFG_CONNECTION, NMIfcfgConnectionClass)) +#define NM_IS_IFCFG_CONNECTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_IFCFG_CONNECTION)) +#define NM_IS_IFCFG_CONNECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), NM_TYPE_IFCFG_CONNECTION)) +#define NM_IFCFG_CONNECTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_IFCFG_CONNECTION, NMIfcfgConnectionClass)) + +#define NM_IFCFG_CONNECTION_FILENAME "filename" +#define NM_IFCFG_CONNECTION_UNMANAGED "unmanaged" +#define NM_IFCFG_CONNECTION_UDI "udi" + +typedef struct { + NMSysconfigConnection parent; +} NMIfcfgConnection; + +typedef struct { + NMSysconfigConnectionClass parent; +} NMIfcfgConnectionClass; + +GType nm_ifcfg_connection_get_type (void); + +NMIfcfgConnection *nm_ifcfg_connection_new (const char *filename, + GError **error, + gboolean *ignore_error); + +const char *nm_ifcfg_connection_get_filename (NMIfcfgConnection *self); + +const char *nm_ifcfg_connection_get_unmanaged_spec (NMIfcfgConnection *self); + +gboolean nm_ifcfg_connection_update (NMIfcfgConnection *self, + GHashTable *new_settings, + GError **error); + +G_END_DECLS + +#endif /* NM_IFCFG_CONNECTION_H */ diff --git a/system-settings/plugins/ifcfg-mdv/parse_wpa_supplicant_conf.c b/system-settings/plugins/ifcfg-mdv/parse_wpa_supplicant_conf.c new file mode 100644 index 0000000..e879c26 --- /dev/null +++ b/system-settings/plugins/ifcfg-mdv/parse_wpa_supplicant_conf.c @@ -0,0 +1,536 @@ +#include <errno.h> +#include <string.h> +#include <glib.h> +#include <glib/gstdio.h> + +#include "utils.h" +#include "common.h" + +#include "parse_wpa_supplicant_conf.h" + +struct _WPAConfig { + gchar *file; /* wpa_supplicant.conf file name */ + GString *line; /* Input buffer */ + GRegex *skip; /* Filter for comments */ + GRegex *network; /* Start of network definition */ + GRegex *fini; /* Closing curly bracket */ + GRegex *keyval; /* (key, val) pair in network def */ + GSList *list; /* list of networks */ + GSList *next; /* list iterator */ +}; + +struct _WPANetwork { + WPAConfig *parent; /* IO channel etc */ + GHashTable *keyvals; /* content */ +}; + +static gchar * +parse_wpa_string(const gchar *value, gsize *len) +{ + gchar *str; + gsize l; + + if (*value == '"') { + const gchar *pos; + value++; + pos = strrchr(value, '"'); + if (pos == NULL || pos[1] != '\0') + return NULL; + l = pos - value; + str = g_malloc(l + 1); + if (str == NULL) + return NULL; + memcpy(str, value, l); + str[l] = '\0'; + } else { + l = strlen(value); + str = utils_hexstr2bin(value, l); + if (str == NULL) + return NULL; + } + + if (len) + *len = l; + return str; +} + + +WPAConfig * +ifcfg_mdv_wpa_config_new(gchar *file) +{ + WPAConfig *wpac; + + g_return_val_if_fail(file != NULL, NULL); + + wpac = g_new(WPAConfig, 1); + if (!wpac) + return NULL; + + wpac->file = g_strdup(file); + wpac->line = g_string_new(""); + + wpac->skip = g_regex_new("^\\s*(#.*)?$", 0, 0, NULL); + wpac->network = g_regex_new("^\\s*network\\s*=\\s*{\\s*$", 0, 0, NULL); + wpac->fini = g_regex_new("^\\s*}\\s*$", 0, 0, NULL); + wpac->keyval = g_regex_new("^\\s*([\\w\\d]+)\\s*=\\s*(\\S+.*\\S*)\\s*$", 0, 0, NULL); + wpac->list = NULL; + wpac->next = NULL; + + if (!wpac->file || !wpac->line || !wpac->skip || + !wpac->network || !wpac->fini || !wpac->keyval) { + ifcfg_mdv_wpa_config_free(wpac); + return NULL; + } + + return wpac; +} + +void +ifcfg_mdv_wpa_config_free(WPAConfig *wpac) +{ + GSList *l; + + if (!wpac) + return; + + for (l = wpac->list; l; l = g_slist_next(l)) + ifcfg_mdv_wpa_network_free(l->data); + + g_slist_free(wpac->list); + + g_regex_unref(wpac->skip); + g_regex_unref(wpac->network); + g_regex_unref(wpac->fini); + g_regex_unref(wpac->keyval); + + g_string_free(wpac->line, TRUE); + g_free(wpac->file); + + g_free(wpac); +} + +WPANetwork * +ifcfg_mdv_wpa_network_new(WPAConfig *wpac) +{ + WPANetwork *wpan; + + // g_return_val_if_fail(wpac != NULL, NULL); + + wpan = g_new(WPANetwork, 1); + if (!wpan) + return NULL; + + wpan->keyvals = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + if (!wpan->keyvals) { + g_free(wpan); + return NULL; + } + wpan->parent = wpac; + + return wpan; +} + +void +ifcfg_mdv_wpa_network_free(WPANetwork *wpan) +{ + if (!wpan) + return; + + g_hash_table_destroy(wpan->keyvals); + g_free(wpan); +} + +static void +free_list(GSList **list) +{ + GSList *n; + + for (n = *list; n; n = g_slist_next(n)) + g_free(n->data); + if (*list) + g_slist_free(*list); + *list = NULL; +} + +gboolean +ifcfg_mdv_wpa_config_parse(WPAConfig *wpac) +{ + WPANetwork *wpan = NULL; + GIOChannel *ioc; + GMatchInfo *mi; + GError *error = NULL; + + g_return_val_if_fail(wpac != NULL, FALSE); + + ioc = g_io_channel_new_file(wpac->file, "r", &error); + if (!ioc) { + if (error->code == G_FILE_ERROR_NOENT) { + g_error_free(error); + return TRUE; + } + return FALSE; + } + + while (g_io_channel_read_line_string(ioc, wpac->line, NULL, NULL) == G_IO_STATUS_NORMAL) { + + if (g_regex_match(wpac->skip, wpac->line->str, 0, NULL)) + continue; + + if (!wpan && g_regex_match(wpac->network, wpac->line->str, 0, NULL)) { + wpan = ifcfg_mdv_wpa_network_new(wpac); + if (!wpan) + return FALSE; + continue; + } + + if (wpan && g_regex_match(wpac->keyval, wpac->line->str, 0, &mi)) { + gchar *key = g_match_info_fetch(mi, 1); + gchar *val = g_match_info_fetch(mi, 2); + ifcfg_mdv_wpa_network_set_val(wpan, key, val); + g_free(key); + g_free(val); + continue; + } + + if (wpan && g_regex_match(wpac->fini, wpac->line->str, 0, NULL)) { + wpac->list = g_slist_prepend(wpac->list, wpan); + wpac->next = wpac->list; + wpan = NULL; + } + } + + g_match_info_free(mi); + g_io_channel_unref(ioc); + + return TRUE; +} + +WPANetwork * +ifcfg_mdv_wpa_config_next(WPAConfig *wpac) +{ + GSList *l = wpac->next; + + if (l) + wpac->next = g_slist_next(l); + else + wpac->next = wpac->list; + + return l == NULL ? NULL : l->data; +} + +void +ifcfg_mdv_wpa_config_rewind(WPAConfig *wpac) +{ + wpac->next = wpac->list; +} + +gpointer +ifcfg_mdv_wpa_network_get_val(WPANetwork *wpan, const gchar *key) +{ + g_return_val_if_fail(wpan != NULL, NULL); + + return g_hash_table_lookup(wpan->keyvals, key); +} + +void +ifcfg_mdv_wpa_network_set_val(WPANetwork *wpan, const gchar *key, const gchar *val) +{ + gchar *k, *v; + + g_return_if_fail(wpan != NULL); + g_return_if_fail(key != NULL); + g_return_if_fail(val != NULL); + + k = g_strdup(key); + v = g_strdup(val); + g_hash_table_replace(wpan->keyvals, k, v); +} + +gchar * +ifcfg_mdv_wpa_network_get_str(WPANetwork *wpan, const gchar *key) +{ + gchar *value; + + g_return_val_if_fail(wpan != NULL, NULL); + g_return_val_if_fail(key != NULL, NULL); + + value = ifcfg_mdv_wpa_network_get_val(wpan, key); + if (!value) + return NULL; + + return parse_wpa_string(value, NULL); +} + +GByteArray * +ifcfg_mdv_wpa_network_get_ssid(WPANetwork *wpan) +{ + gchar *value, *ssid; + gsize len; + GByteArray *a; + + g_return_val_if_fail(wpan != NULL, NULL); + + value = ifcfg_mdv_wpa_network_get_val(wpan, "ssid"); + if (!value) + return NULL; + + ssid = parse_wpa_string(value, &len); + if (!ssid) + return NULL; + if (len == 0 || len > 32) + goto error; + + a = g_byte_array_sized_new (len); + if (!a) + goto error; + + g_byte_array_append (a, (const guint8 *) ssid, len); + g_free(ssid); + return a; + +error: + g_free(ssid); + return NULL; +} + +void +ifcfg_mdv_wpa_network_set_ssid(WPANetwork *wpan, const GByteArray *val) +{ + gchar buf[33]; + + g_return_if_fail(wpan != NULL); + g_return_if_fail(wpan != NULL); + + if (val->len == 0 || val->len > 32) + return; + + memcpy(buf, val->data, val->len); + buf[val->len] = '\0'; + ifcfg_mdv_wpa_network_set_str(wpan, "ssid", buf); +} + +void +ifcfg_mdv_wpa_network_set_str(WPANetwork *wpan, const gchar *key, const gchar *val) +{ + const gchar *p; + gchar *str; + gboolean need_hex = FALSE; + + /* We may get NULL for non-existing values */ + if (!val) { + ifcfg_mdv_wpa_network_unset(wpan, key); + return; + } + + for (p = val; *p; p++) + if (!g_ascii_isprint(*p)) { + need_hex = TRUE; + break; + } + + if (need_hex) + str = utils_bin2hexstr(val, strlen(val), -1); + else + str = g_strdup_printf("\"%s\"", val); + + if (str) + ifcfg_mdv_wpa_network_set_val(wpan, key, str); +#if 0 + else + PLUGIN_WARN(IFCFG_PLUGIN_NAME, " warning: could not set value for wpa key %s", key); +#endif + g_free(str); +} + +void +ifcfg_mdv_wpa_network_unset(WPANetwork *wpan, const gchar *key) +{ + g_return_if_fail(wpan != NULL); + g_return_if_fail(key != NULL); + + g_hash_table_remove(wpan->keyvals, key); +} + +static gboolean +add_line(GSList **list, gchar *s) +{ + gchar *n; + + g_return_val_if_fail(list != NULL, FALSE); + g_return_val_if_fail(s != NULL, FALSE); + + n = g_strdup(s); + if (!n) + return FALSE; + + *list = g_slist_append(*list, n); + if (!*list) + return FALSE; + + return TRUE; +} + +static gboolean +dump_network(GSList **list, WPANetwork *wpan, GError **error) +{ + GHashTableIter iter; + gpointer key, val; + gchar *s; + + g_return_val_if_fail(list != NULL, FALSE); + g_return_val_if_fail(wpan != NULL, FALSE); + + if (!add_line(list, "network={\n")) + return FALSE; + + g_hash_table_iter_init(&iter, wpan->keyvals); + while (g_hash_table_iter_next(&iter, &key, &val)) { + + s = g_strdup_printf("\t%s=%s\n", (gchar *)key, (gchar *)val); + if (!s) { + g_set_error(error, ifcfg_plugin_error_quark(), 0, + "Out of memory"); + return FALSE; + } + *list = g_slist_append(*list, s); + if (!*list) + return FALSE; + } + + if (!add_line(list, "}\n")) + return FALSE; + + return TRUE; +} + +gboolean +ifcfg_mdv_wpa_network_save(WPANetwork *wpan, gchar *file, GError **error) +{ + WPAConfig *wpac = NULL; + WPANetwork *o_wpan = NULL; + GIOStatus ret; + GSList *network = NULL, *wpa_rest = NULL, *l; + GIOChannel *ioc = NULL; + gsize written; + gchar *ssid; + gboolean result = FALSE, found = FALSE, delete = FALSE; + GMatchInfo *mi = NULL; + + g_return_val_if_fail(wpan != NULL, FALSE); + g_return_val_if_fail(file != NULL, FALSE); + + ssid = ifcfg_mdv_wpa_network_get_val(wpan, "ssid"); + if (!ssid || !*ssid) { + g_set_error(error, ifcfg_plugin_error_quark(), 0, + "SSID is missing, unable to store wpa_supplicant configuration"); + goto error; + } + + /* Looks like a hack but it probably is not worth extra function */ + if (ifcfg_mdv_wpa_network_get_val(wpan, "__DELETE__")) + delete = TRUE; + + ioc = g_io_channel_new_file(file, "r", error); + if (!ioc) { + if ((*error)->code == G_FILE_ERROR_NOENT) { + g_error_free(*error); + *error = NULL; + goto no_input; + } + goto error; + } + + wpac = ifcfg_mdv_wpa_config_new(""); + if (!wpac) + goto error; + + /* Read original file skipping network in wpan */ + while ((ret = g_io_channel_read_line_string(ioc, wpac->line, NULL, error)) == G_IO_STATUS_NORMAL) { + + /* shortcut */ + if (found) { + if (!add_line(&wpa_rest, wpac->line->str)) + goto error; + continue; + } + + if (!o_wpan && g_regex_match(wpac->network, wpac->line->str, 0, NULL)) { + if (!add_line(&network, wpac->line->str)) + goto error; + o_wpan = ifcfg_mdv_wpa_network_new(wpac); + if (!o_wpan) + goto error; + continue; + } + + if (o_wpan && g_regex_match(wpac->keyval, wpac->line->str, 0, &mi)) { + gchar *key = g_match_info_fetch(mi, 1); + gchar *val = g_match_info_fetch(mi, 2); + ifcfg_mdv_wpa_network_set_val(o_wpan, key, val); + g_free(key); + g_free(val); + + if (!add_line(&network, wpac->line->str)) + goto error; + continue; + } + + if (o_wpan && g_regex_match(wpac->fini, wpac->line->str, 0, NULL)) { + gchar *o_ssid; + + if (!add_line(&network, wpac->line->str)) + goto error; + o_ssid = ifcfg_mdv_wpa_network_get_val(o_wpan, "ssid"); + if (!o_ssid || g_strcmp0(ssid, o_ssid)) { + wpa_rest = g_slist_concat(wpa_rest, network); + ifcfg_mdv_wpa_network_free(o_wpan); + o_wpan = NULL; + network = NULL; + } else { + ifcfg_mdv_wpa_network_free(o_wpan); + o_wpan = NULL; + found = TRUE; + } + continue; + } + + if (!add_line(&wpa_rest, wpac->line->str)) + goto error; + } + + if (ret != G_IO_STATUS_EOF) + goto error; + + g_io_channel_unref(ioc); + ioc = NULL; + +no_input: + if (!delete && !dump_network(&wpa_rest, wpan, error)) + goto error; + + + ioc = g_io_channel_new_file(file, "w", error); + if (!ioc) + goto error; + g_chmod(file, 0600); + + for (l = wpa_rest; l; l = g_slist_next(l)) + if (g_io_channel_write_chars(ioc, l->data, -1, &written, error) != G_IO_STATUS_NORMAL) + goto error; + if (g_io_channel_flush(ioc, error) != G_IO_STATUS_NORMAL) + goto error; + + result = TRUE; + +error: + ifcfg_mdv_wpa_config_free(wpac); + ifcfg_mdv_wpa_network_free(o_wpan); + free_list(&wpa_rest); + free_list(&network); + if (ioc) + g_io_channel_unref(ioc); + if (mi) + g_match_info_free(mi); + + return result; +} diff --git a/system-settings/plugins/ifcfg-mdv/parse_wpa_supplicant_conf.h b/system-settings/plugins/ifcfg-mdv/parse_wpa_supplicant_conf.h new file mode 100644 index 0000000..3222f85 --- /dev/null +++ b/system-settings/plugins/ifcfg-mdv/parse_wpa_supplicant_conf.h @@ -0,0 +1,27 @@ +#ifndef PARSE_WPA_SUPPLICANT_CONF_H +#define PARSE_WPA_SUPPLICANT_CONF_H + +typedef struct _WPAConfig WPAConfig; +typedef struct _WPANetwork WPANetwork; + +gboolean ifcfg_mdv_wpa_config_parse(WPAConfig *); +void ifcfg_mdv_wpa_config_free(WPAConfig *); +WPAConfig *ifcfg_mdv_wpa_config_new(gchar *); + +WPANetwork *ifcfg_mdv_wpa_config_next(WPAConfig *); +void ifcfg_mdv_wpa_config_rewind(WPAConfig *); + +WPANetwork *ifcfg_mdv_wpa_network_new(WPAConfig *); +void ifcfg_mdv_wpa_network_free(WPANetwork *); + +gpointer ifcfg_mdv_wpa_network_get_val(WPANetwork *, const gchar *); +void ifcfg_mdv_wpa_network_set_val(WPANetwork *, const gchar *, const gchar *); +gchar *ifcfg_mdv_wpa_network_get_str(WPANetwork *, const gchar *); +void ifcfg_mdv_wpa_network_set_str(WPANetwork *, const gchar *, const gchar *); +GByteArray *ifcfg_mdv_wpa_network_get_ssid(WPANetwork *); +void ifcfg_mdv_wpa_network_set_ssid(WPANetwork *, const GByteArray *); +void ifcfg_mdv_wpa_network_unset(WPANetwork *, const gchar *); + +gboolean ifcfg_mdv_wpa_network_save(WPANetwork *, gchar *, GError **); + +#endif /* PARSE_WPA_SUPPLICANT_CONF_H */ diff --git a/system-settings/plugins/ifcfg-mdv/plugin.c b/system-settings/plugins/ifcfg-mdv/plugin.c new file mode 100644 index 0000000..9640025 --- /dev/null +++ b/system-settings/plugins/ifcfg-mdv/plugin.c @@ -0,0 +1,657 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager system settings service + * + * Dan Williams <dcbw@redhat.com> + * Søren Sandmann <sandmann@daimi.au.dk> + * + * 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 (C) 2007 - 2008 Red Hat, Inc. + */ + +#include <config.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <net/ethernet.h> +#include <netinet/ether.h> + +#include <gmodule.h> +#include <glib-object.h> +#include <glib/gi18n.h> +#include <gio/gio.h> + +#include <dbus/dbus-glib.h> + +#include <nm-setting-connection.h> + +#include "common.h" +#include "nm-dbus-glib-types.h" +#include "plugin.h" +#include "nm-system-config-interface.h" +#include "nm-ifcfg-connection.h" +#include "nm-inotify-helper.h" +#include "shvar.h" +#include "writer.h" +#include "utils.h" + +static void system_config_interface_init (NMSystemConfigInterface *system_config_interface_class); + +static void connection_changed_handler (SCPluginIfcfg *plugin, + const char *path, + NMIfcfgConnection *connection, + gboolean *do_remove, + gboolean *do_new); + +static void handle_connection_remove_or_new (SCPluginIfcfg *plugin, + const char *path, + NMIfcfgConnection *connection, + gboolean do_remove, + gboolean do_new); + +G_DEFINE_TYPE_EXTENDED (SCPluginIfcfg, sc_plugin_ifcfg, G_TYPE_OBJECT, 0, + G_IMPLEMENT_INTERFACE (NM_TYPE_SYSTEM_CONFIG_INTERFACE, + system_config_interface_init)) + +#define SC_PLUGIN_IFCFG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SC_TYPE_PLUGIN_IFCFG, SCPluginIfcfgPrivate)) + + +typedef struct { + GHashTable *connections; + + gulong ih_event_id; + int sc_network_wd; + char *hostname; + + GFileMonitor *monitor; + guint monitor_id; +} SCPluginIfcfgPrivate; + + +static void +connection_unmanaged_changed (NMIfcfgConnection *connection, + GParamSpec *pspec, + gpointer user_data) +{ + g_signal_emit_by_name (SC_PLUGIN_IFCFG (user_data), NM_SYSTEM_CONFIG_INTERFACE_UNMANAGED_SPECS_CHANGED); +} + +static void +connection_ifcfg_changed (NMIfcfgConnection *connection, gpointer user_data) +{ + SCPluginIfcfg *plugin = SC_PLUGIN_IFCFG (user_data); + gboolean do_remove = FALSE, do_new = FALSE; + const char *path; + + path = nm_ifcfg_connection_get_filename (connection); + g_return_if_fail (path != NULL); + + connection_changed_handler (plugin, path, connection, &do_remove, &do_new); + handle_connection_remove_or_new (plugin, path, connection, do_remove, do_new); +} + +static NMIfcfgConnection * +read_one_connection (SCPluginIfcfg *plugin, const char *filename) +{ + SCPluginIfcfgPrivate *priv = SC_PLUGIN_IFCFG_GET_PRIVATE (plugin); + NMIfcfgConnection *connection; + GError *error = NULL; + gboolean ignore_error = FALSE; + + PLUGIN_PRINT (IFCFG_PLUGIN_NAME, "parsing %s ... ", filename); + + connection = nm_ifcfg_connection_new (filename, &error, &ignore_error); + if (connection) { + NMSettingConnection *s_con; + const char *cid; + + s_con = (NMSettingConnection *) nm_connection_get_setting (NM_CONNECTION (connection), NM_TYPE_SETTING_CONNECTION); + g_assert (s_con); + + cid = nm_setting_connection_get_id (s_con); + g_assert (cid); + + g_hash_table_insert (priv->connections, + (gpointer) nm_ifcfg_connection_get_filename (connection), + g_object_ref (connection)); + PLUGIN_PRINT (IFCFG_PLUGIN_NAME, " read connection '%s'", cid); + + if (nm_ifcfg_connection_get_unmanaged_spec (connection)) { + PLUGIN_PRINT (IFCFG_PLUGIN_NAME, "Ignoring connection '%s' and its " + "device because NM_CONTROLLED was not true or ONBOOT was set.", cid); + g_signal_emit_by_name (plugin, NM_SYSTEM_CONFIG_INTERFACE_UNMANAGED_SPECS_CHANGED); + } else { + /* Wait for the connection to become unmanaged once it knows the + * UDI of it's device, if/when the device gets plugged in. + */ + g_signal_connect (G_OBJECT (connection), "notify::unmanaged", + G_CALLBACK (connection_unmanaged_changed), plugin); + } + + /* watch changes of ifcfg hardlinks */ + g_signal_connect (G_OBJECT (connection), "ifcfg-changed", + G_CALLBACK (connection_ifcfg_changed), plugin); + } else { + if (!ignore_error) { + PLUGIN_PRINT (IFCFG_PLUGIN_NAME, " error: %s", + (error && error->message) ? error->message : "(unknown)"); + } + g_error_free (error); + } + + return connection; +} + +static void +read_connections (SCPluginIfcfg *plugin) +{ + GDir *dir; + GError *err = NULL; + + dir = g_dir_open (IFCFG_DIR, 0, &err); + if (dir) { + const char *item; + + while ((item = g_dir_read_name (dir))) { + char *full_path; + + if (utils_should_ignore_file (item, TRUE)) + continue; + + full_path = g_build_filename (IFCFG_DIR, item, NULL); + read_one_connection (plugin, full_path); + g_free (full_path); + } + + g_dir_close (dir); + } else { + PLUGIN_WARN (IFCFG_PLUGIN_NAME, "Can not read directory '%s': %s", IFCFG_DIR, err->message); + g_error_free (err); + } +} + +/* Monitoring */ + +static void +connection_changed_handler (SCPluginIfcfg *plugin, + const char *path, + NMIfcfgConnection *connection, + gboolean *do_remove, + gboolean *do_new) +{ + NMIfcfgConnection *new; + GError *error = NULL; + gboolean ignore_error = FALSE; + const char *new_unmanaged = NULL, *old_unmanaged = NULL; + + g_return_if_fail (plugin != NULL); + g_return_if_fail (path != NULL); + g_return_if_fail (connection != NULL); + g_return_if_fail (do_remove != NULL); + g_return_if_fail (do_new != NULL); + + PLUGIN_PRINT (IFCFG_PLUGIN_NAME, "updating %s", path); + + new = (NMIfcfgConnection *) nm_ifcfg_connection_new (path, &error, &ignore_error); + if (!new) { + /* errors reading connection; remove it */ + if (!ignore_error) { + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " error: %s", + (error && error->message) ? error->message : "(unknown)"); + } + g_clear_error (&error); + + PLUGIN_PRINT (IFCFG_PLUGIN_NAME, "removed %s.", path); + *do_remove = TRUE; + return; + } + + /* Successfully read connection changes */ + + old_unmanaged = nm_ifcfg_connection_get_unmanaged_spec (NM_IFCFG_CONNECTION (connection)); + new_unmanaged = nm_ifcfg_connection_get_unmanaged_spec (NM_IFCFG_CONNECTION (new)); + + if (new_unmanaged) { + if (!old_unmanaged) { + /* Unexport the connection by destroying it, then re-creating it as unmanaged */ + *do_remove = *do_new = TRUE; + } + } else { + if (old_unmanaged) { /* now managed */ + NMSettingConnection *s_con; + const char *cid; + + s_con = (NMSettingConnection *) nm_connection_get_setting (NM_CONNECTION (new), NM_TYPE_SETTING_CONNECTION); + g_assert (s_con); + + cid = nm_setting_connection_get_id (s_con); + g_assert (cid); + + PLUGIN_PRINT (IFCFG_PLUGIN_NAME, "Managing connection '%s' and its " + "device because NM_CONTROLLED was true.", cid); + g_signal_emit_by_name (plugin, NM_SYSTEM_CONFIG_INTERFACE_CONNECTION_ADDED, connection); + } + + if (!nm_sysconfig_connection_update (NM_SYSCONFIG_CONNECTION (connection), + NM_CONNECTION (new), + TRUE, + &error)) { + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " error updating: %s", + (error && error->message) ? error->message : "(unknown)"); + g_clear_error (&error); + } + + /* Update unmanaged status */ + g_object_set (connection, "unmanaged", new_unmanaged, NULL); + g_signal_emit_by_name (plugin, NM_SYSTEM_CONFIG_INTERFACE_UNMANAGED_SPECS_CHANGED); + } + g_object_unref (new); +} + +static void +handle_connection_remove_or_new (SCPluginIfcfg *plugin, + const char *path, + NMIfcfgConnection *connection, + gboolean do_remove, + gboolean do_new) +{ + SCPluginIfcfgPrivate *priv = SC_PLUGIN_IFCFG_GET_PRIVATE (plugin); + + g_return_if_fail (plugin != NULL); + g_return_if_fail (path != NULL); + + if (do_remove) { + const char *unmanaged; + + g_return_if_fail (connection != NULL); + + unmanaged = nm_ifcfg_connection_get_unmanaged_spec (connection); + g_hash_table_remove (priv->connections, path); + g_signal_emit_by_name (connection, "removed"); + + /* Emit unmanaged changes _after_ removing the connection */ + if (unmanaged) + g_signal_emit_by_name (plugin, NM_SYSTEM_CONFIG_INTERFACE_UNMANAGED_SPECS_CHANGED); + } + + if (do_new) { + connection = read_one_connection (plugin, path); + if (connection) { + if (!nm_ifcfg_connection_get_unmanaged_spec (connection)) + g_signal_emit_by_name (plugin, NM_SYSTEM_CONFIG_INTERFACE_CONNECTION_ADDED, connection); + } + } +} +static void +dir_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + SCPluginIfcfg *plugin = SC_PLUGIN_IFCFG (user_data); + SCPluginIfcfgPrivate *priv = SC_PLUGIN_IFCFG_GET_PRIVATE (plugin); + char *path, *name; + NMIfcfgConnection *connection; + gboolean do_remove = FALSE, do_new = FALSE; + + path = g_file_get_path (file); + if (utils_should_ignore_file (path, FALSE)) { + g_free (path); + return; + } + + /* Given any ifcfg, keys, or routes file, get the ifcfg file path */ + name = utils_get_ifcfg_path (path); + g_free (path); + + connection = g_hash_table_lookup (priv->connections, name); + if (!connection) { + do_new = TRUE; + } else { + switch (event_type) { + case G_FILE_MONITOR_EVENT_DELETED: + PLUGIN_PRINT (IFCFG_PLUGIN_NAME, "removed %s.", name); + do_remove = TRUE; + break; + case G_FILE_MONITOR_EVENT_CREATED: + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + /* Update */ + connection_changed_handler (plugin, name, connection, &do_remove, &do_new); + break; + default: + break; + } + } + + handle_connection_remove_or_new (plugin, name, connection, do_remove, do_new); + + g_free (name); +} + +static void +setup_ifcfg_monitoring (SCPluginIfcfg *plugin) +{ + SCPluginIfcfgPrivate *priv = SC_PLUGIN_IFCFG_GET_PRIVATE (plugin); + GFile *file; + GFileMonitor *monitor; + + priv->connections = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref); + + file = g_file_new_for_path (IFCFG_DIR "/"); + monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL); + g_object_unref (file); + + if (monitor) { + priv->monitor_id = g_signal_connect (monitor, "changed", G_CALLBACK (dir_changed), plugin); + priv->monitor = monitor; + } +} + +static GSList * +get_connections (NMSystemConfigInterface *config) +{ + SCPluginIfcfg *plugin = SC_PLUGIN_IFCFG (config); + SCPluginIfcfgPrivate *priv = SC_PLUGIN_IFCFG_GET_PRIVATE (plugin); + GSList *list = NULL; + GHashTableIter iter; + gpointer value; + + if (!priv->connections) { + setup_ifcfg_monitoring (plugin); + read_connections (plugin); + } + + g_hash_table_iter_init (&iter, priv->connections); + while (g_hash_table_iter_next (&iter, NULL, &value)) { + NMIfcfgConnection *exported = NM_IFCFG_CONNECTION (value); + + if (!nm_ifcfg_connection_get_unmanaged_spec (exported)) + list = g_slist_prepend (list, value); + } + + return list; +} + +static void +check_unmanaged (gpointer key, gpointer data, gpointer user_data) +{ + GSList **list = (GSList **) user_data; + NMIfcfgConnection *connection = NM_IFCFG_CONNECTION (data); + const char *unmanaged_spec; + GSList *iter; + + unmanaged_spec = nm_ifcfg_connection_get_unmanaged_spec (connection); + if (!unmanaged_spec) + return; + + /* Just return if the unmanaged spec is already in the list */ + for (iter = *list; iter; iter = g_slist_next (iter)) { + if (!strcmp ((char *) iter->data, unmanaged_spec)) + return; + } + + *list = g_slist_prepend (*list, g_strdup (unmanaged_spec)); +} + +static GSList * +get_unmanaged_specs (NMSystemConfigInterface *config) +{ + SCPluginIfcfg *plugin = SC_PLUGIN_IFCFG (config); + SCPluginIfcfgPrivate *priv = SC_PLUGIN_IFCFG_GET_PRIVATE (config); + GSList *list = NULL; + + if (!priv->connections) { + setup_ifcfg_monitoring (plugin); + read_connections (plugin); + } + + g_hash_table_foreach (priv->connections, check_unmanaged, &list); + return list; +} + +static gboolean +add_connection (NMSystemConfigInterface *config, + NMConnection *connection, + GError **error) +{ + return writer_new_connection (connection, IFCFG_DIR, NULL, error); +} + +#define SC_NETWORK_FILE SYSCONFDIR"/sysconfig/network" + +static char * +plugin_get_hostname (SCPluginIfcfg *plugin) +{ + shvarFile *network; + char *hostname; + gboolean ignore_localhost; + + network = svNewFile (SC_NETWORK_FILE); + if (!network) { + PLUGIN_WARN (IFCFG_PLUGIN_NAME, "Could not get hostname: failed to read " SC_NETWORK_FILE); + return FALSE; + } + + hostname = svGetValue (network, "HOSTNAME", FALSE); + ignore_localhost = svTrueValue (network, "NM_IGNORE_HOSTNAME_LOCALHOST", FALSE); + if (ignore_localhost) { + /* Ignore a hostname of 'localhost' or 'localhost.localdomain' to preserve + * 'network' service behavior. + */ + if (hostname && (!strcmp (hostname, "localhost") || !strcmp (hostname, "localhost.localdomain"))) { + g_free (hostname); + hostname = NULL; + } + } + + svCloseFile (network); + return hostname; +} + +static gboolean +plugin_set_hostname (SCPluginIfcfg *plugin, const char *hostname) +{ + SCPluginIfcfgPrivate *priv = SC_PLUGIN_IFCFG_GET_PRIVATE (plugin); + shvarFile *network; + + network = svCreateFile (SC_NETWORK_FILE); + if (!network) { + PLUGIN_WARN (IFCFG_PLUGIN_NAME, "Could not save hostname: failed to create/open " SC_NETWORK_FILE); + return FALSE; + } + + svSetValue (network, "HOSTNAME", hostname, FALSE); + svWriteFile (network, 0644); + svCloseFile (network); + + g_free (priv->hostname); + priv->hostname = hostname ? g_strdup (hostname) : NULL; + return TRUE; +} + +static void +sc_network_changed_cb (NMInotifyHelper *ih, + struct inotify_event *evt, + const char *path, + gpointer user_data) +{ + SCPluginIfcfg *plugin = SC_PLUGIN_IFCFG (user_data); + SCPluginIfcfgPrivate *priv = SC_PLUGIN_IFCFG_GET_PRIVATE (plugin); + char *new_hostname; + + if (evt->wd != priv->sc_network_wd) + return; + + new_hostname = plugin_get_hostname (plugin); + if ( (new_hostname && !priv->hostname) + || (!new_hostname && priv->hostname) + || (priv->hostname && new_hostname && strcmp (priv->hostname, new_hostname))) { + g_free (priv->hostname); + priv->hostname = new_hostname; + g_object_notify (G_OBJECT (plugin), NM_SYSTEM_CONFIG_INTERFACE_HOSTNAME); + } else + g_free (new_hostname); +} + +static void +init (NMSystemConfigInterface *config) +{ +} + +static void +sc_plugin_ifcfg_init (SCPluginIfcfg *plugin) +{ + SCPluginIfcfgPrivate *priv = SC_PLUGIN_IFCFG_GET_PRIVATE (plugin); + NMInotifyHelper *ih; + + ih = nm_inotify_helper_get (); + priv->ih_event_id = g_signal_connect (ih, "event", G_CALLBACK (sc_network_changed_cb), plugin); + priv->sc_network_wd = nm_inotify_helper_add_watch (ih, SC_NETWORK_FILE); + + priv->hostname = plugin_get_hostname (plugin); +} + +static void +dispose (GObject *object) +{ + SCPluginIfcfg *plugin = SC_PLUGIN_IFCFG (object); + SCPluginIfcfgPrivate *priv = SC_PLUGIN_IFCFG_GET_PRIVATE (plugin); + NMInotifyHelper *ih; + + ih = nm_inotify_helper_get (); + + g_signal_handler_disconnect (ih, priv->ih_event_id); + + if (priv->sc_network_wd >= 0) + nm_inotify_helper_remove_watch (ih, priv->sc_network_wd); + + g_free (priv->hostname); + + if (priv->connections) + g_hash_table_destroy (priv->connections); + + if (priv->monitor) { + if (priv->monitor_id) + g_signal_handler_disconnect (priv->monitor, priv->monitor_id); + + g_file_monitor_cancel (priv->monitor); + g_object_unref (priv->monitor); + } + + G_OBJECT_CLASS (sc_plugin_ifcfg_parent_class)->dispose (object); +} + +static void +finalize (GObject *object) +{ + G_OBJECT_CLASS (sc_plugin_ifcfg_parent_class)->finalize (object); +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + SCPluginIfcfgPrivate *priv = SC_PLUGIN_IFCFG_GET_PRIVATE (object); + + switch (prop_id) { + case NM_SYSTEM_CONFIG_INTERFACE_PROP_NAME: + g_value_set_string (value, IFCFG_PLUGIN_NAME); + break; + case NM_SYSTEM_CONFIG_INTERFACE_PROP_INFO: + g_value_set_string (value, IFCFG_PLUGIN_INFO); + break; + case NM_SYSTEM_CONFIG_INTERFACE_PROP_CAPABILITIES: + g_value_set_uint (value, NM_SYSTEM_CONFIG_INTERFACE_CAP_MODIFY_CONNECTIONS | NM_SYSTEM_CONFIG_INTERFACE_CAP_MODIFY_HOSTNAME); + break; + case NM_SYSTEM_CONFIG_INTERFACE_PROP_HOSTNAME: + g_value_set_string (value, priv->hostname); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + const char *hostname; + + switch (prop_id) { + case NM_SYSTEM_CONFIG_INTERFACE_PROP_HOSTNAME: + hostname = g_value_get_string (value); + if (hostname && strlen (hostname) < 1) + hostname = NULL; + plugin_set_hostname (SC_PLUGIN_IFCFG (object), hostname); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +sc_plugin_ifcfg_class_init (SCPluginIfcfgClass *req_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (req_class); + + g_type_class_add_private (req_class, sizeof (SCPluginIfcfgPrivate)); + + object_class->dispose = dispose; + object_class->finalize = finalize; + object_class->get_property = get_property; + object_class->set_property = set_property; + + g_object_class_override_property (object_class, + NM_SYSTEM_CONFIG_INTERFACE_PROP_NAME, + NM_SYSTEM_CONFIG_INTERFACE_NAME); + + g_object_class_override_property (object_class, + NM_SYSTEM_CONFIG_INTERFACE_PROP_INFO, + NM_SYSTEM_CONFIG_INTERFACE_INFO); + + g_object_class_override_property (object_class, + NM_SYSTEM_CONFIG_INTERFACE_PROP_CAPABILITIES, + NM_SYSTEM_CONFIG_INTERFACE_CAPABILITIES); + + g_object_class_override_property (object_class, + NM_SYSTEM_CONFIG_INTERFACE_PROP_HOSTNAME, + NM_SYSTEM_CONFIG_INTERFACE_HOSTNAME); +} + +static void +system_config_interface_init (NMSystemConfigInterface *system_config_interface_class) +{ + /* interface implementation */ + system_config_interface_class->get_connections = get_connections; + system_config_interface_class->add_connection = add_connection; + system_config_interface_class->get_unmanaged_specs = get_unmanaged_specs; + system_config_interface_class->init = init; +} + +G_MODULE_EXPORT GObject * +nm_system_config_factory (void) +{ + static SCPluginIfcfg *singleton = NULL; + + if (!singleton) + singleton = SC_PLUGIN_IFCFG (g_object_new (SC_TYPE_PLUGIN_IFCFG, NULL)); + else + g_object_ref (singleton); + + return G_OBJECT (singleton); +} diff --git a/system-settings/plugins/ifcfg-mdv/plugin.h b/system-settings/plugins/ifcfg-mdv/plugin.h new file mode 100644 index 0000000..d820a26 --- /dev/null +++ b/system-settings/plugins/ifcfg-mdv/plugin.h @@ -0,0 +1,50 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager system settings service + * + * Dan Williams <dcbw@redhat.com> + * Søren Sandmann <sandmann@daimi.au.dk> + * + * 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 (C) 2007 - 2008 Red Hat, Inc. + */ + +#ifndef _PLUGIN_H_ +#define _PLUGIN_H_ + +#include <glib-object.h> + +#define SC_TYPE_PLUGIN_IFCFG (sc_plugin_ifcfg_get_type ()) +#define SC_PLUGIN_IFCFG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SC_TYPE_PLUGIN_IFCFG, SCPluginIfcfg)) +#define SC_PLUGIN_IFCFG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SC_TYPE_PLUGIN_IFCFG, SCPluginIfcfgClass)) +#define SC_IS_PLUGIN_IFCFG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SC_TYPE_PLUGIN_IFCFG)) +#define SC_IS_PLUGIN_IFCFG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SC_TYPE_PLUGIN_IFCFG)) +#define SC_PLUGIN_IFCFG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SC_TYPE_PLUGIN_IFCFG, SCPluginIfcfgClass)) + +typedef struct _SCPluginIfcfg SCPluginIfcfg; +typedef struct _SCPluginIfcfgClass SCPluginIfcfgClass; + +struct _SCPluginIfcfg { + GObject parent; +}; + +struct _SCPluginIfcfgClass { + GObjectClass parent; +}; + +GType sc_plugin_ifcfg_get_type (void); + +#endif /* _PLUGIN_H_ */ + diff --git a/system-settings/plugins/ifcfg-mdv/reader.c b/system-settings/plugins/ifcfg-mdv/reader.c new file mode 100644 index 0000000..5097953 --- /dev/null +++ b/system-settings/plugins/ifcfg-mdv/reader.c @@ -0,0 +1,3452 @@ +/* -*- 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 (C) 2008 - 2009 Red Hat, Inc. + * Mandriva-specific changes by Eugeni Dodonov <eugeni@mandriva.com>. + */ + +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <sys/wait.h> +#include <ctype.h> +#include <sys/inotify.h> +#include <errno.h> +#include <net/if.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <netinet/ether.h> +#include <netinet/in.h> + +#ifndef __user +#define __user +#endif +#include <linux/types.h> +#include <wireless.h> +#undef __user + +#include <glib.h> +#include <glib/gi18n.h> +#include <nm-connection.h> +#include <NetworkManager.h> +#include <nm-setting-connection.h> +#include <nm-setting-ip4-config.h> +#include <nm-setting-ip6-config.h> +#include <nm-setting-wired.h> +#include <nm-setting-wireless.h> +#include <nm-setting-8021x.h> +#include <nm-utils.h> + +#include "common.h" +#include "shvar.h" +#include "utils.h" + +#include "reader.h" +#include "parse_wpa_supplicant_conf.h" + +#define PLUGIN_PRINT(pname, fmt, args...) \ + { g_message (" " pname ": " fmt, ##args); } + +#define PLUGIN_WARN(pname, fmt, args...) \ + { g_warning (" " pname ": " fmt, ##args); } + +static gboolean eap_simple_reader (const char *eap_method, + WPANetwork *wpan, + shvarFile *ifcfg, + shvarFile *keys, + NMSetting8021x *s_8021x, + gboolean phase2, + GError **error); + +static gboolean eap_tls_reader (const char *eap_method, + WPANetwork *wpan, + shvarFile *ifcfg, + shvarFile *keys, + NMSetting8021x *s_8021x, + gboolean phase2, + GError **error); + +static gboolean eap_peap_reader (const char *eap_method, + WPANetwork *wpan, + shvarFile *ifcfg, + shvarFile *keys, + NMSetting8021x *s_8021x, + gboolean phase2, + GError **error); + +static gboolean eap_ttls_reader (const char *eap_method, + WPANetwork *wpan, + shvarFile *ifcfg, + shvarFile *keys, + NMSetting8021x *s_8021x, + gboolean phase2, + GError **error); + +static gboolean +get_int (const char *str, int *value) +{ + char *e; + + errno = 0; + *value = strtol (str, &e, 0); + if (errno || *e != '\0') + return FALSE; + + return TRUE; +} + +static NMSetting * +make_connection_setting (const char *file, + shvarFile *ifcfg, + const char *type, + const char *suggested) +{ + NMSettingConnection *s_con; + const char *ifcfg_name = NULL; + char *new_id = NULL, *uuid = NULL, *value; + // char *ifcfg_id; + + ifcfg_name = utils_get_ifcfg_name (file, TRUE); + if (!ifcfg_name) + return NULL; + + s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ()); + + /* Try the ifcfg file's internally defined name if available */ +#if 0 + /* Mandriva does not use or set NAME */ + // ifcfg_id = svGetValue (ifcfg, "NAME", FALSE); + if (ifcfg_id && strlen (ifcfg_id)) + g_object_set (s_con, NM_SETTING_CONNECTION_ID, ifcfg_id, NULL); +#endif + + if (!nm_setting_connection_get_id (s_con)) { + if (suggested) { + /* For cosmetic reasons, if the suggested name is the same as + * the ifcfg files name, don't use it. Mainly for wifi so that + * the SSID is shown in the connection ID instead of just "wlan0". + */ + if (strcmp (ifcfg_name, suggested)) { + new_id = g_strdup_printf ("%s %s (%s)", reader_get_prefix (), suggested, ifcfg_name); + g_object_set (s_con, NM_SETTING_CONNECTION_ID, new_id, NULL); + } + } + + /* Use the ifcfg file's name as a last resort */ + if (!nm_setting_connection_get_id (s_con)) { + new_id = g_strdup_printf ("%s %s", reader_get_prefix (), ifcfg_name); + g_object_set (s_con, NM_SETTING_CONNECTION_ID, new_id, NULL); + } + } + + g_free (new_id); + // g_free (ifcfg_id); + +#if 0 + /* Try for a UUID key before falling back to hashing the file name */ + uuid = svGetValue (ifcfg, "UUID", FALSE); +#endif + if (!uuid || !strlen (uuid)) { + g_free (uuid); + uuid = nm_utils_uuid_generate_from_string (ifcfg->fileName); + } + g_object_set (s_con, + NM_SETTING_CONNECTION_TYPE, type, + NM_SETTING_CONNECTION_UUID, uuid, + NULL); + g_free (uuid); + + /* Missing ONBOOT is treated as "ONBOOT=true" by the old network service */ + /* FIXME temporary until we can use ONBOOT again */ + g_object_set (s_con, NM_SETTING_CONNECTION_AUTOCONNECT, + svTrueValue (ifcfg, "_NM_ONBOOT", TRUE), + NULL); + + value = svGetValue (ifcfg, "LAST_CONNECT", FALSE); + if (value) { + unsigned long int tmp; + guint64 timestamp; + + errno = 0; + tmp = strtoul (value, NULL, 10); + if (errno == 0) { + timestamp = (guint64) tmp; + g_object_set (s_con, NM_SETTING_CONNECTION_TIMESTAMP, timestamp, NULL); + } else + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: invalid LAST_CONNECT time"); + g_free (value); + } + + return NM_SETTING (s_con); +} + +static gboolean +discover_mac_address(char *device, GByteArray **array, GError **error) +{ + int fd, ret; + struct ifreq ifr; + + g_return_val_if_fail (device != NULL, FALSE); + g_return_val_if_fail (array != NULL, FALSE); + g_return_val_if_fail (*array == NULL, FALSE); + g_return_val_if_fail (error != NULL, FALSE); + g_return_val_if_fail (*error == NULL, FALSE); + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + g_set_error(error, ifcfg_plugin_error_quark(), errno, + "Unable to discover MAC address: socket error"); + return FALSE; + } + + ifr.ifr_addr.sa_family = AF_INET; + strncpy(ifr.ifr_name, device, IFNAMSIZ-1); + + ret = ioctl(fd, SIOCGIFHWADDR, &ifr); + if (ret < 0) { + g_set_error(error, ifcfg_plugin_error_quark(), errno, + "Unable to discover MAC address: ioctl error"); + return FALSE; + } + close(fd); + + *array = g_byte_array_sized_new (ETH_ALEN); + g_byte_array_append (*array, (guint8 *) ifr.ifr_hwaddr.sa_data, ETH_ALEN); + return TRUE; +} + +static gboolean +read_mac_address (shvarFile *ifcfg, GByteArray **array, GError **error) +{ + char *value = NULL; + struct ether_addr *mac; + + g_return_val_if_fail (ifcfg != NULL, FALSE); + g_return_val_if_fail (array != NULL, FALSE); + g_return_val_if_fail (*array == NULL, FALSE); + g_return_val_if_fail (error != NULL, FALSE); + g_return_val_if_fail (*error == NULL, FALSE); + + value = svGetValue (ifcfg, "HWADDR", FALSE); + if (!value || !strlen (value)) { + g_free (value); + return TRUE; + } + + mac = ether_aton (value); + if (!mac) { + g_free (value); + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "The MAC address '%s' was invalid.", value); + return FALSE; + } + + g_free (value); + *array = g_byte_array_sized_new (ETH_ALEN); + g_byte_array_append (*array, (guint8 *) mac->ether_addr_octet, ETH_ALEN); + return TRUE; +} + +/* Mandriva does not seem to ever hex-encode SSID in ifcfg. So do not bother + * as well - just get what we have. This highly simplifies the logic */ +/* FIXME this currently fails for '\0' which is not accepted as input either */ +GByteArray * +ifcfg_mdv_parse_ssid(char *value, GError **error) +{ + gsize ssid_len; + gchar *ssid = NULL; + GByteArray *a; + + ssid = g_strdup(value); + if (!ssid) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Cannot duplicate SSID"); + goto error; + } + svUnescape (ssid); + ssid_len = strlen (ssid); + if (ssid_len > 32 || ssid_len == 0) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid SSID '%s' (size %zu not between 1 and 32 inclusive)", + value, ssid_len); + goto error; + } + + a = g_byte_array_sized_new (ssid_len); + if (!a) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Cannot allocate SSID"); + goto error; + } + + g_byte_array_append (a, (const guint8 *) ssid, ssid_len); + g_free(ssid); + + return a; + +error: + g_free(ssid); + return NULL; +} + +#if 0 +/* no iSCSI on Mandriva */ +static void +iscsiadm_child_setup (gpointer user_data G_GNUC_UNUSED) +{ + /* We are in the child process here; set a different process group to + * ensure signal isolation between child and parent. + */ + pid_t pid = getpid (); + setpgid (pid, pid); +} + +static char * +match_iscsiadm_tag (const char *line, const char *tag, gboolean *skip) +{ + char *p; + + if (g_ascii_strncasecmp (line, tag, strlen (tag))) + return NULL; + + p = strchr (line, '='); + if (!p) { + g_warning ("%s: malformed iscsiadm record: no = in '%s'.", + __func__, line); + *skip = TRUE; + return NULL; + } + + p++; /* advance past = */ + return g_strstrip (p); +} + +#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" + +static gboolean +fill_ip4_setting_from_ibft (shvarFile *ifcfg, + NMSettingIP4Config *s_ip4, + const char *iscsiadm_path, + GError **error) +{ + const char *argv[4] = { iscsiadm_path, "-m", "fw", NULL }; + const char *envp[1] = { NULL }; + gboolean success = FALSE, in_record = FALSE, hwaddr_matched = FALSE, skip = FALSE; + char *out = NULL, *err = NULL; + gint status = 0; + GByteArray *ifcfg_mac = NULL; + char **lines = NULL, **iter; + const char *method = NULL; + struct in_addr ipaddr; + struct in_addr gateway; + struct in_addr dns1; + struct in_addr dns2; + guint32 prefix = 0; + + g_return_val_if_fail (s_ip4 != NULL, FALSE); + g_return_val_if_fail (iscsiadm_path != NULL, FALSE); + + if (!g_spawn_sync ("/", (char **) argv, (char **) envp, 0, + iscsiadm_child_setup, NULL, &out, &err, &status, error)) + return FALSE; + + if (!WIFEXITED (status)) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "%s exited abnormally.", iscsiadm_path); + goto done; + } + + if (WEXITSTATUS (status) != 0) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "%s exited with error %d. Message: '%s'", + iscsiadm_path, WEXITSTATUS (status), err ? err : "(none)"); + goto done; + } + + if (!read_mac_address (ifcfg, &ifcfg_mac, error)) + goto done; + /* Ensure we got a MAC */ + if (!ifcfg_mac) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing device MAC address (no HWADDR tag present)."); + goto done; + } + + memset (&ipaddr, 0, sizeof (ipaddr)); + memset (&gateway, 0, sizeof (gateway)); + memset (&dns1, 0, sizeof (dns1)); + memset (&dns2, 0, sizeof (dns2)); + + /* Success, lets parse the output */ + lines = g_strsplit_set (out, "\n\r", -1); + for (iter = lines; iter && *iter; iter++) { + char *p; + + if (!g_ascii_strcasecmp (*iter, "# BEGIN RECORD")) { + if (in_record) { + g_warning ("%s: malformed iscsiadm record: already parsing record.", __func__); + skip = TRUE; + } + } else if (!g_ascii_strcasecmp (*iter, "# END RECORD")) { + if (!skip && hwaddr_matched) { + /* Record is good; fill IP4 config with its info */ + if (!method) { + g_warning ("%s: malformed iscsiadm record: missing BOOTPROTO.", __func__); + return FALSE; + } + + g_object_set (s_ip4, NM_SETTING_IP4_CONFIG_METHOD, method, NULL); + + if (!strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) { + NMIP4Address *addr; + + if (!ipaddr.s_addr || !prefix) { + g_warning ("%s: malformed iscsiadm record: BOOTPROTO=static " + "but missing IP address or prefix.", __func__); + return FALSE; + } + + addr = nm_ip4_address_new (); + nm_ip4_address_set_address (addr, ipaddr.s_addr); + nm_ip4_address_set_prefix (addr, prefix); + nm_ip4_address_set_gateway (addr, gateway.s_addr); + nm_setting_ip4_config_add_address (s_ip4, addr); + nm_ip4_address_unref (addr); + + if (dns1.s_addr) + nm_setting_ip4_config_add_dns (s_ip4, dns1.s_addr); + if (dns2.s_addr) + nm_setting_ip4_config_add_dns (s_ip4, dns2.s_addr); + + // FIXME: DNS search domains? + } + return TRUE; + } + skip = FALSE; + hwaddr_matched = FALSE; + memset (&ipaddr, 0, sizeof (ipaddr)); + memset (&gateway, 0, sizeof (gateway)); + memset (&dns1, 0, sizeof (dns1)); + memset (&dns2, 0, sizeof (dns2)); + prefix = 0; + method = NULL; + } + + if (skip) + continue; + + /* HWADDR */ + if (!skip && (p = match_iscsiadm_tag (*iter, ISCSI_HWADDR_TAG, &skip))) { + struct ether_addr *ibft_mac; + + ibft_mac = ether_aton (p); + if (!ibft_mac) { + g_warning ("%s: malformed iscsiadm record: invalid hwaddress.", __func__); + skip = TRUE; + continue; + } + + if (memcmp (ifcfg_mac->data, (guint8 *) ibft_mac->ether_addr_octet, ETH_ALEN)) { + /* This record isn't for the current device, ignore it */ + skip = TRUE; + continue; + } + + /* Success, this record is for this device */ + hwaddr_matched = TRUE; + } + + /* BOOTPROTO */ + if (!skip && (p = match_iscsiadm_tag (*iter, ISCSI_BOOTPROTO_TAG, &skip))) { + if (!g_ascii_strcasecmp (p, "dhcp")) + method = NM_SETTING_IP4_CONFIG_METHOD_AUTO; + else if (!g_ascii_strcasecmp (p, "static")) + method = NM_SETTING_IP4_CONFIG_METHOD_MANUAL; + else { + g_warning ("%s: malformed iscsiadm record: unknown BOOTPROTO '%s'.", + __func__, p); + skip = TRUE; + continue; + } + } + + if (!skip && (p = match_iscsiadm_tag (*iter, ISCSI_IPADDR_TAG, &skip))) { + if (inet_pton (AF_INET, p, &ipaddr) < 1) { + g_warning ("%s: malformed iscsiadm record: invalid IP address '%s'.", + __func__, p); + skip = TRUE; + continue; + } + } + + if (!skip && (p = match_iscsiadm_tag (*iter, ISCSI_SUBNET_TAG, &skip))) { + struct in_addr mask; + + if (inet_pton (AF_INET, p, &mask) < 1) { + g_warning ("%s: malformed iscsiadm record: invalid subnet mask '%s'.", + __func__, p); + skip = TRUE; + continue; + } + + prefix = nm_utils_ip4_netmask_to_prefix (mask.s_addr); + } + + if (!skip && (p = match_iscsiadm_tag (*iter, ISCSI_GATEWAY_TAG, &skip))) { + if (inet_pton (AF_INET, p, &gateway) < 1) { + g_warning ("%s: malformed iscsiadm record: invalid IP gateway '%s'.", + __func__, p); + skip = TRUE; + continue; + } + } + + if (!skip && (p = match_iscsiadm_tag (*iter, ISCSI_DNS1_TAG, &skip))) { + if (inet_pton (AF_INET, p, &dns1) < 1) { + g_warning ("%s: malformed iscsiadm record: invalid DNS1 address '%s'.", + __func__, p); + skip = TRUE; + continue; + } + } + + if (!skip && (p = match_iscsiadm_tag (*iter, ISCSI_DNS2_TAG, &skip))) { + if (inet_pton (AF_INET, p, &dns2) < 1) { + g_warning ("%s: malformed iscsiadm record: invalid DNS2 address '%s'.", + __func__, p); + skip = TRUE; + continue; + } + } + } + + success = TRUE; + +done: + if (ifcfg_mac) + g_byte_array_free (ifcfg_mac, TRUE); + g_strfreev (lines); + g_free (out); + g_free (err); + return success; +} +#endif + +static gboolean +read_ip4_address (shvarFile *ifcfg, + const char *tag, + guint32 *out_addr, + GError **error) +{ + char *value = NULL; + struct in_addr ip4_addr; + gboolean success = FALSE; + + g_return_val_if_fail (ifcfg != NULL, FALSE); + g_return_val_if_fail (tag != NULL, FALSE); + g_return_val_if_fail (out_addr != NULL, FALSE); + g_return_val_if_fail (error != NULL, FALSE); + g_return_val_if_fail (*error == NULL, FALSE); + + *out_addr = 0; + + value = svGetValue (ifcfg, tag, FALSE); + if (!value) + return TRUE; + + if (inet_pton (AF_INET, value, &ip4_addr) > 0) { + *out_addr = ip4_addr.s_addr; + success = TRUE; + } else { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid %s IP4 address '%s'", tag, value); + } + g_free (value); + return success; +} + +#if 0 +No IPv6 on Mandriva +static gboolean +parse_ip6_address (const char *value, + struct in6_addr *out_addr, + GError **error) +{ + struct in6_addr ip6_addr; + gboolean success = FALSE; + + g_return_val_if_fail (value != NULL, FALSE); + g_return_val_if_fail (out_addr != NULL, FALSE); + g_return_val_if_fail (error != NULL, FALSE); + g_return_val_if_fail (*error == NULL, FALSE); + + *out_addr = in6addr_any; + + if (inet_pton (AF_INET6, value, &ip6_addr) > 0) { + *out_addr = ip6_addr; + success = TRUE; + } else { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid IP6 address '%s'", value); + } + return success; +} +#endif + +static NMIP4Address * +read_full_ip4_address (shvarFile *ifcfg, + const char *network_file, + guint32 which, + GError **error) +{ + NMIP4Address *addr; + char *ip_tag, *prefix_tag, *netmask_tag, *gw_tag; + guint32 tmp; + gboolean success = FALSE; + shvarFile *network_ifcfg; + char *value; + + g_return_val_if_fail (which > 0, NULL); + g_return_val_if_fail (ifcfg != NULL, NULL); + g_return_val_if_fail (network_file != NULL, NULL); + + /* Mandriva does not seem to use more than one address */ + if (which != 1) + return NULL; + + addr = nm_ip4_address_new (); + if (which == 1) { + ip_tag = g_strdup ("IPADDR"); + prefix_tag = g_strdup ("PREFIX"); + netmask_tag = g_strdup ("NETMASK"); + gw_tag = g_strdup ("GATEWAY"); + } else { + ip_tag = g_strdup_printf ("IPADDR%u", which); + prefix_tag = g_strdup_printf ("PREFIX%u", which); + netmask_tag = g_strdup_printf ("NETMASK%u", which); + gw_tag = g_strdup_printf ("GATEWAY%u", which); + } + + /* IP address */ + if (!read_ip4_address (ifcfg, ip_tag, &tmp, error)) + goto error; + if (!tmp) { + nm_ip4_address_unref (addr); + addr = NULL; + success = TRUE; /* done */ + goto error; + } + nm_ip4_address_set_address (addr, tmp); + + /* Gateway */ + if (!read_ip4_address (ifcfg, gw_tag, &tmp, error)) + goto error; + if (tmp) + nm_ip4_address_set_gateway (addr, tmp); + else { + gboolean read_success; + + /* If no gateway in the ifcfg, try /etc/sysconfig/network instead */ + network_ifcfg = svNewFile (network_file); + if (network_ifcfg) { + read_success = read_ip4_address (network_ifcfg, "GATEWAY", &tmp, error); + svCloseFile (network_ifcfg); + if (!read_success) + goto error; + nm_ip4_address_set_gateway (addr, tmp); + } + } + + /* NETMASK */ + if (!read_ip4_address (ifcfg, netmask_tag, &tmp, error)) + goto error; + nm_ip4_address_set_prefix (addr, nm_utils_ip4_netmask_to_prefix (tmp)); + + + /* Fall back to PERFIX if no NETMASK was specified */ + if (!nm_ip4_address_get_prefix (addr)) { + value = svGetValue (ifcfg, prefix_tag, FALSE); + if (value) { + long int prefix; + + errno = 0; + prefix = strtol (value, NULL, 10); + if (errno || prefix <= 0 || prefix > 32) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid IP4 prefix '%s'", value); + g_free (value); + goto error; + } + nm_ip4_address_set_prefix (addr, (guint32) prefix); + g_free (value); + } + } + + /* Try to autodetermine the prefix for the address' class */ + if (!nm_ip4_address_get_prefix (addr)) { + guint32 tmp_addr, prefix = 0; + + tmp_addr = nm_ip4_address_get_address (addr); + if (((ntohl(tmp_addr) & 0xFF000000) >> 24) <= 127) + prefix = 8; + else if (((ntohl(tmp_addr) & 0xFF000000) >> 24) <= 191) + prefix = 16; + else + prefix = 24; + + nm_ip4_address_set_prefix (addr, prefix); + + value = svGetValue (ifcfg, ip_tag, FALSE); + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: missing %s, assuming %s/%u", + prefix_tag, value, prefix); + g_free (value); + } + + /* Validate the prefix */ + if (nm_ip4_address_get_prefix (addr) > 32) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing or invalid IP4 prefix '%d'", + nm_ip4_address_get_prefix (addr)); + goto error; + } + + success = TRUE; + +error: + if (!success) { + nm_ip4_address_unref (addr); + addr = NULL; + } + + g_free (ip_tag); + g_free (prefix_tag); + g_free (netmask_tag); + g_free (gw_tag); + return addr; +} + +#if 0 +/* No routes on Mandriva */ +static NMIP4Route * +read_one_ip4_route (shvarFile *ifcfg, + const char *network_file, + guint32 which, + GError **error) +{ + NMIP4Route *route; + char *ip_tag, *netmask_tag, *gw_tag, *metric_tag, *value; + guint32 tmp; + gboolean success = FALSE; + + g_return_val_if_fail (ifcfg != NULL, NULL); + g_return_val_if_fail (network_file != NULL, NULL); + g_return_val_if_fail (which >= 0, NULL); + + route = nm_ip4_route_new (); + + ip_tag = g_strdup_printf ("ADDRESS%u", which); + netmask_tag = g_strdup_printf ("NETMASK%u", which); + gw_tag = g_strdup_printf ("GATEWAY%u", which); + metric_tag = g_strdup_printf ("METRIC%u", which); + + /* Destination */ + if (!read_ip4_address (ifcfg, ip_tag, &tmp, error)) + goto out; + if (!tmp) { + /* Check whether IP is missing or 0.0.0.0 */ + char *val; + val = svGetValue (ifcfg, ip_tag, FALSE); + if (!val) { + nm_ip4_route_unref (route); + route = NULL; + success = TRUE; /* done */ + goto out; + } + g_free (val); + } + nm_ip4_route_set_dest (route, tmp); + + /* Next hop */ + if (!read_ip4_address (ifcfg, gw_tag, &tmp, error)) + goto out; + if (!tmp) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing or invalid IP4 gateway address '%d'", + tmp); + goto out; + } + nm_ip4_route_set_next_hop (route, tmp); + + /* Prefix */ + if (!read_ip4_address (ifcfg, netmask_tag, &tmp, error)) + goto out; + nm_ip4_route_set_prefix (route, nm_utils_ip4_netmask_to_prefix (tmp)); + + /* Validate the prefix */ + if ( !nm_ip4_route_get_prefix (route) + || nm_ip4_route_get_prefix (route) > 32) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing or invalid IP4 prefix '%d'", + nm_ip4_route_get_prefix (route)); + goto out; + } + + /* Metric */ + value = svGetValue (ifcfg, metric_tag, FALSE); + if (value) { + long int metric; + + errno = 0; + metric = strtol (value, NULL, 10); + if (errno || metric < 0) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid IP4 route metric '%s'", value); + g_free (value); + goto out; + } + nm_ip4_route_set_metric (route, (guint32) metric); + g_free (value); + } + + success = TRUE; + +out: + if (!success) { + nm_ip4_route_unref (route); + route = NULL; + } + + g_free (ip_tag); + g_free (netmask_tag); + g_free (gw_tag); + g_free (metric_tag); + return route; +} + +static gboolean +read_route_file_legacy (const char *filename, NMSettingIP4Config *s_ip4, GError **error) +{ + char *contents = NULL; + gsize len = 0; + char **lines = NULL, **iter; + GRegex *regex_to1, *regex_to2, *regex_via, *regex_metric; + GMatchInfo *match_info; + NMIP4Route *route; + struct in_addr ip4_addr; + char *dest = NULL, *prefix = NULL, *next_hop = NULL, *metric = NULL; + long int prefix_int, metric_int; + gboolean success = FALSE; + + const char *pattern_empty = "^\\s*(\\#.*)?$"; + const char *pattern_to1 = "^\\s*(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|default)" /* IP or 'default' keyword */ + "(?:/(\\d{1,2}))?"; /* optional prefix */ + const char *pattern_to2 = "to\\s+(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|default)" /* IP or 'default' keyword */ + "(?:/(\\d{1,2}))?"; /* optional prefix */ + const char *pattern_via = "via\\s+(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})"; /* IP of gateway */ + const char *pattern_metric = "metric\\s+(\\d+)"; /* metric */ + + g_return_val_if_fail (filename != NULL, FALSE); + g_return_val_if_fail (s_ip4 != NULL, FALSE); + g_return_val_if_fail (error != NULL, FALSE); + g_return_val_if_fail (*error == NULL, FALSE); + + /* Read the route file */ + if (!g_file_get_contents (filename, &contents, &len, NULL)) + return FALSE; + + if (len == 0) { + g_free (contents); + return FALSE; + } + + /* Create regexes for pieces to be matched */ + regex_to1 = g_regex_new (pattern_to1, 0, 0, NULL); + regex_to2 = g_regex_new (pattern_to2, 0, 0, NULL); + regex_via = g_regex_new (pattern_via, 0, 0, NULL); + regex_metric = g_regex_new (pattern_metric, 0, 0, NULL); + + /* New NMIP4Route structure */ + route = nm_ip4_route_new (); + + /* Iterate through file lines */ + lines = g_strsplit_set (contents, "\n\r", -1); + for (iter = lines; iter && *iter; iter++) { + + /* Skip empty lines */ + if (g_regex_match_simple (pattern_empty, *iter, 0, 0)) + continue; + + /* Destination */ + g_regex_match (regex_to1, *iter, 0, &match_info); + if (!g_match_info_matches (match_info)) { + g_match_info_free (match_info); + g_regex_match (regex_to2, *iter, 0, &match_info); + if (!g_match_info_matches (match_info)) { + g_match_info_free (match_info); + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing IP4 route destination address in record: '%s'", *iter); + goto error; + } + } + dest = g_match_info_fetch (match_info, 1); + g_match_info_free (match_info); + if (!strcmp (dest, "default")) + strcpy (dest, "0.0.0.0"); + if (inet_pton (AF_INET, dest, &ip4_addr) != 1) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid IP4 route destination address '%s'", dest); + g_free (dest); + goto error; + } + nm_ip4_route_set_dest (route, ip4_addr.s_addr); + g_free (dest); + + /* Prefix - is optional; 32 if missing */ + prefix = g_match_info_fetch (match_info, 2); + prefix_int = 32; + if (prefix) { + errno = 0; + prefix_int = strtol (prefix, NULL, 10); + if (errno || prefix_int < 0 || prefix_int > 32) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid IP4 route destination prefix '%s'", prefix); + g_free (prefix); + goto error; + } + } + + nm_ip4_route_set_prefix (route, (guint32) prefix_int); + g_free (prefix); + + /* Next hop */ + g_regex_match (regex_via, *iter, 0, &match_info); + if (!g_match_info_matches (match_info)) { + g_match_info_free (match_info); + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing IP4 route gateway address in record: '%s'", *iter); + goto error; + } + next_hop = g_match_info_fetch (match_info, 1); + g_match_info_free (match_info); + if (inet_pton (AF_INET, next_hop, &ip4_addr) != 1) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid IP4 route gateway address '%s'", next_hop); + g_free (next_hop); + goto error; + } + nm_ip4_route_set_next_hop (route, ip4_addr.s_addr); + g_free (next_hop); + + /* Metric */ + g_regex_match (regex_metric, *iter, 0, &match_info); + metric_int = 0; + if (g_match_info_matches (match_info)) { + metric = g_match_info_fetch (match_info, 1); + errno = 0; + metric_int = strtol (metric, NULL, 10); + if (errno || metric_int < 0) { + g_match_info_free (match_info); + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid IP4 route metric '%s'", metric); + g_free (metric); + goto error; + } + g_free (metric); + } + + nm_ip4_route_set_metric (route, (guint32) metric_int); + g_match_info_free (match_info); + + if (!nm_setting_ip4_config_add_route (s_ip4, route)) + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: duplicate IP4 route"); + + } + + success = TRUE; + +error: + g_free (contents); + g_strfreev (lines); + nm_ip4_route_unref (route); + g_regex_unref (regex_to1); + g_regex_unref (regex_to2); + g_regex_unref (regex_via); + g_regex_unref (regex_metric); + + return success; +} +#endif + +#if 0 +No IPv6 on Mandriva +static NMIP6Address * +parse_full_ip6_address (const char *addr_str, GError **error) +{ + NMIP6Address *addr; + char **list; + char *ip_tag, *prefix_tag; + struct in6_addr tmp = IN6ADDR_ANY_INIT; + gboolean success = FALSE; + + g_return_val_if_fail (addr_str != NULL, NULL); + g_return_val_if_fail (error != NULL, NULL); + g_return_val_if_fail (*error == NULL, NULL); + + /* Split the adddress and prefix */ + list = g_strsplit_set (addr_str, "/", 2); + if (g_strv_length (list) < 1) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid IP6 address '%s'", addr_str); + goto error; + } + + ip_tag = list[0]; + prefix_tag = list[1]; + + addr = nm_ip6_address_new (); + /* IP address */ + if (ip_tag) { + if (!parse_ip6_address (ip_tag, &tmp, error)) + goto error; + } + + nm_ip6_address_set_address (addr, &tmp); + + /* Prefix */ + if (prefix_tag) { + long int prefix; + + errno = 0; + prefix = strtol (prefix_tag, NULL, 10); + if (errno || prefix <= 0 || prefix > 128) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid IP6 prefix '%s'", prefix_tag); + goto error; + } + nm_ip6_address_set_prefix (addr, (guint32) prefix); + } else { + /* Missing prefix is treated as prefix of 64 */ + nm_ip6_address_set_prefix (addr, 64); + } + + success = TRUE; + +error: + if (!success) { + nm_ip6_address_unref (addr); + addr = NULL; + } + + g_strfreev (list); + return addr; +} + +/* IPv6 address is very complex to describe completely by a regular expression, + * so don't try to, rather use looser syntax to comprise all possibilities + * NOTE: The regexes below don't describe all variants allowed by 'ip route add', + * namely destination IP without 'to' keyword is recognized just at line start. + */ +#define IPV6_ADDR_REGEX "[0-9A-Fa-f:.]+" + +static gboolean +read_route6_file (const char *filename, NMSettingIP6Config *s_ip6, GError **error) +{ + char *contents = NULL; + gsize len = 0; + char **lines = NULL, **iter; + GRegex *regex_to1, *regex_to2, *regex_via, *regex_metric; + GMatchInfo *match_info; + NMIP6Route *route; + struct in6_addr ip6_addr; + char *dest = NULL, *prefix = NULL, *next_hop = NULL, *metric = NULL; + long int prefix_int, metric_int; + gboolean success = FALSE; + + const char *pattern_empty = "^\\s*(\\#.*)?$"; + const char *pattern_to1 = "^\\s*(" IPV6_ADDR_REGEX "|default)" /* IPv6 or 'default' keyword */ + "(?:/(\\d{1,2}))?"; /* optional prefix */ + const char *pattern_to2 = "to\\s+(" IPV6_ADDR_REGEX "|default)" /* IPv6 or 'default' keyword */ + "(?:/(\\d{1,2}))?"; /* optional prefix */ + const char *pattern_via = "via\\s+(" IPV6_ADDR_REGEX ")"; /* IPv6 of gateway */ + const char *pattern_metric = "metric\\s+(\\d+)"; /* metric */ + + g_return_val_if_fail (filename != NULL, FALSE); + g_return_val_if_fail (s_ip6 != NULL, FALSE); + g_return_val_if_fail (error != NULL, FALSE); + g_return_val_if_fail (*error == NULL, FALSE); + + /* Read the route file */ + if (!g_file_get_contents (filename, &contents, &len, NULL)) + return FALSE; + + if (len == 0) { + g_free (contents); + return FALSE; + } + + /* Create regexes for pieces to be matched */ + regex_to1 = g_regex_new (pattern_to1, 0, 0, NULL); + regex_to2 = g_regex_new (pattern_to2, 0, 0, NULL); + regex_via = g_regex_new (pattern_via, 0, 0, NULL); + regex_metric = g_regex_new (pattern_metric, 0, 0, NULL); + + /* New NMIP6Route structure */ + route = nm_ip6_route_new (); + + /* Iterate through file lines */ + lines = g_strsplit_set (contents, "\n\r", -1); + for (iter = lines; iter && *iter; iter++) { + + /* Skip empty lines */ + if (g_regex_match_simple (pattern_empty, *iter, 0, 0)) + continue; + + /* Destination */ + g_regex_match (regex_to1, *iter, 0, &match_info); + if (!g_match_info_matches (match_info)) { + g_match_info_free (match_info); + g_regex_match (regex_to2, *iter, 0, &match_info); + if (!g_match_info_matches (match_info)) { + g_match_info_free (match_info); + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing IP6 route destination address in record: '%s'", *iter); + goto error; + } + } + dest = g_match_info_fetch (match_info, 1); + g_match_info_free (match_info); + if (!strcmp (dest, "default")) + strcpy (dest, "::"); + if (inet_pton (AF_INET6, dest, &ip6_addr) != 1) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid IP6 route destination address '%s'", dest); + g_free (dest); + goto error; + } + nm_ip6_route_set_dest (route, &ip6_addr); + g_free (dest); + + /* Prefix - is optional; 128 if missing */ + prefix = g_match_info_fetch (match_info, 2); + prefix_int = 128; + if (prefix) { + errno = 0; + prefix_int = strtol (prefix, NULL, 10); + if (errno || prefix_int < 0 || prefix_int > 128) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid IP6 route destination prefix '%s'", prefix); + g_free (prefix); + goto error; + } + } + + nm_ip6_route_set_prefix (route, (guint32) prefix_int); + g_free (prefix); + + /* Next hop */ + g_regex_match (regex_via, *iter, 0, &match_info); + if (!g_match_info_matches (match_info)) { + g_match_info_free (match_info); + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing IP6 route gateway address in record: '%s'", *iter); + goto error; + } + next_hop = g_match_info_fetch (match_info, 1); + g_match_info_free (match_info); + if (inet_pton (AF_INET6, next_hop, &ip6_addr) != 1) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid IP6 route gateway address '%s'", next_hop); + g_free (next_hop); + goto error; + } + nm_ip6_route_set_next_hop (route, &ip6_addr); + g_free (next_hop); + + /* Metric */ + g_regex_match (regex_metric, *iter, 0, &match_info); + metric_int = 0; + if (g_match_info_matches (match_info)) { + metric = g_match_info_fetch (match_info, 1); + errno = 0; + metric_int = strtol (metric, NULL, 10); + if (errno || metric_int < 0 || metric_int > G_MAXUINT32) { + g_match_info_free (match_info); + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid IP6 route metric '%s'", metric); + g_free (metric); + goto error; + } + g_free (metric); + } + + nm_ip6_route_set_metric (route, (guint32) metric_int); + g_match_info_free (match_info); + + if (!nm_setting_ip6_config_add_route (s_ip6, route)) + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: duplicate IP6 route"); + } + + success = TRUE; + +error: + g_free (contents); + g_strfreev (lines); + nm_ip6_route_unref (route); + g_regex_unref (regex_to1); + g_regex_unref (regex_to2); + g_regex_unref (regex_via); + g_regex_unref (regex_metric); + + return success; +} +#endif + + +static NMSetting * +make_ip4_setting (shvarFile *ifcfg, + const char *network_file, + const char *iscsiadm_path, + GError **error) +{ + NMSettingIP4Config *s_ip4 = NULL; + char *value = NULL; + // char *route_path = NULL; + char *method = NM_SETTING_IP4_CONFIG_METHOD_MANUAL; + guint32 i; + shvarFile *network_ifcfg; + // shvarFile *route_ifcfg; + gboolean never_default = FALSE, tmp_success; + + s_ip4 = (NMSettingIP4Config *) nm_setting_ip4_config_new (); + if (!s_ip4) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Could not allocate IP4 setting"); + return NULL; + } + +#if 0 + /* Mandriva sets DEFROUTE for PPP only */ + /* First check if DEFROUTE is set for this device; DEFROUTE has the + * opposite meaning from never-default. The default if DEFROUTE is not + * specified is DEFROUTE=yes which means that this connection can be used + * as a default route + */ + never_default = !svTrueValue (ifcfg, "DEFROUTE", TRUE); +#endif + + /* Then check if GATEWAYDEV; it's global and overrides DEFROUTE */ + network_ifcfg = svNewFile (network_file); + if (network_ifcfg) { + char *gatewaydev; + + /* Get the connection ifcfg device name and the global gateway device */ + value = svGetValue (ifcfg, "DEVICE", FALSE); + gatewaydev = svGetValue (network_ifcfg, "GATEWAYDEV", FALSE); + + /* If there was a global gateway device specified, then only connections + * for that device can be the default connection. + */ + if (gatewaydev && value) + never_default = !!strcmp (value, gatewaydev); + + g_free (gatewaydev); + g_free (value); + // svCloseFile (network_ifcfg); + } + + value = svGetValue (ifcfg, "BOOTPROTO", FALSE); + if (value) { + if (!g_ascii_strcasecmp (value, "bootp") || !g_ascii_strcasecmp (value, "dhcp")) { + method = NM_SETTING_IP4_CONFIG_METHOD_AUTO; +#if 0 + /* Is not used by Mandriva */ + } else if (!g_ascii_strcasecmp (value, "ibft")) { + /* iSCSI Boot Firmware Table: need to read values from the iSCSI + * firmware for this device and create the IP4 setting using those. + */ + if (fill_ip4_setting_from_ibft (ifcfg, s_ip4, iscsiadm_path, error)) + return NM_SETTING (s_ip4); + g_object_unref (s_ip4); + return NULL; + } else if (!g_ascii_strcasecmp (value, "autoip")) { + g_free (value); + g_object_set (s_ip4, + NM_SETTING_IP4_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL, + NM_SETTING_IP4_CONFIG_NEVER_DEFAULT, never_default, + NULL); + return NM_SETTING (s_ip4); + } else if (!g_ascii_strcasecmp (value, "shared")) { + g_free (value); + g_object_set (s_ip4, + NM_SETTING_IP4_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_SHARED, + NM_SETTING_IP4_CONFIG_NEVER_DEFAULT, never_default, + NULL); + return NM_SETTING (s_ip4); +#endif + } else if (!g_ascii_strcasecmp (value, "none") || !g_ascii_strcasecmp (value, "static")) { + /* Static IP */ + } else if (strlen (value)) { + /* FIXME actually it is static on Mandriva */ + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Unknown BOOTPROTO '%s'", value); + g_free (value); + goto error; + } + g_free (value); + } else { + char *tmp_ip4, *tmp_prefix, *tmp_netmask; + + /* If there is no BOOTPROTO, no IPADDR, no PREFIX, and no NETMASK, + * assume DHCP is to be used. Happens with minimal ifcfg files like: + * + * DEVICE=eth0 + * HWADDR=11:22:33:44:55:66 + * + */ + /* FIXME + * This is not strictly speaking true on Mandriva. Interface + * will be up (ip link up) and zeroconf address will be set + * but no DHCP started */ + tmp_ip4 = svGetValue (ifcfg, "IPADDR", FALSE); + tmp_prefix = svGetValue (ifcfg, "PREFIX", FALSE); + tmp_netmask = svGetValue (ifcfg, "NETMASK", FALSE); + if (!tmp_ip4 && !tmp_prefix && !tmp_netmask) + method = NM_SETTING_IP4_CONFIG_METHOD_AUTO; + g_free (tmp_ip4); + g_free (tmp_prefix); + g_free (tmp_netmask); + } + + g_object_set (s_ip4, + NM_SETTING_IP4_CONFIG_METHOD, method, + NM_SETTING_IP4_CONFIG_IGNORE_AUTO_DNS, !svTrueValue (ifcfg, "PEERDNS", TRUE), + // Not exists on Mandriva + // NM_SETTING_IP4_CONFIG_IGNORE_AUTO_ROUTES, !svTrueValue (ifcfg, "PEERROUTES", TRUE), + NM_SETTING_IP4_CONFIG_NEVER_DEFAULT, never_default, + NULL); + + /* Handle manual settings */ + if (!strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) { + NMIP4Address *addr; + + for (i = 1; i < 256; i++) { + addr = read_full_ip4_address (ifcfg, network_file, i, error); + if (error && *error) + goto error; + if (!addr) + break; + + if (!nm_setting_ip4_config_add_address (s_ip4, addr)) + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: duplicate IP4 address"); + nm_ip4_address_unref (addr); + } + } else if (!strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) { + value = svGetValue (ifcfg, "DHCP_HOSTNAME", FALSE); + if (value && strlen (value)) + g_object_set (s_ip4, NM_SETTING_IP4_CONFIG_DHCP_HOSTNAME, value, NULL); + g_free (value); + +#if 0 + /* Does not seem to be used on Mandriva */ + value = svGetValue (ifcfg, "DHCP_CLIENT_ID", FALSE); + if (value && strlen (value)) + g_object_set (s_ip4, NM_SETTING_IP4_CONFIG_DHCP_CLIENT_ID, value, NULL); + g_free (value); +#endif + } + + /* DNS servers + * Pick up just IPv4 addresses (IPv6 addresses are taken by make_ip6_setting()) + */ + for (i = 1, tmp_success = TRUE; i <= 10 && tmp_success; i++) { + char *tag; + guint32 dns; + // struct in6_addr ip6_dns; + // GError *tmp_err = NULL; + + tag = g_strdup_printf ("DNS%u", i); + tmp_success = read_ip4_address (ifcfg, tag, &dns, error); +#if 0 + /* No IPv6 on Mandriva */ + if (!tmp_success) { + /* if it's IPv6, don't exit */ + dns = 0; + value = svGetValue (ifcfg, tag, FALSE); + if (value) { + tmp_success = parse_ip6_address (value, &ip6_dns, &tmp_err); + g_clear_error (&tmp_err); + g_free (value); + } + if (!tmp_success) { + g_free (tag); + goto error; + } + g_clear_error (error); + } +#endif + + if (dns && !nm_setting_ip4_config_add_dns (s_ip4, dns)) + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: duplicate DNS server %s", tag); + g_free (tag); + } + + /* DNS searches */ + value = svGetValue (ifcfg, "DOMAIN", FALSE); + if (value) { + char **searches = NULL; + + searches = g_strsplit (value, " ", 0); + if (searches) { + char **item; + for (item = searches; *item; item++) { + if (strlen (*item)) { + if (!nm_setting_ip4_config_add_dns_search (s_ip4, *item)) + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: duplicate DNS domain '%s'", *item); + } + } + g_strfreev (searches); + } + g_free (value); + } + +#if 0 + /* Some support is present on Mandriva but no GUI to configure */ + /* Static routes - route-<name> file */ + route_path = utils_get_route_path (ifcfg->fileName); + if (!route_path) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Could not get route file path for '%s'", ifcfg->fileName); + goto error; + } + + /* First test new/legacy syntax */ + if (utils_has_route_file_new_syntax (route_path)) { + /* Parse route file in new syntax */ + g_free (route_path); + route_ifcfg = utils_get_route_ifcfg (ifcfg->fileName, FALSE); + if (route_ifcfg) { + NMIP4Route *route; + for (i = 0; i < 256; i++) { + route = read_one_ip4_route (route_ifcfg, network_file, i, error); + if (error && *error) { + svCloseFile (route_ifcfg); + goto error; + } + if (!route) + break; + + if (!nm_setting_ip4_config_add_route (s_ip4, route)) + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: duplicate IP4 route"); + nm_ip4_route_unref (route); + } + svCloseFile (route_ifcfg); + } + } else { + read_route_file_legacy (route_path, s_ip4, error); + g_free (route_path); + if (error && *error) + goto error; + } +#endif + +#if 0 + /* Does not seem to be used anyhwere on Mandriva */ + /* Legacy value NM used for a while but is incorrect (rh #459370) */ + if (!nm_setting_ip4_config_get_num_dns_searches (s_ip4)) { + value = svGetValue (ifcfg, "SEARCH", FALSE); + if (value) { + char **searches = NULL; + + searches = g_strsplit (value, " ", 0); + if (searches) { + char **item; + for (item = searches; *item; item++) { + if (strlen (*item)) { + if (!nm_setting_ip4_config_add_dns_search (s_ip4, *item)) + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: duplicate DNS search '%s'", *item); + } + } + g_strfreev (searches); + } + g_free (value); + } + } +#endif + + return NM_SETTING (s_ip4); + +error: + g_object_unref (s_ip4); + return NULL; +} + +#if 0 +No IPv6 on Mandriva +static NMSetting * +make_ip6_setting (shvarFile *ifcfg, + const char *network_file, + const char *iscsiadm_path, + GError **error) +{ + NMSettingIP6Config *s_ip6 = NULL; + char *value = NULL; + char *str_value; + char *route6_path = NULL; + gboolean bool_value, ipv6forwarding, ipv6_autoconf; + char *method = NM_SETTING_IP6_CONFIG_METHOD_MANUAL; + guint32 i; + shvarFile *network_ifcfg; + gboolean never_default = FALSE, tmp_success; + + s_ip6 = (NMSettingIP6Config *) nm_setting_ip6_config_new (); + if (!s_ip6) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Could not allocate IP6 setting"); + return NULL; + } + + /* Is IPV6 enabled? Set method to "ignored", when not enabled */ + str_value = svGetValue (ifcfg, "IPV6INIT", FALSE); + bool_value = svTrueValue (ifcfg, "IPV6INIT", FALSE); + if (!str_value) { + network_ifcfg = svNewFile (network_file); + if (network_ifcfg) { + bool_value = svTrueValue (network_ifcfg, "IPV6INIT", FALSE); + svCloseFile (network_ifcfg); + } + } + g_free (str_value); + + if (!bool_value) { + /* IPv6 is disabled */ + g_object_set (s_ip6, + NM_SETTING_IP6_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NULL); + return NM_SETTING (s_ip6); + } + + /* First check if IPV6_DEFROUTE is set for this device; IPV6_DEFROUTE has the + * opposite meaning from never-default. The default if IPV6_DEFROUTE is not + * specified is IPV6_DEFROUTE=yes which means that this connection can be used + * as a default route + */ + never_default = !svTrueValue (ifcfg, "IPV6_DEFROUTE", TRUE); + + /* Then check if IPV6_DEFAULTGW or IPV6_DEFAULTDEV is specified; + * they are global and override IPV6_DEFROUTE + * When both are set, the device specified in IPV6_DEFAULTGW takes preference. + */ + network_ifcfg = svNewFile (network_file); + if (network_ifcfg) { + char *ipv6_defaultgw, *ipv6_defaultdev; + char *default_dev = NULL; + + /* Get the connection ifcfg device name and the global default route device */ + value = svGetValue (ifcfg, "DEVICE", FALSE); + ipv6_defaultgw = svGetValue (network_ifcfg, "IPV6_DEFAULTGW", FALSE); + ipv6_defaultdev = svGetValue (network_ifcfg, "IPV6_DEFAULTDEV", FALSE); + + if (ipv6_defaultgw) { + default_dev = strchr (ipv6_defaultgw, '%'); + if (default_dev) + default_dev++; + } + if (!default_dev) + default_dev = ipv6_defaultdev; + + /* If there was a global default route device specified, then only connections + * for that device can be the default connection. + */ + if (default_dev && value) + never_default = !!strcmp (value, default_dev); + + g_free (ipv6_defaultgw); + g_free (ipv6_defaultdev); + g_free (value); + svCloseFile (network_ifcfg); + } + + /* Find out method property */ + ipv6forwarding = svTrueValue (ifcfg, "IPV6FORWARDING", FALSE); + ipv6_autoconf = svTrueValue (ifcfg, "IPV6_AUTOCONF", !ipv6forwarding); + + if (ipv6_autoconf) + method = NM_SETTING_IP6_CONFIG_METHOD_AUTO; + else { + /* IPV6_AUTOCONF=no and no IPv6 address -> method 'link-local' */ + str_value = svGetValue (ifcfg, "IPV6ADDR", FALSE); + if (!str_value) + str_value = svGetValue (ifcfg, "IPV6ADDR_SECONDARIES", FALSE); + + if (!str_value) + method = NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL; + g_free (str_value); + } + /* TODO - handle other methods */ + + g_object_set (s_ip6, + NM_SETTING_IP6_CONFIG_METHOD, method, + NM_SETTING_IP6_CONFIG_IGNORE_AUTO_DNS, !svTrueValue (ifcfg, "IPV6_PEERDNS", TRUE), + NM_SETTING_IP6_CONFIG_IGNORE_AUTO_ROUTES, !svTrueValue (ifcfg, "IPV6_PEERROUTES", TRUE), + NM_SETTING_IP6_CONFIG_NEVER_DEFAULT, never_default, + NULL); + + if (!strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) { + NMIP6Address *addr; + char *val; + char *ipv6addr, *ipv6addr_secondaries; + char **list = NULL, **iter; + + ipv6addr = svGetValue (ifcfg, "IPV6ADDR", FALSE); + ipv6addr_secondaries = svGetValue (ifcfg, "IPV6ADDR_SECONDARIES", FALSE); + + val = g_strjoin (ipv6addr && ipv6addr_secondaries ? " " : NULL, + ipv6addr ? ipv6addr : "", + ipv6addr_secondaries ? ipv6addr_secondaries : "", + NULL); + g_free (ipv6addr); + g_free (ipv6addr_secondaries); + + list = g_strsplit_set (val, " ", 0); + g_free (val); + for (iter = list; iter && *iter; iter++, i++) { + addr = parse_full_ip6_address (*iter, error); + if (!addr) { + g_strfreev (list); + goto error; + } + + if (!nm_setting_ip6_config_add_address (s_ip6, addr)) + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: duplicate IP6 address"); + nm_ip6_address_unref (addr); + } + g_strfreev (list); + } else if (!strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) { + /* TODO - autoconf or DHCPv6 stuff goes here */ + } + + /* DNS servers + * Pick up just IPv6 addresses (IPv4 addresses are taken by make_ip4_setting()) + */ + for (i = 1, tmp_success = TRUE; i <= 10 && tmp_success; i++) { + char *tag; + struct in6_addr ip6_dns; + + ip6_dns = in6addr_any; + tag = g_strdup_printf ("DNS%u", i); + value = svGetValue (ifcfg, tag, FALSE); + if (value) + tmp_success = parse_ip6_address (value, &ip6_dns, error); + + if (!tmp_success) { + struct in_addr ip4_addr; + if (inet_pton (AF_INET, value, &ip4_addr) != 1) { + g_free (tag); + g_free (value); + goto error; + } + /* ignore error - it is IPv4 address */ + tmp_success = TRUE; + g_clear_error (error); + } + + if (!IN6_IS_ADDR_UNSPECIFIED (&ip6_dns) && !nm_setting_ip6_config_add_dns (s_ip6, &ip6_dns)) + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: duplicate DNS server %s", tag); + g_free (tag); + g_free (value); + } + + /* DNS searches ('DOMAIN' key) are read by make_ip4_setting() and included in NMSettingIP4Config */ + + /* Read static routes from route6-<interface> file */ + route6_path = utils_get_route6_path (ifcfg->fileName); + if (!route6_path) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Could not get route6 file path for '%s'", ifcfg->fileName); + goto error; + } + + read_route6_file (route6_path, s_ip6, error); + g_free (route6_path); + if (error && *error) + goto error; + + return NM_SETTING (s_ip6); + +error: + g_object_unref (s_ip6); + return NULL; +} +#endif + +static gboolean +add_one_wep_key (shvarFile *ifcfg, + const char *shvar_key, + guint8 key_idx, + NMSettingWirelessSecurity *s_wsec, + GError **error) +{ + char *key = NULL; + char *value = NULL; + gboolean success = FALSE; + + g_return_val_if_fail (ifcfg != NULL, FALSE); + g_return_val_if_fail (shvar_key != NULL, FALSE); + g_return_val_if_fail (key_idx <= 3, FALSE); + g_return_val_if_fail (s_wsec != NULL, FALSE); + + value = svGetValue (ifcfg, shvar_key, FALSE); + if (!value || !strlen (value)) { + g_free (value); + return TRUE; + } + + /* Validate keys */ + if (strlen (value) == 10 || strlen (value) == 26) { + /* Hexadecimal WEP key */ + char *p = value; + + while (*p) { + if (!g_ascii_isxdigit (*p)) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid hexadecimal WEP key."); + goto out; + } + p++; + } + key = g_strdup (value); + } else if ( !strncmp (value, "s:", 2) + && (strlen (value) == 7 || strlen (value) == 15)) { + /* ASCII passphrase */ + char *p = value + 2; + + while (*p) { + if (!isascii ((int) (*p))) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid ASCII WEP passphrase."); + goto out; + } + p++; + } + + key = utils_bin2hexstr (value + 2, strlen (value + 2), strlen (value + 2) * 2); + } else { + g_set_error (error, ifcfg_plugin_error_quark (), 0, "Invalid WEP key length."); + } + + if (key) { + nm_setting_wireless_security_set_wep_key (s_wsec, key_idx, key); + success = TRUE; + } + +out: + g_free (value); + return success; +} + +static gboolean +read_wep_keys (shvarFile *ifcfg, + guint8 def_idx, + NMSettingWirelessSecurity *s_wsec, + GError **error) +{ + if (!add_one_wep_key (ifcfg, "WIRELESS_ENC_KEY", 0, s_wsec, error)) + return FALSE; +#if 0 + /* Mandriva is using only one key */ + if (!add_one_wep_key (ifcfg, "KEY1", 0, s_wsec, error)) + return FALSE; + if (!add_one_wep_key (ifcfg, "KEY2", 1, s_wsec, error)) + return FALSE; + if (!add_one_wep_key (ifcfg, "KEY3", 2, s_wsec, error)) + return FALSE; + if (!add_one_wep_key (ifcfg, "KEY4", 3, s_wsec, error)) + return FALSE; + if (!add_one_wep_key (ifcfg, "KEY", def_idx, s_wsec, error)) + return FALSE; +#endif + + return TRUE; +} + +static NMSetting * +make_wep_setting (shvarFile *ifcfg, + const char *file, + GError **error) +{ + NMSettingWirelessSecurity *s_wireless_sec; + char *value; + // shvarFile *keys_ifcfg = NULL; + int default_key_idx = 0; + + s_wireless_sec = NM_SETTING_WIRELESS_SECURITY (nm_setting_wireless_security_new ()); + g_object_set (s_wireless_sec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none", NULL); + +#if 0 + /* Mandriva always assumes defalt key #0 */ + value = svGetValue (ifcfg, "DEFAULTKEY", FALSE); + if (value) { + gboolean success; + + success = get_int (value, &default_key_idx); + if (success && (default_key_idx >= 1) && (default_key_idx <= 4)) { + default_key_idx--; /* convert to [0...3] */ + g_object_set (s_wireless_sec, NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX, default_key_idx, NULL); + } else { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid default WEP key '%s'", value); + g_free (value); + goto error; + } + g_free (value); + } +#endif + + /* Read keys in the ifcfg file */ + if (!read_wep_keys (ifcfg, default_key_idx, s_wireless_sec, error)) + goto error; + +#if 0 + /* No shadow on Mandriva */ + /* Try to get keys from the "shadow" key file */ + keys_ifcfg = utils_get_keys_ifcfg (file, FALSE); + if (keys_ifcfg) { + if (!read_wep_keys (keys_ifcfg, default_key_idx, s_wireless_sec, error)) { + svCloseFile (keys_ifcfg); + goto error; + } + svCloseFile (keys_ifcfg); + } +#endif + +#if 0 + /* Only one key on Mandriva */ + /* If there's a default key, ensure that key exists */ + if ((default_key_idx == 1) && !nm_setting_wireless_security_get_wep_key (s_wireless_sec, 1)) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Default WEP key index was 2, but no valid KEY2 exists."); + goto error; + } else if ((default_key_idx == 2) && !nm_setting_wireless_security_get_wep_key (s_wireless_sec, 2)) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Default WEP key index was 3, but no valid KEY3 exists."); + goto error; + } else if ((default_key_idx == 3) && !nm_setting_wireless_security_get_wep_key (s_wireless_sec, 3)) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Default WEP key index was 4, but no valid KEY4 exists."); + goto error; + } +#endif + + value = svGetValue (ifcfg, "WIRELESS_ENC_MODE", FALSE); + if (value) { + char *lcase; + + lcase = g_ascii_strdown (value, -1); + g_free (value); + + if (!strcmp (lcase, "open")) { + g_object_set (s_wireless_sec, NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", NULL); + } else if (!strcmp (lcase, "restricted")) { + g_object_set (s_wireless_sec, NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "shared", NULL); + } else { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid WEP authentication algorithm '%s'", + lcase); + g_free (lcase); + goto error; + } + g_free (lcase); + } + + if ( !nm_setting_wireless_security_get_wep_key (s_wireless_sec, 0) + && !nm_setting_wireless_security_get_wep_key (s_wireless_sec, 1) + && !nm_setting_wireless_security_get_wep_key (s_wireless_sec, 2) + && !nm_setting_wireless_security_get_wep_key (s_wireless_sec, 3) + && !nm_setting_wireless_security_get_wep_tx_keyidx (s_wireless_sec)) { + const char *auth_alg; + + auth_alg = nm_setting_wireless_security_get_auth_alg (s_wireless_sec); + if (auth_alg && !strcmp (auth_alg, "shared")) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "WEP Shared Key authentication is invalid for " + "unencrypted connections."); + goto error; + } + + /* Unencrypted */ + g_object_unref (s_wireless_sec); + s_wireless_sec = NULL; + } + + return (NMSetting *) s_wireless_sec; + +error: + if (s_wireless_sec) + g_object_unref (s_wireless_sec); + return NULL; +} + +static gboolean +fill_wpa_ciphers (WPANetwork *wpan, + NMSettingWirelessSecurity *wsec, + gboolean group, + gboolean adhoc) +{ + char *value; + char **list = NULL, **iter; + int i = 0; + + value = ifcfg_mdv_wpa_network_get_val (wpan, group ? "group" : "pairwise"); + if (!value) + return TRUE; + + list = g_strsplit_set (value, " ", 0); + for (iter = list; iter && *iter; iter++, i++) { + if (!*iter) + continue; + + /* Ad-Hoc configurations cannot have pairwise ciphers, and can only + * have one group cipher. Ignore any additional group ciphers and + * any pairwise ciphers specified. + */ + if (adhoc) { + if (group && (i > 0)) { + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: ignoring group cipher '%s' (only one group cipher allowed in Ad-Hoc mode)", + *iter); + continue; + } else if (!group) { + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: ignoring pairwise cipher '%s' (pairwise not used in Ad-Hoc mode)", + *iter); + continue; + } + } + + if (!strcmp (*iter, "CCMP")) { + if (group) + nm_setting_wireless_security_add_group (wsec, "ccmp"); + else + nm_setting_wireless_security_add_pairwise (wsec, "ccmp"); + } else if (!strcmp (*iter, "TKIP")) { + if (group) + nm_setting_wireless_security_add_group (wsec, "tkip"); + else + nm_setting_wireless_security_add_pairwise (wsec, "tkip"); + } else if (group && !strcmp (*iter, "WEP104")) + nm_setting_wireless_security_add_group (wsec, "wep104"); + else if (group && !strcmp (*iter, "WEP40")) + nm_setting_wireless_security_add_group (wsec, "wep40"); + else { + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: ignoring invalid %s cipher '%s'", + group ? "group" : "pairwise", + *iter); + } + } + + if (list) + g_strfreev (list); + return TRUE; +} + +static char * +parse_wpa_psk (WPANetwork *wpan, + const char *file, + const GByteArray *ssid, + GError **error) +{ + char *psk = NULL, *p, *hashed = NULL; + gboolean quoted = FALSE; + + /* Passphrase must be between 10 and 66 characters in length becuase WPA + * hex keys are exactly 64 characters (no quoting), and WPA passphrases + * are between 8 and 63 characters (inclusive), plus optional quoting if + * the passphrase contains spaces. + */ + + psk = ifcfg_mdv_wpa_network_get_val (wpan, "psk"); + + if (!psk) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing WPA psk for WPA-PSK key management"); + return NULL; + } + + psk = p = g_strdup (psk); + + if (p[0] == '"') { + if (psk[strlen (psk) - 1] != '"') { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid WPA psk (unterminated quote)"); + goto out; + } + quoted = TRUE; + } + + if (!quoted) { + /* Verify the hex PSK; 64 digits */ + if (strlen (psk) == 64) { + while (*p) { + if (!isxdigit (*p++)) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid WPA psk (contains non-hexadecimal characters)"); + goto out; + } + } + } else { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid WPA psk (hex key not equal 64 characters)"); + goto out; + } + hashed = g_strdup (psk); + } else { + /* Prior to 4f6eef9e77265484555663cf666cde4fa8323469 and + * 28e2e446868b94b92edc4a82aa0bf1e3eda8ec54 the writer may not have + * properly quoted passphrases, so just handle anything that's unquoted + * and between 8 and 63 characters as a passphrase. + */ + + /* Get rid of the quotes */ + p++; + p[strlen (p) - 1] = '\0'; + + /* Length check */ + if (strlen (p) < 8 || strlen (p) > 63) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid WPA psk (passphrases must be between " + "8 and 63 characters long (inclusive))"); + goto out; + } + + hashed = g_strdup (p); + } + + if (!hashed) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid WPA psk (doesn't look like a passphrase or hex key)"); + } + +out: + g_free (psk); + return hashed; +} + +typedef struct { + const char *method; + gboolean (*reader)(const char *eap_method, + WPANetwork *wpan, + shvarFile *ifcfg, + shvarFile *keys, + NMSetting8021x *s_8021x, + gboolean phase2, + GError **error); + gboolean wifi_phase2_only; +} EAPReader; + +static EAPReader eap_readers[] = { + { "md5", eap_simple_reader, TRUE }, + { "pap", eap_simple_reader, TRUE }, + { "chap", eap_simple_reader, TRUE }, + { "mschap", eap_simple_reader, TRUE }, + { "mschapv2", eap_simple_reader, TRUE }, + { "leap", eap_simple_reader, TRUE }, + { "tls", eap_tls_reader, FALSE }, + { "peap", eap_peap_reader, FALSE }, + { "ttls", eap_ttls_reader, FALSE }, + { NULL, NULL } +}; + +static gboolean +eap_simple_reader (const char *eap_method, + WPANetwork *wpan, + shvarFile *ifcfg, + shvarFile *keys, + NMSetting8021x *s_8021x, + gboolean phase2, + GError **error) +{ + char *value = NULL; + + /* FIXME wpa identity can contain '\0' */ + value = ifcfg_mdv_wpa_network_get_str(wpan, "identity"); + if (!value) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing identity for EAP method '%s'.", + eap_method); + return FALSE; + } + g_object_set (s_8021x, NM_SETTING_802_1X_IDENTITY, value, NULL); + g_free(value); + + /* FIXME can we expect hash:XXX password? */ + value = ifcfg_mdv_wpa_network_get_str (wpan, "password"); + if (!value) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing password for EAP method '%s'.", + eap_method); + return FALSE; + } + g_object_set (s_8021x, NM_SETTING_802_1X_PASSWORD, value, NULL); + g_free(value); + + return TRUE; +} + +static gboolean +eap_tls_reader (const char *eap_method, + WPANetwork *wpan, + shvarFile *ifcfg, + shvarFile *keys, + NMSetting8021x *s_8021x, + gboolean phase2, + GError **error) +{ + char *value = NULL; + char *ca_cert = NULL; + char *client_cert = NULL; + char *privkey = NULL; + char *privkey_password = NULL; + gboolean success = FALSE; + NMSetting8021xCKFormat privkey_format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN; + + /* FIXME wpa identity can contain '\0' */ + value = ifcfg_mdv_wpa_network_get_str(wpan, "identity"); + if (!value) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing identity for EAP method '%s'.", + eap_method); + return FALSE; + } + g_object_set (s_8021x, NM_SETTING_802_1X_IDENTITY, value, NULL); + + ca_cert = ifcfg_mdv_wpa_network_get_str(wpan, + phase2 ? "ca_cert2" : "ca_cert"); + if (ca_cert) { + if (phase2) { + if (!nm_setting_802_1x_set_phase2_ca_cert (s_8021x, + ca_cert, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + error)) + goto done; + } else { + if (!nm_setting_802_1x_set_ca_cert (s_8021x, + ca_cert, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + error)) + goto done; + } + } else { + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: missing %s for EAP" + " method '%s'; this is insecure!", + phase2 ? "ca_cert2" : "ca_cert", + eap_method); + } + + /* Private key password */ + privkey_password = ifcfg_mdv_wpa_network_get_str(wpan, + phase2 ? "private_key2_password": "private_key_password"); + if (!privkey_password) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing %s for EAP method '%s'.", + phase2 ? "private_key2_password" : "private_key_password", + eap_method); + goto done; + } + + /* The private key itself */ + privkey = ifcfg_mdv_wpa_network_get_str(wpan, + phase2 ? "private_key2" : "private_key"); + if (!privkey) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing %s for EAP method '%s'.", + phase2 ? "private_key2" : "private_key", + eap_method); + goto done; + } + + if (phase2) { + if (!nm_setting_802_1x_set_phase2_private_key (s_8021x, + privkey, + privkey_password, + NM_SETTING_802_1X_CK_SCHEME_PATH, + &privkey_format, + error)) + goto done; + } else { + if (!nm_setting_802_1x_set_private_key (s_8021x, + privkey, + privkey_password, + NM_SETTING_802_1X_CK_SCHEME_PATH, + &privkey_format, + error)) + goto done; + } + + /* Only set the client certificate if the private key is not PKCS#12 format, + * as NM (due to supplicant restrictions) requires. If the key was PKCS#12, + * then nm_setting_802_1x_set_private_key() already set the client certificate + * to the same value as the private key. + */ + if ( privkey_format == NM_SETTING_802_1X_CK_FORMAT_RAW_KEY + || privkey_format == NM_SETTING_802_1X_CK_FORMAT_X509) { + client_cert = ifcfg_mdv_wpa_network_get_str(wpan, + phase2 ? "client_cert2" : "client_cert"); + if (!client_cert) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing %s for EAP method '%s'.", + phase2 ? "client_cert2" : "client_cert", + eap_method); + goto done; + } + + if (phase2) { + if (!nm_setting_802_1x_set_phase2_client_cert (s_8021x, + client_cert, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + error)) + goto done; + } else { + if (!nm_setting_802_1x_set_client_cert (s_8021x, + client_cert, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + error)) + goto done; + } + } + + success = TRUE; + +done: + g_free(value); + g_free(ca_cert); + g_free(privkey_password); + g_free(privkey); + g_free(client_cert); + return success; +} + +static gboolean +eap_peap_reader (const char *eap_method, + WPANetwork *wpan, + shvarFile *ifcfg, + shvarFile *keys, + NMSetting8021x *s_8021x, + gboolean dummy, + GError **error) +{ + char *ca_cert = NULL; + char *phase1 = NULL; + char *phase2 = NULL; + char *lower; + char **list = NULL, **iter; + gboolean success = FALSE; + + ca_cert = ifcfg_mdv_wpa_network_get_str(wpan, "ca_cert"); + if (ca_cert) { + if (!nm_setting_802_1x_set_ca_cert (s_8021x, + ca_cert, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + error)) + goto done; + } else { + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: missing " + "ca_cert for EAP method '%s'; this is" + " insecure!", + eap_method); + } + + phase1 = ifcfg_mdv_wpa_network_get_str(wpan, "phase1"); + if (phase1) { + list = g_strsplit_set(phase1, " ", 0); + for (iter = list; iter && *iter; iter++) { + char *p; + + if (!**iter) + continue; + + p = strchr(*iter, '='); + if (p) { + *p++ = '\0'; + if (!strcmp(*iter, "peapver")) { + if (!strcmp (p, "0")) + g_object_set (s_8021x, NM_SETTING_802_1X_PHASE1_PEAPVER, "0", NULL); + else if (!strcmp (p, "1")) + g_object_set (s_8021x, NM_SETTING_802_1X_PHASE1_PEAPVER, "1", NULL); + else { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Unknown peapver value '%s'", + p); + goto done; + } + } else if (!strcmp(*iter, "peaplabel")) { + if (!strcmp(p, "1")) { + g_object_set (s_8021x, NM_SETTING_802_1X_PHASE1_PEAPLABEL, "1", NULL); + } + } + } else + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: incorrect " + "phase1 parameter '%s' EAP method '%s';" + " key=value expected!", + *iter, eap_method); + } + if (list) + g_strfreev(list); + } + + phase2 = ifcfg_mdv_wpa_network_get_str(wpan, "phase2"); + if (!phase2) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing phase2 (PEAP inner authentication parameters)."); + goto done; + } + + /* Handle options for the inner auth method */ + list = g_strsplit (phase2, " ", 0); + for (iter = list; iter && *iter; iter++) { + char *p; + + if (!**iter) + continue; + + p = strchr(*iter, '='); + if (!p) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Incorrect phase2 parameter '%s'; key=value expected.", *iter); + goto done; + } + *p++ = '\0'; + if (!strcmp(*iter, "auth")) { + if ( !strcmp (p, "MSCHAPV2") + || !strcmp (p, "MD5") + || !strcmp (p, "GTC")) { + if (!eap_simple_reader (p, wpan, ifcfg, keys, s_8021x, TRUE, error)) + goto done; + } else if (!strcmp (p, "TLS")) { + if (!eap_tls_reader (p, wpan, ifcfg, keys, s_8021x, TRUE, error)) + goto done; + } else { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Unknown PEAP inner authentication method 'auth=%s'.", + p); + goto done; + } + } else if (!strcmp (*iter, "autheap")) { + /* These parameters are for EAP-TTLS */ + continue; + } else { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Unknown phase2 inner authentication method '%s=%s'.", + *iter, p); + goto done; + } + + lower = g_ascii_strdown (p, -1); + g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTH, lower, NULL); + g_free (lower); + break; + } + + if (!nm_setting_802_1x_get_phase2_auth (s_8021x)) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "No valid inner authentication methods found."); + goto done; + } + + success = TRUE; + +done: + g_free(ca_cert); + g_free(phase1); + g_free(phase2); + if (list) + g_strfreev (list); + return success; +} + +static gboolean +eap_ttls_reader (const char *eap_method, + WPANetwork *wpan, + shvarFile *ifcfg, + shvarFile *keys, + NMSetting8021x *s_8021x, + gboolean dummy, + GError **error) +{ + gboolean success = FALSE; + char *anon_ident = NULL; + char *ca_cert = NULL; + char *phase2 = NULL; + char **list = NULL, **iter; + + ca_cert = ifcfg_mdv_wpa_network_get_str(wpan, "ca_cert"); + if (ca_cert) { + if (!nm_setting_802_1x_set_ca_cert (s_8021x, + ca_cert, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NULL, + error)) + goto done; + } else { + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: missing " + "ca_cert for EAP method '%s'; this is" + " insecure!", + eap_method); + } + + anon_ident = ifcfg_mdv_wpa_network_get_str(wpan, "anonymous_identity"); + if (anon_ident && strlen (anon_ident)) + g_object_set (s_8021x, NM_SETTING_802_1X_ANONYMOUS_IDENTITY, anon_ident, NULL); + + phase2 = ifcfg_mdv_wpa_network_get_str(wpan, "phase2"); + if (!phase2) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing phase2 (TTLS inner authentication parameters)."); + goto done; + } + + /* Handle options for the inner auth method */ + list = g_strsplit (phase2, " ", 0); + for (iter = list; iter && *iter; iter++) { + gboolean auth = FALSE, autheap = FALSE; + char *p, *lower = NULL; + + if (!**iter) + continue; + + p = strchr(*iter, '='); + if (!p) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Incorrect phase2 parameter '%s'; key=value expected.", *iter); + goto done; + } + *p++ = '\0'; + + if (!strcmp(*iter, "auth")) { + auth = TRUE; + if ( !strcmp (p, "MSCHAPV2") + || !strcmp (p, "MSCHAP") + || !strcmp (p, "PAP") + || !strcmp (p, "CHAP")) { + if (!eap_simple_reader (p, wpan, ifcfg, keys, s_8021x, TRUE, error)) + goto done; + } else { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Unknown TTLS inner authentication method 'auth=%s'.", + p); + goto done; + } + } else if (!strcmp(*iter, "autheap")) { + autheap = TRUE; + if (!strcmp (p, "TLS")) { + if (!eap_tls_reader (p, wpan, ifcfg, keys, s_8021x, TRUE, error)) + goto done; + } else if (!strcmp (p, "MSCHAPV2") + || !strcmp (p, "MD5")) { + if (!eap_simple_reader (p, wpan, ifcfg, keys, s_8021x, TRUE, error)) + goto done; + } else { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Unknown TTLS inner authentication method 'autheap=%s'.", + p); + goto done; + } + } else { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Unknown phase2 inner authentication method '%s=%s'.", + *iter, p); + goto done; + } + lower = g_ascii_strdown (p, -1); + if (auth) + g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTH, lower, NULL); + if (autheap) + g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTHEAP, p, NULL); + g_free (lower); + break; + } + + success = TRUE; + +done: + g_free(ca_cert); + g_free(anon_ident); + g_free(phase2); + if (list) + g_strfreev (list); + return success; +} + +static NMSetting8021x * +fill_8021x (shvarFile *ifcfg, + WPANetwork *wpan, + const char *file, + const char *key_mgmt, + gboolean wifi, + GError **error) +{ + NMSetting8021x *s_8021x; + shvarFile *keys = NULL; + char *value; + char **list, **iter; + + value = ifcfg_mdv_wpa_network_get_val(wpan, "eap"); + if (!value) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing eap methods for key management '%s'", + key_mgmt); + return NULL; + } + + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new (); + + /* Validate and handle each EAP method */ + list = g_strsplit (value, " ", 0); + for (iter = list; iter && *iter; iter++) { + EAPReader *eap = &eap_readers[0]; + gboolean found = FALSE; + char *lower = NULL; + + lower = g_ascii_strdown (*iter, -1); + while (eap->method && !found) { + if (strcmp (eap->method, lower)) + goto next; + + /* Some EAP methods don't provide keying material, thus they + * cannot be used with WiFi unless they are an inner method + * used with TTLS or PEAP or whatever. + */ + if (wifi && eap->wifi_phase2_only) { + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: ignored invalid " + "IEEE_8021X_EAP_METHOD '%s'; not allowed for wifi.", + lower); + goto next; + } + + /* Parse EAP method specific options */ + if (!(*eap->reader)(lower, wpan, ifcfg, keys, s_8021x, FALSE, error)) { + g_free (lower); + g_strfreev(list); + goto error; + } + nm_setting_802_1x_add_eap_method (s_8021x, lower); + found = TRUE; + + next: + eap++; + } + + if (!found) { + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: ignored unknown" + "IEEE_8021X_EAP_METHOD '%s'.", + lower); + } + g_free (lower); + } + g_strfreev (list); + + if (nm_setting_802_1x_get_num_eap_methods (s_8021x) == 0) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "No valid EAP methods found in IEEE_8021X_EAP_METHODS."); + goto error; + } + + return s_8021x; + +error: + g_object_unref (s_8021x); + return NULL; +} + +static NMSetting * +make_wpa_setting (shvarFile *ifcfg, + WPANetwork *wpan, + const char *file, + const GByteArray *ssid, + gboolean adhoc, + NMSetting8021x **s_8021x, + GError **error) +{ + NMSettingWirelessSecurity *wsec; + char *key_mgmt, *psk, *lower, *proto; + char **list = NULL, **iter; + int np; + + key_mgmt = ifcfg_mdv_wpa_network_get_val (wpan, "key_mgmt"); + /* + * Can NM support two alternative methods? + */ + if (!key_mgmt) + key_mgmt = "WPA-PSK"; + + /* Is it WEP? */ + if (!strcmp(key_mgmt, "NONE")) + return NULL; + + wsec = NM_SETTING_WIRELESS_SECURITY (nm_setting_wireless_security_new ()); + + /* Pairwise and Group ciphers */ + fill_wpa_ciphers (wpan, wsec, FALSE, adhoc); + fill_wpa_ciphers (wpan, wsec, TRUE, adhoc); + + /* + * WPA and/or RSN + * Default to both WPA and RSN allowed. + */ + proto = ifcfg_mdv_wpa_network_get_val(wpan, "proto"); + if (!proto) + proto="WPA RSN"; + + list = g_strsplit_set (proto, " ", 0); + for (np = 0, iter = list; iter && *iter; iter++) { + if (!*iter) + continue; + + if (!strcmp (*iter, "WPA")) { + np++; + nm_setting_wireless_security_add_proto (wsec, "wpa"); + } else if (!strcmp (*iter, "RSN")) { + if (adhoc) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Ad-Hoc mode cannot be used with proto 'RSN'"); + goto free_list; + } + np++; + nm_setting_wireless_security_add_proto (wsec, "rsn"); + } else { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Unknown proto '%s'", *iter); + goto free_list; + } + } + if (list) + g_strfreev(list); + + if (!np) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Empty proto"); + goto error; + } + + /* + * Mandriva adds by default list of available protocols + * FIXME be more intelligent - do not fail completely if + * multiple methods are present; configure the best + * available + */ + if (strstr (key_mgmt, "WPA-EAP") || strstr (key_mgmt, "IEEE8021X")) { + /* Adhoc mode is mutually exclusive with any 802.1x-based authentication */ + if (adhoc) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Ad-Hoc mode cannot be used with key_mgmt type '%s'", key_mgmt); + goto error; + } + + *s_8021x = fill_8021x (ifcfg, wpan, file, key_mgmt, TRUE, error); + if (!*s_8021x) + goto error; + + } else if (strstr (key_mgmt, "WPA-PSK")) { + if (adhoc) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Ad-Hoc mode cannot be used with key_mgmt type 'WPA-PSK'"); + goto error; + } + psk = parse_wpa_psk (wpan, file, ssid, error); + if (!psk) + goto error; + g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_PSK, psk, NULL); + g_free (psk); + } else if (strstr (key_mgmt, "WPA-NONE")) { + if (!adhoc) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "key_mgmt type 'WPA_NONE' allowed only in Ad-Hoc mode"); + goto error; + } + psk = parse_wpa_psk (wpan, file, ssid, error); + if (!psk) + goto error; + g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_PSK, psk, NULL); + g_free (psk); + } else { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Unknown wireless KEY_MGMT type '%s'", key_mgmt); + goto error; + } + lower = g_ascii_strdown (key_mgmt, -1); + g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, lower, NULL); + g_free (lower); + + return (NMSetting *) wsec; + +free_list: + if (list) + g_strfreev(list); +error: + if (wsec) + g_object_unref (wsec); + return NULL; +} + +#if 0 +// LEAP does not seem to be supported by Mandriva +static NMSetting * +make_leap_setting (shvarFile *ifcfg, + const char *file, + GError **error) +{ + NMSettingWirelessSecurity *wsec; + shvarFile *keys_ifcfg; + char *value; + + wsec = NM_SETTING_WIRELESS_SECURITY (nm_setting_wireless_security_new ()); + + value = svGetValue (ifcfg, "KEY_MGMT", FALSE); + if (!value || strcmp (value, "IEEE8021X")) + goto error; /* Not LEAP */ + + g_free (value); + value = svGetValue (ifcfg, "WIRELESS_ENC_MODE", FALSE); + if (!value || strcasecmp (value, "leap")) + goto error; /* Not LEAP */ + + g_free (value); + + value = svGetValue (ifcfg, "IEEE_8021X_PASSWORD", FALSE); + if (!value) { + /* Try to get keys from the "shadow" key file */ + keys_ifcfg = utils_get_keys_ifcfg (file, FALSE); + if (keys_ifcfg) { + value = svGetValue (keys_ifcfg, "IEEE_8021X_PASSWORD", FALSE); + svCloseFile (keys_ifcfg); + } + } + if (value && strlen (value)) + g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD, value, NULL); + g_free (value); + + value = svGetValue (ifcfg, "IEEE_8021X_IDENTITY", FALSE); + if (!value || !strlen (value)) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing LEAP identity"); + goto error; + } + g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, value, NULL); + g_free (value); + + g_object_set (wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "leap", + NULL); + + return (NMSetting *) wsec; + +error: + g_free (value); + if (wsec) + g_object_unref (wsec); + return NULL; +} +#endif + +static NMSetting * +make_wireless_security_setting (shvarFile *ifcfg, + const char *file, + const GByteArray *ssid, + gboolean adhoc, + NMSetting8021x **s_8021x, + GError **error) +{ + NMSetting *wsec = NULL; /* unencrypted by default */ + char *driver; + WPAConfig *wpac = NULL; + WPANetwork *wpan = NULL; + + /* + * Mandriva saves WPA parameters directly in wpa_supplicant.conf + */ + driver = svGetValue (ifcfg, "WIRELESS_WPA_DRIVER", FALSE); + if (driver) { + g_free (driver); + + wpac = ifcfg_mdv_wpa_config_new("/etc/wpa_supplicant.conf"); + if (wpac && ifcfg_mdv_wpa_config_parse(wpac)) { + gboolean found = FALSE; + + ifcfg_mdv_wpa_config_rewind(wpac); + while (!found && (wpan = ifcfg_mdv_wpa_config_next(wpac)) != NULL) { + GByteArray *b_ssid = ifcfg_mdv_wpa_network_get_ssid(wpan); + + if (b_ssid) { + if (b_ssid->len == ssid->len && !memcmp(b_ssid->data, ssid->data, ssid->len)) + found = TRUE; + g_byte_array_unref(b_ssid); + } else + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: no SSID in wpa_supplicant.conf network block"); + } + } else { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "WIRELESS_WPA_DRIVER set but /etc/wpa_supplicant.conf missing"); + goto done; + } + + if (!wpan) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "WIRELESS_WPA_DRIVER set but SSID missing in /etc/wpa_supplicant.conf"); + goto done; + } + } + + if (wpan) { +#if 0 + // LEAP does not seem to be supported by Mandriva + if (!adhoc) { + wsec = make_leap_setting (ifcfg, file, error); + if (wsec) + return wsec; + else if (*error) + goto error; + } +#endif + + wsec = make_wpa_setting (ifcfg, wpan, file, ssid, adhoc, s_8021x, error); + if (wsec || *error) + goto done; + } + + wsec = make_wep_setting (ifcfg, file, error); + +done: + ifcfg_mdv_wpa_config_free (wpac); + return wsec; +} + +static NMSetting * +make_wireless_setting (shvarFile *ifcfg, + gboolean nm_controlled, + char **unmanaged, + char *device, + GError **error) +{ + NMSettingWireless *s_wireless; + GByteArray *array = NULL; + char *value; + + s_wireless = NM_SETTING_WIRELESS (nm_setting_wireless_new ()); + + if (read_mac_address (ifcfg, &array, error)) { + /* if we don't have a HWADDR saved in ifcfg file, try to discover it manually */ + if (!array) { + discover_mac_address(device, &array, error); + } + if (array) { + g_object_set (s_wireless, NM_SETTING_WIRELESS_MAC_ADDRESS, array, NULL); + + /* A connection can only be unmanaged if we know the MAC address */ + if (!nm_controlled) { + *unmanaged = g_strdup_printf ("mac:%02x:%02x:%02x:%02x:%02x:%02x", + array->data[0], array->data[1], array->data[2], + array->data[3], array->data[4], array->data[5]); + } + + g_byte_array_free (array, TRUE); + } else if (!nm_controlled) { + /* If NM_CONTROLLED=no but there wasn't a MAC address, notify + * the user that the device cannot be unmanaged. + */ + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: NM_CONTROLLED was false but HWADDR was missing; device will be managed"); + } + } else { + g_object_unref (s_wireless); + return NULL; + } + + value = svGetValue (ifcfg, "WIRELESS_ESSID", TRUE); + if (value) { + array = ifcfg_mdv_parse_ssid (value, error); + g_free (value); + + if (array) { + g_object_set (s_wireless, NM_SETTING_WIRELESS_SSID, array, NULL); + g_byte_array_free (array, TRUE); + } else + goto error; + } else { + /* Only fail on lack of SSID if device is managed */ + if (nm_controlled) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, "Missing SSID"); + goto error; + } + } + + if (!nm_controlled) + goto done; + + value = svGetValue (ifcfg, "WIRELESS_MODE", FALSE); + if (value) { + char *lcase; + const char *mode = NULL; + + lcase = g_ascii_strdown (value, -1); + g_free (value); + + if (!strcmp (lcase, "ad-hoc")) { + mode = "adhoc"; + } else if (!strcmp (lcase, "managed") || !strcmp (lcase, "auto")) { + mode = "infrastructure"; + } else { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid mode '%s' (not 'Ad-Hoc', 'Managed', or 'Auto')", + lcase); + g_free (lcase); + goto error; + } + g_free (lcase); + + g_object_set (s_wireless, NM_SETTING_WIRELESS_MODE, mode, NULL); + } + +#if 0 + value = svGetValue (ifcfg, "WIRELESS_BSSID", FALSE); + if (value) { + struct ether_addr *eth; + GByteArray *bssid; + + eth = ether_aton (value); + if (!eth) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid BSSID '%s'", value); + goto error; + } + + bssid = g_byte_array_sized_new (ETH_ALEN); + g_byte_array_append (bssid, eth->ether_addr_octet, ETH_ALEN); + g_object_set (s_wireless, NM_SETTING_WIRELESS_BSSID, bssid, NULL); + g_byte_array_free (bssid, TRUE); + } +#endif + + value = svGetValue (ifcfg, "WIRELESS_CHANNEL", FALSE); + if (value) { + long int chan; + + errno = 0; + chan = strtol (value, NULL, 10); + if (errno || chan <= 0 || chan > 196) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid wireless channel '%s'", value); + g_free (value); + goto error; + } + g_object_set (s_wireless, NM_SETTING_WIRELESS_CHANNEL, (guint32) chan, NULL); + if (chan > 14) + g_object_set (s_wireless, NM_SETTING_WIRELESS_BAND, "a", NULL); + else + g_object_set (s_wireless, NM_SETTING_WIRELESS_BAND, "bg", NULL); + } + + value = svGetValue (ifcfg, "MTU", FALSE); + if (value) { + long int mtu; + + errno = 0; + mtu = strtol (value, NULL, 10); + if (errno || mtu < 0 || mtu > 50000) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid wireless MTU '%s'", value); + g_free (value); + goto error; + } + g_object_set (s_wireless, NM_SETTING_WIRELESS_MTU, (guint32) mtu, NULL); + } + +done: + return NM_SETTING (s_wireless); + +error: + if (s_wireless) + g_object_unref (s_wireless); + return NULL; +} + +static NMConnection * +wireless_connection_from_ifcfg (const char *file, + shvarFile *ifcfg, + gboolean nm_controlled, + char **unmanaged, + char *device, + GError **error) +{ + NMConnection *connection = NULL; + NMSetting *con_setting = NULL; + NMSetting *wireless_setting = NULL; + NMSetting8021x *s_8021x = NULL; + const GByteArray *ssid; + NMSetting *security_setting = NULL; + char *printable_ssid = NULL; + const char *mode; + gboolean adhoc = FALSE; + + g_return_val_if_fail (file != NULL, NULL); + g_return_val_if_fail (ifcfg != NULL, NULL); + g_return_val_if_fail (error != NULL, NULL); + g_return_val_if_fail (*error == NULL, NULL); + + connection = nm_connection_new (); + if (!connection) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Failed to allocate new connection for %s.", file); + return NULL; + } + + /* Wireless */ + wireless_setting = make_wireless_setting (ifcfg, nm_controlled, unmanaged, device, error); + if (!wireless_setting) { + g_object_unref (connection); + return NULL; + } + nm_connection_add_setting (connection, wireless_setting); + + ssid = nm_setting_wireless_get_ssid (NM_SETTING_WIRELESS (wireless_setting)); + if (ssid) + printable_ssid = nm_utils_ssid_to_utf8 ((const char *) ssid->data, ssid->len); + else + printable_ssid = g_strdup_printf ("unmanaged"); + + if (nm_controlled) { + mode = nm_setting_wireless_get_mode (NM_SETTING_WIRELESS (wireless_setting)); + if (mode && !strcmp (mode, "adhoc")) + adhoc = TRUE; + + /* Wireless security */ + security_setting = make_wireless_security_setting (ifcfg, file, ssid, adhoc, &s_8021x, error); + if (*error) { + g_object_unref (connection); + return NULL; + } + if (security_setting) { + nm_connection_add_setting (connection, security_setting); + if (s_8021x) + nm_connection_add_setting (connection, NM_SETTING (s_8021x)); + + g_object_set (wireless_setting, NM_SETTING_WIRELESS_SEC, + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NULL); + } + } + + /* Connection */ + con_setting = make_connection_setting (file, ifcfg, + NM_SETTING_WIRELESS_SETTING_NAME, + printable_ssid); + g_free (printable_ssid); + if (!con_setting) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Failed to create connection setting."); + g_object_unref (connection); + return NULL; + } + nm_connection_add_setting (connection, con_setting); + + /* Don't verify if unmanaged since we may not have an SSID or whatever */ + if (nm_controlled) { + if (!nm_connection_verify (connection, error)) { + g_object_unref (connection); + return NULL; + } + } + + return connection; +} + +static NMSetting * +make_wired_setting (shvarFile *ifcfg, + const char *file, + gboolean nm_controlled, + char **unmanaged, + NMSetting8021x **s_8021x, + char *device, + GError **error) +{ + NMSettingWired *s_wired; + char *value = NULL; + int mtu; + GByteArray *mac = NULL; + + s_wired = NM_SETTING_WIRED (nm_setting_wired_new ()); + + value = svGetValue (ifcfg, "MTU", FALSE); + if (value) { + if (get_int (value, &mtu)) { + if (mtu >= 0 && mtu < 65536) + g_object_set (s_wired, NM_SETTING_WIRED_MTU, mtu, NULL); + } else { + /* Shouldn't be fatal... */ + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: invalid MTU '%s'", value); + } + g_free (value); + } + + if (read_mac_address (ifcfg, &mac, error)) { + /* if we don't have a HWADDR saved in ifcfg file, try to discover it manually */ + if (!mac) { + discover_mac_address(device, &mac, error); + } + if (mac) { + g_object_set (s_wired, NM_SETTING_WIRED_MAC_ADDRESS, mac, NULL); + + /* A connection can only be unmanaged if we know the MAC address */ + if (!nm_controlled) { + *unmanaged = g_strdup_printf ("mac:%02x:%02x:%02x:%02x:%02x:%02x", + mac->data[0], mac->data[1], mac->data[2], + mac->data[3], mac->data[4], mac->data[5]); + } + + g_byte_array_free (mac, TRUE); + } else if (!nm_controlled) { + /* If NM_CONTROLLED=no but there wasn't a MAC address, notify + * the user that the device cannot be unmanaged. + */ + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: NM_CONTROLLED was false but HWADDR was missing; device will be managed"); + } + } else { + g_object_unref (s_wired); + s_wired = NULL; + } + +#if 0 + /* Mandriva does not support IEEE802.1x on wired connections */ + value = svGetValue (ifcfg, "KEY_MGMT", FALSE); + if (value) { + if (!strcmp (value, "IEEE8021X")) { + *s_8021x = fill_8021x (ifcfg, file, value, FALSE, error); + if (!*s_8021x) + goto error; + } else { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Unknown wired KEY_MGMT type '%s'", value); + goto error; + } + g_free (value); + } +#endif + + return (NMSetting *) s_wired; + +#if 0 + /* Mandriva does not support IEEE802.1x on wired connections; + * this is unreacheable */ +error: + g_free (value); + g_object_unref (s_wired); + return NULL; +#endif +} + +static NMConnection * +wired_connection_from_ifcfg (const char *file, + shvarFile *ifcfg, + gboolean nm_controlled, + char **unmanaged, + char *device, + GError **error) +{ + NMConnection *connection = NULL; + NMSetting *con_setting = NULL; + NMSetting *wired_setting = NULL; + NMSetting8021x *s_8021x = NULL; + + g_return_val_if_fail (file != NULL, NULL); + g_return_val_if_fail (ifcfg != NULL, NULL); + + connection = nm_connection_new (); + if (!connection) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Failed to allocate new connection for %s.", file); + return NULL; + } + + con_setting = make_connection_setting (file, ifcfg, NM_SETTING_WIRED_SETTING_NAME, NULL); + if (!con_setting) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Failed to create connection setting."); + g_object_unref (connection); + return NULL; + } + nm_connection_add_setting (connection, con_setting); + + wired_setting = make_wired_setting (ifcfg, file, nm_controlled, unmanaged, &s_8021x, device, error); + if (!wired_setting) { + g_object_unref (connection); + return NULL; + } + nm_connection_add_setting (connection, wired_setting); + +#if 0 + /* Always NULL on Mandriva */ + if (s_8021x) + nm_connection_add_setting (connection, NM_SETTING (s_8021x)); +#endif + + if (!nm_connection_verify (connection, error)) { + g_object_unref (connection); + return NULL; + } + + return connection; +} + +static gboolean +is_wireless_device (const char *iface) +{ + int fd; + struct iw_range range; + struct iwreq wrq; + gboolean is_wireless = FALSE; + + g_return_val_if_fail (iface != NULL, FALSE); + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (!fd) + return FALSE; + + memset (&wrq, 0, sizeof (struct iwreq)); + memset (&range, 0, sizeof (struct iw_range)); + strncpy (wrq.ifr_name, iface, IFNAMSIZ); + wrq.u.data.pointer = (caddr_t) ⦥ + wrq.u.data.length = sizeof (struct iw_range); + + if (ioctl (fd, SIOCGIWRANGE, &wrq) == 0) + is_wireless = TRUE; + else { + if (errno == EOPNOTSUPP) + is_wireless = FALSE; + else { + /* Sigh... some wired devices (kvm/qemu) return EINVAL when the + * device is down even though it's not a wireless device. So try + * IWNAME as a fallback. + */ + memset (&wrq, 0, sizeof (struct iwreq)); + strncpy (wrq.ifr_name, iface, IFNAMSIZ); + if (ioctl (fd, SIOCGIWNAME, &wrq) == 0) + is_wireless = TRUE; + } + } + + close (fd); + return is_wireless; +} + +NMConnection * +connection_from_file (const char *filename, + const char *network_file, /* for unit tests only */ + const char *test_type, /* for unit tests only */ + const char *iscsiadm_path, /* for unit tests only */ + char **unmanaged, + char **keyfile, + char **routefile, + char **route6file, + GError **error, + gboolean *ignore_error) +{ + NMConnection *connection = NULL; + shvarFile *parsed; + char *type, *nmc = NULL; + NMSetting *s_ip4; + const char *ifcfg_name = NULL; + gboolean nm_controlled = FALSE, onboot; + char *device; + + g_return_val_if_fail (filename != NULL, NULL); + g_return_val_if_fail (unmanaged != NULL, NULL); + g_return_val_if_fail (*unmanaged == NULL, NULL); + g_return_val_if_fail (keyfile != NULL, NULL); + g_return_val_if_fail (*keyfile == NULL, NULL); + g_return_val_if_fail (routefile != NULL, NULL); + g_return_val_if_fail (*routefile == NULL, NULL); + g_return_val_if_fail (route6file != NULL, NULL); + g_return_val_if_fail (*route6file == NULL, NULL); + + /* Non-NULL only for unit tests; normally use /etc/sysconfig/network */ + if (!network_file) + network_file = SYSCONFDIR "/sysconfig/network"; + + if (!iscsiadm_path) + iscsiadm_path = SBINDIR "/iscsiadm"; + + ifcfg_name = utils_get_ifcfg_name (filename, TRUE); + if (!ifcfg_name) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Ignoring connection '%s' because it's not an ifcfg file.", filename); + return NULL; + } + + parsed = svNewFile (filename); + if (!parsed) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Couldn't parse file '%s'", filename); + return NULL; + } + + /* Read the device */ + device = svGetValue (parsed, "DEVICE", FALSE); + if (!device) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "File '%s' had neither TYPE nor DEVICE keys.", filename); + goto done; + } + + if (!strcmp (device, "lo")) { + *ignore_error = TRUE; + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Ignoring loopback device config."); + g_free (device); + goto done; + } + + type = svGetValue (parsed, "TYPE", FALSE); + if (!type) { + + /* If no type, if the device has wireless extensions, it's wifi, + * otherwise it's ethernet. + */ + if (!test_type) { + /* Test wireless extensions */ + if (is_wireless_device (device)) + type = g_strdup (TYPE_WIRELESS); + else + type = g_strdup (TYPE_ETHERNET); + } else { + /* For the unit tests, there won't necessarily be any + * adapters of the connection's type in the system so the + * type can't be tested with ioctls. + */ + type = g_strdup (test_type); + } + + } + + nmc = svGetValue (parsed, "NM_CONTROLLED", FALSE); + if (nmc) { + char *lower; + + lower = g_ascii_strdown (nmc, -1); + g_free (nmc); + + if (!strcmp (lower, "yes") || !strcmp (lower, "y") || !strcmp (lower, "true")) + nm_controlled = TRUE; + g_free (lower); + } + + /* + * FIXME + * ONBOOT is used by Mandriva initscripts. For now use different + * variable; otherwise both initscripts and NM will try to + * bring interface online. Do not try to control interface + * if ONBOOT was set to true + */ + onboot = svTrueValue (parsed, "ONBOOT", TRUE); + nm_controlled = nm_controlled && !onboot; + + if (!strcasecmp (type, TYPE_ETHERNET)) + connection = wired_connection_from_ifcfg (filename, parsed, nm_controlled, unmanaged, device, error); + else if (!strcasecmp (type, TYPE_WIRELESS)) + connection = wireless_connection_from_ifcfg (filename, parsed, nm_controlled, unmanaged, device, error); + else { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Unknown connection type '%s'", type); + } + + if (nm_controlled) { + g_free (*unmanaged); + *unmanaged = NULL; + } + + g_free (type); + g_free (device); + + /* Don't bother reading the connection fully if it's unmanaged */ + if (!connection || *unmanaged) + goto done; + + s_ip4 = make_ip4_setting (parsed, network_file, iscsiadm_path, error); + if (*error) { + g_object_unref (connection); + connection = NULL; + goto done; + } else if (s_ip4) { + nm_connection_add_setting (connection, s_ip4); + } + +#if 0 + /* No IPv6 on Mandriva */ + s_ip6 = make_ip6_setting (parsed, network_file, iscsiadm_path, error); + if (*error) { + g_object_unref (connection); + connection = NULL; + goto done; + } else if (s_ip6) + nm_connection_add_setting (connection, s_ip6); +#endif + +#if 0 + /* no iSCSI on Mandriva */ + /* iSCSI / ibft connections are read-only since their settings are + * stored in NVRAM and can only be changed in BIOS. + */ + bootproto = svGetValue (parsed, "BOOTPROTO", FALSE); + if ( bootproto + && connection + && !g_ascii_strcasecmp (bootproto, "ibft")) { + NMSettingConnection *s_con; + + s_con = (NMSettingConnection *) nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION); + g_assert (s_con); + + g_object_set (G_OBJECT (s_con), NM_SETTING_CONNECTION_READ_ONLY, TRUE, NULL); + } +#endif + + if (!nm_connection_verify (connection, error)) { + g_object_unref (connection); + connection = NULL; + } + + *keyfile = utils_get_keys_path (filename); + *routefile = utils_get_route_path (filename); + *route6file = utils_get_route6_path (filename); + +done: + svCloseFile (parsed); + return connection; +} + +const char * +reader_get_prefix (void) +{ + return _("System"); +} + diff --git a/system-settings/plugins/ifcfg-mdv/reader.h b/system-settings/plugins/ifcfg-mdv/reader.h new file mode 100644 index 0000000..94fb235 --- /dev/null +++ b/system-settings/plugins/ifcfg-mdv/reader.h @@ -0,0 +1,43 @@ +/* -*- 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 (C) 2008 Red Hat, Inc. + */ + +#ifndef __READER_H__ +#define __READER_H__ + +#include <glib.h> +#include <nm-connection.h> + +#include "shvar.h" + +NMConnection *connection_from_file (const char *filename, + const char *network_file, /* for unit tests only */ + const char *test_type, /* for unit tests only */ + const char *iscsiadm_path, /* for unit tests only */ + char **unmanaged, + char **keyfile, + char **routefile, + char **route6file, + GError **error, + gboolean *ignore_error); + +const char *reader_get_prefix (void); +GByteArray *ifcfg_mdv_parse_ssid(char *, GError **); + +#endif /* __READER_H__ */ diff --git a/system-settings/plugins/ifcfg-mdv/writer.c b/system-settings/plugins/ifcfg-mdv/writer.c new file mode 100644 index 0000000..8d80a84 --- /dev/null +++ b/system-settings/plugins/ifcfg-mdv/writer.c @@ -0,0 +1,1721 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager system settings service - keyfile plugin + * + * 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 (C) 2009 Red Hat, Inc. + * Mandriva-specific changes by Eugeni Dodonov <eugeni@mandriva.com>. + */ + +#include <ctype.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <errno.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdio.h> + +#include <nm-setting-connection.h> +#include <nm-setting-wired.h> +#include <nm-setting-wireless.h> +#include <nm-setting-8021x.h> +#include <nm-setting-ip4-config.h> +#include <nm-setting-ip6-config.h> +#include <nm-setting-pppoe.h> +#include <nm-utils.h> + +#include "common.h" +#include "shvar.h" +#include "reader.h" +#include "writer.h" +#include "utils.h" +#include "crypto.h" + +#include "parse_wpa_supplicant_conf.h" + +#define PLUGIN_WARN(pname, fmt, args...) \ + { g_warning (" " pname ": " fmt, ##args); } + + +/* + * ifcfg reader converts ASCII to HEX. This converts key back to ASCII + * before writing + */ + +static gchar * +wep4ifcfg(const gchar *value) +{ + gchar *s; + gsize len; + GString *str; + + if (!value) + return NULL; + + len = strlen(value); + str = g_string_new(""); + + if (len == 5 || len == 13) { + g_string_printf(str, "s:%s", value); + } else if (len == 10 || len == 26) { + gchar *p; + s = utils_hexstr2bin (value, len); + for (p = s; *p; p++) { + if (!isascii (*p)) { + g_free(s); + g_string_free(str, TRUE); + return g_strdup(value); + } + } + g_string_printf(str, "s:%s", s); + g_free(s); + } else { + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: invalid WEP key length"); + g_string_free(str, TRUE); + return NULL; + } + + s = svEscape(str->str); + g_string_free(str, TRUE); + return s; +} +static void +set_wep_secret (shvarFile *ifcfg, const char *key, const char *value, gboolean verbatim) +{ + char *v = 0; + + /* Clear the secret from the actual ifcfg */ + svSetValue (ifcfg, key, NULL, FALSE); + + /* WEP -> WPA will set empty key */ + if (!value) + return; + + v = wep4ifcfg(value); + if (!v) + return; + + /* Try setting the secret in the actual ifcfg */ + svSetValue (ifcfg, key, v, TRUE); + g_free(v); +} + +#if 0 +static gboolean +write_secret_file (const char *path, + const char *data, + gsize len, + GError **error) +{ + char *tmppath; + int fd = -1, written; + gboolean success = FALSE; + + tmppath = g_malloc0 (strlen (path) + 10); + if (!tmppath) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Could not allocate memory for temporary file for '%s'", + path); + return FALSE; + } + + memcpy (tmppath, path, strlen (path)); + strcat (tmppath, ".XXXXXX"); + + errno = 0; + fd = mkstemp (tmppath); + if (fd < 0) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Could not create temporary file for '%s': %d", + path, errno); + goto out; + } + + /* Only readable by root */ + errno = 0; + if (fchmod (fd, S_IRUSR | S_IWUSR)) { + close (fd); + unlink (tmppath); + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Could not set permissions for temporary file '%s': %d", + path, errno); + goto out; + } + + errno = 0; + written = write (fd, data, len); + if (written != len) { + close (fd); + unlink (tmppath); + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Could not write temporary file for '%s': %d", + path, errno); + goto out; + } + close (fd); + + /* Try to rename */ + errno = 0; + if (rename (tmppath, path)) { + unlink (tmppath); + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Could not rename temporary file to '%s': %d", + path, errno); + goto out; + } + success = TRUE; + +out: + return success; +} +#endif + +typedef NMSetting8021xCKScheme (*SchemeFunc)(NMSetting8021x *setting); +typedef const char * (*PathFunc) (NMSetting8021x *setting); +typedef const GByteArray * (*BlobFunc) (NMSetting8021x *setting); + +typedef struct ObjectType { + const char *setting_key; + SchemeFunc scheme_func; + PathFunc path_func; + BlobFunc blob_func; + const char *ifcfg_key; + const char *suffix; +} ObjectType; + +static const ObjectType ca_type = { + NM_SETTING_802_1X_CA_CERT, + nm_setting_802_1x_get_ca_cert_scheme, + nm_setting_802_1x_get_ca_cert_path, + nm_setting_802_1x_get_ca_cert_blob, + "ca_cert", + "ca-cert.der" +}; + +static const ObjectType phase2_ca_type = { + NM_SETTING_802_1X_PHASE2_CA_CERT, + nm_setting_802_1x_get_phase2_ca_cert_scheme, + nm_setting_802_1x_get_phase2_ca_cert_path, + nm_setting_802_1x_get_phase2_ca_cert_blob, + "ca_cert2", + "inner-ca-cert.der" +}; + +static const ObjectType client_type = { + NM_SETTING_802_1X_CLIENT_CERT, + nm_setting_802_1x_get_client_cert_scheme, + nm_setting_802_1x_get_client_cert_path, + nm_setting_802_1x_get_client_cert_blob, + "client_cert", + "client-cert.der" +}; + +static const ObjectType phase2_client_type = { + NM_SETTING_802_1X_PHASE2_CLIENT_CERT, + nm_setting_802_1x_get_phase2_client_cert_scheme, + nm_setting_802_1x_get_phase2_client_cert_path, + nm_setting_802_1x_get_phase2_client_cert_blob, + "client_cert2", + "inner-client-cert.der" +}; + +static const ObjectType pk_type = { + NM_SETTING_802_1X_PRIVATE_KEY, + nm_setting_802_1x_get_private_key_scheme, + nm_setting_802_1x_get_private_key_path, + nm_setting_802_1x_get_private_key_blob, + "private_key", + "private-key.pem" +}; + +static const ObjectType phase2_pk_type = { + NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, + nm_setting_802_1x_get_phase2_private_key_scheme, + nm_setting_802_1x_get_phase2_private_key_path, + nm_setting_802_1x_get_phase2_private_key_blob, + "private_key2", + "inner-private-key.pem" +}; + +static const ObjectType p12_type = { + NM_SETTING_802_1X_PRIVATE_KEY, + nm_setting_802_1x_get_private_key_scheme, + nm_setting_802_1x_get_private_key_path, + nm_setting_802_1x_get_private_key_blob, + "private_key", + "private-key.p12" +}; + +static const ObjectType phase2_p12_type = { + NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, + nm_setting_802_1x_get_phase2_private_key_scheme, + nm_setting_802_1x_get_phase2_private_key_path, + nm_setting_802_1x_get_phase2_private_key_blob, + "private_key2", + "inner-private-key.p12" +}; + +/* + * FIXME the name is misleading and should be changed + * Mandriva does not use explicit certifcate store so we do nto either + * If given BLOB - fail, informing user + */ +static gboolean +write_object (NMSetting8021x *s_8021x, + WPANetwork *wpan, + shvarFile *ifcfg, + const GByteArray *override_data, + const ObjectType *objtype, + GError **error) +{ + NMSetting8021xCKScheme scheme; + const char *path = NULL; + const GByteArray *blob = NULL; + + g_return_val_if_fail (ifcfg != NULL, FALSE); + g_return_val_if_fail (objtype != NULL, FALSE); + + if (override_data) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "ifcfg-mdv does not support raw certificate data"); + return FALSE; + /* if given explicit data to save, always use that instead of asking + * the setting what to do. + */ + // blob = override_data; + } else { + scheme = (*(objtype->scheme_func))(s_8021x); + switch (scheme) { + case NM_SETTING_802_1X_CK_SCHEME_BLOB: + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "ifcfg-mdv does not support raw certificate data"); + return FALSE; + // blob = (*(objtype->blob_func))(s_8021x); + // break; + case NM_SETTING_802_1X_CK_SCHEME_PATH: + path = (*(objtype->path_func))(s_8021x); + break; + default: + break; + } + } + + /* If certificate/private key wasn't sent, the connection may no longer be + * 802.1x and thus we clear out the paths and certs. + */ + if (!path && !blob) { + // char *standard_file; + // int ignored; + + /* Since no cert/private key is now being used, delete any standard file + * that was created for this connection, but leave other files alone. + * Thus, for example, + * /etc/sysconfig/network-scripts/ca-cert-Test_Write_Wifi_WPA_EAP-TLS.der + * will be deleted, but /etc/pki/tls/cert.pem will not. + */ +#if 0 + standard_file = utils_cert_path (ifcfg->fileName, objtype->suffix); + if (g_file_test (standard_file, G_FILE_TEST_EXISTS)) + ignored = unlink (standard_file); + g_free (standard_file); +#endif + + ifcfg_mdv_wpa_network_unset(wpan, objtype->ifcfg_key); + return TRUE; + } + + /* If the object path was specified, prefer that over any raw cert data that + * may have been sent. + */ + if (path) { + ifcfg_mdv_wpa_network_set_str(wpan, objtype->ifcfg_key, path); + return TRUE; + } + +#if 0 + /* If it's raw certificate data, write the cert data out to the standard file */ + if (blob) { + gboolean success; + char *new_file; + GError *write_error = NULL; + + new_file = utils_cert_path (ifcfg->fileName, objtype->suffix); + if (!new_file) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Could not create file path for %s / %s", + NM_SETTING_802_1X_SETTING_NAME, objtype->setting_key); + return FALSE; + } + + /* Write the raw certificate data out to the standard file so that we + * can use paths from now on instead of pushing around the certificate + * data itself. + */ + success = write_secret_file (new_file, (const char *) blob->data, blob->len, &write_error); + if (success) { + svSetValue (ifcfg, objtype->ifcfg_key, new_file, FALSE); + return TRUE; + } else { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Could not write certificate/key for %s / %s: %s", + NM_SETTING_802_1X_SETTING_NAME, objtype->setting_key, + (write_error && write_error->message) ? write_error->message : "(unknown)"); + g_clear_error (&write_error); + } + g_free (new_file); + } +#endif + + return FALSE; +} + +static gboolean +write_8021x_certs (NMSetting8021x *s_8021x, + WPANetwork *wpan, + gboolean phase2, + shvarFile *ifcfg, + GError **error) +{ + GByteArray *enc_key = NULL; + const char *password = NULL; + // char *generated_pw = NULL; + gboolean success = FALSE, is_pkcs12 = FALSE; + const ObjectType *otype = NULL; + const GByteArray *blob = NULL; + + /* CA certificate */ + if (phase2) + otype = &phase2_ca_type; + else + otype = &ca_type; + + if (!write_object (s_8021x, wpan, ifcfg, NULL, otype, error)) + return FALSE; + + /* Private key */ + if (phase2) { + if (nm_setting_802_1x_get_phase2_private_key_scheme (s_8021x) != NM_SETTING_802_1X_CK_SCHEME_UNKNOWN) { + if (nm_setting_802_1x_get_phase2_private_key_format (s_8021x) == NM_SETTING_802_1X_CK_FORMAT_PKCS12) + is_pkcs12 = TRUE; + } + password = nm_setting_802_1x_get_phase2_private_key_password (s_8021x); + } else { + if (nm_setting_802_1x_get_private_key_scheme (s_8021x) != NM_SETTING_802_1X_CK_SCHEME_UNKNOWN) { + if (nm_setting_802_1x_get_private_key_format (s_8021x) == NM_SETTING_802_1X_CK_FORMAT_PKCS12) + is_pkcs12 = TRUE; + } + password = nm_setting_802_1x_get_private_key_password (s_8021x); + } + + if (is_pkcs12) + otype = phase2 ? &phase2_p12_type : &p12_type; + else + otype = phase2 ? &phase2_pk_type : &pk_type; + + if ((*(otype->scheme_func))(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_BLOB) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "ifcfg-mdv does not support raw certificate data"); + return FALSE; + // blob = (*(otype->blob_func))(s_8021x); + } + +#if 0 + /* Only do the private key re-encrypt dance if we got the raw key data, which + * by definition will be unencrypted. If we're given a direct path to the + * private key file, it'll be encrypted, so we don't need to re-encrypt. + */ + if (blob && !is_pkcs12) { + /* Encrypt the unencrypted private key with the fake password */ + enc_key = nm_utils_rsa_key_encrypt (blob, password, &generated_pw, error); + if (!enc_key) + goto out; + + if (generated_pw) + password = generated_pw; + } +#endif + + /* Save the private key */ + if (!write_object (s_8021x, wpan, ifcfg, enc_key ? enc_key : blob, otype, error)) + goto out; + + /* Private key password */ + /* FIXME what about hash:XXX? */ + if (phase2) + ifcfg_mdv_wpa_network_set_str(wpan, "private_key2_password", password); + else + ifcfg_mdv_wpa_network_set_str(wpan, "private_key_password", password); + + /* Client certificate */ + if (is_pkcs12) { + ifcfg_mdv_wpa_network_unset(wpan, + phase2 ? "client_cert2" : "client_cert"); + } else { + if (phase2) + otype = &phase2_client_type; + else + otype = &client_type; + + /* Save the client certificate */ + if (!write_object (s_8021x, wpan, ifcfg, NULL, otype, error)) + goto out; + } + + success = TRUE; + +out: +#if 0 + if (generated_pw) { + memset (generated_pw, 0, strlen (generated_pw)); + g_free (generated_pw); + } + if (enc_key) { + memset (enc_key->data, 0, enc_key->len); + g_byte_array_free (enc_key, TRUE); + } +#endif + return success; +} + +static gboolean +write_8021x_setting (NMConnection *connection, + WPANetwork *wpan, + shvarFile *ifcfg, + gboolean wired, + GError **error) +{ + NMSetting8021x *s_8021x; + const char *value; + char *tmp = NULL; + gboolean success = FALSE; + GString *phase2_auth; + GString *str; + + s_8021x = (NMSetting8021x *) nm_connection_get_setting (connection, NM_TYPE_SETTING_802_1X); + if (!s_8021x) { +#if 0 + /* No wired security in Mandriva */ + /* If wired, clear KEY_MGMT */ + if (wired) + svSetValue (ifcfg, "KEY_MGMT", NULL, FALSE); +#endif + return TRUE; + } + +#if 0 + /* No wired security in Mandriva */ + /* If wired, write KEY_MGMT */ + if (wired) + svSetValue (ifcfg, "KEY_MGMT", "IEEE8021X", FALSE); +#endif + + /* EAP method */ + if (nm_setting_802_1x_get_num_eap_methods (s_8021x)) { + value = nm_setting_802_1x_get_eap_method (s_8021x, 0); + if (value) + tmp = g_ascii_strup (value, -1); + } + ifcfg_mdv_wpa_network_set_val(wpan, "eap", tmp ? tmp : NULL); + g_free (tmp); + + ifcfg_mdv_wpa_network_set_str(wpan, "identity", + nm_setting_802_1x_get_identity (s_8021x)); + + ifcfg_mdv_wpa_network_set_str(wpan, "anonymous_identity", + nm_setting_802_1x_get_anonymous_identity (s_8021x)); + + ifcfg_mdv_wpa_network_set_str(wpan, "password", nm_setting_802_1x_get_password (s_8021x)); + + str = g_string_new(""); + + /* PEAP version */ + value = nm_setting_802_1x_get_phase1_peapver (s_8021x); + if (value && (!strcmp (value, "0") || !strcmp (value, "1"))) + g_string_printf(str, "peapver=%s", value); + + /* Force new PEAP label */ + value = nm_setting_802_1x_get_phase1_peaplabel (s_8021x); + if (value && !strcmp (value, "1")) { + if (str->len) + g_string_append_c(str, ' '); + g_string_printf(str, "peaplabel=%s", value); + } + + if (str->len) + ifcfg_mdv_wpa_network_set_str(wpan, "phase1", str->str); + g_string_free(str, TRUE); + + /* Phase2 auth methods */ + phase2_auth = g_string_new (NULL); + + value = nm_setting_802_1x_get_phase2_auth (s_8021x); + if (value) { + tmp = g_ascii_strup (value, -1); + g_string_printf (phase2_auth, "auth=%s", tmp); + g_free (tmp); + } + + value = nm_setting_802_1x_get_phase2_autheap (s_8021x); + if (value) { + if (phase2_auth->len) + g_string_append_c (phase2_auth, ' '); + + tmp = g_ascii_strup (value, -1); + g_string_append_printf (phase2_auth, "autheap=%s", tmp); + g_free (tmp); + } + + if (phase2_auth->len) + ifcfg_mdv_wpa_network_set_str(wpan, "phase2", phase2_auth->str); + g_string_free (phase2_auth, TRUE); + + success = write_8021x_certs (s_8021x, wpan, FALSE, ifcfg, error); + if (success) { + /* phase2/inner certs */ + success = write_8021x_certs (s_8021x, wpan, TRUE, ifcfg, error); + } + + return success; +} + +static gboolean +write_wireless_security_setting (NMConnection *connection, + shvarFile *ifcfg, + WPANetwork *wpan, + gboolean adhoc, + gboolean *no_8021x, + GError **error) +{ + NMSettingWirelessSecurity *s_wsec; + const char *key_mgmt, *auth_alg, *key, *proto, *cipher; + gboolean wep = FALSE, wpa = FALSE; + char *tmp; + guint32 i, num; + GString *str; + + s_wsec = (NMSettingWirelessSecurity *) nm_connection_get_setting (connection, NM_TYPE_SETTING_WIRELESS_SECURITY); + if (!s_wsec) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing '%s' setting", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME); + return FALSE; + } + + key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wsec); + g_assert (key_mgmt); + + auth_alg = nm_setting_wireless_security_get_auth_alg (s_wsec); + + // svSetValue (ifcfg, "DEFAULTKEY", NULL, FALSE); + + if (!strcmp (key_mgmt, "none")) { + wep = TRUE; + *no_8021x = TRUE; + } else if (!strcmp (key_mgmt, "wpa-none") || !strcmp (key_mgmt, "wpa-psk")) { + ifcfg_mdv_wpa_network_set_val(wpan, "key_mgmt", "WPA-PSK"); + wpa = TRUE; + *no_8021x = TRUE; + } else if (!strcmp (key_mgmt, "ieee8021x")) { + ifcfg_mdv_wpa_network_set_val(wpan, "key_mgmt", "IEEE8021X"); + } else if (!strcmp (key_mgmt, "wpa-eap")) { + ifcfg_mdv_wpa_network_set_val(wpan, "key_mgmt", "WPA-EAP"); + wpa = TRUE; + } + + /* TODO add additional fields to private object to store extra + * values during parsing configuration */ + if (strcmp(key_mgmt, "none")) + ifcfg_mdv_wpa_network_set_val(wpan, "priority", "1"); + + svSetValue (ifcfg, "WIRELESS_ENC_MODE", NULL, FALSE); + if (auth_alg) { + if (!strcmp (auth_alg, "shared")) { + if (wep) + svSetValue (ifcfg, "WIRELESS_ENC_MODE", "restricted", FALSE); + ifcfg_mdv_wpa_network_set_val(wpan, "auth_alg", "SHARED"); + } else if (!strcmp (auth_alg, "open")) { + if (wep) + svSetValue (ifcfg, "WIRELESS_ENC_MODE", "open", FALSE); + ifcfg_mdv_wpa_network_set_val(wpan, "auth_alg", "OPEN"); + } else if (!strcmp (auth_alg, "leap")) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "ifcfg-mdv does not support LEAP authentication"); + return FALSE; +#if 0 + /* Not used by Mandriva */ + svSetValue (ifcfg, "WIRELESS_ENC_MODE", "leap", FALSE); + svSetValue (ifcfg, "IEEE_8021X_IDENTITY", + nm_setting_wireless_security_get_leap_username (s_wsec), + FALSE); + set_secret (ifcfg, "IEEE_8021X_PASSWORD", + nm_setting_wireless_security_get_leap_password (s_wsec), + FALSE); + *no_8021x = TRUE; +#endif + } + } + +#if 0 + if (wep) { + /* Default WEP TX key index */ + tmp = g_strdup_printf ("%d", nm_setting_wireless_security_get_wep_tx_keyidx (s_wsec) + 1); + svSetValue (ifcfg, "DEFAULTKEY", tmp, FALSE); + g_free (tmp); + } +#endif + + /* WEP keys */ + /* Mandriva always sets key_idx == 0 */ + key = nm_setting_wireless_security_get_wep_key (s_wsec, 0); + set_wep_secret (ifcfg, "WIRELESS_ENC_KEY", (wep && key) ? key : NULL, FALSE); + + /* FIXME What about roaming mode? */ + if (wep) { + /* remove WPA driver to indicate WEP mode */ + svSetValue (ifcfg, "WIRELESS_WPA_DRIVER", NULL, FALSE); + + /* remove network from wpa_suplicant.conf */ + ifcfg_mdv_wpa_network_set_val(wpan, "__DELETE__", "yes"); + + return TRUE; + } + + /* wpa_supplicant driver. NM always uses wext for wireless */ + svSetValue (ifcfg, "WIRELESS_WPA_DRIVER", "wext", FALSE); + + /* WPA protos */ + str = g_string_new (NULL); + num = nm_setting_wireless_security_get_num_protos (s_wsec); + for (i = 0; i < num; i++) { + gchar *p = NULL; + + proto = nm_setting_wireless_security_get_proto (s_wsec, i); + if (proto && !strcmp (proto, "wpa")) + p = "WPA"; + else if (proto && !strcmp (proto, "rsn")) + p = "RSN"; + if (p) { + if (i > 0) + g_string_append_c(str, ' '); + g_string_append(str, p); + } + } + if (strlen (str->str)) + ifcfg_mdv_wpa_network_set_val(wpan, "proto", str->str); + + /* WPA Pairwise ciphers */ + g_string_set_size (str, 0); + num = nm_setting_wireless_security_get_num_pairwise (s_wsec); + for (i = 0; i < num; i++) { + if (i > 0) + g_string_append_c (str, ' '); + cipher = nm_setting_wireless_security_get_pairwise (s_wsec, i); + tmp = g_ascii_strup (cipher, -1); + g_string_append (str, tmp); + g_free (tmp); + } + if (strlen (str->str)) + ifcfg_mdv_wpa_network_set_val(wpan, "pairwise", str->str); + + /* WPA Group ciphers */ + g_string_set_size (str, 0); + num = nm_setting_wireless_security_get_num_groups (s_wsec); + for (i = 0; i < num; i++) { + if (i > 0) + g_string_append_c (str, ' '); + cipher = nm_setting_wireless_security_get_group (s_wsec, i); + tmp = g_ascii_strup (cipher, -1); + g_string_append (str, tmp); + g_free (tmp); + } + if (strlen (str->str)) + ifcfg_mdv_wpa_network_set_val(wpan, "group", str->str); + + + /* WPA Passphrase */ + if (wpa) { + const char *psk = nm_setting_wireless_security_get_psk(s_wsec); + if (psk) { + g_string_assign(str, psk); + if (str->len != 64) { + /* Quote the PSK since it's a passphrase */ + g_string_prepend_c (str, '"'); + g_string_append_c (str, '"'); + } + + ifcfg_mdv_wpa_network_set_val(wpan, "psk", str->str); + } + } + g_string_free (str, TRUE); + + return TRUE; +} + +static gboolean +write_wireless_setting (NMConnection *connection, + shvarFile *ifcfg, + WPANetwork *wpan, + gboolean *no_8021x, + GError **error) +{ + NMSettingWireless *s_wireless; + char *tmp = NULL; + const GByteArray *ssid, *bssid; + GByteArray *old_ssid = NULL; + const char *mode; + guint32 mtu, chan, i; + gboolean adhoc = FALSE; + gchar buf[33]; + + s_wireless = (NMSettingWireless *) nm_connection_get_setting (connection, NM_TYPE_SETTING_WIRELESS); + if (!s_wireless) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing '%s' setting", NM_SETTING_WIRELESS_SETTING_NAME); + return FALSE; + } + + /* Mandriva does not store HWADDR in ifcfg-XXX */ +#if 0 + svSetValue (ifcfg, "HWADDR", NULL, FALSE); + mac = nm_setting_wireless_get_mac_address (s_wireless); + if (mac) { + tmp = g_strdup_printf ("%02X:%02X:%02X:%02X:%02X:%02X", + mac->data[0], mac->data[1], mac->data[2], + mac->data[3], mac->data[4], mac->data[5]); + svSetValue (ifcfg, "HWADDR", tmp, FALSE); + g_free (tmp); + } +#endif + + svSetValue (ifcfg, "MTU", NULL, FALSE); + mtu = nm_setting_wireless_get_mtu (s_wireless); + if (mtu) { + tmp = g_strdup_printf ("%u", mtu); + svSetValue (ifcfg, "MTU", tmp, FALSE); + g_free (tmp); + } + + ssid = nm_setting_wireless_get_ssid (s_wireless); + if (!ssid) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing SSID in '%s' setting", NM_SETTING_WIRELESS_SETTING_NAME); + return FALSE; + } + if (!ssid->len || ssid->len > 32) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid SSID in '%s' setting", NM_SETTING_WIRELESS_SETTING_NAME); + return FALSE; + } + + /* + * Mandriva is using SSID as part of file name; check for characters + * that cannot included */ + for (i = 0; i < ssid->len; i++) + if (G_DIR_SEPARATOR == ssid->data[i] || ssid->data[i] == '\0') { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid SSID in '%s' setting", NM_SETTING_WIRELESS_SETTING_NAME); + return FALSE; + } + + /* + * If SID changed we have to remove it from wpa_supplicant.conf + */ + tmp = svGetValue(ifcfg, "WIRELESS_ESSID", TRUE); + if (!tmp) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing WIRELESS_SSID in '%s'", ifcfg->fileName); + return FALSE; + } + old_ssid = ifcfg_mdv_parse_ssid(tmp, error); + if (!old_ssid) + goto free; + + if (ssid->len != old_ssid->len || !memcmp(ssid->data, old_ssid->data, ssid->len)) { + WPANetwork *del = ifcfg_mdv_wpa_network_new(NULL); + + if (!del) { + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: could not allocate WPANetwork to remove SSID '%s'", + tmp); + goto free; + } + + ifcfg_mdv_wpa_network_set_ssid(del, old_ssid); + ifcfg_mdv_wpa_network_set_val(del, "__DELETE__", "yes"); + ifcfg_mdv_wpa_network_save(del, "/etc/wpa_supplicant.conf", error); + ifcfg_mdv_wpa_network_free(del); + if (*error) { + goto free; + } + } + g_free(tmp); + g_byte_array_free(old_ssid, TRUE); + + /* we just verified that it does not contain '\0' and fits in buf */ + memcpy(buf, ssid->data, ssid->len); + buf[ssid->len] = '\0'; + tmp = svEscape(buf); + svSetValue (ifcfg, "WIRELESS_ESSID", tmp, TRUE); + g_free(tmp); + ifcfg_mdv_wpa_network_set_ssid(wpan, ssid); + + mode = nm_setting_wireless_get_mode (s_wireless); + if (!mode || !strcmp (mode, "infrastructure")) { + svSetValue (ifcfg, "WIRELESS_MODE", "Managed", FALSE); + } else if (!strcmp (mode, "adhoc")) { + svSetValue (ifcfg, "WIRELESS_MODE", "Ad-Hoc", FALSE); + adhoc = TRUE; + } else { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Invalid mode '%s' in '%s' setting", + mode, NM_SETTING_WIRELESS_SETTING_NAME); + return FALSE; + } + + svSetValue (ifcfg, "WIRELESS_CHANNEL", NULL, FALSE); + chan = nm_setting_wireless_get_channel (s_wireless); + if (chan) { + tmp = g_strdup_printf ("%u", chan); + svSetValue (ifcfg, "WIRELESS_CHANNEL", tmp, FALSE); + g_free (tmp); + } + + if (nm_setting_wireless_get_security (s_wireless)) { + if (!write_wireless_security_setting (connection, ifcfg, wpan, adhoc, no_8021x, error)) + return FALSE; + } + + // svSetValue (ifcfg, "BSSID", NULL, FALSE); + bssid = nm_setting_wireless_get_bssid (s_wireless); + if (bssid) { + tmp = g_strdup_printf ("%02X:%02X:%02X:%02X:%02X:%02X", + bssid->data[0], bssid->data[1], bssid->data[2], + bssid->data[3], bssid->data[4], bssid->data[5]); + ifcfg_mdv_wpa_network_set_val(wpan, "bssid", tmp); + // svSetValue (ifcfg, "BSSID", tmp, FALSE); + g_free (tmp); + } + + + // svSetValue (ifcfg, "TYPE", TYPE_WIRELESS, FALSE); + + return TRUE; +free: + g_free(tmp); + if (old_ssid) + g_byte_array_free(old_ssid, TRUE); + + return FALSE; +} + +static gboolean +write_wired_setting (NMConnection *connection, shvarFile *ifcfg, GError **error) +{ + NMSettingWired *s_wired; + // const GByteArray *mac; + char *tmp; + guint32 mtu; + + s_wired = (NMSettingWired *) nm_connection_get_setting (connection, NM_TYPE_SETTING_WIRED); + if (!s_wired) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing '%s' setting", NM_SETTING_WIRED_SETTING_NAME); + return FALSE; + } + + /* Mandriva does not store HWADDR in ifcfg-XXX */ +#if 0 + mac = nm_setting_wired_get_mac_address (s_wired); + if (mac) { + tmp = g_strdup_printf ("%02X:%02X:%02X:%02X:%02X:%02X", + mac->data[0], mac->data[1], mac->data[2], + mac->data[3], mac->data[4], mac->data[5]); + svSetValue (ifcfg, "HWADDR", tmp, FALSE); + g_free (tmp); + } +#endif + + mtu = nm_setting_wired_get_mtu (s_wired); + if (mtu) { + tmp = g_strdup_printf ("%u", mtu); + svSetValue (ifcfg, "MTU", tmp, FALSE); + g_free (tmp); + } + + // svSetValue (ifcfg, "TYPE", TYPE_ETHERNET, FALSE); + + return TRUE; +} + +static void +write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg) +{ + char *tmp; + + // svSetValue (ifcfg, "NAME", nm_setting_connection_get_id (s_con), FALSE); + // svSetValue (ifcfg, "UUID", nm_setting_connection_get_uuid (s_con), FALSE); + /* FIXME temporary until we can use ONBOOT again */ + svSetValue (ifcfg, "ONBOOT", "no", FALSE); + svSetValue (ifcfg, "_NM_ONBOOT", + nm_setting_connection_get_autoconnect (s_con) ? "yes" : "no", + FALSE); + + svSetValue (ifcfg, "LAST_CONNECT", NULL, FALSE); + if (nm_setting_connection_get_timestamp (s_con)) { + tmp = g_strdup_printf ("%" G_GUINT64_FORMAT, nm_setting_connection_get_timestamp (s_con)); + svSetValue (ifcfg, "LAST_CONNECT", tmp, FALSE); + g_free (tmp); + } +} + +#if 0 +No route file on Mandriva +static gboolean +write_route_file_legacy (const char *filename, NMSettingIP4Config *s_ip4, GError **error) +{ + char dest[INET_ADDRSTRLEN]; + char next_hop[INET_ADDRSTRLEN]; + char **route_items; + char *route_contents; + NMIP4Route *route; + guint32 ip, prefix, metric; + guint32 i, num; + gboolean success = FALSE; + + g_return_val_if_fail (filename != NULL, FALSE); + g_return_val_if_fail (s_ip4 != NULL, FALSE); + g_return_val_if_fail (error != NULL, FALSE); + g_return_val_if_fail (*error == NULL, FALSE); + + num = nm_setting_ip4_config_get_num_routes (s_ip4); + if (num == 0) { + unlink (filename); + return TRUE; + } + + route_items = g_malloc0 (sizeof (char*) * (num + 1)); + for (i = 0; i < num; i++) { + route = nm_setting_ip4_config_get_route (s_ip4, i); + + memset (dest, 0, sizeof (dest)); + ip = nm_ip4_route_get_dest (route); + inet_ntop (AF_INET, (const void *) &ip, &dest[0], sizeof (dest)); + + prefix = nm_ip4_route_get_prefix (route); + + memset (next_hop, 0, sizeof (next_hop)); + ip = nm_ip4_route_get_next_hop (route); + inet_ntop (AF_INET, (const void *) &ip, &next_hop[0], sizeof (next_hop)); + + metric = nm_ip4_route_get_metric (route); + + route_items[i] = g_strdup_printf ("%s/%u via %s metric %u\n", dest, prefix, next_hop, metric); + } + route_items[num] = NULL; + route_contents = g_strjoinv (NULL, route_items); + g_strfreev (route_items); + + if (!g_file_set_contents (filename, route_contents, -1, NULL)) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Writing route file '%s' failed", filename); + goto error; + } + + success = TRUE; + +error: + g_free (route_contents); + + return success; +} +#endif + +static char * +ip4_address_as_string (guint32 ip) +{ + char *ip_string; + struct in_addr tmp_addr; + + tmp_addr.s_addr = ip; + ip_string = g_malloc0 (INET_ADDRSTRLEN + 1); + if (!inet_ntop (AF_INET, &tmp_addr, ip_string, INET_ADDRSTRLEN)) + strcpy (ip_string, "(none)"); + return ip_string; +} + +static gboolean +write_ip4_setting (NMConnection *connection, shvarFile *ifcfg, GError **error) +{ + NMSettingIP4Config *s_ip4; + const char *value; + char *addr_key, *prefix_key, *netmask_key, *gw_key, /**metric_key,*/ *tmp; + // char *route_path = NULL; + guint32 i, num; + GString *searches; + gboolean success = FALSE; + + s_ip4 = (NMSettingIP4Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP4_CONFIG); + if (!s_ip4) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing '%s' setting", NM_SETTING_IP4_CONFIG_SETTING_NAME); + return FALSE; + } + + value = nm_setting_ip4_config_get_method (s_ip4); + g_assert (value); + if (!strcmp (value, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) + svSetValue (ifcfg, "BOOTPROTO", "dhcp", FALSE); + else if (!strcmp (value, NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) + svSetValue (ifcfg, "BOOTPROTO", "static", FALSE); +#if 0 + else if (!strcmp (value, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)) + svSetValue (ifcfg, "BOOTPROTO", "autoip", FALSE); + else if (!strcmp (value, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) + svSetValue (ifcfg, "BOOTPROTO", "shared", FALSE); +#endif + else { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "ifcfg-mdv: unsupported activation method '%s'", value); + return FALSE; + } + + num = nm_setting_ip4_config_get_num_addresses (s_ip4); + if (num > 1) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "ifcfg-mdv: multiple IPADDRs per interface are not supported"); + return FALSE; + } + //for (i = 0; i < 254; i++) { + { + char buf[INET_ADDRSTRLEN + 1]; + NMIP4Address *addr; + guint32 ip, netmask; + + // if (i == 0) { + addr_key = g_strdup ("IPADDR"); + prefix_key = g_strdup ("PREFIX"); + netmask_key = g_strdup ("NETMASK"); + gw_key = g_strdup ("GATEWAY"); +#if 0 + } else { + addr_key = g_strdup_printf ("IPADDR%d", i + 1); + prefix_key = g_strdup_printf ("PREFIX%d", i + 1); + gw_key = g_strdup_printf ("GATEWAY%d", i + 1); + } +#endif + /* Clean PREFIX in case it was present, otherwise it + * will fool reader.c next time */ + svSetValue (ifcfg, prefix_key, NULL, FALSE); + + // if (i >= num) { + if (num == 0) { + svSetValue (ifcfg, addr_key, NULL, FALSE); + svSetValue (ifcfg, netmask_key, NULL, FALSE); + svSetValue (ifcfg, gw_key, NULL, FALSE); + } else { + addr = nm_setting_ip4_config_get_address (s_ip4, i); + + memset (buf, 0, sizeof (buf)); + ip = nm_ip4_address_get_address (addr); + inet_ntop (AF_INET, (const void *) &ip, &buf[0], sizeof (buf)); + svSetValue (ifcfg, addr_key, &buf[0], FALSE); + + netmask = nm_utils_ip4_prefix_to_netmask (nm_ip4_address_get_prefix (addr)); + tmp = ip4_address_as_string(netmask); + svSetValue (ifcfg, netmask_key, tmp, FALSE); + g_free (tmp); + + if (nm_ip4_address_get_gateway (addr)) { + memset (buf, 0, sizeof (buf)); + ip = nm_ip4_address_get_gateway (addr); + inet_ntop (AF_INET, (const void *) &ip, &buf[0], sizeof (buf)); + svSetValue (ifcfg, gw_key, &buf[0], FALSE); + } else + svSetValue (ifcfg, gw_key, NULL, FALSE); + } + + g_free (addr_key); + g_free (prefix_key); + g_free (netmask_key); + g_free (gw_key); + } + + num = nm_setting_ip4_config_get_num_dns (s_ip4); + if (num > 2) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "ifcfg-mdv: max two DNS servers per interface are supported"); + return FALSE; + } + for (i = 0; i <= 2; i++) { + char buf[INET_ADDRSTRLEN + 1]; + guint32 ip; + + addr_key = g_strdup_printf ("DNS%d", i + 1); + + if (i >= num) + svSetValue (ifcfg, addr_key, NULL, FALSE); + else { + ip = nm_setting_ip4_config_get_dns (s_ip4, i); + + memset (buf, 0, sizeof (buf)); + inet_ntop (AF_INET, (const void *) &ip, &buf[0], sizeof (buf)); + svSetValue (ifcfg, addr_key, &buf[0], FALSE); + } + g_free (addr_key); + } + + num = nm_setting_ip4_config_get_num_dns_searches (s_ip4); + if (num > 0) { + searches = g_string_new (NULL); + for (i = 0; i < num; i++) { + if (i > 0) + g_string_append_c (searches, ' '); + g_string_append (searches, nm_setting_ip4_config_get_dns_search (s_ip4, i)); + } + svSetValue (ifcfg, "DOMAIN", searches->str, FALSE); + g_string_free (searches, TRUE); + } else + svSetValue (ifcfg, "DOMAIN", NULL, FALSE); + + /* + * Mandriva supports DEFROUTE for PPP connections only, which are + * currently not implemented by ifcfg-mdv + */ + if (nm_setting_ip4_config_get_never_default (s_ip4)){ + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: ignoring unsupported setting DEFROUTE=no"); + } +#if 0 + /* DEFROUTE; remember that it has the opposite meaning from never-default */ + svSetValue (ifcfg, "DEFROUTE", + nm_setting_ip4_config_get_never_default (s_ip4) ? "no" : "yes", + FALSE); +#endif + + /* Mandriva does not support PEERROUTES at all */ + svSetValue (ifcfg, "PEERDNS", NULL, FALSE); + // svSetValue (ifcfg, "PEERROUTES", NULL, FALSE); + svSetValue (ifcfg, "DHCP_HOSTNAME", NULL, FALSE); + // svSetValue (ifcfg, "DHCP_CLIENT_ID", NULL, FALSE); + if (!strcmp (value, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) { + svSetValue (ifcfg, "PEERDNS", + nm_setting_ip4_config_get_ignore_auto_dns (s_ip4) ? "no" : "yes", + FALSE); + + if (nm_setting_ip4_config_get_ignore_auto_routes (s_ip4)) { + PLUGIN_WARN (IFCFG_PLUGIN_NAME, " warning: ignoring unsupported setting PEERROUTESno"); + } +#if 0 + svSetValue (ifcfg, "PEERROUTES", + nm_setting_ip4_config_get_ignore_auto_routes (s_ip4) ? "no" : "yes", + FALSE); +#endif + + value = nm_setting_ip4_config_get_dhcp_hostname (s_ip4); + if (value) + svSetValue (ifcfg, "DHCP_HOSTNAME", value, FALSE); + + /* Mandriva does not support client ID */ + value = nm_setting_ip4_config_get_dhcp_client_id (s_ip4); + if (value) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "ifcfg-mdv: DHCP_CLIENT_ID is not supported"); + return FALSE; + // svSetValue (ifcfg, "DHCP_CLIENT_ID", value, FALSE); + } + } + +#if 0 + No routes on Mandriva + /* Static routes - route-<name> file */ + route_path = utils_get_route_path (ifcfg->fileName); + if (!route_path) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Could not get route file path for '%s'", ifcfg->fileName); + goto out; + } +#endif + + num = nm_setting_ip4_config_get_num_routes (s_ip4); + if (num > 0) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "ifcfg-mdv: static routes are not supported"); + return FALSE; + } +#if 0 + if (utils_has_route_file_new_syntax (route_path)) { + shvarFile *routefile; + + g_free (route_path); + routefile = utils_get_route_ifcfg (ifcfg->fileName, TRUE); + if (!routefile) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Could not create route file '%s'", routefile->fileName); + goto out; + } + + num = nm_setting_ip4_config_get_num_routes (s_ip4); + for (i = 0; i < 256; i++) { + char buf[INET_ADDRSTRLEN]; + NMIP4Route *route; + guint32 ip, metric; + + addr_key = g_strdup_printf ("ADDRESS%d", i); + netmask_key = g_strdup_printf ("NETMASK%d", i); + gw_key = g_strdup_printf ("GATEWAY%d", i); + metric_key = g_strdup_printf ("METRIC%d", i); + + if (i >= num) { + svSetValue (routefile, addr_key, NULL, FALSE); + svSetValue (routefile, netmask_key, NULL, FALSE); + svSetValue (routefile, gw_key, NULL, FALSE); + svSetValue (routefile, metric_key, NULL, FALSE); + } else { + route = nm_setting_ip4_config_get_route (s_ip4, i); + + memset (buf, 0, sizeof (buf)); + ip = nm_ip4_route_get_dest (route); + inet_ntop (AF_INET, (const void *) &ip, &buf[0], sizeof (buf)); + svSetValue (routefile, addr_key, &buf[0], FALSE); + + memset (buf, 0, sizeof (buf)); + ip = nm_utils_ip4_prefix_to_netmask (nm_ip4_route_get_prefix (route)); + inet_ntop (AF_INET, (const void *) &ip, &buf[0], sizeof (buf)); + svSetValue (routefile, netmask_key, &buf[0], FALSE); + + memset (buf, 0, sizeof (buf)); + ip = nm_ip4_route_get_next_hop (route); + inet_ntop (AF_INET, (const void *) &ip, &buf[0], sizeof (buf)); + svSetValue (routefile, gw_key, &buf[0], FALSE); + + memset (buf, 0, sizeof (buf)); + metric = nm_ip4_route_get_metric (route); + if (metric == 0) + svSetValue (routefile, metric_key, NULL, FALSE); + else { + tmp = g_strdup_printf ("%u", metric); + svSetValue (routefile, metric_key, tmp, FALSE); + g_free (tmp); + } + } + + g_free (addr_key); + g_free (netmask_key); + g_free (gw_key); + g_free (metric_key); + } + if (svWriteFile (routefile, 0644)) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Could not update route file '%s'", routefile->fileName); + svCloseFile (routefile); + goto out; + } + svCloseFile (routefile); + } else { + write_route_file_legacy (route_path, s_ip4, error); + g_free (route_path); + if (error && *error) + goto out; + } +#endif + + success = TRUE; + +// out: + return success; +} + +#if 0 +No IPv6 on Mandriva +static gboolean +write_route6_file (const char *filename, NMSettingIP6Config *s_ip6, GError **error) +{ + char dest[INET6_ADDRSTRLEN]; + char next_hop[INET6_ADDRSTRLEN]; + char **route_items; + char *route_contents; + NMIP6Route *route; + const struct in6_addr *ip; + guint32 prefix, metric; + guint32 i, num; + gboolean success = FALSE; + + g_return_val_if_fail (filename != NULL, FALSE); + g_return_val_if_fail (s_ip6 != NULL, FALSE); + g_return_val_if_fail (error != NULL, FALSE); + g_return_val_if_fail (*error == NULL, FALSE); + + num = nm_setting_ip6_config_get_num_routes (s_ip6); + if (num == 0) { + unlink (filename); + return TRUE; + } + + route_items = g_malloc0 (sizeof (char*) * (num + 1)); + for (i = 0; i < num; i++) { + route = nm_setting_ip6_config_get_route (s_ip6, i); + + memset (dest, 0, sizeof (dest)); + ip = nm_ip6_route_get_dest (route); + inet_ntop (AF_INET6, (const void *) ip, &dest[0], sizeof (dest)); + + prefix = nm_ip6_route_get_prefix (route); + + memset (next_hop, 0, sizeof (next_hop)); + ip = nm_ip6_route_get_next_hop (route); + inet_ntop (AF_INET6, (const void *) ip, &next_hop[0], sizeof (next_hop)); + + metric = nm_ip6_route_get_metric (route); + + route_items[i] = g_strdup_printf ("%s/%u via %s metric %u\n", dest, prefix, next_hop, metric); + } + route_items[num] = NULL; + route_contents = g_strjoinv (NULL, route_items); + g_strfreev (route_items); + + if (!g_file_set_contents (filename, route_contents, -1, NULL)) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Writing route6 file '%s' failed", filename); + goto error; + } + + success = TRUE; + +error: + g_free (route_contents); + return success; +} +#endif + +static gboolean +write_ip6_setting (NMConnection *connection, shvarFile *ifcfg, GError **error) +{ + NMSettingIP6Config *s_ip6; + // NMSettingIP4Config *s_ip4; + const char *value; +#if 0 + char *addr_key, *prefix; + guint32 i, num, num4; + GString *searches; + char buf[INET6_ADDRSTRLEN]; + NMIP6Address *addr; + const struct in6_addr *ip; + GString *ip_str1, *ip_str2, *ip_ptr; + char *route6_path; +#endif + + s_ip6 = (NMSettingIP6Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP6_CONFIG); + if (!s_ip6) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing '%s' setting", NM_SETTING_IP6_CONFIG_SETTING_NAME); + return FALSE; + } + + value = nm_setting_ip6_config_get_method (s_ip6); + g_assert (value); + if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) { + //svSetValue (ifcfg, "IPV6INIT", "no", FALSE); + return TRUE; +#if 0 + } else if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) { + svSetValue (ifcfg, "IPV6INIT", "yes", FALSE); + svSetValue (ifcfg, "IPV6_AUTOCONF", "yes", FALSE); + } else if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) { + svSetValue (ifcfg, "IPV6INIT", "yes", FALSE); + svSetValue (ifcfg, "IPV6_AUTOCONF", "no", FALSE); + } else if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL)) { + svSetValue (ifcfg, "IPV6INIT", "yes", FALSE); + svSetValue (ifcfg, "IPV6_AUTOCONF", "no", FALSE); + } else if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_SHARED)) { + svSetValue (ifcfg, "IPV6INIT", "yes", FALSE); + /* TODO */ +#endif + } + return TRUE; + +#if 0 + if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) { + /* Write out IP addresses */ + num = nm_setting_ip6_config_get_num_addresses (s_ip6); + + ip_str1 = g_string_new (NULL); + ip_str2 = g_string_new (NULL); + for (i = 0; i < num; i++) { + if (i == 0) + ip_ptr = ip_str1; + else + ip_ptr = ip_str2; + + addr = nm_setting_ip6_config_get_address (s_ip6, i); + ip = nm_ip6_address_get_address (addr); + prefix = g_strdup_printf ("%u", nm_ip6_address_get_prefix (addr)); + memset (buf, 0, sizeof (buf)); + inet_ntop (AF_INET6, (const void *) ip, buf, sizeof (buf)); + if (i > 1) + g_string_append_c (ip_ptr, ' '); /* separate addresses in IPV6ADDR_SECONDARIES */ + g_string_append (ip_ptr, buf); + g_string_append_c (ip_ptr, '/'); + g_string_append (ip_ptr, prefix); + g_free (prefix); + } + + svSetValue (ifcfg, "IPV6ADDR", ip_str1->str, FALSE); + svSetValue (ifcfg, "IPV6ADDR_SECONDARIES", ip_str2->str, FALSE); + g_string_free (ip_str1, TRUE); + g_string_free (ip_str2, TRUE); + } else { + svSetValue (ifcfg, "IPV6ADDR", NULL, FALSE); + svSetValue (ifcfg, "IPV6ADDR_SECONDARIES", NULL, FALSE); + } + + /* Write out DNS - 'DNS' key is used both for IPv4 and IPv6 */ + s_ip4 = (NMSettingIP4Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP4_CONFIG); + num4 = s_ip4 ? nm_setting_ip4_config_get_num_dns (s_ip4) : 0; /* from where to start with IPv6 entries */ + num = nm_setting_ip6_config_get_num_dns (s_ip6); + for (i = 0; i < 254; i++) { + addr_key = g_strdup_printf ("DNS%d", i + num4 + 1); + + if (i >= num) + svSetValue (ifcfg, addr_key, NULL, FALSE); + else { + ip = nm_setting_ip6_config_get_dns (s_ip6, i); + + memset (buf, 0, sizeof (buf)); + inet_ntop (AF_INET6, (const void *) ip, buf, sizeof (buf)); + svSetValue (ifcfg, addr_key, buf, FALSE); + } + g_free (addr_key); + } + + /* Write out DNS domains - 'DOMAIN' key is shared for both IPv4 and IPv6 domains */ + num = nm_setting_ip6_config_get_num_dns_searches (s_ip6); + if (num > 0) { + char *ip4_domains; + ip4_domains = svGetValue (ifcfg, "DOMAIN", FALSE); + searches = g_string_new (ip4_domains); + for (i = 0; i < num; i++) { + if (searches->len > 0) + g_string_append_c (searches, ' '); + g_string_append (searches, nm_setting_ip6_config_get_dns_search (s_ip6, i)); + } + svSetValue (ifcfg, "DOMAIN", searches->str, FALSE); + g_string_free (searches, TRUE); + g_free (ip4_domains); + } + + /* handle IPV6_DEFROUTE */ + /* IPV6_DEFROUTE has the opposite meaning from 'never-default' */ + if (nm_setting_ip6_config_get_never_default(s_ip6)) + svSetValue (ifcfg, "IPV6_DEFROUTE", "no", FALSE); + else + svSetValue (ifcfg, "IPV6_DEFROUTE", "yes", FALSE); + + svSetValue (ifcfg, "IPV6_PEERDNS", NULL, FALSE); + svSetValue (ifcfg, "IPV6_PEERROUTES", NULL, FALSE); + if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) { + svSetValue (ifcfg, "IPV6_PEERDNS", + nm_setting_ip6_config_get_ignore_auto_dns (s_ip6) ? "no" : "yes", + FALSE); + + svSetValue (ifcfg, "IPV6_PEERROUTES", + nm_setting_ip6_config_get_ignore_auto_routes (s_ip6) ? "no" : "yes", + FALSE); + } + + /* Static routes go to route6-<dev> file */ + route6_path = utils_get_route6_path (ifcfg->fileName); + if (!route6_path) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Could not get route6 file path for '%s'", ifcfg->fileName); + goto error; + } + write_route6_file (route6_path, s_ip6, error); + g_free (route6_path); + if (error && *error) + goto error; + + return TRUE; + +error: + return FALSE; +#endif +} + +static char * +escape_id (const char *id) +{ + char *escaped = g_strdup (id); + char *p = escaped; + + /* Escape random stuff */ + while (*p) { + if (*p == ' ') + *p = '_'; + else if (*p == '/') + *p = '-'; + else if (*p == '\\') + *p = '-'; + p++; + } + + return escaped; +} + +static gboolean +write_connection (NMConnection *connection, + const char *ifcfg_dir, + const char *filename, + const char *keyfile, + char **out_filename, + GError **error) +{ + NMSettingConnection *s_con; + NMSettingIP6Config *s_ip6; + gboolean success = FALSE; + shvarFile *ifcfg = NULL; + char *ifcfg_name = NULL; + const char *type; + gboolean no_8021x = FALSE; + gboolean wired = FALSE; + WPANetwork *wpan = NULL; + + s_con = NM_SETTING_CONNECTION (nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION)); + if (!s_con) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing '%s' setting", NM_SETTING_CONNECTION_SETTING_NAME); + return FALSE; + } + + if (filename) { + /* For existing connections, 'filename' should be full path to ifcfg file */ + ifcfg = svNewFile (filename); + ifcfg_name = g_strdup (filename); + } else { + char *escaped; + + escaped = escape_id (nm_setting_connection_get_id (s_con)); + ifcfg_name = g_strdup_printf ("%s/ifcfg-%s", ifcfg_dir, escaped); + ifcfg = svCreateFile (ifcfg_name); + g_free (escaped); + } + + if (!ifcfg) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Failed to open/create ifcfg file '%s'", ifcfg_name); + goto out; + } + + type = nm_setting_connection_get_connection_type (s_con); + if (!type) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Missing connection type!"); + goto out; + } + + /* Indicate that NM will manage this connection */ + svSetValue (ifcfg, "NM_CONTROLLED", "yes", FALSE); + + if (!strcmp (type, NM_SETTING_WIRED_SETTING_NAME)) { + // FIXME: can't write PPPoE at this time + if (nm_connection_get_setting (connection, NM_TYPE_SETTING_PPPOE)) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Can't write connection type '%s'", + NM_SETTING_PPPOE_SETTING_NAME); + goto out; + } + + if (!write_wired_setting (connection, ifcfg, error)) + goto out; + wired = TRUE; + } else if (!strcmp (type, NM_SETTING_WIRELESS_SETTING_NAME)) { + wpan = ifcfg_mdv_wpa_network_new(NULL); + if (!wpan) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Unable to allocate WPA network"); + goto out; + } + + if (!write_wireless_setting (connection, ifcfg, wpan, &no_8021x, error)) + goto out; + } else { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Can't write connection type '%s'", type); + goto out; + } + + if (!no_8021x) { + if (!write_8021x_setting (connection, wpan, ifcfg, wired, error)) + goto out; + } + + if (!write_ip4_setting (connection, ifcfg, error)) + goto out; + + s_ip6 = (NMSettingIP6Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP6_CONFIG); + if (s_ip6) { + if (!write_ip6_setting (connection, ifcfg, error)) + goto out; + } + + write_connection_setting (s_con, ifcfg); + + if (svWriteFile (ifcfg, 0600)) { + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Can't write connection '%s'", ifcfg->fileName); + goto out; + } + if (wpan) + if (!ifcfg_mdv_wpa_network_save(wpan, "/etc/wpa_supplicant.conf", error)) { + goto out; + } + + + /* Only return the filename if this was a newly written ifcfg */ + if (out_filename && !filename) + *out_filename = g_strdup (ifcfg_name); + + success = TRUE; + +out: + if (ifcfg) + svCloseFile (ifcfg); + g_free (ifcfg_name); + ifcfg_mdv_wpa_network_free(wpan); + return success; +} + +gboolean +writer_new_connection (NMConnection *connection, + const char *ifcfg_dir, + char **out_filename, + GError **error) +{ + // return write_connection (connection, ifcfg_dir, NULL, NULL, out_filename, error); + /* For now, disable creation of system connection on Mandriva */ + g_set_error (error, ifcfg_plugin_error_quark (), 0, + "Creation of system connection not yet implemented in ifcfg-mdv"); + return FALSE; +} + +gboolean +writer_update_connection (NMConnection *connection, + const char *ifcfg_dir, + const char *filename, + const char *keyfile, + GError **error) +{ + return write_connection (connection, ifcfg_dir, filename, keyfile, NULL, error); +} + diff --git a/system-settings/plugins/ifcfg-mdv/writer.h b/system-settings/plugins/ifcfg-mdv/writer.h new file mode 100644 index 0000000..edeac0c --- /dev/null +++ b/system-settings/plugins/ifcfg-mdv/writer.h @@ -0,0 +1,39 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager system settings service - keyfile plugin + * + * 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 (C) 2009 Red Hat, Inc. + */ + +#ifndef _WRITER_H_ +#define _WRITER_H_ + +#include <sys/types.h> +#include <glib.h> +#include <nm-connection.h> + +gboolean writer_new_connection (NMConnection *connection, + const char *ifcfg_dir, + char **out_filename, + GError **error); + +gboolean writer_update_connection (NMConnection *connection, + const char *ifcfg_dir, + const char *filename, + const char *keyfile, + GError **error); + +#endif /* _WRITER_H_ */