about summary refs log tree commit diff
path: root/src/settings/nm-settings.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/settings/nm-settings.c')
-rw-r--r--src/settings/nm-settings.c1700
1 files changed, 1700 insertions, 0 deletions
diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c
new file mode 100644
index 00000000..87fa4b6c
--- /dev/null
+++ b/src/settings/nm-settings.c
@@ -0,0 +1,1700 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager system settings service
+ *
+ * Søren Sandmann <sandmann@daimi.au.dk>
+ * Dan Williams <dcbw@redhat.com>
+ * Tambet Ingo <tambet@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2007 - 2011 Red Hat, Inc.
+ * (C) Copyright 2008 Novell, Inc.
+ */
+
+#include "config.h"
+
+#include <unistd.h>
+#include <string.h>
+#include <gmodule.h>
+#include <net/ethernet.h>
+#include <netinet/ether.h>
+#include <pwd.h>
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include <NetworkManager.h>
+#include <nm-connection.h>
+#include <nm-setting-8021x.h>
+#include <nm-setting-bluetooth.h>
+#include <nm-setting-cdma.h>
+#include <nm-setting-connection.h>
+#include <nm-setting-gsm.h>
+#include <nm-setting-ip4-config.h>
+#include <nm-setting-ip6-config.h>
+#include <nm-setting-olpc-mesh.h>
+#include <nm-setting-ppp.h>
+#include <nm-setting-pppoe.h>
+#include <nm-setting-serial.h>
+#include <nm-setting-vpn.h>
+#include <nm-setting-wired.h>
+#include <nm-setting-wireless.h>
+#include <nm-setting-wireless-security.h>
+
+#include "../nm-device-ethernet.h"
+#include "nm-dbus-glib-types.h"
+#include "nm-settings.h"
+#include "nm-settings-connection.h"
+#include "nm-polkit-helpers.h"
+#include "nm-settings-error.h"
+#include "nm-default-wired-connection.h"
+#include "nm-logging.h"
+#include "nm-dbus-manager.h"
+#include "nm-manager-auth.h"
+#include "nm-session-monitor.h"
+#include "plugins/keyfile/plugin.h"
+#include "nm-agent-manager.h"
+#include "nm-settings-utils.h"
+
+#define CONFIG_KEY_NO_AUTO_DEFAULT "no-auto-default"
+
+/* LINKER CRACKROCK */
+#define EXPORT(sym) void * __export_##sym = &sym;
+
+#include "nm-inotify-helper.h"
+EXPORT(nm_inotify_helper_get_type)
+EXPORT(nm_inotify_helper_get)
+EXPORT(nm_inotify_helper_add_watch)
+EXPORT(nm_inotify_helper_remove_watch)
+
+EXPORT(nm_settings_connection_get_type)
+EXPORT(nm_settings_connection_replace_settings)
+EXPORT(nm_settings_connection_replace_and_commit)
+/* END LINKER CRACKROCK */
+
+static void claim_connection (NMSettings *self,
+                              NMSettingsConnection *connection,
+                              gboolean do_export);
+
+static gboolean impl_settings_list_connections (NMSettings *self,
+                                                GPtrArray **connections,
+                                                GError **error);
+
+static gboolean impl_settings_get_connection_by_uuid (NMSettings *self,
+                                                      const char *uuid,
+                                                      char **out_object_path,
+                                                      GError **error);
+
+static void impl_settings_add_connection (NMSettings *self,
+                                          GHashTable *settings,
+                                          DBusGMethodInvocation *context);
+
+static void impl_settings_save_hostname (NMSettings *self,
+                                         const char *hostname,
+                                         DBusGMethodInvocation *context);
+
+#include "nm-settings-glue.h"
+
+static void unmanaged_specs_changed (NMSystemConfigInterface *config, gpointer user_data);
+
+typedef struct {
+	NMDBusManager *dbus_mgr;
+	DBusGConnection *bus;
+
+	NMAgentManager *agent_mgr;
+
+	PolkitAuthority *authority;
+	guint auth_changed_id;
+	char *config_file;
+
+	NMSessionMonitor *session_monitor;
+	GSList *auths;
+
+	GSList *plugins;
+	gboolean connections_loaded;
+	GHashTable *connections;
+	GSList *unmanaged_specs;
+} NMSettingsPrivate;
+
+G_DEFINE_TYPE (NMSettings, nm_settings, G_TYPE_OBJECT)
+
+#define NM_SETTINGS_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_SETTINGS, NMSettingsPrivate))
+
+enum {
+	PROPERTIES_CHANGED,
+	CONNECTION_ADDED,
+	CONNECTION_UPDATED,
+	CONNECTION_REMOVED,
+	CONNECTION_VISIBILITY_CHANGED,
+	CONNECTIONS_LOADED,
+
+	NEW_CONNECTION, /* exported, not used internally */
+	LAST_SIGNAL
+};
+static guint signals[LAST_SIGNAL] = { 0 };
+
+enum {
+	PROP_0,
+	PROP_UNMANAGED_SPECS,
+	PROP_HOSTNAME,
+	PROP_CAN_MODIFY,
+
+	LAST_PROP
+};
+
+static void
+load_connections (NMSettings *self)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	GSList *iter;
+
+	if (priv->connections_loaded)
+		return;
+
+	for (iter = priv->plugins; iter; iter = g_slist_next (iter)) {
+		NMSystemConfigInterface *plugin = NM_SYSTEM_CONFIG_INTERFACE (iter->data);
+		GSList *plugin_connections;
+		GSList *elt;
+
+		plugin_connections = nm_system_config_interface_get_connections (plugin);
+
+		// FIXME: ensure connections from plugins loaded with a lower priority
+		// get rejected when they conflict with connections from a higher
+		// priority plugin.
+
+		for (elt = plugin_connections; elt; elt = g_slist_next (elt))
+			claim_connection (self, NM_SETTINGS_CONNECTION (elt->data), TRUE);
+
+		g_slist_free (plugin_connections);
+	}
+
+	priv->connections_loaded = TRUE;
+
+	/* FIXME: Bad hack */
+	unmanaged_specs_changed (NULL, self);
+
+	g_signal_emit (self, signals[CONNECTIONS_LOADED], 0);
+}
+
+void
+nm_settings_for_each_connection (NMSettings *self,
+                                 NMSettingsForEachFunc for_each_func,
+                                 gpointer user_data)
+{
+	NMSettingsPrivate *priv;
+	GHashTableIter iter;
+	gpointer data;
+
+	g_return_if_fail (self != NULL);
+	g_return_if_fail (NM_IS_SETTINGS (self));
+	g_return_if_fail (for_each_func != NULL);
+	
+	priv = NM_SETTINGS_GET_PRIVATE (self);
+
+	load_connections (self);
+
+	g_hash_table_iter_init (&iter, priv->connections);
+	while (g_hash_table_iter_next (&iter, NULL, &data))
+		for_each_func (self, NM_SETTINGS_CONNECTION (data), user_data);
+}
+
+static gboolean
+impl_settings_list_connections (NMSettings *self,
+                                GPtrArray **connections,
+                                GError **error)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	GHashTableIter iter;
+	gpointer key;
+
+	load_connections (self);
+
+	*connections = g_ptr_array_sized_new (g_hash_table_size (priv->connections) + 1);
+	g_hash_table_iter_init (&iter, priv->connections);
+	while (g_hash_table_iter_next (&iter, &key, NULL))
+		g_ptr_array_add (*connections, g_strdup ((const char *) key));
+	return TRUE;
+}
+
+static gboolean
+impl_settings_get_connection_by_uuid (NMSettings *self,
+                                      const char *uuid,
+                                      char **out_object_path,
+                                      GError **error)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	GHashTableIter iter;
+	NMConnection *candidate = NULL;
+	gboolean found = FALSE;
+
+	load_connections (self);
+
+	g_hash_table_iter_init (&iter, priv->connections);
+	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &candidate)) {
+		if (g_strcmp0 (uuid, nm_connection_get_uuid (candidate)) == 0) {
+			*out_object_path = g_strdup (nm_connection_get_path (candidate));
+			found = TRUE;
+			break;
+		}
+	}
+
+	if (!found) {
+		g_set_error_literal (error,
+		                     NM_SETTINGS_ERROR,
+		                     NM_SETTINGS_ERROR_INVALID_CONNECTION,
+		                     "No connection with the UUID was found.");
+	}
+
+	return found;
+}
+
+static int
+connection_sort (gconstpointer pa, gconstpointer pb)
+{
+	NMConnection *a = NM_CONNECTION (pa);
+	NMSettingConnection *con_a;
+	NMConnection *b = NM_CONNECTION (pb);
+	NMSettingConnection *con_b;
+	guint64 ts_a, ts_b;
+
+	con_a = (NMSettingConnection *) nm_connection_get_setting (a, NM_TYPE_SETTING_CONNECTION);
+	g_assert (con_a);
+	con_b = (NMSettingConnection *) nm_connection_get_setting (b, NM_TYPE_SETTING_CONNECTION);
+	g_assert (con_b);
+
+	if (nm_setting_connection_get_autoconnect (con_a) != nm_setting_connection_get_autoconnect (con_b)) {
+		if (nm_setting_connection_get_autoconnect (con_a))
+			return -1;
+		return 1;
+	}
+
+	ts_a = nm_settings_connection_get_timestamp (NM_SETTINGS_CONNECTION (pa));
+	ts_b = nm_settings_connection_get_timestamp (NM_SETTINGS_CONNECTION (pb));
+	if (ts_a > ts_b)
+		return -1;
+	else if (ts_a == ts_b)
+		return 0;
+	return 1;
+}
+
+/* Returns a list of NMSettingsConnections.  Caller must free the list with
+ * g_slist_free().
+ */
+GSList *
+nm_settings_get_connections (NMSettings *self)
+{
+	GHashTableIter iter;
+	gpointer data = NULL;
+	GSList *list = NULL;
+
+	g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
+
+	g_hash_table_iter_init (&iter, NM_SETTINGS_GET_PRIVATE (self)->connections);
+	while (g_hash_table_iter_next (&iter, NULL, &data))
+		list = g_slist_insert_sorted (list, data, connection_sort);
+	return list;
+}
+
+NMSettingsConnection *
+nm_settings_get_connection_by_path (NMSettings *self, const char *path)
+{
+	NMSettingsPrivate *priv;
+
+	g_return_val_if_fail (self != NULL, NULL);
+	g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
+	g_return_val_if_fail (path != NULL, NULL);
+
+	priv = NM_SETTINGS_GET_PRIVATE (self);
+
+	load_connections (self);
+
+	return (NMSettingsConnection *) g_hash_table_lookup (priv->connections, path);
+}
+
+static void
+clear_unmanaged_specs (NMSettings *self)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+
+	g_slist_foreach (priv->unmanaged_specs, (GFunc) g_free, NULL);
+	g_slist_free (priv->unmanaged_specs);
+	priv->unmanaged_specs = NULL;
+}
+
+static char*
+uscore_to_wincaps (const char *uscore)
+{
+	const char *p;
+	GString *str;
+	gboolean last_was_uscore;
+
+	last_was_uscore = TRUE;
+  
+	str = g_string_new (NULL);
+	p = uscore;
+	while (p && *p) {
+		if (*p == '-' || *p == '_')
+			last_was_uscore = TRUE;
+		else {
+			if (last_was_uscore) {
+				g_string_append_c (str, g_ascii_toupper (*p));
+				last_was_uscore = FALSE;
+			} else
+				g_string_append_c (str, *p);
+		}
+		++p;
+	}
+
+	return g_string_free (str, FALSE);
+}
+
+static void
+notify (GObject *object, GParamSpec *pspec)
+{
+	GValue *value;
+	GHashTable *hash;
+
+	value = g_slice_new0 (GValue);
+	hash = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL);
+
+	g_value_init (value, pspec->value_type);
+	g_object_get_property (object, pspec->name, value);
+	g_hash_table_insert (hash, uscore_to_wincaps (pspec->name), value);
+	g_signal_emit (object, signals[PROPERTIES_CHANGED], 0, hash);
+	g_hash_table_destroy (hash);
+	g_value_unset (value);
+	g_slice_free (GValue, value);
+}
+
+const GSList *
+nm_settings_get_unmanaged_specs (NMSettings *self)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+
+	load_connections (self);
+	return priv->unmanaged_specs;
+}
+
+static NMSystemConfigInterface *
+get_plugin (NMSettings *self, guint32 capability)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	GSList *iter;
+
+	g_return_val_if_fail (self != NULL, NULL);
+
+	/* Do any of the plugins support setting the hostname? */
+	for (iter = priv->plugins; iter; iter = iter->next) {
+		NMSystemConfigInterfaceCapabilities caps = NM_SYSTEM_CONFIG_INTERFACE_CAP_NONE;
+
+		g_object_get (G_OBJECT (iter->data), NM_SYSTEM_CONFIG_INTERFACE_CAPABILITIES, &caps, NULL);
+		if (caps & capability)
+			return NM_SYSTEM_CONFIG_INTERFACE (iter->data);
+	}
+
+	return NULL;
+}
+
+/* Returns an allocated string which the caller owns and must eventually free */
+char *
+nm_settings_get_hostname (NMSettings *self)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	GSList *iter;
+	char *hostname = NULL;
+
+	/* Hostname returned is the hostname returned from the first plugin
+	 * that provides one.
+	 */
+	for (iter = priv->plugins; iter; iter = iter->next) {
+		NMSystemConfigInterfaceCapabilities caps = NM_SYSTEM_CONFIG_INTERFACE_CAP_NONE;
+
+		g_object_get (G_OBJECT (iter->data), NM_SYSTEM_CONFIG_INTERFACE_CAPABILITIES, &caps, NULL);
+		if (caps & NM_SYSTEM_CONFIG_INTERFACE_CAP_MODIFY_HOSTNAME) {
+			g_object_get (G_OBJECT (iter->data), NM_SYSTEM_CONFIG_INTERFACE_HOSTNAME, &hostname, NULL);
+			if (hostname && strlen (hostname))
+				return hostname;
+			g_free (hostname);
+		}
+	}
+
+	return NULL;
+}
+
+static void
+plugin_connection_added (NMSystemConfigInterface *config,
+                         NMSettingsConnection *connection,
+                         gpointer user_data)
+{
+	claim_connection (NM_SETTINGS (user_data), connection, TRUE);
+}
+
+static gboolean
+find_unmanaged_device (NMSettings *self, const char *needle)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	GSList *iter;
+
+	for (iter = priv->unmanaged_specs; iter; iter = g_slist_next (iter)) {
+		if (!strcmp ((const char *) iter->data, needle))
+			return TRUE;
+	}
+	return FALSE;
+}
+
+static void
+unmanaged_specs_changed (NMSystemConfigInterface *config,
+                         gpointer user_data)
+{
+	NMSettings *self = NM_SETTINGS (user_data);
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	GSList *iter;
+
+	clear_unmanaged_specs (self);
+
+	/* Ask all the plugins for their unmanaged specs */
+	for (iter = priv->plugins; iter; iter = g_slist_next (iter)) {
+		GSList *specs, *specs_iter;
+
+		specs = nm_system_config_interface_get_unmanaged_specs (NM_SYSTEM_CONFIG_INTERFACE (iter->data));
+		for (specs_iter = specs; specs_iter; specs_iter = specs_iter->next) {
+			if (!find_unmanaged_device (self, (const char *) specs_iter->data)) {
+				priv->unmanaged_specs = g_slist_prepend (priv->unmanaged_specs, specs_iter->data);
+			} else
+				g_free (specs_iter->data);
+		}
+
+		g_slist_free (specs);
+	}
+
+	g_object_notify (G_OBJECT (self), NM_SETTINGS_UNMANAGED_SPECS);
+}
+
+static void
+hostname_changed (NMSystemConfigInterface *config,
+                  GParamSpec *pspec,
+                  gpointer user_data)
+{
+	g_object_notify (G_OBJECT (user_data), NM_SETTINGS_HOSTNAME);
+}
+
+static void
+add_plugin (NMSettings *self, NMSystemConfigInterface *plugin)
+{
+	NMSettingsPrivate *priv;
+	char *pname = NULL;
+	char *pinfo = NULL;
+
+	g_return_if_fail (NM_IS_SETTINGS (self));
+	g_return_if_fail (NM_IS_SYSTEM_CONFIG_INTERFACE (plugin));
+
+	priv = NM_SETTINGS_GET_PRIVATE (self);
+
+	priv->plugins = g_slist_append (priv->plugins, g_object_ref (plugin));
+
+	g_signal_connect (plugin, NM_SYSTEM_CONFIG_INTERFACE_CONNECTION_ADDED,
+	                  G_CALLBACK (plugin_connection_added), self);
+	g_signal_connect (plugin, "notify::hostname", G_CALLBACK (hostname_changed), self);
+
+	nm_system_config_interface_init (plugin, NULL);
+
+	g_object_get (G_OBJECT (plugin),
+	              NM_SYSTEM_CONFIG_INTERFACE_NAME, &pname,
+	              NM_SYSTEM_CONFIG_INTERFACE_INFO, &pinfo,
+	              NULL);
+
+	g_signal_connect (plugin, NM_SYSTEM_CONFIG_INTERFACE_UNMANAGED_SPECS_CHANGED,
+	                  G_CALLBACK (unmanaged_specs_changed), self);
+
+	nm_log_info (LOGD_SETTINGS, "Loaded plugin %s: %s", pname, pinfo);
+	g_free (pname);
+	g_free (pinfo);
+}
+
+static GObject *
+find_plugin (GSList *list, const char *pname)
+{
+	GSList *iter;
+	GObject *obj = NULL;
+
+	g_return_val_if_fail (pname != NULL, NULL);
+
+	for (iter = list; iter && !obj; iter = g_slist_next (iter)) {
+		NMSystemConfigInterface *plugin = NM_SYSTEM_CONFIG_INTERFACE (iter->data);
+		char *list_pname = NULL;
+
+		g_object_get (G_OBJECT (plugin),
+		              NM_SYSTEM_CONFIG_INTERFACE_NAME,
+		              &list_pname,
+		              NULL);
+		if (list_pname && !strcmp (pname, list_pname))
+			obj = G_OBJECT (plugin);
+
+		g_free (list_pname);
+	}
+
+	return obj;
+}
+
+static gboolean
+load_plugins (NMSettings *self, const char *plugins, GError **error)
+{
+	GSList *list = NULL;
+	char **plist;
+	char **iter;
+	gboolean success = TRUE;
+
+	plist = g_strsplit (plugins, ",", 0);
+	if (!plist)
+		return FALSE;
+
+	for (iter = plist; *iter; iter++) {
+		GModule *plugin;
+		char *full_name, *path;
+		const char *pname = g_strstrip (*iter);
+		GObject *obj;
+		GObject * (*factory_func) (void);
+
+		/* keyfile plugin built in now */
+		if (!strcmp (pname, "keyfile"))
+			continue;
+
+		/* ifcfg-fedora was renamed ifcfg-rh; handle old configs here */
+		if (!strcmp (pname, "ifcfg-fedora"))
+			pname = "ifcfg-rh";
+
+		obj = find_plugin (list, pname);
+		if (obj)
+			continue;
+
+		full_name = g_strdup_printf ("nm-settings-plugin-%s", pname);
+		path = g_module_build_path (PLUGINDIR, full_name);
+
+		plugin = g_module_open (path, G_MODULE_BIND_LOCAL);
+		if (!plugin) {
+			g_set_error (error, 0, 0,
+			             "Could not load plugin '%s': %s",
+			             pname, g_module_error ());
+			g_free (full_name);
+			g_free (path);
+			success = FALSE;
+			break;
+		}
+
+		g_free (full_name);
+		g_free (path);
+
+		if (!g_module_symbol (plugin, "nm_system_config_factory", (gpointer) (&factory_func))) {
+			g_set_error (error, 0, 0,
+			             "Could not find plugin '%s' factory function.",
+			             pname);
+			success = FALSE;
+			break;
+		}
+
+		obj = (*factory_func) ();
+		if (!obj || !NM_IS_SYSTEM_CONFIG_INTERFACE (obj)) {
+			g_set_error (error, 0, 0,
+			             "Plugin '%s' returned invalid system config object.",
+			             pname);
+			success = FALSE;
+			break;
+		}
+
+		g_module_make_resident (plugin);
+		g_object_weak_ref (obj, (GWeakNotify) g_module_close, plugin);
+		add_plugin (self, NM_SYSTEM_CONFIG_INTERFACE (obj));
+		list = g_slist_append (list, obj);
+	}
+
+	g_strfreev (plist);
+
+	g_slist_foreach (list, (GFunc) g_object_unref, NULL);
+	g_slist_free (list);
+
+	return success;
+}
+
+#define REMOVED_ID_TAG "removed-id-tag"
+#define UPDATED_ID_TAG "updated-id-tag"
+#define VISIBLE_ID_TAG "visible-id-tag"
+#define UNREG_ID_TAG "unreg-id-tag"
+
+static void
+connection_removed (NMSettingsConnection *obj, gpointer user_data)
+{
+	GObject *connection = G_OBJECT (obj);
+	guint id;
+	g_object_ref (connection);
+
+	/* Disconnect signal handlers, as plugins might still keep references
+	 * to the connection (and thus the signal handlers would still be live)
+	 * even after NMSettings has dropped all its references.
+	 */
+
+	id = GPOINTER_TO_UINT (g_object_get_data (connection, REMOVED_ID_TAG));
+	if (id)
+		g_signal_handler_disconnect (connection, id);
+
+	id = GPOINTER_TO_UINT (g_object_get_data (connection, UPDATED_ID_TAG));
+	if (id)
+		g_signal_handler_disconnect (connection, id);
+
+	id = GPOINTER_TO_UINT (g_object_get_data (connection, VISIBLE_ID_TAG));
+	if (id)
+		g_signal_handler_disconnect (connection, id);
+
+	/* Forget about the connection internall */
+	g_hash_table_remove (NM_SETTINGS_GET_PRIVATE (user_data)->connections,
+	                     (gpointer) nm_connection_get_path (NM_CONNECTION (connection)));
+
+	/* Re-emit for listeners like NMPolicy */
+	g_signal_emit (NM_SETTINGS (user_data), signals[CONNECTION_REMOVED], 0, connection);
+
+	g_object_unref (connection);
+}
+
+static void
+connection_unregister (NMSettingsConnection *obj, gpointer user_data)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (user_data);
+	GObject *connection = G_OBJECT (obj);
+	guint id;
+
+	/* Make sure it's unregistered from the bus now that's removed */
+	dbus_g_connection_unregister_g_object (priv->bus, connection);
+
+	id = GPOINTER_TO_UINT (g_object_get_data (connection, UNREG_ID_TAG));
+	if (id)
+		g_signal_handler_disconnect (connection, id);
+}
+
+static void
+connection_updated (NMSettingsConnection *connection, gpointer user_data)
+{
+	/* Re-emit for listeners like NMPolicy */
+	g_signal_emit (NM_SETTINGS (user_data),
+	               signals[CONNECTION_UPDATED],
+	               0,
+	               connection);
+}
+
+static void
+connection_visibility_changed (NMSettingsConnection *connection,
+                               GParamSpec *pspec,
+                               gpointer user_data)
+{
+	/* Re-emit for listeners like NMPolicy */
+	g_signal_emit (NM_SETTINGS (user_data),
+	               signals[CONNECTION_VISIBILITY_CHANGED],
+	               0,
+	               connection);
+}
+
+static void
+claim_connection (NMSettings *self,
+                  NMSettingsConnection *connection,
+                  gboolean do_export)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	static guint32 ec_counter = 0;
+	GError *error = NULL;
+	GHashTableIter iter;
+	gpointer data;
+	char *path;
+	guint id;
+
+	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (connection));
+	g_return_if_fail (nm_connection_get_path (NM_CONNECTION (connection)) == NULL);
+
+	g_hash_table_iter_init (&iter, priv->connections);
+	while (g_hash_table_iter_next (&iter, NULL, &data)) {
+		/* prevent duplicates */
+		if (data == connection)
+			return;
+	}
+
+	if (!nm_connection_verify (NM_CONNECTION (connection), &error)) {
+		nm_log_warn (LOGD_SETTINGS, "plugin provided invalid connection: '%s' / '%s' invalid: %d",
+		             g_type_name (nm_connection_lookup_setting_type_by_quark (error->domain)),
+		             error->message, error->code);
+		g_error_free (error);
+		return;
+	}
+
+	/* Read timestamp from look-aside file and put it into the connection's data */
+	nm_settings_connection_read_and_fill_timestamp (connection);
+
+	/* Ensure it's initial visibility is up-to-date */
+	nm_settings_connection_recheck_visibility (connection);
+
+	id = g_signal_connect (connection, NM_SETTINGS_CONNECTION_REMOVED,
+	                       G_CALLBACK (connection_removed),
+	                       self);
+	g_object_set_data (G_OBJECT (connection), REMOVED_ID_TAG, GUINT_TO_POINTER (id));
+
+	id = g_signal_connect (connection, "unregister",
+	                       G_CALLBACK (connection_unregister),
+	                       self);
+	g_object_set_data (G_OBJECT (connection), UNREG_ID_TAG, GUINT_TO_POINTER (id));
+
+	id = g_signal_connect (connection, NM_SETTINGS_CONNECTION_UPDATED,
+	                       G_CALLBACK (connection_updated),
+	                       self);
+	g_object_set_data (G_OBJECT (connection), UPDATED_ID_TAG, GUINT_TO_POINTER (id));
+
+	id = g_signal_connect (connection, "notify::" NM_SETTINGS_CONNECTION_VISIBLE,
+	                       G_CALLBACK (connection_visibility_changed),
+	                       self);
+	g_object_set_data (G_OBJECT (connection), VISIBLE_ID_TAG, GUINT_TO_POINTER (id));
+
+	/* Export the connection over D-Bus */
+	g_warn_if_fail (nm_connection_get_path (NM_CONNECTION (connection)) == NULL);
+	path = g_strdup_printf ("%s/%u", NM_DBUS_PATH_SETTINGS, ec_counter++);
+	nm_connection_set_path (NM_CONNECTION (connection), path);
+	dbus_g_connection_register_g_object (priv->bus, path, G_OBJECT (connection));
+	g_free (path);
+
+	g_hash_table_insert (priv->connections,
+	                     (gpointer) nm_connection_get_path (NM_CONNECTION (connection)),
+	                     g_object_ref (connection));
+
+	/* Only emit the individual connection-added signal after connections
+	 * have been initially loaded.  While getting the first list of connections
+	 * we suppress it, then send the connections-loaded signal after we're all
+	 * done to minimize processing.
+	 */
+	if (priv->connections_loaded) {
+		/* Internal added signal */
+		g_signal_emit (self, signals[CONNECTION_ADDED], 0, connection);
+
+		/* Exported D-Bus signal */
+		g_signal_emit (self, signals[NEW_CONNECTION], 0, connection);
+	}
+}
+
+// TODO it seems that this is only ever used to remove a
+// NMDefaultWiredConnection, and it probably needs to stay that way. So this
+// *needs* a better name!
+static void
+remove_default_wired_connection (NMSettings *self,
+                                 NMSettingsConnection *connection,
+                                 gboolean do_signal)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	const char *path = nm_connection_get_path (NM_CONNECTION (connection));
+
+	if (g_hash_table_lookup (priv->connections, path)) {
+		g_signal_emit_by_name (G_OBJECT (connection), NM_SETTINGS_CONNECTION_REMOVED);
+		g_hash_table_remove (priv->connections, path);
+	}
+}
+
+static NMSettingsConnection *
+add_new_connection (NMSettings *self,
+                    NMConnection *connection,
+                    GError **error)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	GSList *iter;
+	NMSettingsConnection *added = NULL;
+	GHashTableIter citer;
+	NMConnection *candidate = NULL;
+
+	/* Make sure a connection with this UUID doesn't already exist */
+	g_hash_table_iter_init (&citer, priv->connections);
+	while (g_hash_table_iter_next (&citer, NULL, (gpointer *) &candidate)) {
+		if (g_strcmp0 (nm_connection_get_uuid (connection),
+		               nm_connection_get_uuid (candidate)) == 0) {
+			g_set_error_literal (error,
+			                     NM_SETTINGS_ERROR,
+			                     NM_SETTINGS_ERROR_UUID_EXISTS,
+			                     "A connection with this UUID already exists.");
+			return NULL;
+		}
+	}
+
+	/* 1) plugin writes the NMConnection to disk
+	 * 2) plugin creates a new NMSettingsConnection subclass with the settings
+	 *     from the NMConnection and returns it to the settings service
+	 * 3) settings service exports the new NMSettingsConnection subclass
+	 * 4) plugin notices that something on the filesystem has changed
+	 * 5) plugin reads the changes and ignores them because they will
+	 *     contain the same data as the connection it already knows about
+	 */
+	for (iter = priv->plugins; iter; iter = g_slist_next (iter)) {
+		NMSystemConfigInterface *plugin = NM_SYSTEM_CONFIG_INTERFACE (iter->data);
+		GError *add_error = NULL;
+
+		g_clear_error (error);
+		added = nm_system_config_interface_add_connection (plugin, connection, &add_error);
+		if (added) {
+			claim_connection (self, added, TRUE);
+			return added;
+		}
+		g_propagate_error (error, add_error);
+	}
+	return NULL;
+}
+
+static void
+pk_add_cb (NMAuthChain *chain,
+           GError *chain_error,
+           DBusGMethodInvocation *context,
+           gpointer user_data)
+{
+	NMSettings *self = NM_SETTINGS (user_data);
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	NMAuthCallResult result;
+	GError *error = NULL, *add_error = NULL;
+	NMConnection *connection;
+	NMSettingsConnection *added = NULL;
+	NMSettingsAddCallback callback;
+	gpointer callback_data;
+	const char *perm;
+
+	priv->auths = g_slist_remove (priv->auths, chain);
+
+	if (chain_error) {
+		error = g_error_new (NM_SETTINGS_ERROR,
+		                     NM_SETTINGS_ERROR_GENERAL,
+		                     "Error checking authorization: %s",
+		                     chain_error->message ? chain_error->message : "(unknown)");
+		goto done;
+	}
+
+	perm = nm_auth_chain_get_data (chain, "perm");
+	g_assert (perm);
+	result = nm_auth_chain_get_result (chain, perm);
+
+	/* Caller didn't successfully authenticate */
+	if (result != NM_AUTH_CALL_RESULT_YES) {
+		error = g_error_new_literal (NM_SETTINGS_ERROR,
+		                             NM_SETTINGS_ERROR_NOT_PRIVILEGED,
+		                             "Insufficient privileges.");
+		goto done;
+	}
+
+	connection = nm_auth_chain_get_data (chain, "connection");
+	g_assert (connection);
+	added = add_new_connection (self, connection, &add_error);
+	if (!added) {
+		error = g_error_new (NM_SETTINGS_ERROR,
+		                     NM_SETTINGS_ERROR_ADD_FAILED,
+		                     "Saving connection failed: (%d) %s",
+		                     add_error ? add_error->code : -1,
+		                     (add_error && add_error->message) ? add_error->message : "(unknown)");
+		g_error_free (add_error);
+	}
+
+done:
+	callback = nm_auth_chain_get_data (chain, "callback");
+	callback_data = nm_auth_chain_get_data (chain, "callback-data");
+
+	callback (self, added, error, context, callback_data);
+
+	g_clear_error (&error);
+	nm_auth_chain_unref (chain);
+}
+
+static void
+add_cb (NMSettings *self,
+        NMSettingsConnection *connection,
+        GError *error,
+        DBusGMethodInvocation *context,
+        gpointer user_data)
+{
+	if (error)
+		dbus_g_method_return_error (context, error);
+	else
+		dbus_g_method_return (context, nm_connection_get_path (NM_CONNECTION (connection)));
+}
+
+void
+nm_settings_add_connection (NMSettings *self,
+                            NMConnection *connection,
+                            DBusGMethodInvocation *context,
+                            NMSettingsAddCallback callback,
+                            gpointer user_data)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	NMSettingConnection *s_con;
+	NMAuthChain *chain;
+	GError *error = NULL, *tmp_error = NULL;
+	gulong caller_uid = G_MAXULONG;
+	char *error_desc = NULL;
+	const char *perm;
+
+	/* Connection must be valid, of course */
+	if (!nm_connection_verify (connection, &tmp_error)) {
+		error = g_error_new (NM_SETTINGS_ERROR,
+		                     NM_SETTINGS_ERROR_INVALID_CONNECTION,
+		                     "The connection was invalid: %s",
+		                     tmp_error ? tmp_error->message : "(unknown)");
+		g_error_free (tmp_error);
+		callback (self, NULL, error, context, user_data);
+		g_error_free (error);
+		return;
+	}
+
+	/* Do any of the plugins support adding? */
+	if (!get_plugin (self, NM_SYSTEM_CONFIG_INTERFACE_CAP_MODIFY_CONNECTIONS)) {
+		error = g_error_new_literal (NM_SETTINGS_ERROR,
+		                             NM_SETTINGS_ERROR_ADD_NOT_SUPPORTED,
+		                             "None of the registered plugins support add.");
+		callback (self, NULL, error, context, user_data);
+		g_error_free (error);
+		return;
+	}
+
+	/* Get the caller's UID */
+	if (!nm_auth_get_caller_uid (context, priv->dbus_mgr, &caller_uid, &error_desc)) {
+		error = g_error_new (NM_SETTINGS_ERROR,
+		                     NM_SETTINGS_ERROR_NOT_PRIVILEGED,
+		                     "Unable to determine UID of request: %s.",
+		                     error_desc ? error_desc : "(unknown)");
+		g_free (error_desc);
+		callback (self, NULL, error, context, user_data);
+		g_error_free (error);
+		return;
+	}
+
+	/* Ensure the caller's username exists in the connection's permissions,
+	 * or that the permissions is empty (ie, visible by everyone).
+	 */
+	if (0 != caller_uid) {
+		if (!nm_auth_uid_in_acl (connection, priv->session_monitor, caller_uid, &error_desc)) {
+			error = g_error_new_literal (NM_SETTINGS_ERROR,
+			                             NM_SETTINGS_ERROR_NOT_PRIVILEGED,
+			                             error_desc);
+			g_free (error_desc);
+			callback (self, NULL, error, context, user_data);
+			g_error_free (error);
+			return;
+		}
+
+		/* Caller is allowed to add this connection */
+	}
+
+	/* If the caller is the only user in the connection's permissions, then
+	 * we use the 'modify.own' permission instead of 'modify.system'.  If the
+	 * request affects more than just the caller, require 'modify.system'.
+	 */
+	s_con = (NMSettingConnection *) nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION);
+	g_assert (s_con);
+	if (nm_setting_connection_get_num_permissions (s_con) == 1)
+		perm = NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN;
+	else
+		perm = NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM;
+
+	/* Otherwise validate the user request */
+	chain = nm_auth_chain_new (priv->authority, context, NULL, pk_add_cb, self);
+	g_assert (chain);
+	priv->auths = g_slist_append (priv->auths, chain);
+	nm_auth_chain_add_call (chain, perm, TRUE);
+	nm_auth_chain_set_data (chain, "perm", (gpointer) perm, NULL);
+	nm_auth_chain_set_data (chain, "connection", g_object_ref (connection), g_object_unref);
+	nm_auth_chain_set_data (chain, "callback", callback, NULL);
+	nm_auth_chain_set_data (chain, "callback-data", user_data, NULL);
+}
+
+static void
+impl_settings_add_connection (NMSettings *self,
+                              GHashTable *settings,
+                              DBusGMethodInvocation *context)
+{
+	NMConnection *connection;
+	GError *error = NULL;
+
+	connection = nm_connection_new_from_hash (settings, &error);
+	if (connection) {
+		nm_settings_add_connection (self, connection, context, add_cb, NULL);
+		g_object_unref (connection);
+	} else {
+		g_assert (error);
+		dbus_g_method_return_error (context, error);
+		g_error_free (error);
+	}
+}
+
+static void
+pk_hostname_cb (NMAuthChain *chain,
+                GError *chain_error,
+                DBusGMethodInvocation *context,
+                gpointer user_data)
+{
+	NMSettings *self = NM_SETTINGS (user_data);
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	NMAuthCallResult result;
+	gboolean success = FALSE;
+	GError *error = NULL;
+	GSList *iter;
+	const char *hostname;
+
+	priv->auths = g_slist_remove (priv->auths, chain);
+
+	/* If our NMSettingsConnection is already gone, do nothing */
+	if (chain_error) {
+		error = g_error_new (NM_SETTINGS_ERROR,
+		                     NM_SETTINGS_ERROR_GENERAL,
+		                     "Error checking authorization: %s",
+		                     chain_error->message ? chain_error->message : "(unknown)");
+		goto done;
+	}
+
+	result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME);
+
+	/* Caller didn't successfully authenticate */
+	if (result != NM_AUTH_CALL_RESULT_YES) {
+		error = g_error_new_literal (NM_SETTINGS_ERROR,
+		                             NM_SETTINGS_ERROR_NOT_PRIVILEGED,
+		                             "Insufficient privileges.");
+		goto done;
+	}
+
+	/* Set the hostname in all plugins */
+	hostname = nm_auth_chain_get_data (chain, "hostname");
+	for (iter = priv->plugins; iter; iter = iter->next) {
+		NMSystemConfigInterfaceCapabilities caps = NM_SYSTEM_CONFIG_INTERFACE_CAP_NONE;
+
+		g_object_get (G_OBJECT (iter->data), NM_SYSTEM_CONFIG_INTERFACE_CAPABILITIES, &caps, NULL);
+		if (caps & NM_SYSTEM_CONFIG_INTERFACE_CAP_MODIFY_HOSTNAME) {
+			g_object_set (G_OBJECT (iter->data), NM_SYSTEM_CONFIG_INTERFACE_HOSTNAME, hostname, NULL);
+			success = TRUE;
+		}
+	}
+
+	if (!success) {
+		error = g_error_new_literal (NM_SETTINGS_ERROR,
+		                             NM_SETTINGS_ERROR_SAVE_HOSTNAME_FAILED,
+		                             "Saving the hostname failed.");
+	}
+
+done:
+	if (error)
+		dbus_g_method_return_error (context, error);
+	else
+		dbus_g_method_return (context);
+
+	g_clear_error (&error);
+	nm_auth_chain_unref (chain);
+}
+
+static void
+impl_settings_save_hostname (NMSettings *self,
+                             const char *hostname,
+                             DBusGMethodInvocation *context)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	NMAuthChain *chain;
+	GError *error = NULL;
+
+	/* Do any of the plugins support setting the hostname? */
+	if (!get_plugin (self, NM_SYSTEM_CONFIG_INTERFACE_CAP_MODIFY_HOSTNAME)) {
+		error = g_error_new_literal (NM_SETTINGS_ERROR,
+		                             NM_SETTINGS_ERROR_SAVE_HOSTNAME_NOT_SUPPORTED,
+		                             "None of the registered plugins support setting the hostname.");
+		dbus_g_method_return_error (context, error);
+		g_error_free (error);
+		return;
+	}
+
+	/* Otherwise validate the user request */
+	chain = nm_auth_chain_new (priv->authority, context, NULL, pk_hostname_cb, self);
+	g_assert (chain);
+	priv->auths = g_slist_append (priv->auths, chain);
+	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME, TRUE);
+	nm_auth_chain_set_data (chain, "hostname", g_strdup (hostname), g_free);
+}
+
+static gboolean
+have_connection_for_device (NMSettings *self, GByteArray *mac)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	GHashTableIter iter;
+	gpointer data;
+	NMSettingConnection *s_con;
+	NMSettingWired *s_wired;
+	const GByteArray *setting_mac;
+	gboolean ret = FALSE;
+
+	g_return_val_if_fail (NM_IS_SETTINGS (self), FALSE);
+	g_return_val_if_fail (mac != NULL, FALSE);
+
+	/* Find a wired connection locked to the given MAC address, if any */
+	g_hash_table_iter_init (&iter, priv->connections);
+	while (g_hash_table_iter_next (&iter, NULL, &data)) {
+		NMConnection *connection = NM_CONNECTION (data);
+		const char *ctype;
+
+		s_con = nm_connection_get_setting_connection (connection);
+		ctype = nm_setting_connection_get_connection_type (s_con);
+
+		if (   strcmp (ctype, NM_SETTING_WIRED_SETTING_NAME)
+		    && strcmp (ctype, NM_SETTING_PPPOE_SETTING_NAME))
+			continue;
+
+		s_wired = nm_connection_get_setting_wired (connection);
+
+		/* No wired setting; therefore the PPPoE connection applies to any device */
+		if (!s_wired && !strcmp (ctype, NM_SETTING_PPPOE_SETTING_NAME)) {
+			ret = TRUE;
+			break;
+		}
+
+		setting_mac = nm_setting_wired_get_mac_address (s_wired);
+		if (setting_mac) {
+			/* A connection mac-locked to this device */
+			if (!memcmp (setting_mac->data, mac->data, ETH_ALEN)) {
+				ret = TRUE;
+				break;
+			}
+		} else {
+			/* A connection that applies to any wired device */
+			ret = TRUE;
+			break;
+		}
+	}
+
+	return ret;
+}
+
+/* Search through the list of blacklisted MAC addresses in the config file. */
+static gboolean
+is_mac_auto_wired_blacklisted (NMSettings *self, const GByteArray *mac)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	GKeyFile *config;
+	char **list, **iter;
+	gboolean found = FALSE;
+
+	g_return_val_if_fail (mac != NULL, FALSE);
+
+	if (!priv->config_file)
+		return FALSE;
+
+	config = g_key_file_new ();
+	if (!config) {
+		nm_log_warn (LOGD_SETTINGS, "not enough memory to load config file.");
+		return FALSE;
+	}
+
+	g_key_file_set_list_separator (config, ',');
+	if (!g_key_file_load_from_file (config, priv->config_file, G_KEY_FILE_NONE, NULL))
+		goto out;
+
+	list = g_key_file_get_string_list (config, "main", CONFIG_KEY_NO_AUTO_DEFAULT, NULL, NULL);
+	for (iter = list; iter && *iter; iter++) {
+		struct ether_addr *candidate;
+
+		if (strcmp(g_strstrip(*iter), "*") == 0) {
+			found = TRUE;
+			break;
+		}
+
+		candidate = ether_aton (*iter);
+		if (candidate && !memcmp (mac->data, candidate->ether_addr_octet, ETH_ALEN)) {
+			found = TRUE;
+			break;
+		}
+	}
+
+	if (list)
+		g_strfreev (list);
+
+out:
+	g_key_file_free (config);
+	return found;
+}
+
+#define DEFAULT_WIRED_TAG "default-wired"
+
+static void
+default_wired_deleted (NMDefaultWiredConnection *wired,
+                       const GByteArray *mac,
+                       NMSettings *self)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	NMSettingConnection *s_con;
+	char *tmp;
+	GKeyFile *config;
+	char **list, **iter, **updated;
+	gboolean found = FALSE;
+	gsize len = 0, i;
+	char *data;
+
+	/* If there was no config file specified, there's nothing to do */
+	if (!priv->config_file)
+		goto cleanup;
+
+	/* When the default wired connection is removed (either deleted or saved
+	 * to a new persistent connection by a plugin), write the MAC address of
+	 * the wired device to the config file and don't create a new default wired
+	 * connection for that device again.
+	 */
+
+	s_con = (NMSettingConnection *) nm_connection_get_setting (NM_CONNECTION (wired),
+	                                                           NM_TYPE_SETTING_CONNECTION);
+	g_assert (s_con);
+
+	/* Ignore removals of read-only connections, since they couldn't have
+	 * been removed by the user.
+	 */
+	if (nm_setting_connection_get_read_only (s_con))
+		goto cleanup;
+
+	config = g_key_file_new ();
+	if (!config)
+		goto cleanup;
+
+	g_key_file_set_list_separator (config, ',');
+	g_key_file_load_from_file (config, priv->config_file, G_KEY_FILE_KEEP_COMMENTS, NULL);
+
+	list = g_key_file_get_string_list (config, "main", CONFIG_KEY_NO_AUTO_DEFAULT, &len, NULL);
+	for (iter = list; iter && *iter; iter++) {
+		struct ether_addr *candidate;
+
+		if (strcmp(g_strstrip(*iter), "*") == 0) {
+			found = TRUE;
+			break;
+		}
+
+		candidate = ether_aton (*iter);
+		if (candidate && !memcmp (mac->data, candidate->ether_addr_octet, ETH_ALEN)) {
+			found = TRUE;
+			break;
+		}
+	}
+
+	/* Add this device's MAC to the list */
+	if (!found) {
+		tmp = g_strdup_printf ("%02x:%02x:%02x:%02x:%02x:%02x",
+		                       mac->data[0], mac->data[1], mac->data[2],
+		                       mac->data[3], mac->data[4], mac->data[5]);
+
+		/* New list; size + 1 for the new element, + 1 again for ending NULL */
+		updated = g_malloc0 (sizeof (char*) * (len + 2));
+
+		/* Copy original list and add new MAC */
+		for (i = 0; list && list[i]; i++)
+			updated[i] = list[i];
+		updated[i++] = tmp;
+		updated[i] = NULL;
+
+		g_key_file_set_string_list (config,
+		                            "main", CONFIG_KEY_NO_AUTO_DEFAULT,
+		                            (const char **) updated,
+		                            len + 2);
+		/* g_free() not g_strfreev() since 'updated' isn't a deep-copy */
+		g_free (updated);
+		g_free (tmp);
+
+		data = g_key_file_to_data (config, &len, NULL);
+		if (data) {
+			g_file_set_contents (priv->config_file, data, len, NULL);
+			g_free (data);
+		}
+	}
+
+	if (list)
+		g_strfreev (list);
+	g_key_file_free (config);
+
+cleanup:
+	g_object_set_data (G_OBJECT (nm_default_wired_connection_get_device (wired)),
+	                   DEFAULT_WIRED_TAG,
+	                   NULL);
+}
+
+static void
+delete_cb (NMSettingsConnection *connection, GError *error, gpointer user_data)
+{
+}
+
+static gboolean
+default_wired_try_update (NMDefaultWiredConnection *wired,
+                          NMSettings *self)
+{
+	GError *error = NULL;
+	const char *id;
+	NMSettingsConnection *added;
+
+	/* Try to move this default wired conneciton to a plugin so that it has
+	 * persistent storage.
+	 */
+
+	id = nm_connection_get_id (NM_CONNECTION (wired));
+	g_assert (id);
+
+	remove_default_wired_connection (self, NM_SETTINGS_CONNECTION (wired), FALSE);
+	added = add_new_connection (self, NM_CONNECTION (wired), &error);
+	if (added) {
+		nm_settings_connection_delete (NM_SETTINGS_CONNECTION (wired), delete_cb, NULL);
+
+		g_object_set_data (G_OBJECT (nm_default_wired_connection_get_device (wired)),
+		                   DEFAULT_WIRED_TAG,
+		                   NULL);
+		nm_log_info (LOGD_SETTINGS, "Saved default wired connection '%s' to persistent storage", id);
+		return FALSE;
+	}
+
+	nm_log_warn (LOGD_SETTINGS, "couldn't save default wired connection '%s': %d / %s",
+	             id,
+	             error ? error->code : -1,
+	             (error && error->message) ? error->message : "(unknown)");
+	g_clear_error (&error);
+
+	/* If there was an error, don't destroy the default wired connection,
+	 * but add it back to the system settings service. Connection is already
+	 * exported on the bus, don't export it again, thus do_export == FALSE.
+	 */
+	claim_connection (self, NM_SETTINGS_CONNECTION (wired), FALSE);
+	return TRUE;
+}
+
+void
+nm_settings_device_added (NMSettings *self, NMDevice *device)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	GByteArray *mac = NULL;
+	struct ether_addr tmp;
+	NMDefaultWiredConnection *wired;
+	gboolean read_only = TRUE;
+	const char *id;
+	char *defname;
+
+	if (nm_device_get_device_type (device) != NM_DEVICE_TYPE_ETHERNET)
+		return;
+
+	/* If the device isn't managed or it already has a default wired connection,
+	 * ignore it.
+	 */
+	if (   !nm_device_get_managed (device)
+	    || g_object_get_data (G_OBJECT (device), DEFAULT_WIRED_TAG))
+		return;
+
+	nm_device_ethernet_get_address (NM_DEVICE_ETHERNET (device), &tmp);
+
+	mac = g_byte_array_sized_new (ETH_ALEN);
+	g_byte_array_append (mac, tmp.ether_addr_octet, ETH_ALEN);
+
+	if (   have_connection_for_device (self, mac)
+	    || is_mac_auto_wired_blacklisted (self, mac))
+		goto ignore;
+
+	if (get_plugin (self, NM_SYSTEM_CONFIG_INTERFACE_CAP_MODIFY_CONNECTIONS))
+		read_only = FALSE;
+
+	defname = nm_settings_utils_get_default_wired_name (priv->connections);
+	wired = nm_default_wired_connection_new (mac, device, defname, read_only);
+	g_free (defname);
+	if (!wired)
+		goto ignore;
+
+	id = nm_connection_get_id (NM_CONNECTION (wired));
+	g_assert (id);
+
+	nm_log_info (LOGD_SETTINGS, "Added default wired connection '%s' for %s",
+	             id, nm_device_get_udi (device));
+
+	g_signal_connect (wired, "try-update", (GCallback) default_wired_try_update, self);
+	g_signal_connect (wired, "deleted", (GCallback) default_wired_deleted, self);
+	claim_connection (self, NM_SETTINGS_CONNECTION (wired), TRUE);
+	g_object_unref (wired);
+
+	g_object_set_data (G_OBJECT (device), DEFAULT_WIRED_TAG, wired);
+
+ignore:
+	g_byte_array_free (mac, TRUE);
+}
+
+void
+nm_settings_device_removed (NMSettings *self, NMDevice *device)
+{
+	NMDefaultWiredConnection *connection;
+
+	if (nm_device_get_device_type (device) != NM_DEVICE_TYPE_ETHERNET)
+		return;
+
+	connection = (NMDefaultWiredConnection *) g_object_get_data (G_OBJECT (device), DEFAULT_WIRED_TAG);
+	if (connection)
+		remove_default_wired_connection (self, NM_SETTINGS_CONNECTION (connection), TRUE);
+}
+
+/***************************************************************/
+
+NMSettings *
+nm_settings_new (const char *config_file,
+                 const char *plugins,
+                 GError **error)
+{
+	NMSettings *self;
+	NMSettingsPrivate *priv;
+	GObject *keyfile_plugin;
+
+	self = g_object_new (NM_TYPE_SETTINGS, NULL);
+	if (!self)
+		return NULL;
+
+	priv = NM_SETTINGS_GET_PRIVATE (self);
+
+	priv->config_file = g_strdup (config_file);
+	priv->dbus_mgr = nm_dbus_manager_get ();
+	priv->bus = nm_dbus_manager_get_connection (priv->dbus_mgr);
+
+	if (plugins) {
+		/* Load the plugins; fail if a plugin is not found. */
+		if (!load_plugins (self, plugins, error)) {
+			g_object_unref (self);
+			return NULL;
+		}
+	}
+
+	/* Add the keyfile plugin last */
+	keyfile_plugin = nm_settings_keyfile_plugin_new ();
+	g_assert (keyfile_plugin);
+	add_plugin (self, NM_SYSTEM_CONFIG_INTERFACE (keyfile_plugin));
+
+	unmanaged_specs_changed (NULL, self);
+
+	dbus_g_connection_register_g_object (priv->bus, NM_DBUS_PATH_SETTINGS, G_OBJECT (self));
+	return self;
+}
+
+static void
+nm_settings_init (NMSettings *self)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	GError *error = NULL;
+
+	priv->connections = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref);
+
+	priv->authority = polkit_authority_get_sync (NULL, &error);
+	if (!priv->authority) {
+		nm_log_warn (LOGD_SETTINGS, "failed to create PolicyKit authority: (%d) %s",
+		             error ? error->code : -1,
+		             error && error->message ? error->message : "(unknown)");
+		g_clear_error (&error);
+	}
+
+	priv->session_monitor = nm_session_monitor_get ();
+
+	/* Hold a reference to the agent manager so it stays alive; the only
+	 * other holders are NMSettingsConnection objects which are often
+	 * transient, and we don't want the agent manager to get destroyed and
+	 * recreated often.
+	 */
+	priv->agent_mgr = nm_agent_manager_get ();
+}
+
+static void
+dispose (GObject *object)
+{
+	NMSettings *self = NM_SETTINGS (object);
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	GSList *iter;
+
+	if (priv->auth_changed_id) {
+		g_signal_handler_disconnect (priv->authority, priv->auth_changed_id);
+		priv->auth_changed_id = 0;
+	}
+
+	for (iter = priv->auths; iter; iter = g_slist_next (iter))
+		nm_auth_chain_unref ((NMAuthChain *) iter->data);
+	g_slist_free (priv->auths);
+
+	g_object_unref (priv->dbus_mgr);
+
+	g_object_unref (priv->session_monitor);
+	g_object_unref (priv->agent_mgr);
+
+	G_OBJECT_CLASS (nm_settings_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+	NMSettings *self = NM_SETTINGS (object);
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+
+	g_hash_table_destroy (priv->connections);
+
+	clear_unmanaged_specs (self);
+
+	g_slist_foreach (priv->plugins, (GFunc) g_object_unref, NULL);
+	g_slist_free (priv->plugins);
+
+	g_free (priv->config_file);
+
+	G_OBJECT_CLASS (nm_settings_parent_class)->finalize (object);
+}
+
+static void
+get_property (GObject *object, guint prop_id,
+			  GValue *value, GParamSpec *pspec)
+{
+	NMSettings *self = NM_SETTINGS (object);
+	const GSList *specs, *iter;
+	GSList *copy = NULL;
+
+	switch (prop_id) {
+	case PROP_UNMANAGED_SPECS:
+		specs = nm_settings_get_unmanaged_specs (self);
+		for (iter = specs; iter; iter = g_slist_next (iter))
+			copy = g_slist_append (copy, g_strdup (iter->data));
+		g_value_take_boxed (value, copy);
+		break;
+	case PROP_HOSTNAME:
+		g_value_take_string (value, nm_settings_get_hostname (self));
+
+		/* Don't ever pass NULL through D-Bus */
+		if (!g_value_get_string (value))
+			g_value_set_static_string (value, "");
+		break;
+	case PROP_CAN_MODIFY:
+		g_value_set_boolean (value, !!get_plugin (self, NM_SYSTEM_CONFIG_INTERFACE_CAP_MODIFY_CONNECTIONS));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+nm_settings_class_init (NMSettingsClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+	
+	g_type_class_add_private (class, sizeof (NMSettingsPrivate));
+
+	/* virtual methods */
+	object_class->notify = notify;
+	object_class->get_property = get_property;
+	object_class->dispose = dispose;
+	object_class->finalize = finalize;
+
+	/* properties */
+
+	g_object_class_install_property
+		(object_class, PROP_UNMANAGED_SPECS,
+		 g_param_spec_boxed (NM_SETTINGS_UNMANAGED_SPECS,
+							 "Unamanged device specs",
+							 "Unmanaged device specs",
+							 DBUS_TYPE_G_LIST_OF_STRING,
+							 G_PARAM_READABLE));
+
+	g_object_class_install_property
+		(object_class, PROP_HOSTNAME,
+		 g_param_spec_string (NM_SETTINGS_HOSTNAME,
+		                      "Hostname",
+		                      "Persistent hostname",
+		                      NULL,
+		                      G_PARAM_READABLE));
+
+	g_object_class_install_property
+		(object_class, PROP_CAN_MODIFY,
+		 g_param_spec_boolean (NM_SETTINGS_CAN_MODIFY,
+		                       "CanModify",
+		                       "Can modify anything (hostname, connections, etc)",
+		                       FALSE,
+		                       G_PARAM_READABLE));
+
+	/* signals */
+	signals[PROPERTIES_CHANGED] = 
+	                g_signal_new ("properties-changed",
+	                              G_OBJECT_CLASS_TYPE (object_class),
+	                              G_SIGNAL_RUN_FIRST,
+	                              G_STRUCT_OFFSET (NMSettingsClass, properties_changed),
+	                              NULL, NULL,
+	                              g_cclosure_marshal_VOID__BOXED,
+	                              G_TYPE_NONE, 1, DBUS_TYPE_G_MAP_OF_VARIANT);
+	signals[CONNECTION_ADDED] = 
+	                g_signal_new (NM_SETTINGS_SIGNAL_CONNECTION_ADDED,
+	                              G_OBJECT_CLASS_TYPE (object_class),
+	                              G_SIGNAL_RUN_FIRST,
+	                              G_STRUCT_OFFSET (NMSettingsClass, connection_added),
+	                              NULL, NULL,
+	                              g_cclosure_marshal_VOID__OBJECT,
+	                              G_TYPE_NONE, 1, G_TYPE_OBJECT);
+
+	signals[CONNECTION_UPDATED] = 
+	                g_signal_new (NM_SETTINGS_SIGNAL_CONNECTION_UPDATED,
+	                              G_OBJECT_CLASS_TYPE (object_class),
+	                              G_SIGNAL_RUN_FIRST,
+	                              G_STRUCT_OFFSET (NMSettingsClass, connection_updated),
+	                              NULL, NULL,
+	                              g_cclosure_marshal_VOID__OBJECT,
+	                              G_TYPE_NONE, 1, G_TYPE_OBJECT);
+
+	signals[CONNECTION_REMOVED] = 
+	                g_signal_new (NM_SETTINGS_SIGNAL_CONNECTION_REMOVED,
+	                              G_OBJECT_CLASS_TYPE (object_class),
+	                              G_SIGNAL_RUN_FIRST,
+	                              G_STRUCT_OFFSET (NMSettingsClass, connection_removed),
+	                              NULL, NULL,
+	                              g_cclosure_marshal_VOID__OBJECT,
+	                              G_TYPE_NONE, 1, G_TYPE_OBJECT);
+
+	signals[CONNECTION_VISIBILITY_CHANGED] = 
+	                g_signal_new (NM_SETTINGS_SIGNAL_CONNECTION_VISIBILITY_CHANGED,
+	                              G_OBJECT_CLASS_TYPE (object_class),
+	                              G_SIGNAL_RUN_FIRST,
+	                              G_STRUCT_OFFSET (NMSettingsClass, connection_visibility_changed),
+	                              NULL, NULL,
+	                              g_cclosure_marshal_VOID__OBJECT,
+	                              G_TYPE_NONE, 1, G_TYPE_OBJECT);
+
+	signals[CONNECTIONS_LOADED] = 
+	                g_signal_new (NM_SETTINGS_SIGNAL_CONNECTIONS_LOADED,
+	                              G_OBJECT_CLASS_TYPE (object_class),
+	                              G_SIGNAL_RUN_FIRST,
+	                              G_STRUCT_OFFSET (NMSettingsClass, connections_loaded),
+	                              NULL, NULL,
+	                              g_cclosure_marshal_VOID__VOID,
+	                              G_TYPE_NONE, 0);
+
+	signals[NEW_CONNECTION] = 
+	                g_signal_new ("new-connection",
+	                              G_OBJECT_CLASS_TYPE (object_class),
+	                              G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
+	                              g_cclosure_marshal_VOID__OBJECT,
+	                              G_TYPE_NONE, 1, G_TYPE_OBJECT);
+
+	dbus_g_error_domain_register (NM_SETTINGS_ERROR,
+	                              NM_DBUS_IFACE_SETTINGS,
+	                              NM_TYPE_SETTINGS_ERROR);
+
+	/* And register all the settings errors with D-Bus */
+	dbus_g_error_domain_register (NM_CONNECTION_ERROR, NULL, NM_TYPE_CONNECTION_ERROR);
+	dbus_g_error_domain_register (NM_SETTING_802_1X_ERROR, NULL, NM_TYPE_SETTING_802_1X_ERROR);
+	dbus_g_error_domain_register (NM_SETTING_BLUETOOTH_ERROR, NULL, NM_TYPE_SETTING_BLUETOOTH_ERROR);
+	dbus_g_error_domain_register (NM_SETTING_CDMA_ERROR, NULL, NM_TYPE_SETTING_CDMA_ERROR);
+	dbus_g_error_domain_register (NM_SETTING_CONNECTION_ERROR, NULL, NM_TYPE_SETTING_CONNECTION_ERROR);
+	dbus_g_error_domain_register (NM_SETTING_GSM_ERROR, NULL, NM_TYPE_SETTING_GSM_ERROR);
+	dbus_g_error_domain_register (NM_SETTING_IP4_CONFIG_ERROR, NULL, NM_TYPE_SETTING_IP4_CONFIG_ERROR);
+	dbus_g_error_domain_register (NM_SETTING_IP6_CONFIG_ERROR, NULL, NM_TYPE_SETTING_IP6_CONFIG_ERROR);
+	dbus_g_error_domain_register (NM_SETTING_OLPC_MESH_ERROR, NULL, NM_TYPE_SETTING_OLPC_MESH_ERROR);
+	dbus_g_error_domain_register (NM_SETTING_PPP_ERROR, NULL, NM_TYPE_SETTING_PPP_ERROR);
+	dbus_g_error_domain_register (NM_SETTING_PPPOE_ERROR, NULL, NM_TYPE_SETTING_PPPOE_ERROR);
+	dbus_g_error_domain_register (NM_SETTING_SERIAL_ERROR, NULL, NM_TYPE_SETTING_SERIAL_ERROR);
+	dbus_g_error_domain_register (NM_SETTING_VPN_ERROR, NULL, NM_TYPE_SETTING_VPN_ERROR);
+	dbus_g_error_domain_register (NM_SETTING_WIRED_ERROR, NULL, NM_TYPE_SETTING_WIRED_ERROR);
+	dbus_g_error_domain_register (NM_SETTING_WIRELESS_SECURITY_ERROR, NULL, NM_TYPE_SETTING_WIRELESS_SECURITY_ERROR);
+	dbus_g_error_domain_register (NM_SETTING_WIRELESS_ERROR, NULL, NM_TYPE_SETTING_WIRELESS_ERROR);
+	dbus_g_error_domain_register (NM_SETTING_ERROR, NULL, NM_TYPE_SETTING_ERROR);
+
+	dbus_g_object_type_install_info (NM_TYPE_SETTINGS, &dbus_glib_nm_settings_object_info);
+
+}
+