about summary refs log tree commit diff
path: root/src/settings/plugins/ifcfg-rh/writer.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/settings/plugins/ifcfg-rh/writer.c')
-rw-r--r--src/settings/plugins/ifcfg-rh/writer.c1746
1 files changed, 1746 insertions, 0 deletions
diff --git a/src/settings/plugins/ifcfg-rh/writer.c b/src/settings/plugins/ifcfg-rh/writer.c
new file mode 100644
index 00000000..6924262f
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/writer.c
@@ -0,0 +1,1746 @@
+/* -*- 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 - 2011 Red Hat, Inc.
+ */
+
+#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"
+
+#define PLUGIN_WARN(pname, fmt, args...) \
+	{ g_warning ("   " pname ": " fmt, ##args); }
+
+static void
+save_secret_flags (shvarFile *ifcfg,
+                   const char *key,
+                   NMSettingSecretFlags flags)
+{
+	GString *str;
+
+	g_return_if_fail (ifcfg != NULL);
+	g_return_if_fail (key != NULL);
+
+	if (flags == NM_SETTING_SECRET_FLAG_NONE) {
+		svSetValue (ifcfg, key, NULL, FALSE);
+		return;
+	}
+
+	/* Convert flags bitfield into string representation */
+	str = g_string_sized_new (20);
+	if (flags & NM_SETTING_SECRET_FLAG_AGENT_OWNED)
+		g_string_append (str, SECRET_FLAG_AGENT);
+
+	if (flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) {
+		if (str->len)
+			g_string_append_c (str, ' ');
+		g_string_append (str, SECRET_FLAG_NOT_SAVED);
+	}
+
+	if (flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED) {
+		if (str->len)
+			g_string_append_c (str, ' ');
+		g_string_append (str, SECRET_FLAG_NOT_REQUIRED);
+	}
+
+	svSetValue (ifcfg, key, str->len ? str->str : NULL, FALSE);
+	g_string_free (str, TRUE);
+}
+
+static void
+set_secret (shvarFile *ifcfg,
+            const char *key,
+            const char *value,
+            const char *flags_key,
+            NMSettingSecretFlags flags,
+            gboolean verbatim)
+{
+	shvarFile *keyfile;
+	
+	/* Clear the secret from the ifcfg and the associated "keys" file */
+	svSetValue (ifcfg, key, NULL, FALSE);
+
+	/* Save secret flags */
+	save_secret_flags (ifcfg, flags_key, flags);
+
+	keyfile = utils_get_keys_ifcfg (ifcfg->fileName, TRUE);
+	if (!keyfile) {
+		PLUGIN_WARN (IFCFG_PLUGIN_NAME, "    warning: could not create key file for '%s'",
+		             ifcfg->fileName);
+		goto error;
+	}
+
+	/* Clear the secret from the associated "keys" file */
+	svSetValue (keyfile, key, NULL, FALSE);
+
+	/* Only write the secret if it's system owned and supposed to be saved */
+	if (flags == NM_SETTING_SECRET_FLAG_NONE)
+		svSetValue (keyfile, key, value, verbatim);
+
+	if (svWriteFile (keyfile, 0600)) {
+		PLUGIN_WARN (IFCFG_PLUGIN_NAME, "    warning: could not update key file '%s'",
+		             keyfile->fileName);
+		svCloseFile (keyfile);
+		goto error;
+	}
+	svCloseFile (keyfile);
+	return;
+
+error:
+	/* Try setting the secret in the actual ifcfg */
+	svSetValue (ifcfg, key, value, FALSE);
+}
+
+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, 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, 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, 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, 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, 0,
+		             "Could not rename temporary file to '%s': %d",
+		             path, errno);
+		goto out;
+	}
+	success = TRUE;
+
+out:
+	g_free (tmppath);
+	return success;
+}
+
+typedef struct ObjectType {
+	const char *setting_key;
+	NMSetting8021xCKScheme (*scheme_func)(NMSetting8021x *setting);
+	const char *           (*path_func)  (NMSetting8021x *setting);
+	const GByteArray *     (*blob_func)  (NMSetting8021x *setting);
+	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,
+	"IEEE_8021X_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,
+	"IEEE_8021X_INNER_CA_CERT",
+	"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,
+	"IEEE_8021X_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,
+	"IEEE_8021X_INNER_CLIENT_CERT",
+	"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,
+	"IEEE_8021X_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,
+	"IEEE_8021X_INNER_PRIVATE_KEY",
+	"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,
+	"IEEE_8021X_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,
+	"IEEE_8021X_INNER_PRIVATE_KEY",
+	"inner-private-key.p12"
+};
+
+static gboolean
+write_object (NMSetting8021x *s_8021x,
+              shvarFile *ifcfg,
+              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);
+
+	scheme = (*(objtype->scheme_func))(s_8021x);
+	switch (scheme) {
+	case NM_SETTING_802_1X_CK_SCHEME_BLOB:
+		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.
+		 */
+		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);
+
+		svSetValue (ifcfg, objtype->ifcfg_key, NULL, FALSE);
+		return TRUE;
+	}
+
+	/* If the object path was specified, prefer that over any raw cert data that
+	 * may have been sent.
+	 */
+	if (path) {
+		svSetValue (ifcfg, objtype->ifcfg_key, path, FALSE);
+		return TRUE;
+	}
+
+	/* If it's raw certificate data, write the 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, 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, 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);
+	}
+
+	return FALSE;
+}
+
+static gboolean
+write_8021x_certs (NMSetting8021x *s_8021x,
+                   gboolean phase2,
+                   shvarFile *ifcfg,
+                   GError **error)
+{
+	const char *password = NULL;
+	gboolean success = FALSE, is_pkcs12 = FALSE;
+	const ObjectType *otype = NULL;
+	NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE;
+
+	/* CA certificate */
+	if (!write_object (s_8021x, ifcfg, phase2 ? &phase2_ca_type : &ca_type, error))
+		return FALSE;
+
+	/* Private key */
+	if (phase2) {
+		otype = &phase2_pk_type;
+		if (nm_setting_802_1x_get_phase2_private_key_format (s_8021x) == NM_SETTING_802_1X_CK_FORMAT_PKCS12) {
+			otype = &phase2_p12_type;
+			is_pkcs12 = TRUE;
+		}
+		password = nm_setting_802_1x_get_phase2_private_key_password (s_8021x);
+		flags = nm_setting_802_1x_get_phase2_private_key_password_flags (s_8021x);
+	} else {
+		otype = &pk_type;
+		if (nm_setting_802_1x_get_private_key_format (s_8021x) == NM_SETTING_802_1X_CK_FORMAT_PKCS12) {
+			otype = &p12_type;
+			is_pkcs12 = TRUE;
+		}
+		password = nm_setting_802_1x_get_private_key_password (s_8021x);
+		flags = nm_setting_802_1x_get_private_key_password_flags (s_8021x);
+	}
+
+	/* Save the private key */
+	if (!write_object (s_8021x, ifcfg, otype, error))
+		goto out;
+
+	/* Private key password */
+	if (phase2) {
+		set_secret (ifcfg,
+		            "IEEE_8021X_INNER_PRIVATE_KEY_PASSWORD",
+		            password,
+		            "IEEE_8021X_INNER_PRIVATE_KEY_PASSWORD_FLAGS",
+		            flags,
+		            FALSE);
+	} else {
+		set_secret (ifcfg,
+		            "IEEE_8021X_PRIVATE_KEY_PASSWORD",
+		            password,
+		            "IEEE_8021X_PRIVATE_KEY_PASSWORD_FLAGS",
+		            flags,
+		            FALSE);
+	}
+
+	/* Client certificate */
+	if (is_pkcs12) {
+		/* Don't need a client certificate with PKCS#12 since the file is both
+		 * the client certificate and the private key in one file.
+		 */
+		svSetValue (ifcfg,
+		            phase2 ? "IEEE_8021X_INNER_CLIENT_CERT" : "IEEE_8021X_CLIENT_CERT",
+		            NULL, FALSE);
+	} else {
+		/* Save the client certificate */
+		if (!write_object (s_8021x, ifcfg, phase2 ? &phase2_client_type : &client_type, error))
+			goto out;
+	}
+
+	success = TRUE;
+
+out:
+	return success;
+}
+
+static gboolean
+write_8021x_setting (NMConnection *connection,
+                     shvarFile *ifcfg,
+                     gboolean wired,
+                     GError **error)
+{
+	NMSetting8021x *s_8021x;
+	const char *value;
+	char *tmp = NULL;
+	gboolean success = FALSE;
+	GString *phase2_auth;
+
+	s_8021x = (NMSetting8021x *) nm_connection_get_setting (connection, NM_TYPE_SETTING_802_1X);
+	if (!s_8021x) {
+		/* If wired, clear KEY_MGMT */
+		if (wired)
+			svSetValue (ifcfg, "KEY_MGMT", NULL, FALSE);
+		return TRUE;
+	}
+
+	/* If wired, write KEY_MGMT */
+	if (wired)
+		svSetValue (ifcfg, "KEY_MGMT", "IEEE8021X", FALSE);
+
+	/* 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);
+	}
+	svSetValue (ifcfg, "IEEE_8021X_EAP_METHODS", tmp ? tmp : NULL, FALSE);
+	g_free (tmp);
+
+	svSetValue (ifcfg, "IEEE_8021X_IDENTITY",
+	            nm_setting_802_1x_get_identity (s_8021x),
+	            FALSE);
+
+	svSetValue (ifcfg, "IEEE_8021X_ANON_IDENTITY",
+	            nm_setting_802_1x_get_anonymous_identity (s_8021x),
+	            FALSE);
+
+	set_secret (ifcfg,
+	            "IEEE_8021X_PASSWORD",
+	            nm_setting_802_1x_get_password (s_8021x),
+	            "IEEE_8021X_PASSWORD_FLAGS",
+	            nm_setting_802_1x_get_password_flags (s_8021x),
+	            FALSE);
+
+	/* PEAP version */
+	value = nm_setting_802_1x_get_phase1_peapver (s_8021x);
+	svSetValue (ifcfg, "IEEE_8021X_PEAP_VERSION", NULL, FALSE);
+	if (value && (!strcmp (value, "0") || !strcmp (value, "1")))
+		svSetValue (ifcfg, "IEEE_8021X_PEAP_VERSION", value, FALSE);
+
+	/* Force new PEAP label */
+	value = nm_setting_802_1x_get_phase1_peaplabel (s_8021x);
+	svSetValue (ifcfg, "IEEE_8021X_PEAP_FORCE_NEW_LABEL", NULL, FALSE);
+	if (value && !strcmp (value, "1"))
+		svSetValue (ifcfg, "IEEE_8021X_PEAP_FORCE_NEW_LABEL", "yes", FALSE);
+
+	/* Phase2 auth methods */
+	svSetValue (ifcfg, "IEEE_8021X_INNER_AUTH_METHODS", NULL, FALSE);
+	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_append (phase2_auth, 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, "EAP-%s", tmp);
+		g_free (tmp);
+	}
+
+	svSetValue (ifcfg, "IEEE_8021X_INNER_AUTH_METHODS",
+	            phase2_auth->len ? phase2_auth->str : NULL,
+	            FALSE);
+
+	g_string_free (phase2_auth, TRUE);
+
+	success = write_8021x_certs (s_8021x, FALSE, ifcfg, error);
+	if (success) {
+		/* phase2/inner certs */
+		success = write_8021x_certs (s_8021x, TRUE, ifcfg, error);
+	}
+
+	return success;
+}
+
+static gboolean
+write_wireless_security_setting (NMConnection *connection,
+                                 shvarFile *ifcfg,
+                                 gboolean adhoc,
+                                 gboolean *no_8021x,
+                                 GError **error)
+{
+	NMSettingWirelessSecurity *s_wsec;
+	const char *key_mgmt, *auth_alg, *key, *proto, *cipher, *psk;
+	gboolean wep = FALSE, wpa = FALSE, dynamic_wep = 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, 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")) {
+		svSetValue (ifcfg, "KEY_MGMT", "WPA-PSK", FALSE);
+		wpa = TRUE;
+		*no_8021x = TRUE;
+	} else if (!strcmp (key_mgmt, "ieee8021x")) {
+		svSetValue (ifcfg, "KEY_MGMT", "IEEE8021X", FALSE);
+		dynamic_wep = TRUE;
+	} else if (!strcmp (key_mgmt, "wpa-eap")) {
+		svSetValue (ifcfg, "KEY_MGMT", "WPA-EAP", FALSE);
+		wpa = TRUE;
+	}
+
+	svSetValue (ifcfg, "SECURITYMODE", NULL, FALSE);
+	if (auth_alg) {
+		if (!strcmp (auth_alg, "shared"))
+			svSetValue (ifcfg, "SECURITYMODE", "restricted", FALSE);
+		else if (!strcmp (auth_alg, "open"))
+			svSetValue (ifcfg, "SECURITYMODE", "open", FALSE);
+		else if (!strcmp (auth_alg, "leap")) {
+			svSetValue (ifcfg, "SECURITYMODE", "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),
+			            "IEEE_8021X_PASSWORD_FLAGS",
+			            nm_setting_wireless_security_get_leap_password_flags (s_wsec),
+			            FALSE);
+			*no_8021x = TRUE;
+		}
+	}
+
+	/* WEP keys */
+
+	/* Clear any default key */
+	set_secret (ifcfg, "KEY", NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE, FALSE);
+
+	/* Clear existing keys */
+	for (i = 0; i < 4; i++) {
+		tmp = g_strdup_printf ("KEY_PASSPHRASE%d", i + 1);
+		set_secret (ifcfg, tmp, NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE, FALSE);
+		g_free (tmp);
+
+		tmp = g_strdup_printf ("KEY%d", i + 1);
+		set_secret (ifcfg, tmp, NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE, FALSE);
+		g_free (tmp);
+	}
+
+	/* And write the new ones out */
+	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);
+
+		for (i = 0; i < 4; i++) {
+			NMWepKeyType key_type;
+
+			key = nm_setting_wireless_security_get_wep_key (s_wsec, i);
+			if (key) {
+				char *ascii_key = NULL;
+
+				/* Passphrase needs a different ifcfg key since with WEP, there
+				 * are some passphrases that are indistinguishable from WEP hex
+				 * keys.
+				 */
+				key_type = nm_setting_wireless_security_get_wep_key_type (s_wsec);
+				if (key_type == NM_WEP_KEY_TYPE_PASSPHRASE)
+					tmp = g_strdup_printf ("KEY_PASSPHRASE%d", i + 1);
+				else {
+					tmp = g_strdup_printf ("KEY%d", i + 1);
+
+					/* Add 's:' prefix for ASCII keys */
+					if (strlen (key) == 5 || strlen (key) == 13) {
+						ascii_key = g_strdup_printf ("s:%s", key);
+						key = ascii_key;
+					}
+				}
+
+				set_secret (ifcfg,
+				            tmp,
+				            key,
+				            "WEP_KEY_FLAGS",
+				            nm_setting_wireless_security_get_wep_key_flags (s_wsec),
+				            FALSE);
+				g_free (tmp);
+				g_free (ascii_key);
+			}
+		}
+	}
+
+	/* WPA protos */
+	svSetValue (ifcfg, "WPA_ALLOW_WPA", NULL, FALSE);
+	svSetValue (ifcfg, "WPA_ALLOW_WPA2", NULL, FALSE);
+	num = nm_setting_wireless_security_get_num_protos (s_wsec);
+	for (i = 0; i < num; i++) {
+		proto = nm_setting_wireless_security_get_proto (s_wsec, i);
+		if (proto && !strcmp (proto, "wpa"))
+			svSetValue (ifcfg, "WPA_ALLOW_WPA", "yes", FALSE);
+		else if (proto && !strcmp (proto, "rsn"))
+			svSetValue (ifcfg, "WPA_ALLOW_WPA2", "yes", FALSE);
+	}
+
+	/* WPA Pairwise ciphers */
+	svSetValue (ifcfg, "CIPHER_PAIRWISE", NULL, FALSE);
+	str = g_string_new (NULL);
+	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);
+
+		/* Don't write out WEP40 or WEP104 if for some reason they are set; they
+		 * are not valid pairwise ciphers.
+		 */
+		if (strcmp (cipher, "wep40") && strcmp (cipher, "wep104")) {
+			tmp = g_ascii_strup (cipher, -1);
+			g_string_append (str, tmp);
+			g_free (tmp);
+		}
+	}
+	if (strlen (str->str) && (dynamic_wep == FALSE))
+		svSetValue (ifcfg, "CIPHER_PAIRWISE", str->str, FALSE);
+	g_string_free (str, TRUE);
+
+	/* WPA Group ciphers */
+	svSetValue (ifcfg, "CIPHER_GROUP", NULL, FALSE);
+	str = g_string_new (NULL);
+	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) && (dynamic_wep == FALSE))
+		svSetValue (ifcfg, "CIPHER_GROUP", str->str, FALSE);
+	g_string_free (str, TRUE);
+
+	/* WPA Passphrase */
+	if (wpa) {
+		GString *quoted = NULL;
+
+		psk = nm_setting_wireless_security_get_psk (s_wsec);
+		if (psk && (strlen (psk) != 64)) {
+			/* Quote the PSK since it's a passphrase */
+			quoted = g_string_sized_new (strlen (psk) + 2); /* 2 for quotes */
+			g_string_append_c (quoted, '"');
+			g_string_append (quoted, psk);
+			g_string_append_c (quoted, '"');
+		}
+		set_secret (ifcfg,
+		            "WPA_PSK",
+		            quoted ? quoted->str : psk,
+		            "WPA_PSK_FLAGS",
+		            nm_setting_wireless_security_get_psk_flags (s_wsec),
+		            TRUE);
+		if (quoted)
+			g_string_free (quoted, TRUE);
+	} else {
+		set_secret (ifcfg,
+		            "WPA_PSK",
+		            NULL,
+		            "WPA_PSK_FLAGS",
+		            NM_SETTING_SECRET_FLAG_NONE,
+		            FALSE);
+	}
+
+	return TRUE;
+}
+
+static gboolean
+write_wireless_setting (NMConnection *connection,
+                        shvarFile *ifcfg,
+                        gboolean *no_8021x,
+                        GError **error)
+{
+	NMSettingWireless *s_wireless;
+	char *tmp, *tmp2;
+	const GByteArray *ssid, *device_mac, *cloned_mac, *bssid;
+	const char *mode;
+	char buf[33];
+	guint32 mtu, chan, i;
+	gboolean adhoc = FALSE, hex_ssid = FALSE;
+
+	s_wireless = (NMSettingWireless *) nm_connection_get_setting (connection, NM_TYPE_SETTING_WIRELESS);
+	if (!s_wireless) {
+		g_set_error (error, IFCFG_PLUGIN_ERROR, 0,
+		             "Missing '%s' setting", NM_SETTING_WIRELESS_SETTING_NAME);
+		return FALSE;
+	}
+
+	svSetValue (ifcfg, "HWADDR", NULL, FALSE);
+	device_mac = nm_setting_wireless_get_mac_address (s_wireless);
+	if (device_mac) {
+		tmp = g_strdup_printf ("%02X:%02X:%02X:%02X:%02X:%02X",
+		                       device_mac->data[0], device_mac->data[1], device_mac->data[2],
+		                       device_mac->data[3], device_mac->data[4], device_mac->data[5]);
+		svSetValue (ifcfg, "HWADDR", tmp, FALSE);
+		g_free (tmp);
+	}
+
+	svSetValue (ifcfg, "MACADDR", NULL, FALSE);
+	cloned_mac = nm_setting_wireless_get_cloned_mac_address (s_wireless);
+	if (cloned_mac) {
+		tmp = g_strdup_printf ("%02X:%02X:%02X:%02X:%02X:%02X",
+		                       cloned_mac->data[0], cloned_mac->data[1], cloned_mac->data[2],
+		                       cloned_mac->data[3], cloned_mac->data[4], cloned_mac->data[5]);
+		svSetValue (ifcfg, "MACADDR", tmp, FALSE);
+		g_free (tmp);
+	}
+
+	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, 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, 0,
+		             "Invalid SSID in '%s' setting", NM_SETTING_WIRELESS_SETTING_NAME);
+		return FALSE;
+	}
+
+	/* If the SSID contains any non-printable characters, we need to use the
+	 * hex notation of the SSID instead.
+	 */
+	for (i = 0; i < ssid->len; i++) {
+		if (!isprint (ssid->data[i])) {
+			hex_ssid = TRUE;
+			break;
+		}
+	}
+
+	if (hex_ssid) {
+		GString *str;
+
+		/* Hex SSIDs don't get quoted */
+		str = g_string_sized_new (ssid->len * 2 + 3);
+		g_string_append (str, "0x");
+		for (i = 0; i < ssid->len; i++)
+			g_string_append_printf (str, "%02X", ssid->data[i]);
+		svSetValue (ifcfg, "ESSID", str->str, TRUE);
+		g_string_free (str, TRUE);
+	} else {
+		/* Printable SSIDs always get quoted */
+		memset (buf, 0, sizeof (buf));
+		memcpy (buf, ssid->data, ssid->len);
+		tmp = svEscape (buf);
+
+		/* svEscape will usually quote the string, but just for consistency,
+		 * if svEscape doesn't quote the ESSID, we quote it ourselves.
+		 */
+		if (tmp[0] != '"' && tmp[strlen (tmp) - 1] != '"') {
+			tmp2 = g_strdup_printf ("\"%s\"", tmp);
+			svSetValue (ifcfg, "ESSID", tmp2, TRUE);
+			g_free (tmp2);
+		} else
+			svSetValue (ifcfg, "ESSID", tmp, TRUE);
+		g_free (tmp);
+	}
+
+	mode = nm_setting_wireless_get_mode (s_wireless);
+	if (!mode || !strcmp (mode, "infrastructure")) {
+		svSetValue (ifcfg, "MODE", "Managed", FALSE);
+	} else if (!strcmp (mode, "adhoc")) {
+		svSetValue (ifcfg, "MODE", "Ad-Hoc", FALSE);
+		adhoc = TRUE;
+	} else {
+		g_set_error (error, IFCFG_PLUGIN_ERROR, 0,
+		             "Invalid mode '%s' in '%s' setting",
+		             mode, NM_SETTING_WIRELESS_SETTING_NAME);
+		return FALSE;
+	}
+
+	svSetValue (ifcfg, "CHANNEL", NULL, FALSE);
+	chan = nm_setting_wireless_get_channel (s_wireless);
+	if (chan) {
+		tmp = g_strdup_printf ("%u", chan);
+		svSetValue (ifcfg, "CHANNEL", tmp, FALSE);
+		g_free (tmp);
+	}
+
+	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]);
+		svSetValue (ifcfg, "BSSID", tmp, FALSE);
+		g_free (tmp);
+	}
+
+	/* Ensure DEFAULTKEY and SECURITYMODE are cleared unless there's security;
+	 * otherwise there's no way to detect WEP vs. open when WEP keys aren't
+	 * saved.
+	 */
+	svSetValue (ifcfg, "DEFAULTKEY", NULL, FALSE);
+	svSetValue (ifcfg, "SECURITYMODE", NULL, FALSE);
+
+	if (nm_setting_wireless_get_security (s_wireless)) {
+		if (!write_wireless_security_setting (connection, ifcfg, adhoc, no_8021x, error))
+			return FALSE;
+	}
+
+	svSetValue (ifcfg, "TYPE", TYPE_WIRELESS, FALSE);
+
+	return TRUE;
+}
+
+static gboolean
+write_wired_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
+{
+	NMSettingWired *s_wired;
+	const GByteArray *device_mac, *cloned_mac;
+	char *tmp;
+	const char *nettype, *portname, *s390_key, *s390_val;
+	guint32 mtu, num_opts, i;
+	const GPtrArray *s390_subchannels;
+	GString *str;
+
+	s_wired = (NMSettingWired *) nm_connection_get_setting (connection, NM_TYPE_SETTING_WIRED);
+	if (!s_wired) {
+		g_set_error (error, IFCFG_PLUGIN_ERROR, 0,
+		             "Missing '%s' setting", NM_SETTING_WIRED_SETTING_NAME);
+		return FALSE;
+	}
+
+	svSetValue (ifcfg, "HWADDR", NULL, FALSE);
+	device_mac = nm_setting_wired_get_mac_address (s_wired);
+	if (device_mac) {
+		tmp = g_strdup_printf ("%02X:%02X:%02X:%02X:%02X:%02X",
+		                       device_mac->data[0], device_mac->data[1], device_mac->data[2],
+		                       device_mac->data[3], device_mac->data[4], device_mac->data[5]);
+		svSetValue (ifcfg, "HWADDR", tmp, FALSE);
+		g_free (tmp);
+	}
+
+	cloned_mac = nm_setting_wired_get_cloned_mac_address (s_wired);
+	if (cloned_mac) {
+		tmp = g_strdup_printf ("%02X:%02X:%02X:%02X:%02X:%02X",
+		                       cloned_mac->data[0], cloned_mac->data[1], cloned_mac->data[2],
+		                       cloned_mac->data[3], cloned_mac->data[4], cloned_mac->data[5]);
+		svSetValue (ifcfg, "MACADDR", tmp, FALSE);
+		g_free (tmp);
+	}
+
+	svSetValue (ifcfg, "MTU", NULL, FALSE);
+	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, "SUBCHANNELS", NULL, FALSE);
+	s390_subchannels = nm_setting_wired_get_s390_subchannels (s_wired);
+	if (s390_subchannels) {
+		tmp = NULL;
+	    if (s390_subchannels->len == 2) {
+			tmp = g_strdup_printf ("%s,%s",
+				                   (const char *) g_ptr_array_index (s390_subchannels, 0),
+				                   (const char *) g_ptr_array_index (s390_subchannels, 1));
+	    } else if (s390_subchannels->len == 3) {
+			tmp = g_strdup_printf ("%s,%s,%s",
+				                   (const char *) g_ptr_array_index (s390_subchannels, 0),
+				                   (const char *) g_ptr_array_index (s390_subchannels, 1),
+				                   (const char *) g_ptr_array_index (s390_subchannels, 2));
+		}
+		svSetValue (ifcfg, "SUBCHANNELS", tmp, FALSE);
+		g_free (tmp);
+	}
+
+	svSetValue (ifcfg, "NETTYPE", NULL, FALSE);
+	nettype = nm_setting_wired_get_s390_nettype (s_wired);
+	if (nettype)
+		svSetValue (ifcfg, "NETTYPE", nettype, FALSE);
+
+	svSetValue (ifcfg, "PORTNAME", NULL, FALSE);
+	portname = nm_setting_wired_get_s390_option_by_key (s_wired, "portname");
+	if (portname)
+		svSetValue (ifcfg, "PORTNAME", portname, FALSE);
+
+	svSetValue (ifcfg, "OPTIONS", NULL, FALSE);
+	num_opts = nm_setting_wired_get_num_s390_options (s_wired);
+	if (s390_subchannels && num_opts) {
+		str = g_string_sized_new (30);
+		for (i = 0; i < num_opts; i++) {
+			nm_setting_wired_get_s390_option (s_wired, i, &s390_key, &s390_val);
+
+			/* portname is handled separately */
+			if (!strcmp (s390_key, "portname"))
+				continue;
+
+			if (str->len)
+				g_string_append_c (str, ' ');
+			g_string_append_printf (str, "%s=%s", s390_key, s390_val);
+		}
+		if (str->len)
+			svSetValue (ifcfg, "OPTIONS", str->str, FALSE);
+		g_string_free (str, TRUE);
+	}
+
+	svSetValue (ifcfg, "TYPE", TYPE_ETHERNET, FALSE);
+
+	return TRUE;
+}
+
+static void
+write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
+{
+	guint32 n, i;
+	GString *str;
+
+	svSetValue (ifcfg, "NAME", nm_setting_connection_get_id (s_con), FALSE);
+	svSetValue (ifcfg, "UUID", nm_setting_connection_get_uuid (s_con), FALSE);
+	svSetValue (ifcfg, "ONBOOT",
+	            nm_setting_connection_get_autoconnect (s_con) ? "yes" : "no",
+	            FALSE);
+
+	/* Permissions */
+	svSetValue (ifcfg, "USERS", NULL, FALSE);
+	n = nm_setting_connection_get_num_permissions (s_con);
+	if (n > 0) {
+		str = g_string_sized_new (n * 20);
+
+		for (i = 0; i < n; i++) {
+			const char *puser = NULL;
+
+			/* Items separated by space for consistency with eg
+			 * IPV6ADDR_SECONDARIES and DOMAIN.
+			 */
+			if (str->len)
+				g_string_append_c (str, ' ');
+
+			if (nm_setting_connection_get_permission (s_con, i, NULL, &puser, NULL))
+				g_string_append (str, puser);
+		}
+		svSetValue (ifcfg, "USERS", str->str, FALSE);
+		g_string_free (str, TRUE);
+	}
+}
+
+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, 0,
+		             "Writing route file '%s' failed", filename);
+		goto error;
+	}
+
+	success = TRUE;
+
+error:
+	g_free (route_contents);
+
+	return success;
+}
+
+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;
+	gboolean fake_ip4 = FALSE;
+	const char *method = NULL;
+
+	s_ip4 = (NMSettingIP4Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP4_CONFIG);
+	if (s_ip4)
+		method = nm_setting_ip4_config_get_method (s_ip4);
+
+	/* Missing IP4 setting is assumed to be DHCP */
+	if (!method)
+		method = NM_SETTING_IP4_CONFIG_METHOD_AUTO;
+
+	if (!strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)) {
+		int result;
+
+		/* IPv4 disabled, clear IPv4 related parameters */
+		svSetValue (ifcfg, "BOOTPROTO", NULL, FALSE);
+		for (i = 0; i < 254; i++) {
+			if (i == 0) {
+				addr_key = g_strdup ("IPADDR");
+				prefix_key = g_strdup ("PREFIX");
+				gw_key = g_strdup ("GATEWAY");
+			} 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);
+			}
+
+			svSetValue (ifcfg, addr_key, NULL, FALSE);
+			svSetValue (ifcfg, prefix_key, NULL, FALSE);
+			svSetValue (ifcfg, gw_key, NULL, FALSE);
+		}
+
+		route_path = utils_get_route_path (ifcfg->fileName);
+		result = unlink (route_path);
+		g_free (route_path);
+		return TRUE;
+	}
+
+	/* Temporarily create fake IP4 setting if missing; method set to DHCP above */
+	if (!s_ip4) {
+		s_ip4 = (NMSettingIP4Config *) nm_setting_ip4_config_new ();
+		fake_ip4 = TRUE;
+	}
+
+	if (!strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO))
+		svSetValue (ifcfg, "BOOTPROTO", "dhcp", FALSE);
+	else if (!strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL))
+		svSetValue (ifcfg, "BOOTPROTO", "none", FALSE);
+	else if (!strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL))
+		svSetValue (ifcfg, "BOOTPROTO", "autoip", FALSE);
+	else if (!strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED))
+		svSetValue (ifcfg, "BOOTPROTO", "shared", FALSE);
+
+	num = nm_setting_ip4_config_get_num_addresses (s_ip4);
+	for (i = 0; i < 254; i++) {
+		char buf[INET_ADDRSTRLEN + 1];
+		NMIP4Address *addr;
+		guint32 ip;
+
+		if (i == 0) {
+			addr_key = g_strdup ("IPADDR");
+			prefix_key = g_strdup ("PREFIX");
+			gw_key = g_strdup ("GATEWAY");
+		} 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);
+		}
+
+		if (i >= num) {
+			svSetValue (ifcfg, addr_key, NULL, FALSE);
+			svSetValue (ifcfg, prefix_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);
+
+			tmp = g_strdup_printf ("%u", nm_ip4_address_get_prefix (addr));
+			svSetValue (ifcfg, prefix_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 (gw_key);
+	}
+
+	num = nm_setting_ip4_config_get_num_dns (s_ip4);
+	for (i = 0; i < 254; 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);
+
+	/* 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);
+
+	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 (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
+		svSetValue (ifcfg, "PEERDNS",
+		            nm_setting_ip4_config_get_ignore_auto_dns (s_ip4) ? "no" : "yes",
+		            FALSE);
+
+		svSetValue (ifcfg, "PEERROUTES",
+		            nm_setting_ip4_config_get_ignore_auto_routes (s_ip4) ? "no" : "yes",
+		            FALSE);
+
+		value = nm_setting_ip4_config_get_dhcp_hostname (s_ip4);
+		if (value)
+			svSetValue (ifcfg, "DHCP_HOSTNAME", value, FALSE);
+
+		value = nm_setting_ip4_config_get_dhcp_client_id (s_ip4);
+		if (value)
+			svSetValue (ifcfg, "DHCP_CLIENT_ID", value, FALSE);
+	}
+
+	svSetValue (ifcfg, "IPV4_FAILURE_FATAL",
+	            nm_setting_ip4_config_get_may_fail (s_ip4) ? "no" : "yes",
+	            FALSE);
+
+	/* Static routes - route-<name> file */
+	route_path = utils_get_route_path (ifcfg->fileName);
+	if (!route_path) {
+		g_set_error (error, IFCFG_PLUGIN_ERROR, 0,
+		             "Could not get route file path for '%s'", ifcfg->fileName);
+		goto out;
+	}
+
+	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, 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, 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;
+	}
+
+	success = TRUE;
+
+out:
+	if (fake_ip4)
+		g_object_unref (s_ip4);
+
+	return success;
+}
+
+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, 0,
+		             "Writing route6 file '%s' failed", filename);
+		goto error;
+	}
+
+	success = TRUE;
+
+error:
+	g_free (route_contents);
+	return success;
+}
+
+static gboolean
+write_ip6_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
+{
+	NMSettingIP6Config *s_ip6;
+	NMSettingIP4Config *s_ip4;
+	const char *value;
+	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;
+
+	s_ip6 = (NMSettingIP6Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP6_CONFIG);
+	if (!s_ip6) {
+		g_set_error (error, IFCFG_PLUGIN_ERROR, 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);
+		svSetValue (ifcfg, "DHCPV6C", NULL, FALSE);
+		return TRUE;
+	} else if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
+		svSetValue (ifcfg, "IPV6INIT", "yes", FALSE);
+		svSetValue (ifcfg, "IPV6_AUTOCONF", "yes", FALSE);
+		svSetValue (ifcfg, "DHCPV6C", NULL, FALSE);
+	} else if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) {
+		svSetValue (ifcfg, "IPV6INIT", "yes", FALSE);
+		svSetValue (ifcfg, "IPV6_AUTOCONF", "no", FALSE);
+		svSetValue (ifcfg, "DHCPV6C", "yes", FALSE);
+	} else if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) {
+		svSetValue (ifcfg, "IPV6INIT", "yes", FALSE);
+		svSetValue (ifcfg, "IPV6_AUTOCONF", "no", FALSE);
+		svSetValue (ifcfg, "DHCPV6C", NULL, FALSE);
+	} else if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL)) {
+		svSetValue (ifcfg, "IPV6INIT", "yes", FALSE);
+		svSetValue (ifcfg, "IPV6_AUTOCONF", "no", FALSE);
+		svSetValue (ifcfg, "DHCPV6C", NULL, FALSE);
+	} else if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_SHARED)) {
+		svSetValue (ifcfg, "IPV6INIT", "yes", FALSE);
+		svSetValue (ifcfg, "DHCPV6C", NULL, FALSE);
+		/* TODO */
+	}
+
+	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);
+
+			/* We only support gateway for the first IP address for now */
+			if (i == 0) {
+				ip = nm_ip6_address_get_gateway (addr);
+				inet_ntop (AF_INET6, (const void *) ip, buf, sizeof (buf));
+				svSetValue (ifcfg, "IPV6_DEFAULTGW", buf, FALSE);
+			}
+		}
+
+		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);
+		svSetValue (ifcfg, "IPV6_DEFAULTGW", 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);
+	}
+
+	svSetValue (ifcfg, "IPV6_FAILURE_FATAL",
+	            nm_setting_ip6_config_get_may_fail (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, 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;
+}
+
+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;
+
+	s_con = NM_SETTING_CONNECTION (nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION));
+	if (!s_con) {
+		g_set_error (error, IFCFG_PLUGIN_ERROR, 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);
+
+		/* If a file with this path already exists then we need another name.
+		 * Multiple connections can have the same ID (ie if two connections with
+		 * the same ID are visible to different users) but of course can't have
+		 * the same path.
+		 */
+		if (g_file_test (ifcfg_name, G_FILE_TEST_EXISTS)) {
+			guint32 idx = 0;
+
+			g_free (ifcfg_name);
+			while (idx++ < 500) {
+				ifcfg_name = g_strdup_printf ("%s/ifcfg-%s %u", ifcfg_dir, escaped, idx);
+				if (g_file_test (ifcfg_name, G_FILE_TEST_EXISTS) == FALSE)
+					break;
+				g_free (ifcfg_name);
+				ifcfg_name = NULL;
+			}
+		}
+		g_free (escaped);
+
+		if (ifcfg_name == NULL) {
+			g_set_error_literal (error, IFCFG_PLUGIN_ERROR, 0,
+			                     "Failed to find usable ifcfg file name");
+			return FALSE;
+		}
+
+		ifcfg = svCreateFile (ifcfg_name);
+	}
+
+	if (!ifcfg) {
+		g_set_error (error, IFCFG_PLUGIN_ERROR, 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, 0,
+		             "Missing connection type!");
+		goto out;
+	}
+
+	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, 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)) {
+		if (!write_wireless_setting (connection, ifcfg, &no_8021x, error))
+			goto out;
+	} else {
+		g_set_error (error, IFCFG_PLUGIN_ERROR, 0,
+		             "Can't write connection type '%s'", type);
+		goto out;
+	}
+
+	if (!no_8021x) {
+		if (!write_8021x_setting (connection, 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, 0644)) {
+		g_set_error (error, IFCFG_PLUGIN_ERROR, 0,
+		             "Can't write connection '%s'", ifcfg->fileName);
+		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);
+	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);
+}
+
+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);
+}
+