diff --git a/ChangeLog b/ChangeLog index 7966017a6..26f6c681d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,64 +1,14 @@ -2008-02-27 Saleem Abdulrasool +2008-02-28 Tambet Ingo - reviewed by: steev@steev.net + Implement suse plugin for system settings daemon. - * initscript/Gentoo/NetworkManager.in: - * initscript/Gentoo/NetworkManagerDispatcher.in: - Update the scripts and move them to Gentoo style. + * system-settings/plugins/ifcfg-suse/*: Implement. -2008-02-27 Dan Williams + * system-settings/plugins/Makefile.am: Add ifcfg-suse to subdirs when targeting + suse. - Patch from Will Stephenson - - * Makefile.am - configure.in - - Set up spec autogeneration infrastructure - - * docs/NetworkManager DBUS API.txt - - Note how old this doc is and where to look for the canonical - D-Bus specification - - * introspection/* - - Add annotations and comments - -2008-02-27 Dan Williams - - * src/nm-device-802-11-wireless.c - - (device_cleanup): cleanup any association attempt that might be in - progress - - (nm_device_802_11_wireless_dispose): device_cleanup() already - destroys the AP list - -2008-02-25 Dan Williams - - * libnm-glib/nm-settings.c - - (set_property): use g_value_dup_object() to ref the object as was - intended originally - -2008-02-25 Dan Williams - - * libnm-util/nm-utils.c - - (nm_utils_ssid_to_utf8): use a dynamically allocated buffer - -2008-02-25 Dan Williams - - * libnm-util/nm-setting.c - - (nm_setting_compare): Fix C&P error from r3068 that caused settings - comparisons to always succeed; clarify assignment of values to - 'different' - -2008-02-24 Dan Williams - - * libnm-util/nm-setting.c - libnm-util/nm-setting.h - - (nm_setting_compare): fix 'fuzzy' compare logic; add IGNORE_ID bits; - fix return value to match nm_connection_compare() meaning - -2008-02-24 Dan Williams - - * libnm-util/nm-setting-wireless.c - - (nm_setting_wireless_class_init): 'seen bssids' should be ignored for - fuzzy matches + * configure.in: Check (without failing) for gio. + Create ifcfg-suse plugin's Makefile. 2008-02-20 Dan Williams diff --git a/configure.in b/configure.in index 8582f2c7e..77dbe993b 100644 --- a/configure.in +++ b/configure.in @@ -52,18 +52,6 @@ AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE,"$GETTEXT_PACKAGE", [Gettext package]) IT_PROG_INTLTOOL([0.35.0]) AM_GLIB_GNU_GETTEXT -dnl -dnl GNOME support -dnl -AC_ARG_WITH(docs, AC_HELP_STRING([--with-docs], [Build NetworkManager documentation])) -AM_CONDITIONAL(WITH_DOCS, test "x$with_docs" = "xyes") -case $with_docs in - yes) ;; - *) - with_docs=no - ;; -esac - dnl dnl Make sha1.c happy on big endian systems dnl @@ -195,6 +183,11 @@ PKG_CHECK_MODULES(GOBJECT, gobject-2.0) AC_SUBST(GOBJECT_CFLAGS) AC_SUBST(GOBJECT_LIBS) +# This is optional, at least for now. +PKG_CHECK_MODULES(GIO, gio-2.0,,true) +AC_SUBST(GIO_CFLAGS) +AC_SUBST(GIO_LIBS) + PKG_CHECK_MODULES(HAL, hal >= 0.5.0) AC_SUBST(HAL_CFLAGS) AC_SUBST(HAL_LIBS) @@ -290,6 +283,7 @@ system-settings/Makefile system-settings/src/Makefile system-settings/plugins/Makefile system-settings/plugins/ifcfg-fedora/Makefile +system-settings/plugins/ifcfg-suse/Makefile test/Makefile test/test-common/Makefile initscript/Makefile @@ -325,8 +319,3 @@ AC_OUTPUT echo echo Distribution targeting: ${with_distro} echo 'if this is not correct, please specifiy your distro with --with-distro=DISTRO' - -echo -echo Building documentation: ${with_docs} -echo - diff --git a/system-settings/plugins/Makefile.am b/system-settings/plugins/Makefile.am index 4f09e2c35..b32120c19 100644 --- a/system-settings/plugins/Makefile.am +++ b/system-settings/plugins/Makefile.am @@ -1,2 +1,7 @@ +if TARGET_REDHAT SUBDIRS=ifcfg-fedora +endif +if TARGET_SUSE +SUBDIRS=ifcfg-suse +endif diff --git a/system-settings/plugins/ifcfg-suse/Makefile.am b/system-settings/plugins/ifcfg-suse/Makefile.am new file mode 100644 index 000000000..65e224696 --- /dev/null +++ b/system-settings/plugins/ifcfg-suse/Makefile.am @@ -0,0 +1,28 @@ + +pkglib_LTLIBRARIES = libnm-settings-plugin-ifcfg-suse.la + +libnm_settings_plugin_ifcfg_suse_la_SOURCES = \ + shvar.c \ + shvar.h \ + parser.c \ + parser.h \ + plugin.c \ + plugin.h + +libnm_settings_plugin_ifcfg_suse_la_CPPFLAGS = \ + $(GLIB_CFLAGS) \ + $(GMODULE_CFLAGS) \ + $(DBUS_CFLAGS) \ + -DG_DISABLE_DEPRECATED \ + -I${top_srcdir}/system-settings/src \ + -I$(top_srcdir)/include \ + -I$(top_srcdir)/libnm-util \ + -DSYSCONFDIR=\"$(sysconfdir)\" + +libnm_settings_plugin_ifcfg_suse_la_LDFLAGS = -module -avoid-version +libnm_settings_plugin_ifcfg_suse_la_LIBADD = \ + $(GLIB_LIBS) \ + $(GMODULE_LIBS) \ + $(GIO_LIBS) \ + $(top_builddir)/libnm-util/libnm-util.la + diff --git a/system-settings/plugins/ifcfg-suse/parser.c b/system-settings/plugins/ifcfg-suse/parser.c new file mode 100644 index 000000000..cefb73fb3 --- /dev/null +++ b/system-settings/plugins/ifcfg-suse/parser.c @@ -0,0 +1,798 @@ +/* -*- Mode: C; tab-width: 5; indent-tabs-mode: t; c-basic-offset: 5 -*- */ + +/* NetworkManager system settings service + * + * Søren Sandmann + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * (C) Copyright 2007 Red Hat, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "shvar.h" +#include "parser.h" +#include "plugin.h" + + +static gboolean +get_int (const char *str, int *value) +{ + char *e; + + *value = strtol (str, &e, 0); + if (*e != '\0') + return FALSE; + + return TRUE; +} + +#if 0 +static gboolean +read_startmode (shvarFile *file) +{ + char *value; + gboolean automatic = TRUE; + + value = svGetValue (file, "STARTMODE"); + if (value) { + if (!g_ascii_strcasecmp (value, "manual")) + automatic = FALSE; + else if (!g_ascii_strcasecmp (value, "off")) { + // FIXME: actually ignore the device, not the connection + g_message ("Ignoring connection '%s' because NM_CONTROLLED was false", file); + automatic = FALSE; + } + + g_free (value); + } + + return automatic; +} +#endif + +static NMSetting * +make_connection_setting (const char *file, + shvarFile *ifcfg, + const char *type, + const char *suggested) +{ + NMSettingConnection *s_con; + char *basename = NULL; + int len; + char *ifcfg_name; + + basename = g_path_get_basename (file); + if (!basename) + goto error; + len = strlen (basename); + + if (len < strlen (IFCFG_TAG) + 1) + goto error; + + if (strncmp (basename, IFCFG_TAG, strlen (IFCFG_TAG))) + goto error; + + /* ignore .bak files */ + if ((len > 4) && !strcmp (basename + len - 4, BAK_TAG)) + goto error; + + s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ()); + + ifcfg_name = (char *) (basename + strlen (IFCFG_TAG)); + + if (suggested) { + /* For cosmetic reasons, if the suggested name is the same as + * the ifcfg files name, don't use it. + */ + if (strcmp (ifcfg_name, suggested)) { + s_con->id = g_strdup_printf ("System %s (%s)", suggested, ifcfg_name); + ifcfg_name = NULL; + } + } + + if (ifcfg_name) + s_con->id = g_strdup_printf ("System %s", ifcfg_name); + + s_con->type = g_strdup (type); + s_con->autoconnect = TRUE; + + return (NMSetting *) s_con; + +error: + g_free (basename); + return NULL; +} + +static guint32 +ip4_prefix_to_netmask (int prefix) +{ + guint32 msk = 0x80000000; + guint32 netmask = 0; + + while (prefix > 0) { + netmask |= msk; + msk >>= 1; + prefix--; + } + + return htonl (netmask); +} + +static NMSetting * +make_ip4_setting (shvarFile *ifcfg, GError **error) +{ + NMSettingIP4Config *s_ip4 = NULL; + char *value = NULL; + NMSettingIP4Address tmp = { 0, 0, 0 }; + gboolean manual = TRUE; + + value = svGetValue (ifcfg, "BOOTPROTO"); + if (!value) + return NULL; + + if (!g_ascii_strcasecmp (value, "bootp") || !g_ascii_strcasecmp (value, "dhcp")) { + manual = FALSE; + return NULL; + } + + value = svGetValue (ifcfg, "IPADDR"); + if (value) { + char **pieces; + struct in_addr ip4_addr; + + pieces = g_strsplit (value, "/", 2); + + if (inet_pton (AF_INET, pieces[0], &ip4_addr)) + tmp.address = ip4_addr.s_addr; + else { + g_strfreev (pieces); + g_set_error (error, ifcfg_plugin_error_quark (), 0, "Invalid IP4 address '%s'", value); + goto error; + } + + if (g_strv_length (pieces) == 2) + tmp.netmask = ip4_prefix_to_netmask (atoi (pieces[1])); + + g_strfreev (pieces); + g_free (value); + } + + if (tmp.netmask == 0) { + value = svGetValue (ifcfg, "PREFIXLEN"); + if (value) { + tmp.netmask = ip4_prefix_to_netmask (atoi (value)); + g_free (value); + } + } + + if (tmp.netmask == 0) { + value = svGetValue (ifcfg, "NETMASK"); + if (value) { + struct in_addr mask_addr; + + if (inet_pton (AF_INET, value, &mask_addr)) + tmp.netmask = mask_addr.s_addr; + else { + g_set_error (error, ifcfg_plugin_error_quark (), 0, "Invalid IP4 netmask '%s'", value); + goto error; + } + g_free (value); + } + } + + s_ip4 = (NMSettingIP4Config *) nm_setting_ip4_config_new (); + s_ip4->manual = manual; + if (tmp.address || tmp.netmask || tmp.gateway) { + NMSettingIP4Address *addr; + addr = g_new0 (NMSettingIP4Address, 1); + memcpy (addr, &tmp, sizeof (NMSettingIP4Address)); + s_ip4->addresses = g_slist_append (s_ip4->addresses, addr); + } + + return NM_SETTING (s_ip4); + +error: + g_free (value); + if (s_ip4) + g_object_unref (s_ip4); + return NULL; +} + +#if 0 +/* + * utils_bin2hexstr + * + * Convert a byte-array into a hexadecimal string. + * + * Code originally by Alex Larsson and + * copyright Red Hat, Inc. under terms of the LGPL. + * + */ +static char * +utils_bin2hexstr (const char *bytes, int len, int final_len) +{ + static char hex_digits[] = "0123456789abcdef"; + char * result; + int i; + + g_return_val_if_fail (bytes != NULL, NULL); + g_return_val_if_fail (len > 0, NULL); + g_return_val_if_fail (len < 256, NULL); /* Arbitrary limit */ + + result = g_malloc0 (len * 2 + 1); + for (i = 0; i < len; i++) + { + result[2*i] = hex_digits[(bytes[i] >> 4) & 0xf]; + result[2*i+1] = hex_digits[bytes[i] & 0xf]; + } + /* Cut converted key off at the correct length for this cipher type */ + if (final_len > -1) + result[final_len] = '\0'; + + return result; +} +#endif + +static char * +get_one_wep_key (shvarFile *ifcfg, guint8 idx, GError **err) +{ + char *shvar_key; + char *key = NULL; + char *value = NULL; + char *p; + + g_return_val_if_fail (idx <= 3, NULL); + + shvar_key = g_strdup_printf ("WIRELESS_KEY_%d", idx); + value = svGetValue (ifcfg, shvar_key); + g_free (shvar_key); + + /* Ignore empty keys */ + if (!value) + return NULL; + + if (strlen (value) < 1) { + g_free (value); + return NULL; + } + + /* ASCII */ + if (g_str_has_prefix (value, "s:")) { + p = value + 2; + if (strlen (p) != 5 || strlen (p) != 13) + g_set_error (err, ifcfg_plugin_error_quark (), 0, "Invalid hexadecimal WEP key."); + else { + while (*p) { + if (!isascii (*p)) { + g_set_error (err, ifcfg_plugin_error_quark (), 0, "Invalid hexadecimal WEP key."); + break; + } + p++; + } + } + + if (!err) + key = g_strdup (p); + } else if (g_str_has_prefix (value, "h:")) { + /* Hashed passphrase */ + p = value + 2; + if (p && (strlen (p) > 0 || strlen (p) < 65)) + key = g_strdup (p); + else + g_set_error (err, ifcfg_plugin_error_quark (), 0, "Invalid WEP passphrase."); + } else { + /* Hexadecimal */ + GString *str; + + str = g_string_sized_new (26); + p = value + 2; + while (*p) { + if (g_ascii_isxdigit (*p)) + str = g_string_append_c (str, *p); + else if (*p != '-') { + g_set_error (err, ifcfg_plugin_error_quark (), 0, "Invalid hexadecimal WEP key."); + break; + } + p++; + } + + p = str->str; + + if (p && (strlen (p) == 10 || strlen (p) == 26)) + key = g_string_free (str, FALSE); + else + g_set_error (err, ifcfg_plugin_error_quark (), 0, "Invalid hexadecimal WEP key."); + } + + g_free (value); + + return key; +} + +#define READ_WEP_KEY(idx) \ + { \ + char *key = get_one_wep_key (ifcfg, idx, err); \ + if (*err) \ + goto error; \ + if (key) { \ + g_object_set_data_full (G_OBJECT (security), \ + NM_SETTING_WIRELESS_SECURITY_WEP_KEY##idx, \ + key, \ + g_free); \ + have_key = TRUE; \ + } \ + } + + +static void +read_wep_settings (shvarFile *ifcfg, NMSettingWirelessSecurity *security, GError **err) +{ + char *value; + gboolean have_key = FALSE; + + READ_WEP_KEY(0) + READ_WEP_KEY(1) + READ_WEP_KEY(2) + READ_WEP_KEY(3) + + if (have_key) + security->key_mgmt = g_strdup ("none"); + + value = svGetValue (ifcfg, "WIRELESS_DEFAULT_KEY"); + if (value) { + gboolean success; + int key_idx = 0; + + success = get_int (value, &key_idx); + if (success && (key_idx >= 0) && (key_idx <= 3)) + security->wep_tx_keyidx = key_idx; + else + g_set_error (err, ifcfg_plugin_error_quark (), 0, "Invalid defualt WEP key '%s'", value); + + g_free (value); + } + +error: + return; +} + +/* Copied from applet/src/wireless-secuirty/wireless-security.c */ +static void +ws_wpa_fill_default_ciphers (NMSettingWirelessSecurity *s_wireless_sec) +{ + // FIXME: allow protocol selection and filter on device capabilities + s_wireless_sec->proto = g_slist_append (s_wireless_sec->proto, g_strdup ("wpa")); + s_wireless_sec->proto = g_slist_append (s_wireless_sec->proto, g_strdup ("rsn")); + + // FIXME: allow pairwise cipher selection and filter on device capabilities + s_wireless_sec->pairwise = g_slist_append (s_wireless_sec->pairwise, g_strdup ("tkip")); + s_wireless_sec->pairwise = g_slist_append (s_wireless_sec->pairwise, g_strdup ("ccmp")); + + // FIXME: allow group cipher selection and filter on device capabilities + s_wireless_sec->group = g_slist_append (s_wireless_sec->group, g_strdup ("wep40")); + s_wireless_sec->group = g_slist_append (s_wireless_sec->group, g_strdup ("wep104")); + s_wireless_sec->group = g_slist_append (s_wireless_sec->group, g_strdup ("tkip")); + s_wireless_sec->group = g_slist_append (s_wireless_sec->group, g_strdup ("ccmp")); +} + +static void +read_wpa_psk_settings (shvarFile *ifcfg, NMSettingWirelessSecurity *security, GError **err) +{ + char *value; + + value = svGetValue (ifcfg, "WIRELESS_WPA_PSK"); + if (value) { + if (strlen (value) == 64) { + /* HEX key */ + security->psk = value; + } else { + /* passphrase */ + + /* FIXME: */ +/* unsigned char *buf = g_malloc0 (WPA_PMK_LEN * 2); */ +/* pbkdf2_sha1 (value, (char *) s_wireless->ssid->data, s_wireless->ssid->len, 4096, buf, WPA_PMK_LEN); */ +/* security->psk = utils_bin2hexstr ((const char *) buf, WPA_PMK_LEN, WPA_PMK_LEN * 2); */ +/* g_free (buf); */ + g_free (value); + } + + ws_wpa_fill_default_ciphers (security); + } else + g_set_error (err, ifcfg_plugin_error_quark (), 0, "Missing WPA-PSK key."); +} + +static void +read_wpa_eap_settings (shvarFile *ifcfg, NMSettingWirelessSecurity *security, GError **err) +{ + char *value; + + value = svGetValue (ifcfg, "WIRELESS_EAP_AUTH"); + if (value) { + /* valid values are TLS PEAP TTLS */ + security->eap = g_slist_append (NULL, value); + } + + value = svGetValue (ifcfg, "WIRELESS_WPA_PROTO"); + if (value) { + /* valid values are WPA RSN (WPA2) */ + security->proto = g_slist_append (NULL, value); + } + + security->identity = svGetValue (ifcfg, "WIRELESS_WPA_IDENTITY"); + + /* FIXME: This should be in get_secrets? */ + value = svGetValue (ifcfg, "WIRELESS_WPA_PASSWORD"); + if (value) { + g_free (value); + } + + security->anonymous_identity = svGetValue (ifcfg, "WIRELESS_WPA_ANONID"); + + value = svGetValue (ifcfg, "WIRELESS_CA_CERT"); + if (value) { + g_free (value); + } + + value = svGetValue (ifcfg, "WIRELESS_CLIENT_CERT"); + if (value) { + g_free (value); + } + + /* FIXME: This should be in get_secrets? */ + value = svGetValue (ifcfg, "WIRELESS_CLIENT_KEY"); + if (value) { + g_free (value); + } + + /* FIXME: This should be in get_secrets? */ + value = svGetValue (ifcfg, "WIRELESS_CLIENT_KEY_PASSWORD"); + if (value) { + g_free (value); + } + + ws_wpa_fill_default_ciphers (security); +} + +static NMSetting * +make_wireless_security_setting (shvarFile *ifcfg, GError **err) +{ + NMSettingWirelessSecurity *s_wireless_sec = NULL; + char *value; + + value = svGetValue (ifcfg, "WIRELESS_AUTH_MODE"); + if (!value) + return NULL; + + if (!g_ascii_strcasecmp (value, "no-encryption")) { + g_free (value); + return NULL; + } + + s_wireless_sec = NM_SETTING_WIRELESS_SECURITY (nm_setting_wireless_security_new ()); + + if (!g_ascii_strcasecmp (value, "open")) { + s_wireless_sec->auth_alg = g_strdup ("open"); + read_wep_settings (ifcfg, s_wireless_sec, err); + } else if (!g_ascii_strcasecmp (value, "sharedkey")) { + s_wireless_sec->auth_alg = g_strdup ("shared"); + read_wep_settings (ifcfg, s_wireless_sec, err); + } + + else if (!g_ascii_strcasecmp (value, "psk")) { + s_wireless_sec->key_mgmt = g_strdup ("wpa-psk"); + read_wpa_psk_settings (ifcfg, s_wireless_sec, err); + } else if (!g_ascii_strcasecmp (value, "eap")) { + s_wireless_sec->key_mgmt = g_strdup ("wps-eap"); + read_wpa_eap_settings (ifcfg, s_wireless_sec, err); + } else + g_set_error (err, ifcfg_plugin_error_quark (), 0, "Invalid authentication algoritm '%s'", value); + + g_free (value); + + if (*err == NULL) + return NM_SETTING (s_wireless_sec); + + if (s_wireless_sec) + g_object_unref (s_wireless_sec); + return NULL; +} + +static NMSetting * +make_wireless_setting (shvarFile *ifcfg, + NMSetting *security, + GError **err) +{ + NMSettingWireless *s_wireless; + char *value; + + s_wireless = NM_SETTING_WIRELESS (nm_setting_wireless_new ()); + + value = svGetValue (ifcfg, "WIRELESS_ESSID"); + if (value) { + gsize len = strlen (value); + + if (len > 32 || len == 0) { + g_set_error (err, ifcfg_plugin_error_quark (), 0, + "Invalid SSID '%s' (size %zu not between 1 and 32 inclusive)", + value, len); + goto error; + } + + s_wireless->ssid = g_byte_array_sized_new (strlen (value)); + g_byte_array_append (s_wireless->ssid, (const guint8 *) value, len); + g_free (value); + } + + value = svGetValue (ifcfg, "WIRLESS_MODE"); + if (value) { + if (!g_ascii_strcasecmp (value, "ad-hoc")) { + s_wireless->mode = g_strdup ("adhoc"); + } else if (!g_ascii_strcasecmp (value, "managed")) { + s_wireless->mode = g_strdup ("infrastructure"); + } else { + g_set_error (err, ifcfg_plugin_error_quark (), 0, + "Invalid mode '%s' (not ad-hoc or managed)", value); + g_free (value); + goto error; + } + g_free (value); + } + + if (security) + s_wireless->security = g_strdup (NM_SETTING_WIRELESS_SECURITY_SETTING_NAME); + + // FIXME: channel/freq, other L2 parameters like RTS + + 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, GError **err) +{ + NMConnection *connection = NULL; + NMSetting *con_setting = NULL; + NMSetting *wireless_setting = NULL; + NMSettingWireless *tmp; + NMSetting *security_setting = NULL; + char *printable_ssid = NULL; + + connection = nm_connection_new (); + if (!connection) { + g_set_error (err, ifcfg_plugin_error_quark (), 0, + "Failed to allocate new connection for %s.", file); + return NULL; + } + + /* Wireless security */ + security_setting = make_wireless_security_setting (ifcfg, err); + if (*err) + goto error; + if (security_setting) + nm_connection_add_setting (connection, security_setting); + + /* Wireless */ + wireless_setting = make_wireless_setting (ifcfg, security_setting, err); + if (!wireless_setting) + goto error; + + nm_connection_add_setting (connection, wireless_setting); + + tmp = NM_SETTING_WIRELESS (wireless_setting); + printable_ssid = nm_utils_ssid_to_utf8 ((const char *) tmp->ssid->data, + (guint32) tmp->ssid->len); + + con_setting = make_connection_setting (file, ifcfg, + NM_SETTING_WIRELESS_SETTING_NAME, + printable_ssid); + g_free (printable_ssid); + + if (!con_setting) { + g_set_error (err, ifcfg_plugin_error_quark (), 0, + "Failed to create connection setting."); + goto error; + } + nm_connection_add_setting (connection, con_setting); + + if (!nm_connection_verify (connection)) { + g_set_error (err, ifcfg_plugin_error_quark (), 0, + "Connection from %s was invalid.", file); + goto error; + } + + return connection; + +error: + g_object_unref (connection); + if (con_setting) + g_object_unref (con_setting); + if (wireless_setting) + g_object_unref (wireless_setting); + return NULL; +} + +static NMSetting * +make_wired_setting (shvarFile *ifcfg, GError **err) +{ + NMSettingWired *s_wired; + char *value; + int mtu; + + s_wired = NM_SETTING_WIRED (nm_setting_wired_new ()); + + value = svGetValue (ifcfg, "MTU"); + if (value) { + if (strlen (value) < 1) + /* Ignore empty MTU */ + ; + else if (get_int (value, &mtu)) { + if (mtu >= 0 && mtu < 65536) + s_wired->mtu = mtu; + } else { + g_set_error (err, ifcfg_plugin_error_quark (), 0, "Invalid MTU '%s'", value); + g_object_unref (s_wired); + s_wired = NULL; + } + g_free (value); + } + + return (NMSetting *) s_wired; +} + +static NMConnection * +wired_connection_from_ifcfg (const char *file, shvarFile *ifcfg, GError **err) +{ + NMConnection *connection = NULL; + NMSetting *con_setting = NULL; + NMSetting *wired_setting = NULL; + + connection = nm_connection_new (); + con_setting = make_connection_setting (file, ifcfg, NM_SETTING_WIRED_SETTING_NAME, NULL); + if (!con_setting) { + g_set_error (err, ifcfg_plugin_error_quark (), 0, + "Failed to create connection setting."); + goto error; + } + nm_connection_add_setting (connection, con_setting); + + wired_setting = make_wired_setting (ifcfg, err); + if (!wired_setting) + goto error; + + nm_connection_add_setting (connection, wired_setting); + + if (!nm_connection_verify (connection)) { + g_set_error (err, ifcfg_plugin_error_quark (), 0, + "Connection from %s was invalid.", file); + goto error; + } + + return connection; + +error: + g_object_unref (connection); + if (con_setting) + g_object_unref (con_setting); + if (wired_setting) + g_object_unref (wired_setting); + return NULL; +} + +NMConnection * +parser_parse_ifcfg (const char *file, GError **err) +{ + NMConnection *connection = NULL; + shvarFile *parsed; + char *type; + char *nmc = NULL; + NMSetting *s_ip4; + + g_return_val_if_fail (file != NULL, NULL); + + parsed = svNewFile (file); + if (!parsed) { + g_set_error (err, ifcfg_plugin_error_quark (), 0, + "Couldn't parse file '%s'", file); + return NULL; + } + + nmc = svGetValue (parsed, "NM_CONTROLLED"); + if (nmc) { + if (!svTrueValue (parsed, nmc, 1)) { + g_free (nmc); + // FIXME: actually ignore the device, not the connection + g_message ("Ignoring connection '%s' because NM_CONTROLLED was false", file); + goto done; + } + g_free (nmc); + } + + type = svGetValue (parsed, "WIRELESS_ESSID"); + if (type) { + g_free (type); + connection = wireless_connection_from_ifcfg (file, parsed, err); + } else + connection = wired_connection_from_ifcfg (file, parsed, err); + + if (!connection) + goto done; + + s_ip4 = make_ip4_setting (parsed, err); + if (*err) { + g_object_unref (connection); + connection = NULL; + goto done; + } else if (s_ip4) { + nm_connection_add_setting (connection, s_ip4); + } + + if (!nm_connection_verify (connection)) { + g_object_unref (connection); + connection = NULL; + g_set_error (err, ifcfg_plugin_error_quark (), 0, + "Connection was invalid"); + } + +done: + svCloseFile (parsed); + return connection; +} + +guint32 +parser_parse_routes (const char *file, GError **err) +{ + FILE *f; + char *buf; + char buffer[512]; + guint route = 0; + + if ((f = fopen (SYSCONFDIR"/sysconfig/network/routes", "r"))) { + while (fgets (buffer, 512, f) && !feof (f)) { + buf = strtok (buffer, " "); + if (strcmp (buf, "default") == 0) { + buf = strtok (NULL, " "); + if (buf) + route = inet_addr (buf); + break; + } + fclose (f); + } + } + + return route; +} diff --git a/system-settings/plugins/ifcfg-suse/parser.h b/system-settings/plugins/ifcfg-suse/parser.h new file mode 100644 index 000000000..45e04dba4 --- /dev/null +++ b/system-settings/plugins/ifcfg-suse/parser.h @@ -0,0 +1,35 @@ +/* NetworkManager system settings service + * + * Søren Sandmann + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * (C) Copyright 2007 Red Hat, Inc. + */ + +#ifndef _PARSER_H_ +#define _PARSER_H_ + +#include +#include + +#define IFCFG_TAG "ifcfg-" +#define BAK_TAG ".bak" + +NMConnection * parser_parse_ifcfg (const char *file, GError **error); +guint32 parser_parse_routes (const char *file, GError **err); + + +#endif /* _PARSER_H_ */ diff --git a/system-settings/plugins/ifcfg-suse/plugin.c b/system-settings/plugins/ifcfg-suse/plugin.c new file mode 100644 index 000000000..7ab58621f --- /dev/null +++ b/system-settings/plugins/ifcfg-suse/plugin.c @@ -0,0 +1,438 @@ +/* -*- Mode: C; tab-width: 5; indent-tabs-mode: t; c-basic-offset: 5 -*- */ + +/* NetworkManager system settings service + * + * Søren Sandmann + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * (C) Copyright 2007 Red Hat, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "plugin.h" +#include "parser.h" +#include "nm-system-config-interface.h" + +#define IFCFG_PLUGIN_NAME "ifcfg-suse" +#define IFCFG_PLUGIN_INFO "(C) 2008 Novell, Inc. To report bugs please use the NetworkManager mailing list." +#define IFCFG_DIR SYSCONFDIR "/sysconfig/network" + +static void system_config_interface_init (NMSystemConfigInterface *system_config_interface_class); + +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)) + + +#define IFCFG_FILE_PATH_TAG "ifcfg-file-path" + +typedef struct { + gboolean initialized; + GSList *connections; + + GFileMonitor *monitor; + guint monitor_id; +} SCPluginIfcfgPrivate; + + +GQuark +ifcfg_plugin_error_quark (void) +{ + static GQuark error_quark = 0; + + if (G_UNLIKELY (error_quark == 0)) + error_quark = g_quark_from_static_string ("ifcfg-plugin-error-quark"); + + return error_quark; +} + +struct FindInfo { + const char *path; + gboolean found; +}; + +static gboolean +is_ifcfg_file (const char *file) +{ + return g_str_has_prefix (file, IFCFG_TAG) && strcmp (file, IFCFG_TAG "lo"); +} + +static NMConnection * +build_one_connection (const char *ifcfg_file) +{ + NMConnection *connection; + GError *err = NULL; + + PLUGIN_PRINT (PLUGIN_NAME, "parsing %s ... ", ifcfg_file); + + connection = parser_parse_ifcfg (ifcfg_file, &err); + if (connection) { + NMSettingConnection *s_con; + + s_con = NM_SETTING_CONNECTION (nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION)); + g_assert (s_con); + g_assert (s_con->id); + PLUGIN_PRINT (PLUGIN_NAME, " found connection '%s'", s_con->id); + } else + PLUGIN_PRINT (PLUGIN_NAME, " error: %s", err->message ? err->message : "(unknown)"); + + return connection; +} + +typedef struct { + SCPluginIfcfg *plugin; + NMConnection *connection; + GFileMonitor *monitor; + guint monitor_id; +} ConnectionMonitor; + +static void +connection_monitor_destroy (gpointer data) +{ + ConnectionMonitor *monitor = (ConnectionMonitor *) data; + + g_signal_handler_disconnect (monitor->monitor, monitor->monitor_id); + g_file_monitor_cancel (monitor->monitor); + g_object_unref (monitor->monitor); + + g_free (monitor); +} + +static void +connection_file_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + ConnectionMonitor *cm = (ConnectionMonitor *) user_data; + gboolean remove_connection = FALSE; + + switch (event_type) { + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: { + NMConnection *new_connection; + GHashTable *new_settings; + char *filename; + char *ifcfg_file; + + /* In case anything goes wrong */ + remove_connection = TRUE; + + filename = g_file_get_basename (file); + ifcfg_file = g_build_filename (IFCFG_DIR, filename, NULL); + g_free (filename); + + new_connection = build_one_connection (ifcfg_file); + g_free (ifcfg_file); + + if (new_connection) { + new_settings = nm_connection_to_hash (new_connection); + if (nm_connection_replace_settings (cm->connection, new_settings)) { + /* Nothing went wrong */ + remove_connection = FALSE; + g_signal_emit_by_name (cm->plugin, "connection-updated", cm->connection); + } + + g_object_unref (new_connection); + } + + break; + } + case G_FILE_MONITOR_EVENT_DELETED: + remove_connection = TRUE; + break; + default: + break; + } + + if (remove_connection) { + SCPluginIfcfgPrivate *priv = SC_PLUGIN_IFCFG_GET_PRIVATE (cm->plugin); + + priv->connections = g_slist_remove (priv->connections, cm->connection); + g_signal_emit_by_name (cm->plugin, "connection-removed", cm->connection); + g_object_unref (cm->connection); + PLUGIN_PRINT (IFCFG_PLUGIN_NAME, " removed connection"); + } +} + +static void +monitor_connection (NMSystemConfigInterface *config, NMConnection *connection, const char *filename) +{ + GFile *file; + GFileMonitor *monitor; + + file = g_file_new_for_path (filename); + monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, NULL); + g_object_unref (file); + + if (monitor) { + ConnectionMonitor *cm; + + cm = g_new (ConnectionMonitor, 1); + cm->plugin = SC_PLUGIN_IFCFG (config); + cm->connection = connection; + cm->monitor = monitor; + cm->monitor_id = g_signal_connect (monitor, "changed", G_CALLBACK (connection_file_changed), cm); + g_object_set_data_full (G_OBJECT (connection), "file-monitor", cm, connection_monitor_destroy); + } +} + +static void +add_one_connection (NMSystemConfigInterface *config, const char *filename, gboolean emit_added) +{ + char *ifcfg_file; + NMConnection *connection; + + if (!is_ifcfg_file (filename)) + return; + + ifcfg_file = g_build_filename (IFCFG_DIR, filename, NULL); + connection = build_one_connection (ifcfg_file); + if (connection) { + SCPluginIfcfgPrivate *priv = SC_PLUGIN_IFCFG_GET_PRIVATE (config); + + monitor_connection (config, connection, ifcfg_file); + priv->connections = g_slist_append (priv->connections, connection); + + if (emit_added) + g_signal_emit_by_name (config, "connection-added", connection); + } + + g_free (ifcfg_file); +} + +static void +update_default_routes (NMSystemConfigInterface *config, gboolean emit_updated) +{ + SCPluginIfcfgPrivate *priv = SC_PLUGIN_IFCFG_GET_PRIVATE (config); + GSList *iter; + NMConnection *connection; + NMSettingIP4Config *ip4_setting; + gboolean got_manual = FALSE; + guint32 default_route; + + /* First, make sure we have any non-DHCP connections */ + for (iter = priv->connections; iter; iter = iter->next) { + connection = NM_CONNECTION (iter->data); + ip4_setting = (NMSettingIP4Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP4_CONFIG); + if (ip4_setting && ip4_setting->manual) { + got_manual = TRUE; + break; + } + } + + if (!got_manual) + return; + + default_route = parser_parse_routes (IFCFG_DIR "/routes", NULL); + if (!default_route) + return; + + for (iter = priv->connections; iter; iter = iter->next) { + connection = NM_CONNECTION (iter->data); + ip4_setting = (NMSettingIP4Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP4_CONFIG); + if (ip4_setting && ip4_setting->manual) { + GSList *address_iter; + + for (address_iter = ip4_setting->addresses; address_iter; address_iter = address_iter->next) { + NMSettingIP4Address *addr = (NMSettingIP4Address *) address_iter->data; + + addr->gateway = default_route; + if (emit_updated) + g_signal_emit_by_name (config, "connection-updated", connection); + } + } + } +} + +static GSList * +get_connections (NMSystemConfigInterface *config) +{ + SCPluginIfcfgPrivate *priv = SC_PLUGIN_IFCFG_GET_PRIVATE (config); + + if (!priv->initialized) { + GDir *dir; + const char *item; + GError *err = NULL; + + dir = g_dir_open (IFCFG_DIR, 0, &err); + if (!dir) { + PLUGIN_WARN (PLUGIN_NAME, "couldn't access network directory '%s': %s.", IFCFG_DIR, err->message); + g_error_free (err); + return NULL; + } + + while ((item = g_dir_read_name (dir))) + add_one_connection (config, item, FALSE); + + g_dir_close (dir); + priv->initialized = TRUE; + } + + if (!priv->connections) + /* No need to do any futher work, we have nothing. */ + return priv->connections; + + update_default_routes (config, FALSE); + + return priv->connections; +} + +static void +ifcfg_dir_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + NMSystemConfigInterface *config = NM_SYSTEM_CONFIG_INTERFACE (user_data); + char *name; + + name = g_file_get_basename (file); + + if (event_type == G_FILE_MONITOR_EVENT_CREATED) { + add_one_connection (config, name, TRUE); + } + + if (!strcmp (name, "routes")) + update_default_routes (config, TRUE); + + g_free (name); +} + +static void +init (NMSystemConfigInterface *config) +{ + GFile *file; + GFileMonitor *monitor; + SCPluginIfcfgPrivate *priv = SC_PLUGIN_IFCFG_GET_PRIVATE (config); + + 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 (ifcfg_dir_changed), config); + priv->monitor = monitor; + } +} + +static void +release_one_connection (gpointer item, gpointer user_data) +{ + NMConnection *connection = NM_CONNECTION (item); + SCPluginIfcfg *plugin = SC_PLUGIN_IFCFG (user_data); + + g_signal_emit_by_name (plugin, "connection-removed", connection); + g_object_unref (connection); +} + +static void +sc_plugin_ifcfg_init (SCPluginIfcfg *plugin) +{ +} + +static void +dispose (GObject *object) +{ + SCPluginIfcfgPrivate *priv = SC_PLUGIN_IFCFG_GET_PRIVATE (object); + + if (priv->connections) { + g_slist_foreach (priv->connections, release_one_connection, object); + g_slist_free (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 +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + 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; + 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->get_property = get_property; + object_class->dispose = dispose; + + 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); +} + +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->init = init; +} + +G_MODULE_EXPORT GObject * +nm_system_config_factory (void) +{ + static GStaticMutex mutex = G_STATIC_MUTEX_INIT; + static SCPluginIfcfg *singleton = NULL; + + g_static_mutex_lock (&mutex); + if (!singleton) + singleton = SC_PLUGIN_IFCFG (g_object_new (SC_TYPE_PLUGIN_IFCFG, NULL)); + g_object_ref (singleton); + g_static_mutex_unlock (&mutex); + + return G_OBJECT (singleton); +} diff --git a/system-settings/plugins/ifcfg-suse/plugin.h b/system-settings/plugins/ifcfg-suse/plugin.h new file mode 100644 index 000000000..e5cce353e --- /dev/null +++ b/system-settings/plugins/ifcfg-suse/plugin.h @@ -0,0 +1,52 @@ +/* NetworkManager system settings service + * + * Søren Sandmann + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * (C) Copyright 2007 Red Hat, Inc. + */ + +#ifndef _PLUGIN_H_ +#define _PLUGIN_H_ + +#include + +#define PLUGIN_NAME "ifcfg" + +#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); + +GQuark ifcfg_plugin_error_quark (void); + +#endif /* _PLUGIN_H_ */ + diff --git a/system-settings/plugins/ifcfg-suse/shvar.c b/system-settings/plugins/ifcfg-suse/shvar.c new file mode 100644 index 000000000..85577ea46 --- /dev/null +++ b/system-settings/plugins/ifcfg-suse/shvar.c @@ -0,0 +1,402 @@ +/* + * shvar.c + * + * Implementation of non-destructively reading/writing files containing + * only shell variable declarations and full-line comments. + * + * Includes explicit inheritance mechanism intended for use with + * Red Hat Linux ifcfg-* files. There is no protection against + * inheritance loops; they will generally cause stack overflows. + * Furthermore, they are only intended for one level of inheritance; + * the value setting algorithm assumes this. + * + * Copyright 1999,2000 Red Hat, Inc. + * + * This 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "shvar.h" + +/* Open the file , returning a shvarFile on success and NULL on failure. + Add a wrinkle to let the caller specify whether or not to create the file + (actually, return a structure anyway) if it doesn't exist. */ +static shvarFile * +svOpenFile(const char *name, gboolean create) +{ + shvarFile *s = NULL; + int closefd = 0; + + s = g_malloc0(sizeof(shvarFile)); + +#if 1 /* NetworkManager local change */ + s->fd = open(name, O_RDONLY); /* NOT O_CREAT */ + if (s->fd != -1) closefd = 1; +#else + s->fd = open(name, O_RDWR); /* NOT O_CREAT */ + if (s->fd == -1) { + /* try read-only */ + s->fd = open(name, O_RDONLY); /* NOT O_CREAT */ + if (s->fd != -1) closefd = 1; + } +#endif + s->fileName = g_strdup(name); + + if (s->fd != -1) { + struct stat buf; + char *p, *q; + + if (fstat(s->fd, &buf) < 0) goto bail; + s->arena = g_malloc0(buf.st_size + 1); + + if (read(s->fd, s->arena, buf.st_size) < 0) goto bail; + + /* we'd use g_strsplit() here, but we want a list, not an array */ + for(p = s->arena; (q = strchr(p, '\n')) != NULL; p = q + 1) { + s->lineList = g_list_append(s->lineList, g_strndup(p, q - p)); + } + + /* closefd is set if we opened the file read-only, so go ahead and + close it, because we can't write to it anyway */ + if (closefd) { + close(s->fd); + s->fd = -1; + } + + return s; + } + + if (create) { + return s; + } + +bail: + if (s->fd != -1) close(s->fd); + if (s->arena) g_free (s->arena); + if (s->fileName) g_free (s->fileName); + g_free (s); + return NULL; +} + +/* Open the file , return shvarFile on success, NULL on failure */ +shvarFile * +svNewFile(const char *name) +{ + return svOpenFile(name, FALSE); +} + +/* Create a new file structure, returning actual data if the file exists, + * and a suitable starting point if it doesn't. */ +shvarFile * +svCreateFile(const char *name) +{ + return svOpenFile(name, TRUE); +} + +/* remove escaped characters in place */ +static void +unescape(char *s) { + int len, i; + + len = strlen(s); + if ((s[0] == '"' || s[0] == '\'') && s[0] == s[len-1]) { + i = len - 2; + if (i == 0) + s[0] = '\0'; + else { + memmove(s, s+1, i); + s[i+1] = '\0'; + len = i; + } + } + for (i = 0; i < len; i++) { + if (s[i] == '\\') { + memmove(s+i, s+i+1, len-(i+1)); + len--; + } + s[len] = '\0'; + } +} + + +/* create a new string with all necessary characters escaped. + * caller must free returned string + */ +static const char escapees[] = "\"'\\$~`"; /* must be escaped */ +static const char spaces[] = " \t|&;()<>"; /* only require "" */ +static char * +escape(const char *s) { + char *new; + int i, j, mangle = 0, space = 0; + int newlen, slen; + static int esclen, splen; + + if (!esclen) esclen = strlen(escapees); + if (!splen) splen = strlen(spaces); + slen = strlen(s); + + for (i = 0; i < slen; i++) { + if (strchr(escapees, s[i])) mangle++; + if (strchr(spaces, s[i])) space++; + } + if (!mangle && !space) return strdup(s); + + newlen = slen + mangle + 3; /* 3 is extra ""\0 */ + new = g_malloc0(newlen); + if (!new) return NULL; + + j = 0; + new[j++] = '"'; + for (i = 0; i < slen; i++) { + if (strchr(escapees, s[i])) { + new[j++] = '\\'; + } + new[j++] = s[i]; + } + new[j++] = '"'; + g_assert(j == slen + mangle + 2); /* j is the index of the '\0' */ + + return new; +} + +/* Get the value associated with the key, and leave the current pointer + * pointing at the line containing the value. The char* returned MUST + * be freed by the caller. + */ +char * +svGetValue(shvarFile *s, const char *key) +{ + char *value = NULL; + char *line; + char *keyString; + int len; + + g_assert(s); + g_assert(key); + + keyString = g_malloc0(strlen(key) + 2); + strcpy(keyString, key); + keyString[strlen(key)] = '='; + len = strlen(keyString); + + for (s->current = s->lineList; s->current; s->current = s->current->next) { + line = s->current->data; + if (!strncmp(keyString, line, len)) { + value = g_strdup(line + len); + unescape(value); + break; + } + } + g_free(keyString); + + if (value) { + if (value[0]) { + return value; + } else { + g_free(value); + return NULL; + } + } + if (s->parent) value = svGetValue(s->parent, key); + return value; +} + +/* return 1 if resolves to any truth value (e.g. "yes", "y", "true") + * return 0 if resolves to any non-truth value (e.g. "no", "n", "false") + * return otherwise + */ +int +svTrueValue(shvarFile *s, const char *key, int def) +{ + char *tmp; + int returnValue = def; + + tmp = svGetValue(s, key); + if (!tmp) return returnValue; + + if ( (!strcasecmp("yes", tmp)) || + (!strcasecmp("true", tmp)) || + (!strcasecmp("t", tmp)) || + (!strcasecmp("y", tmp)) ) returnValue = 1; + else + if ( (!strcasecmp("no", tmp)) || + (!strcasecmp("false", tmp)) || + (!strcasecmp("f", tmp)) || + (!strcasecmp("n", tmp)) ) returnValue = 0; + + g_free (tmp); + return returnValue; +} + + +/* Set the variable equal to the value . + * If does not exist, and the pointer is set, append + * the key=value pair after that line. Otherwise, prepend the pair + * to the top of the file. Here's the algorithm, as the C code + * seems to be rather dense: + * + * if (value == NULL), then: + * if val2 (parent): change line to key= or append line key= + * if val1 (this) : delete line + * else noop + * else use this table: + * val2 + * NULL value other + * v NULL append line noop append line + * a + * l value noop noop noop + * 1 + * other change line delete line change line + * + * No changes are ever made to the parent config file, only to the + * specific file passed on the command line. + * + */ +void +svSetValue(shvarFile *s, const char *key, const char *value) +{ + char *newval = NULL, *val1 = NULL, *val2 = NULL; + char *keyValue; + + g_assert(s); + g_assert(key); + /* value may be NULL */ + + if (value) newval = escape(value); + keyValue = g_strdup_printf("%s=%s", key, newval ? newval : ""); + + val1 = svGetValue(s, key); + if (val1 && newval && !strcmp(val1, newval)) goto bail; + if (s->parent) val2 = svGetValue(s->parent, key); + + if (!newval || !newval[0]) { + /* delete value somehow */ + if (val2) { + /* change/append line to get key= */ + if (s->current) s->current->data = keyValue; + else s->lineList = g_list_append(s->lineList, keyValue); + s->freeList = g_list_append(s->freeList, keyValue); + s->modified = 1; + } else if (val1) { + /* delete line */ + s->lineList = g_list_remove_link(s->lineList, s->current); + g_list_free_1(s->current); + s->modified = 1; + goto bail; /* do not need keyValue */ + } + goto end; + } + + if (!val1) { + if (val2 && !strcmp(val2, newval)) goto end; + /* append line */ + s->lineList = g_list_append(s->lineList, keyValue); + s->freeList = g_list_append(s->freeList, keyValue); + s->modified = 1; + goto end; + } + + /* deal with a whole line of noops */ + if (val1 && !strcmp(val1, newval)) goto end; + + /* At this point, val1 && val1 != value */ + if (val2 && !strcmp(val2, newval)) { + /* delete line */ + s->lineList = g_list_remove_link(s->lineList, s->current); + g_list_free_1(s->current); + s->modified = 1; + goto bail; /* do not need keyValue */ + } else { + /* change line */ + if (s->current) s->current->data = keyValue; + else s->lineList = g_list_append(s->lineList, keyValue); + s->freeList = g_list_append(s->freeList, keyValue); + s->modified = 1; + } + +end: + if (newval) free(newval); + if (val1) free(val1); + if (val2) free(val2); + return; + +bail: + if (keyValue) free (keyValue); + goto end; +} + +/* Write the current contents iff modified. Returns -1 on error + * and 0 on success. Do not write if no values have been modified. + * The mode argument is only used if creating the file, not if + * re-writing an existing file, and is passed unchanged to the + * open() syscall. + */ +int +svWriteFile(shvarFile *s, int mode) +{ + FILE *f; + int tmpfd; + + if (s->modified) { + if (s->fd == -1) + s->fd = open(s->fileName, O_WRONLY|O_CREAT, mode); + if (s->fd == -1) + return -1; + if (ftruncate(s->fd, 0) < 0) + return -1; + + tmpfd = dup(s->fd); + f = fdopen(tmpfd, "w"); + fseek(f, 0, SEEK_SET); + for (s->current = s->lineList; s->current; s->current = s->current->next) { + char *line = s->current->data; + fprintf(f, "%s\n", line); + } + fclose(f); + } + + return 0; +} + + +/* Close the file descriptor (if open) and delete the shvarFile. + * Returns -1 on error and 0 on success. + */ +int +svCloseFile(shvarFile *s) +{ + + g_assert(s); + + if (s->fd != -1) close(s->fd); + + g_free(s->arena); + for (s->current = s->freeList; s->current; s->current = s->current->next) { + g_free(s->current->data); + } + g_free(s->fileName); + g_list_free(s->freeList); + g_list_free(s->lineList); /* implicitly frees s->current */ + g_free(s); + return 0; +} diff --git a/system-settings/plugins/ifcfg-suse/shvar.h b/system-settings/plugins/ifcfg-suse/shvar.h new file mode 100644 index 000000000..50d10680d --- /dev/null +++ b/system-settings/plugins/ifcfg-suse/shvar.h @@ -0,0 +1,103 @@ +/* + * shvar.h + * + * Interface for non-destructively reading/writing files containing + * only shell variable declarations and full-line comments. + * + * Includes explicit inheritance mechanism intended for use with + * Red Hat Linux ifcfg-* files. There is no protection against + * inheritance loops; they will generally cause stack overflows. + * Furthermore, they are only intended for one level of inheritance; + * the value setting algorithm assumes this. + * + * Copyright 1999 Red Hat, Inc. + * + * This 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ +#ifndef _SHVAR_H +#define _SHVAR_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct _shvarFile shvarFile; +struct _shvarFile { + char *fileName; /* read-only */ + int fd; /* read-only */ + char *arena; /* ignore */ + GList *lineList; /* read-only */ + GList *freeList; /* ignore */ + GList *current; /* set implicitly or explicitly, + points to element of lineList */ + shvarFile *parent; /* set explicitly */ + int modified; /* ignore */ +}; + + +/* Create the file , return shvarFile on success, NULL on failure */ +shvarFile * +svCreateFile(const char *name); + +/* Open the file , return shvarFile on success, NULL on failure */ +shvarFile * +svNewFile(const char *name); + +/* Get the value associated with the key, and leave the current pointer + * pointing at the line containing the value. The char* returned MUST + * be freed by the caller. + */ +char * +svGetValue(shvarFile *s, const char *key); + +/* return 1 if resolves to any truth value (e.g. "yes", "y", "true") + * return 0 if resolves to any non-truth value (e.g. "no", "n", "false") + * return otherwise + */ +int +svTrueValue(shvarFile *s, const char *key, int def); + +/* Set the variable equal to the value . + * If does not exist, and the pointer is set, append + * the key=value pair after that line. Otherwise, prepend the pair + * to the top of the file. + */ +void +svSetValue(shvarFile *s, const char *key, const char *value); + + +/* Write the current contents iff modified. Returns -1 on error + * and 0 on success. Do not write if no values have been modified. + * The mode argument is only used if creating the file, not if + * re-writing an existing file, and is passed unchanged to the + * open() syscall. + */ +int +svWriteFile(shvarFile *s, int mode); + +/* Close the file descriptor (if open) and delete the shvarFile. + * Returns -1 on error and 0 on success. + */ +int +svCloseFile(shvarFile *s); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* ! _SHVAR_H */