summary refs log tree commit diff
path: root/src/nm-manager.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2010-11-29 19:00:22 +0100
committerMichael Biebl <biebl@debian.org>2010-11-29 19:00:22 +0100
commita6b89d35fabd9f5934b84d7cde22e9268f92c3d3 (patch)
tree9e44755880d5933fcc90c6008d4585a76298a1bd /src/nm-manager.c
parent9fae01fa351805b2903e535736e06971bc0b925e (diff)
Imported Upstream version 0.8.2 upstream/0.8.2
Diffstat (limited to 'src/nm-manager.c')
-rw-r--r--src/nm-manager.c2299
1 files changed, 1847 insertions, 452 deletions
diff --git a/src/nm-manager.c b/src/nm-manager.c
index 7d46285b..eb393fac 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -19,6 +19,8 @@
  * Copyright (C) 2007 - 2010 Red Hat, Inc.
  */
 
+#include <config.h>
+
 #include <netinet/ether.h>
 #include <string.h>
 #include <dbus/dbus-glib-lowlevel.h>
@@ -54,25 +56,35 @@
 #include "nm-secrets-provider-interface.h"
 #include "nm-settings-interface.h"
 #include "nm-settings-system-interface.h"
+#include "nm-manager-auth.h"
 
 #define NM_AUTOIP_DBUS_SERVICE "org.freedesktop.nm_avahi_autoipd"
 #define NM_AUTOIP_DBUS_IFACE   "org.freedesktop.nm_avahi_autoipd"
 
+#define UPOWER_DBUS_SERVICE "org.freedesktop.UPower"
+
 static gboolean impl_manager_get_devices (NMManager *manager, GPtrArray **devices, GError **err);
 static void impl_manager_activate_connection (NMManager *manager,
-								  const char *service_name,
-								  const char *connection_path,
-								  const char *device_path,
-								  const char *specific_object_path,
-								  DBusGMethodInvocation *context);
+                                              const char *service_name,
+                                              const char *connection_path,
+                                              const char *device_path,
+                                              const char *specific_object_path,
+                                              DBusGMethodInvocation *context);
 
-static gboolean impl_manager_deactivate_connection (NMManager *manager,
-                                                    const char *connection_path,
-                                                    GError **error);
+static void impl_manager_deactivate_connection (NMManager *manager,
+                                                const char *connection_path,
+                                                DBusGMethodInvocation *context);
 
-static gboolean impl_manager_sleep (NMManager *manager, gboolean sleep, GError **err);
+static void impl_manager_sleep (NMManager *manager,
+                                gboolean do_sleep,
+                                DBusGMethodInvocation *context);
 
-static gboolean impl_manager_enable (NMManager *manager, gboolean enable, GError **err);
+static void impl_manager_enable (NMManager *manager,
+                                 gboolean enable,
+                                 DBusGMethodInvocation *context);
+
+static void impl_manager_get_permissions (NMManager *manager,
+                                          DBusGMethodInvocation *context);
 
 static gboolean impl_manager_set_logging (NMManager *manager,
                                           const char *level,
@@ -81,14 +93,12 @@ static gboolean impl_manager_set_logging (NMManager *manager,
 
 /* Legacy 0.6 compatibility interface */
 
-static gboolean impl_manager_legacy_sleep (NMManager *manager, GError **err);
-static gboolean impl_manager_legacy_wake  (NMManager *manager, GError **err);
+static void impl_manager_legacy_sleep (NMManager *manager, DBusGMethodInvocation *context);
+static void impl_manager_legacy_wake  (NMManager *manager, DBusGMethodInvocation *context);
 static gboolean impl_manager_legacy_state (NMManager *manager, guint32 *state, GError **err);
 
 #include "nm-manager-glue.h"
 
-static void user_destroy_connections (NMManager *manager);
-
 static void connection_added_default_handler (NMManager *manager,
 									 NMConnection *connection,
 									 NMConnectionScope scope);
@@ -133,25 +143,52 @@ static NMDevice *find_device_by_iface (NMManager *self, const gchar *iface);
 static GSList * remove_one_device (NMManager *manager,
                                    GSList *list,
                                    NMDevice *device,
-                                   gboolean quitting,
-                                   gboolean force_unmanage);
+                                   gboolean quitting);
 
 static NMDevice *nm_manager_get_device_by_udi (NMManager *manager, const char *udi);
 
+/* Fix for polkit 0.97 and later */
+#if !HAVE_POLKIT_AUTHORITY_GET_SYNC
+static inline PolkitAuthority *
+polkit_authority_get_sync (GCancellable *cancellable, GError **error)
+{
+	PolkitAuthority *authority;
+
+	authority = polkit_authority_get ();
+	if (!authority)
+		g_set_error (error, 0, 0, "failed to get the PolicyKit authority");
+	return authority;
+}
+#endif
+
 #define SSD_POKE_INTERVAL 120
 #define ORIGDEV_TAG "originating-device"
 
-typedef struct {
+typedef struct PendingActivation PendingActivation;
+typedef void (*PendingActivationFunc) (PendingActivation *pending,
+                                       GError *error);
+
+struct PendingActivation {
+	NMManager *manager;
+
 	DBusGMethodInvocation *context;
+	PolkitAuthority *authority;
+	PendingActivationFunc callback;
+	NMAuthChain *chain;
+
+	gboolean have_connection;
+	gboolean authorized;
+
 	NMConnectionScope scope;
 	char *connection_path;
 	char *specific_object_path;
 	char *device_path;
 	guint timeout_id;
-} PendingConnectionInfo;
+};
 
 typedef struct {
-	gboolean enabled;
+	gboolean user_enabled;
+	gboolean sw_enabled;
 	gboolean hw_enabled;
 	RfKillType rtype;
 	const char *desc;
@@ -174,6 +211,8 @@ typedef struct {
 
 	GHashTable *user_connections;
 	DBusGProxy *user_proxy;
+	NMAuthCallResult user_con_perm;
+	NMAuthCallResult user_net_perm;
 
 	GHashTable *system_connections;
 	NMSysconfigSettings *sys_settings;
@@ -181,7 +220,7 @@ typedef struct {
 
 	GSList *secrets_calls;
 
-	PendingConnectionInfo *pending_connection_info;
+	GSList *pending_activations;
 
 	RadioState radio_states[RFKILL_TYPE_MAX];
 	gboolean sleeping;
@@ -195,6 +234,18 @@ typedef struct {
 	guint modem_removed_id;
 
 	DBusGProxy *aipd_proxy;
+	DBusGProxy *upower_proxy;
+
+	PolkitAuthority *authority;
+	guint auth_changed_id;
+	GSList *auth_chains;
+
+	/* Firmware dir monitor */
+	GFileMonitor *fw_monitor;
+	guint fw_monitor_id;
+	guint fw_changed_id;
+
+	guint timestamp_update_id;
 
 	gboolean disposed;
 } NMManagerPrivate;
@@ -215,6 +266,8 @@ enum {
 	CONNECTION_ADDED,
 	CONNECTION_UPDATED,
 	CONNECTION_REMOVED,
+	CHECK_PERMISSIONS,
+	USER_PERMISSIONS_CHANGED,
 
 	LAST_SIGNAL
 };
@@ -223,6 +276,7 @@ static guint signals[LAST_SIGNAL] = { 0 };
 
 enum {
 	PROP_0,
+	PROP_VERSION,
 	PROP_STATE,
 	PROP_NETWORKING_ENABLED,
 	PROP_WIRELESS_ENABLED,
@@ -339,8 +393,7 @@ modem_added (NMModemManager *modem_manager,
 		priv->devices = remove_one_device (NM_MANAGER (user_data),
 		                                   priv->devices,
 		                                   replace_device,
-		                                   FALSE,
-		                                   TRUE);
+		                                   FALSE);
 	}
 
 	/* Give Bluetooth DUN devices first chance to claim the modem */
@@ -411,6 +464,45 @@ nm_manager_update_state (NMManager *manager)
 }
 
 static void
+ignore_cb (NMSettingsConnectionInterface *connection, GError *error, gpointer user_data)
+{
+}
+
+static void
+update_active_connection_timestamp (NMManager *manager, NMDevice *device)
+{
+	NMActRequest *req;
+	NMConnection *connection;
+	NMSettingConnection *s_con;
+	NMSettingsConnectionInterface *connection_interface;
+	NMManagerPrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE (device));
+
+	priv = NM_MANAGER_GET_PRIVATE (manager);
+	req = nm_device_get_act_request (device);
+	if (!req)
+		return;
+
+	connection = nm_act_request_get_connection (req);
+	g_assert (connection);
+
+	if (nm_connection_get_scope (connection) != NM_CONNECTION_SCOPE_SYSTEM)
+		return;
+
+	s_con = NM_SETTING_CONNECTION (nm_connection_get_setting (NM_CONNECTION (connection), NM_TYPE_SETTING_CONNECTION));
+	g_assert (s_con);
+	g_object_set (s_con, NM_SETTING_CONNECTION_TIMESTAMP, (guint64) time (NULL), NULL);
+
+	if (nm_setting_connection_get_read_only (s_con))
+		return;
+
+	connection_interface = nm_settings_interface_get_connection_by_path (NM_SETTINGS_INTERFACE (priv->sys_settings),
+	                                                                     nm_connection_get_path (connection));
+	nm_settings_connection_interface_update (connection_interface, ignore_cb, NULL);
+}
+
+static void
 manager_device_state_changed (NMDevice *device,
                               NMDeviceState new_state,
                               NMDeviceState old_state,
@@ -432,6 +524,9 @@ manager_device_state_changed (NMDevice *device,
 	}
 
 	nm_manager_update_state (manager);
+
+	if (new_state == NM_DEVICE_STATE_ACTIVATED)
+		update_active_connection_timestamp (manager, device);
 }
 
 /* Removes a device from a device list; returns the start of the new device list */
@@ -439,20 +534,22 @@ static GSList *
 remove_one_device (NMManager *manager,
                    GSList *list,
                    NMDevice *device,
-                   gboolean quitting,
-                   gboolean force_unmanage)
+                   gboolean quitting)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
 
 	if (nm_device_get_managed (device)) {
-		gboolean unmanage = !quitting;
-
-		/* Don't unmanage active assume-connection-capable devices at shutdown */
-		if (   nm_device_interface_can_assume_connection (NM_DEVICE_INTERFACE (device))
-		    && nm_device_get_state (device) == NM_DEVICE_STATE_ACTIVATED)
-			unmanage = FALSE;
+		/* When quitting, we want to leave up interfaces & connections
+		 * that can be taken over again (ie, "assumed") when NM restarts
+		 * so that '/etc/init.d/NetworkManager restart' will not distrupt
+		 * networking for interfaces that support connection assumption.
+		 * All other devices get unmanaged when NM quits so that their
+		 * connections get torn down and the interface is deactivated.
+		 */
 
-		if (unmanage || force_unmanage)
+		if (   !nm_device_interface_can_assume_connections (NM_DEVICE_INTERFACE (device))
+		    || (nm_device_get_state (device) != NM_DEVICE_STATE_ACTIVATED)
+		    || !quitting)
 			nm_device_set_managed (device, FALSE, NM_DEVICE_STATE_REASON_REMOVED);
 	}
 
@@ -486,7 +583,7 @@ modem_removed (NMModemManager *modem_manager,
 	/* Otherwise remove the standalone modem */
 	found = nm_manager_get_device_by_udi (self, nm_modem_get_path (modem));
 	if (found)
-		priv->devices = remove_one_device (self, priv->devices, found, FALSE, TRUE);
+		priv->devices = remove_one_device (self, priv->devices, found, FALSE);
 }
 
 static void
@@ -560,19 +657,237 @@ emit_removed (gpointer key, gpointer value, gpointer user_data)
 }
 
 static void
-pending_connection_info_destroy (PendingConnectionInfo *info)
+nm_manager_pending_activation_remove (NMManager *self,
+                                      PendingActivation *pending)
+{
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+
+	priv->pending_activations = g_slist_remove (priv->pending_activations, pending);
+}
+
+static PendingActivation *
+pending_activation_new (NMManager *manager,
+                        PolkitAuthority *authority,
+                        DBusGMethodInvocation *context,
+                        const char *device_path,
+                        NMConnectionScope scope,
+                        const char *connection_path,
+                        const char *specific_object_path,
+                        PendingActivationFunc callback)
+{
+	PendingActivation *pending;
+
+	g_return_val_if_fail (manager != NULL, NULL);
+	g_return_val_if_fail (authority != NULL, NULL);
+	g_return_val_if_fail (context != NULL, NULL);
+	g_return_val_if_fail (device_path != NULL, NULL);
+	g_return_val_if_fail (connection_path != NULL, NULL);
+
+	pending = g_slice_new0 (PendingActivation);
+	pending->manager = manager;
+	pending->authority = authority;
+	pending->context = context;
+	pending->callback = callback;
+
+	pending->device_path = g_strdup (device_path);
+	pending->scope = scope;
+	pending->connection_path = g_strdup (connection_path);
+
+	/* "/" is special-cased to NULL to get through D-Bus */
+	if (specific_object_path && strcmp (specific_object_path, "/"))
+		pending->specific_object_path = g_strdup (specific_object_path);
+
+	return pending;
+}
+
+static void
+pending_auth_user_done (NMAuthChain *chain,
+                        GError *error,
+                        DBusGMethodInvocation *context,
+                        gpointer user_data)
+{
+	PendingActivation *pending = user_data;
+	NMAuthCallResult result;
+
+	pending->chain = NULL;
+
+	if (error) {
+		pending->callback (pending, error);
+		goto out;
+	}
+
+	/* Caller has had a chance to obtain authorization, so we only need to
+	 * check for 'yes' here.
+	 */
+	result = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, NM_AUTH_PERMISSION_USE_USER_CONNECTIONS));
+	if (result != NM_AUTH_CALL_RESULT_YES) {
+		error = g_error_new_literal (NM_MANAGER_ERROR,
+		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
+		                             "Not authorized to use user connections.");
+		pending->callback (pending, error);
+		g_error_free (error);
+	} else
+		pending->callback (pending, NULL);
+
+out:
+	nm_auth_chain_unref (chain);
+}
+
+static void
+pending_auth_net_done (NMAuthChain *chain,
+                       GError *error,
+                       DBusGMethodInvocation *context,
+                       gpointer user_data)
+{
+	PendingActivation *pending = user_data;
+	NMAuthCallResult result;
+
+	pending->chain = NULL;
+
+	if (error) {
+		pending->callback (pending, error);
+		goto out;
+	}
+
+	/* Caller has had a chance to obtain authorization, so we only need to
+	 * check for 'yes' here.
+	 */
+	result = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL));
+	if (result != NM_AUTH_CALL_RESULT_YES) {
+		error = g_error_new_literal (NM_MANAGER_ERROR,
+		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
+		                             "Not authorized to control networking.");
+		pending->callback (pending, error);
+		g_error_free (error);
+		goto out;
+	}
+
+	if (pending->scope == NM_CONNECTION_SCOPE_SYSTEM) {
+		/* System connection and the user is authorized for that if they have
+		 * the network-control permission.
+		 */
+		pending->callback (pending, NULL);
+	} else {
+		g_assert (pending->scope == NM_CONNECTION_SCOPE_USER);
+
+		/* User connection, check the 'use-user-connections' permission */
+		pending->chain = nm_auth_chain_new (pending->authority,
+			                                pending->context,
+			                                NULL,
+			                                pending_auth_user_done,
+			                                pending);
+		nm_auth_chain_add_call (pending->chain,
+			                    NM_AUTH_PERMISSION_USE_USER_CONNECTIONS,
+			                    TRUE);
+	}
+
+out:
+	nm_auth_chain_unref (chain);
+}
+
+static gboolean
+check_user_authorized (NMDBusManager *dbus_mgr,
+                       DBusGProxy *user_proxy,
+                       DBusGMethodInvocation *context,
+                       NMConnectionScope scope,
+                       gulong *out_sender_uid,
+                       const char **out_error_desc)
+{
+	g_return_val_if_fail (dbus_mgr != NULL, FALSE);
+	g_return_val_if_fail (context != NULL, FALSE);
+	g_return_val_if_fail (out_sender_uid != NULL, FALSE);
+	g_return_val_if_fail (out_error_desc != NULL, FALSE);
+
+	*out_sender_uid = G_MAXULONG;
+
+	/* Get the UID */
+	if (!nm_auth_get_caller_uid (context, dbus_mgr, out_sender_uid, out_error_desc))
+		return FALSE;
+
+	/* root gets to do anything */
+	if (0 == *out_sender_uid)
+		return TRUE;
+
+	/* Check whether the UID is authorized for user connections */
+	if (   scope == NM_CONNECTION_SCOPE_USER
+	    && !nm_auth_uid_authorized (*out_sender_uid,
+	                                dbus_mgr,
+	                                user_proxy,
+	                                out_error_desc))
+		return FALSE;
+
+	return TRUE;
+}
+
+static void
+pending_activation_check_authorized (PendingActivation *pending,
+                                     NMDBusManager *dbus_mgr,
+                                     DBusGProxy *user_proxy)
 {
-	if (!info)
+	const char *error_desc = NULL;
+	gulong sender_uid = G_MAXULONG;
+	GError *error;
+
+	g_return_if_fail (pending != NULL);
+	g_return_if_fail (dbus_mgr != NULL);
+
+	if (!check_user_authorized (dbus_mgr,
+	                            user_proxy,
+	                            pending->context,
+	                            pending->scope,
+	                            &sender_uid,
+	                            &error_desc)) {
+		error = g_error_new_literal (NM_MANAGER_ERROR,
+		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
+		                             error_desc);
+		pending->callback (pending, error);
+		g_error_free (error);
 		return;
+	}
 
-	if (info->timeout_id)
-		g_source_remove (info->timeout_id);
+	/* Yay for root */
+	if (0 == sender_uid) {
+		pending->callback (pending, NULL);
+		return;
+	}
 
-	g_free (info->connection_path);
-	g_free (info->specific_object_path);
-	g_free (info->device_path);
+	/* First check if the user is allowed to use networking at all, giving
+	 * the user a chance to authenticate to gain the permission.
+	 */
+	pending->chain = nm_auth_chain_new (pending->authority,
+	                                    pending->context,
+	                                    NULL,
+	                                    pending_auth_net_done,
+	                                    pending);
+	g_assert (pending->chain);
+	nm_auth_chain_add_call (pending->chain,
+	                        NM_AUTH_PERMISSION_NETWORK_CONTROL,
+	                        TRUE);
+}
 
-	g_slice_free (PendingConnectionInfo, info);
+static void
+pending_activation_destroy (PendingActivation *pending,
+                            GError *error,
+                            const char *ac_path)
+{
+	g_return_if_fail (pending != NULL);
+
+	if (pending->timeout_id)
+		g_source_remove (pending->timeout_id);
+	g_free (pending->connection_path);
+	g_free (pending->specific_object_path);
+	g_free (pending->device_path);
+
+	if (error)
+		dbus_g_method_return_error (pending->context, error);
+	else if (ac_path)
+		dbus_g_method_return (pending->context, ac_path);
+
+	if (pending->chain)
+		nm_auth_chain_unref (pending->chain);
+
+	memset (pending, 0, sizeof (PendingActivation));
+	g_slice_free (PendingActivation, pending);
 }
 
 static GPtrArray *
@@ -632,27 +947,34 @@ remove_connection (NMManager *manager,
 /*******************************************************************/
 
 static void
-user_destroy_connections (NMManager *manager)
+user_proxy_cleanup (NMManager *self, gboolean resync_bt)
 {
-	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 
 	if (priv->user_connections) {
-		g_hash_table_foreach (priv->user_connections, emit_removed, manager);
+		g_hash_table_foreach (priv->user_connections, emit_removed, self);
 		g_hash_table_remove_all (priv->user_connections);
 	}
 
+	priv->user_net_perm = NM_AUTH_CALL_RESULT_UNKNOWN;
+	priv->user_con_perm = NM_AUTH_CALL_RESULT_UNKNOWN;
+
 	if (priv->user_proxy) {
 		g_object_unref (priv->user_proxy);
 		priv->user_proxy = NULL;
 	}
+
+	if (resync_bt) {
+		/* Resync BT devices since they are generated from connections */
+		bluez_manager_resync_devices (self);
+	}
 }
 
 typedef struct GetSettingsInfo {
 	NMManager *manager;
 	NMConnection *connection;
 	DBusGProxy *proxy;
-	DBusGProxyCall *call;
-	GSList **calls;
+	guint32 *calls;
 } GetSettingsInfo;
 
 static void
@@ -664,10 +986,9 @@ free_get_settings_info (gpointer data)
 	 * send out the connections-added signal.
 	 */
 	if (info->calls) {
-		*(info->calls) = g_slist_remove (*(info->calls), info->call);
-		if (g_slist_length (*(info->calls)) == 0) {
-			g_slist_free (*(info->calls));
-			g_slice_free (GSList, (gpointer) info->calls);
+		(*info->calls)--;
+		if (*info->calls == 0) {
+			g_slice_free (guint32, (gpointer) info->calls);
 			g_signal_emit (info->manager, signals[CONNECTIONS_ADDED], 0, NM_CONNECTION_SCOPE_USER);
 
 			/* Update the Bluetooth connections for all the new connections */
@@ -838,20 +1159,18 @@ user_connection_updated_cb (DBusGProxy *proxy,
 }
 
 static void
-user_internal_new_connection_cb (DBusGProxy *proxy,
+user_internal_new_connection_cb (NMManager *manager,
                                  const char *path,
-                                 NMManager *manager,
-                                 GSList **calls)
+                                 guint32 *counter)
 {
-	struct GetSettingsInfo *info;
+	GetSettingsInfo *info;
 	DBusGProxy *con_proxy;
 	DBusGConnection *g_connection;
-	DBusGProxyCall *call;
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
 
 	g_connection = nm_dbus_manager_get_connection (priv->dbus_mgr);
 	con_proxy = dbus_g_proxy_new_for_name (g_connection,
-	                                       dbus_g_proxy_get_bus_name (proxy),
+	                                       NM_DBUS_SERVICE_USER_SETTINGS,
 	                                       path,
 	                                       NM_DBUS_IFACE_SETTINGS_CONNECTION);
 	if (!con_proxy) {
@@ -875,16 +1194,16 @@ user_internal_new_connection_cb (DBusGProxy *proxy,
 
 	info = g_slice_new0 (GetSettingsInfo);
 	info->manager = g_object_ref (manager);
-	info->calls = calls;
-	call = dbus_g_proxy_begin_call (con_proxy, "GetSettings",
-	                                user_connection_get_settings_cb,
-	                                info,
-	                                free_get_settings_info,
-	                                G_TYPE_INVALID);
-	info->call = call;
 	info->proxy = con_proxy;
-	if (info->calls)
-		*(info->calls) = g_slist_prepend (*(info->calls), call);
+	if (counter) {
+		info->calls = counter;
+		(*info->calls)++;
+	}
+	dbus_g_proxy_begin_call (con_proxy, "GetSettings",
+	                         user_connection_get_settings_cb,
+	                         info,
+	                         free_get_settings_info,
+	                         G_TYPE_INVALID);
 }
 
 static void
@@ -895,7 +1214,7 @@ user_list_connections_cb  (DBusGProxy *proxy,
 	NMManager *manager = NM_MANAGER (user_data);
 	GError *err = NULL;
 	GPtrArray *ops;
-	GSList **calls = NULL;
+	guint32 *counter = NULL;
 	int i;
 
 	if (!dbus_g_proxy_end_call (proxy, call_id, &err,
@@ -903,72 +1222,166 @@ user_list_connections_cb  (DBusGProxy *proxy,
 	                            G_TYPE_INVALID)) {
 		nm_log_warn (LOGD_USER_SET, "couldn't retrieve connections: %s",
 		             err && err->message ? err->message : "(unknown)");
-		g_error_free (err);
-		goto out;
+		g_clear_error (&err);
+		return;
 	}
 
 	/* Keep track of all calls made here; don't want to emit connection-added for
 	 * each one, but emit connections-added when they are all done.
 	 */
-	calls = g_slice_new0 (GSList *);
-
+	counter = g_slice_new0 (guint32);
 	for (i = 0; i < ops->len; i++) {
 		char *op = g_ptr_array_index (ops, i);
 
-		user_internal_new_connection_cb (proxy, op, manager, calls);
+		user_internal_new_connection_cb (manager, op, counter);
 		g_free (op);
 	}
-
 	g_ptr_array_free (ops, TRUE);
+}
 
-out:
-	return;
+static void
+user_proxy_destroyed_cb (DBusGProxy *proxy, NMManager *self)
+{
+	nm_log_dbg (LOGD_USER_SET, "Removing user connections...");
+
+	/* At this point the user proxy is already being disposed */
+	NM_MANAGER_GET_PRIVATE (self)->user_proxy = NULL;
+
+	/* User Settings service disappeared; throw away user connections */
+	user_proxy_cleanup (self, TRUE);
 }
 
 static void
 user_new_connection_cb (DBusGProxy *proxy, const char *path, gpointer user_data)
 {
-	user_internal_new_connection_cb (proxy, path, NM_MANAGER (user_data), NULL);
+	user_internal_new_connection_cb (NM_MANAGER (user_data), path, NULL);
+}
+
+static gboolean
+user_settings_authorized (NMManager *self, NMAuthChain *chain)
+{
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	NMAuthCallResult old_net_perm = priv->user_net_perm;
+	NMAuthCallResult old_con_perm = priv->user_con_perm;
+
+	/* If the user could potentially get authorization to use networking and/or
+	 * to use user connections, the user settings service is authorized.
+	 */
+	priv->user_net_perm = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL));
+	priv->user_con_perm = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, NM_AUTH_PERMISSION_USE_USER_CONNECTIONS));
+
+	nm_log_dbg (LOGD_USER_SET, "User connections permissions: net %d, con %d",
+	            priv->user_net_perm, priv->user_con_perm);
+
+	if (old_net_perm != priv->user_net_perm || old_con_perm != priv->user_con_perm)
+		g_signal_emit (self, signals[USER_PERMISSIONS_CHANGED], 0);
+
+	/* If the user can't control the network they certainly aren't allowed
+	 * to provide user connections.
+	 */
+	if (   priv->user_net_perm == NM_AUTH_CALL_RESULT_UNKNOWN
+	    || priv->user_net_perm == NM_AUTH_CALL_RESULT_NO)
+		return FALSE;
+
+	/* And of course if they aren't allowed to use user connections, they can't
+	 * provide them either.
+	 */
+	if (   priv->user_con_perm == NM_AUTH_CALL_RESULT_UNKNOWN
+	    || priv->user_con_perm == NM_AUTH_CALL_RESULT_NO)
+		return FALSE;
+
+	return TRUE;
 }
 
 static void
-user_query_connections (NMManager *manager)
+user_proxy_auth_done (NMAuthChain *chain,
+                      GError *error,
+                      DBusGMethodInvocation *context,
+                      gpointer user_data)
 {
-	NMManagerPrivate *priv;
-	DBusGProxyCall *call;
-	DBusGConnection *g_connection;
+	NMManager *self = NM_MANAGER (user_data);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	gboolean authorized = FALSE;
 
-	g_return_if_fail (NM_IS_MANAGER (manager));
+	priv->auth_chains = g_slist_remove (priv->auth_chains, chain);
 
-	priv = NM_MANAGER_GET_PRIVATE (manager);
-	if (!priv->user_proxy) {
-		g_connection = nm_dbus_manager_get_connection (priv->dbus_mgr);
-		priv->user_proxy = dbus_g_proxy_new_for_name (g_connection,
-		                                              NM_DBUS_SERVICE_USER_SETTINGS,
-		                                              NM_DBUS_PATH_SETTINGS,
-		                                              NM_DBUS_IFACE_SETTINGS);
-		if (!priv->user_proxy) {
-			nm_log_err (LOGD_USER_SET, "could not init user settings proxy");
-			return;
-		}
+	if (error) {
+		nm_log_warn (LOGD_USER_SET, "User connections unavailable: (%d) %s",
+		             error->code, error->message ? error->message : "(unknown)");
+	} else
+		authorized = user_settings_authorized (self, chain);
 
-		dbus_g_proxy_add_signal (priv->user_proxy,
-		                         "NewConnection",
-		                         DBUS_TYPE_G_OBJECT_PATH,
-		                         G_TYPE_INVALID);
+	if (authorized) {
+		/* If authorized, finish setting up the user settings service proxy */
+		nm_log_dbg (LOGD_USER_SET, "Requesting user connections...");
+
+		authorized = TRUE;
 
+		dbus_g_proxy_add_signal (priv->user_proxy,
+			                     "NewConnection",
+			                     DBUS_TYPE_G_OBJECT_PATH,
+			                     G_TYPE_INVALID);
 		dbus_g_proxy_connect_signal (priv->user_proxy, "NewConnection",
-		                             G_CALLBACK (user_new_connection_cb),
-		                             manager,
-		                             NULL);
+			                         G_CALLBACK (user_new_connection_cb),
+			                         self,
+			                         NULL);
+
+		/* Clean up when the user settings proxy goes away */
+		g_signal_connect (priv->user_proxy, "destroy",
+			              G_CALLBACK (user_proxy_destroyed_cb),
+			              self);
+
+		/* Request user connections */
+		dbus_g_proxy_begin_call (priv->user_proxy, "ListConnections",
+			                     user_list_connections_cb,
+			                     self,
+			                     NULL,
+			                     G_TYPE_INVALID);
+	} else {
+		/* Otherwise, we ignore the user settings service completely */
+		user_proxy_cleanup (self, TRUE);
+	}
+
+	nm_auth_chain_unref (chain);
+}
+
+static void
+user_proxy_init (NMManager *self)
+{
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	DBusGConnection *bus;
+	NMAuthChain *chain;
+	GError *error = NULL;
+
+	g_return_if_fail (self != NULL);
+	g_return_if_fail (priv->user_proxy == NULL);
+
+	bus = nm_dbus_manager_get_connection (priv->dbus_mgr);
+	priv->user_proxy = dbus_g_proxy_new_for_name_owner (bus,
+	                                                    NM_DBUS_SERVICE_USER_SETTINGS,
+	                                                    NM_DBUS_PATH_SETTINGS,
+	                                                    NM_DBUS_IFACE_SETTINGS,
+	                                                    &error);
+	if (!priv->user_proxy) {
+		nm_log_err (LOGD_USER_SET, "could not init user settings proxy: (%d) %s",
+		            error ? error->code : -1,
+		            error && error->message ? error->message : "(unknown)");
+		g_clear_error (&error);
+		return;
 	}
 
-	/* grab connections */
-	call = dbus_g_proxy_begin_call (priv->user_proxy, "ListConnections",
-	                                user_list_connections_cb,
-	                                manager,
-	                                NULL,
-	                                G_TYPE_INVALID);
+	/* Kick off some PolicyKit authorization requests to figure out what
+	 * permissions this user settings service has.
+	 */
+	chain = nm_auth_chain_new (priv->authority,
+	                           NULL,
+	                           priv->user_proxy,
+	                           user_proxy_auth_done,
+	                           self);
+	priv->auth_chains = g_slist_prepend (priv->auth_chains, chain);
+
+	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_USE_USER_CONNECTIONS, FALSE);
+	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL, FALSE);
 }
 
 /*******************************************************************/
@@ -1042,6 +1455,7 @@ system_internal_new_connection (NMManager *manager,
 	path = nm_connection_get_path (NM_CONNECTION (connection));
 	g_hash_table_insert (priv->system_connections, g_strdup (path),
 	                     g_object_ref (connection));
+	g_signal_emit (manager, signals[CONNECTION_ADDED], 0, connection, NM_CONNECTION_SCOPE_SYSTEM);
 }
 
 static void
@@ -1150,14 +1564,10 @@ nm_manager_name_owner_changed (NMDBusManager *mgr,
 	gboolean new_owner_good = (new && (strlen (new) > 0));
 
 	if (strcmp (name, NM_DBUS_SERVICE_USER_SETTINGS) == 0) {
-		if (!old_owner_good && new_owner_good) {
-			/* User Settings service appeared, update stuff */
-			user_query_connections (manager);
-		} else {
-			/* User Settings service disappeared, throw them away (?) */
-			user_destroy_connections (manager);
-			bluez_manager_resync_devices (manager);
-		}
+		if (!old_owner_good && new_owner_good)
+			user_proxy_init (manager);
+		else
+			user_proxy_cleanup (manager, TRUE);
 	}
 }
 
@@ -1211,50 +1621,40 @@ write_value_to_state_file (const char *filename,
 	return ret;
 }
 
+static gboolean
+radio_enabled_for_rstate (RadioState *rstate)
+{
+	return rstate->user_enabled && rstate->sw_enabled && rstate->hw_enabled;
+}
+
+static gboolean
+radio_enabled_for_type (NMManager *self, RfKillType rtype)
+{
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+
+	return radio_enabled_for_rstate (&priv->radio_states[rtype]);
+}
+
 static void
-manager_set_radio_enabled (NMManager *manager,
-                           RadioState *rstate,
-                           gboolean enabled)
+manager_update_radio_enabled (NMManager *self, RadioState *rstate)
 {
-	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	GSList *iter;
-	GError *error = NULL;
 
 	/* Do nothing for radio types not yet implemented */
 	if (!rstate->prop)
 		return;
 
-	if (rstate->enabled == enabled)
-		return;
-
-	/* Can't set wireless enabled if it's disabled in hardware */
-	if (!rstate->hw_enabled && enabled)
-		return;
-
-	rstate->enabled = enabled;
-
-	g_object_notify (G_OBJECT (manager), rstate->prop);
-
-	/* Update enabled key in state file */
-	if (priv->state_file) {
-		if (!write_value_to_state_file (priv->state_file,
-		                                "main", rstate->key,
-		                                G_TYPE_BOOLEAN, (gpointer) &enabled,
-		                                &error)) {
-			nm_log_warn (LOGD_CORE, "writing to state file %s failed: (%d) %s.",
-			             priv->state_file,
-			             error ? error->code : -1,
-			             (error && error->message) ? error->message : "unknown");
-		}
-	}
+	g_object_notify (G_OBJECT (self), rstate->prop);
 
 	/* Don't touch devices if asleep/networking disabled */
-	if (manager_sleeping (manager))
+	if (manager_sleeping (self))
 		return;
 
 	/* enable/disable wireless devices as required */
 	for (iter = priv->devices; iter; iter = iter->next) {
 		RfKillType devtype = RFKILL_TYPE_UNKNOWN;
+		gboolean enabled = radio_enabled_for_rstate (rstate);
 
 		g_object_get (G_OBJECT (iter->data), NM_DEVICE_INTERFACE_RFKILL_TYPE, &devtype, NULL);
 		if (devtype == rstate->rtype) {
@@ -1377,6 +1777,21 @@ nm_manager_get_modem_enabled_state (NMManager *self)
 }
 
 static void
+update_rstate_from_rfkill (RadioState *rstate, RfKillState rfkill)
+{
+	if (rfkill == RFKILL_UNBLOCKED) {
+		rstate->sw_enabled = TRUE;
+		rstate->hw_enabled = TRUE;
+	} else if (rfkill == RFKILL_SOFT_BLOCKED) {
+		rstate->sw_enabled = FALSE;
+		rstate->hw_enabled = TRUE;
+	} else if (rfkill == RFKILL_HARD_BLOCKED) {
+		rstate->sw_enabled = FALSE;
+		rstate->hw_enabled = FALSE;
+	}
+}
+
+static void
 manager_rfkill_update_one_type (NMManager *self,
                                 RadioState *rstate,
                                 RfKillType rtype)
@@ -1385,7 +1800,12 @@ manager_rfkill_update_one_type (NMManager *self,
 	RfKillState udev_state = RFKILL_UNBLOCKED;
 	RfKillState other_state = RFKILL_UNBLOCKED;
 	RfKillState composite;
-	gboolean new_e = TRUE, new_he = TRUE;
+	gboolean old_enabled, new_enabled, old_rfkilled, new_rfkilled;
+	gboolean old_hwe;
+
+	old_enabled = radio_enabled_for_rstate (rstate);
+	old_rfkilled = rstate->hw_enabled && rstate->sw_enabled;
+	old_hwe = rstate->hw_enabled;
 
 	udev_state = nm_udev_manager_get_rfkill_state (priv->udev_mgr, rtype);
 
@@ -1400,38 +1820,31 @@ manager_rfkill_update_one_type (NMManager *self,
 	else
 		composite = RFKILL_UNBLOCKED;
 
-	switch (composite) {
-	case RFKILL_UNBLOCKED:
-		new_e = TRUE;
-		new_he = TRUE;
-		break;
-	case RFKILL_SOFT_BLOCKED:
-		new_e = FALSE;
-		new_he = TRUE;
-		break;
-	case RFKILL_HARD_BLOCKED:
-		new_e = FALSE;
-		new_he = FALSE;
-		break;
-	default:
-		break;
-	}
+	update_rstate_from_rfkill (rstate, composite);
 
 	if (rstate->desc) {
-		nm_log_dbg (LOGD_RFKILL, "%s hw-enabled %d enabled %d",
-		            rstate->desc, new_he, new_e);
+		nm_log_dbg (LOGD_RFKILL, "%s hw-enabled %d sw-enabled %d",
+		            rstate->desc, rstate->hw_enabled, rstate->sw_enabled);
 	}
 
-	if (new_he != rstate->hw_enabled) {
+	/* Log new killswitch state */
+	new_rfkilled = rstate->hw_enabled && rstate->sw_enabled;
+	if (old_rfkilled != new_rfkilled) {
 		nm_log_info (LOGD_RFKILL, "%s now %s by radio killswitch",
 		             rstate->desc,
-		             (new_e && new_he) ? "enabled" : "disabled");
+		             new_rfkilled ? "enabled" : "disabled");
+	}
 
-		rstate->hw_enabled = new_he;
+	/* Send out property changed signal for HW enabled */
+	if (rstate->hw_enabled != old_hwe) {
 		if (rstate->hw_prop)
 			g_object_notify (G_OBJECT (self), rstate->hw_prop);
 	}
-	manager_set_radio_enabled (self, rstate, new_e);
+
+	/* And finally update the actual device radio state itself */
+	new_enabled = radio_enabled_for_rstate (rstate);
+	if (new_enabled != old_enabled)
+		manager_update_radio_enabled (self, rstate);
 }
 
 static void
@@ -1464,6 +1877,172 @@ manager_modem_enabled_changed (NMModem *device, gpointer user_data)
 	nm_manager_rfkill_update (NM_MANAGER (user_data), RFKILL_TYPE_WWAN);
 }
 
+static GError *
+deactivate_disconnect_check_error (GError *auth_error,
+                                   NMAuthCallResult result,
+                                   const char *detail)
+{
+	if (auth_error) {
+		nm_log_dbg (LOGD_CORE, "%s request failed: %s", detail, auth_error->message);
+		return g_error_new (NM_MANAGER_ERROR,
+		                    NM_MANAGER_ERROR_PERMISSION_DENIED,
+		                    "%s request failed: %s",
+		                    detail, auth_error->message);
+	} else if (result != NM_AUTH_CALL_RESULT_YES) {
+		return g_error_new (NM_MANAGER_ERROR,
+		                    NM_MANAGER_ERROR_PERMISSION_DENIED,
+		                    "Not authorized to %s user connections",
+		                    detail);
+	}
+	return NULL;
+}
+
+static void
+disconnect_user_auth_done_cb (NMAuthChain *chain,
+                              GError *error,
+                              DBusGMethodInvocation *context,
+                              gpointer user_data)
+{
+	NMManager *self = NM_MANAGER (user_data);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	GError *ret_error = NULL;
+	NMAuthCallResult result;
+	NMDevice *device;
+
+	priv->auth_chains = g_slist_remove (priv->auth_chains, chain);
+
+	result = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, NM_AUTH_PERMISSION_USE_USER_CONNECTIONS));
+	ret_error = deactivate_disconnect_check_error (error, result, "Disconnect");
+	if (!ret_error) {
+		/* Everything authorized, deactivate the connection */
+		device = nm_auth_chain_get_data (chain, "device");
+		if (nm_device_interface_disconnect (NM_DEVICE_INTERFACE (device), &ret_error))
+			dbus_g_method_return (context);
+	}
+
+	if (ret_error)
+		dbus_g_method_return_error (context, ret_error);
+	g_clear_error (&ret_error);
+
+	nm_auth_chain_unref (chain);
+}
+
+static void
+disconnect_net_auth_done_cb (NMAuthChain *chain,
+                             GError *error,
+                             DBusGMethodInvocation *context,
+                             gpointer user_data)
+{
+	NMManager *self = NM_MANAGER (user_data);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	GError *ret_error = NULL;
+	NMAuthCallResult result;
+	NMConnectionScope scope;
+	NMDevice *device;
+
+	priv->auth_chains = g_slist_remove (priv->auth_chains, chain);
+
+	result = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL));
+	ret_error = deactivate_disconnect_check_error (error, result, "Disconnect");
+	if (ret_error) {
+		dbus_g_method_return_error (context, ret_error);
+		g_error_free (ret_error);
+		goto done;
+	}
+
+	/* If it's a system connection, we're done */
+	device = nm_auth_chain_get_data (chain, "device");
+	scope = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "scope"));
+	if (scope == NM_CONNECTION_SCOPE_USER) {
+		NMAuthChain *user_chain;
+
+		/* It's a user connection, so we need to ensure the caller is
+		 * authorized to manipulate user connections.
+		 */
+		user_chain = nm_auth_chain_new (priv->authority, context, NULL, disconnect_user_auth_done_cb, self);
+		g_assert (user_chain);
+		priv->auth_chains = g_slist_append (priv->auth_chains, user_chain);
+
+		nm_auth_chain_set_data (user_chain, "device", g_object_ref (device), g_object_unref);
+		nm_auth_chain_set_data (user_chain, "scope", GUINT_TO_POINTER (scope), NULL);
+		nm_auth_chain_add_call (user_chain, NM_AUTH_PERMISSION_USE_USER_CONNECTIONS, TRUE);
+	} else {
+		if (!nm_device_interface_disconnect (NM_DEVICE_INTERFACE (device), &ret_error)) {
+			dbus_g_method_return_error (context, ret_error);
+			g_clear_error (&ret_error);
+		} else
+			dbus_g_method_return (context);
+	}
+
+done:
+	nm_auth_chain_unref (chain);
+}
+
+static void
+manager_device_disconnect_request (NMDevice *device,
+                                   DBusGMethodInvocation *context,
+                                   NMManager *self)
+{
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	NMActRequest *req;
+	NMConnection *connection;
+	GError *error = NULL;
+	NMConnectionScope scope;
+	gulong sender_uid = G_MAXULONG;
+	const char *error_desc = NULL;
+
+	req = nm_device_get_act_request (device);
+	if (!req) {
+		error = g_error_new_literal (NM_MANAGER_ERROR,
+		                             NM_MANAGER_ERROR_UNKNOWN_CONNECTION,
+		                             "This device is not active");
+		dbus_g_method_return_error (context, error);
+		g_error_free (error);
+		return;
+	}
+
+	connection = nm_act_request_get_connection (req);
+	g_assert (connection);
+
+	/* Need to check the caller's permissions and stuff before we can
+	 * deactivate the connection.
+	 */
+	scope = nm_connection_get_scope (connection);
+	if (!check_user_authorized (priv->dbus_mgr,
+	                            priv->user_proxy,
+	                            context,
+	                            scope,
+	                            &sender_uid,
+	                            &error_desc)) {
+		error = g_error_new_literal (NM_MANAGER_ERROR,
+		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
+		                             error_desc);
+		dbus_g_method_return_error (context, error);
+		g_error_free (error);
+		return;
+	}
+
+	/* Yay for root */
+	if (0 == sender_uid) {
+		if (!nm_device_interface_disconnect (NM_DEVICE_INTERFACE (device), &error)) {
+			dbus_g_method_return_error (context, error);
+			g_clear_error (&error);
+		} else
+			dbus_g_method_return (context);
+	} else {
+		NMAuthChain *chain;
+
+		/* Otherwise validate the user request */
+		chain = nm_auth_chain_new (priv->authority, context, NULL, disconnect_net_auth_done_cb, self);
+		g_assert (chain);
+		priv->auth_chains = g_slist_append (priv->auth_chains, chain);
+
+		nm_auth_chain_set_data (chain, "device", g_object_ref (device), g_object_unref);
+		nm_auth_chain_set_data (chain, "scope", GUINT_TO_POINTER (scope), NULL);
+		nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL, TRUE);
+	}
+}
+
 static void
 add_device (NMManager *self, NMDevice *device)
 {
@@ -1475,7 +2054,7 @@ add_device (NMManager *self, NMDevice *device)
 	NMConnection *existing = NULL;
 	GHashTableIter iter;
 	gpointer value;
-	gboolean managed = FALSE;
+	gboolean managed = FALSE, enabled = FALSE;
 
 	iface = nm_device_get_ip_iface (device);
 	g_assert (iface);
@@ -1491,6 +2070,10 @@ add_device (NMManager *self, NMDevice *device)
 					  G_CALLBACK (manager_device_state_changed),
 					  self);
 
+	g_signal_connect (device, NM_DEVICE_INTERFACE_DISCONNECT_REQUEST,
+					  G_CALLBACK (manager_device_disconnect_request),
+					  self);
+
 	if (NM_IS_DEVICE_WIFI (device)) {
 		/* Attach to the access-point-added signal so that the manager can fill
 		 * non-SSID-broadcasting APs with an SSID.
@@ -1510,14 +2093,15 @@ add_device (NMManager *self, NMDevice *device)
 		 * then set this device's rfkill state based on the global state.
 		 */
 		nm_manager_rfkill_update (self, RFKILL_TYPE_WLAN);
-		nm_device_interface_set_enabled (NM_DEVICE_INTERFACE (device),
-		                                 priv->radio_states[RFKILL_TYPE_WLAN].enabled);
+		enabled = radio_enabled_for_type (self, RFKILL_TYPE_WLAN);
+		nm_device_interface_set_enabled (NM_DEVICE_INTERFACE (device), enabled);
 	} else if (NM_IS_DEVICE_MODEM (device)) {
 		g_signal_connect (device, NM_DEVICE_MODEM_ENABLE_CHANGED,
 		                  G_CALLBACK (manager_modem_enabled_changed),
 		                  self);
 
 		nm_manager_rfkill_update (self, RFKILL_TYPE_WWAN);
+		enabled = radio_enabled_for_type (self, RFKILL_TYPE_WWAN);
 		/* Until we start respecting WWAN rfkill switches the modem itself
 		 * is the source of the enabled/disabled state, so the manager shouldn't
 		 * touch it here.
@@ -1545,7 +2129,7 @@ add_device (NMManager *self, NMDevice *device)
 	/* Check if we should assume the device's active connection by matching its
 	 * config with an existing system connection.
 	 */
-	if (nm_device_interface_can_assume_connection (NM_DEVICE_INTERFACE (device))) {
+	if (nm_device_interface_can_assume_connections (NM_DEVICE_INTERFACE (device))) {
 		GSList *connections = NULL;
 
 		g_hash_table_iter_init (&iter, priv->system_connections);
@@ -1705,7 +2289,7 @@ bluez_manager_resync_devices (NMManager *self)
 		priv->devices = keep;
 
 		while (g_slist_length (gone))
-			gone = remove_one_device (self, gone, NM_DEVICE (gone->data), FALSE, TRUE);
+			gone = remove_one_device (self, gone, NM_DEVICE (gone->data), FALSE);
 	} else {
 		g_slist_free (keep);
 		g_slist_free (gone);
@@ -1774,7 +2358,7 @@ bluez_manager_bdaddr_removed_cb (NMBluezManager *bluez_mgr,
 		NMDevice *device = NM_DEVICE (iter->data);
 
 		if (!strcmp (nm_device_get_udi (device), object_path)) {
-			priv->devices = remove_one_device (self, priv->devices, device, FALSE, TRUE);
+			priv->devices = remove_one_device (self, priv->devices, device, FALSE);
 			break;
 		}
 	}
@@ -1843,7 +2427,7 @@ udev_device_removed_cb (NMUdevManager *manager,
 	ifindex = g_udev_device_get_property_as_int (udev_device, "IFINDEX");
 	device = find_device_by_ifindex (self, ifindex);
 	if (device)
-		priv->devices = remove_one_device (self, priv->devices, device, FALSE, TRUE);
+		priv->devices = remove_one_device (self, priv->devices, device, FALSE);
 }
 
 static void
@@ -2240,25 +2824,21 @@ internal_activate_device (NMManager *manager,
 static gboolean
 wait_for_connection_expired (gpointer data)
 {
-	NMManager *manager = NM_MANAGER (data);
-	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
-	PendingConnectionInfo *info = priv->pending_connection_info;
+	PendingActivation *pending = data;
 	GError *error = NULL;
 
-	g_return_val_if_fail (info != NULL, FALSE);
+	g_return_val_if_fail (pending != NULL, FALSE);
 
-	g_set_error (&error,
-	             NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_CONNECTION,
-	             "%s", "Connection was not provided by any settings service");
-	nm_log_warn (LOGD_CORE, "connection (%d) %s failed to activate (timeout): (%d) %s",
-	             info->scope, info->connection_path, error->code, error->message);
-	dbus_g_method_return_error (info->context, error);
-	g_error_free (error);
+	nm_log_warn (LOGD_CORE, "connection %s (scope %d) failed to activate (timeout)",
+	             pending->connection_path, pending->scope);
 
-	info->timeout_id = 0;
-	pending_connection_info_destroy (priv->pending_connection_info);
-	priv->pending_connection_info = NULL;
+	nm_manager_pending_activation_remove (pending->manager, pending);
 
+	error = g_error_new_literal (NM_MANAGER_ERROR,
+	                             NM_MANAGER_ERROR_UNKNOWN_CONNECTION,
+	                             "Connection was not provided by any settings service");
+	pending_activation_destroy (pending, error, NULL);
+	g_error_free (error);
 	return FALSE;
 }
 
@@ -2274,7 +2854,7 @@ nm_manager_activate_connection (NMManager *manager,
 	NMDevice *device = NULL;
 	NMSettingConnection *s_con;
 	NMVPNConnection *vpn_connection;
-	const char *path;
+	const char *path = NULL;
 
 	g_return_val_if_fail (manager != NULL, NULL);
 	g_return_val_if_fail (connection != NULL, NULL);
@@ -2331,11 +2911,13 @@ nm_manager_activate_connection (NMManager *manager,
 		                                                     req,
 		                                                     device,
 		                                                     error);
-		g_signal_connect (vpn_connection, "manager-get-secrets",
-		                  G_CALLBACK (provider_get_secrets), manager);
-		g_signal_connect (vpn_connection, "manager-cancel-secrets",
-		                  G_CALLBACK (provider_cancel_secrets), manager);
-		path = nm_vpn_connection_get_active_connection_path (vpn_connection);
+		if (vpn_connection) {
+			g_signal_connect (vpn_connection, "manager-get-secrets",
+			                  G_CALLBACK (provider_get_secrets), manager);
+			g_signal_connect (vpn_connection, "manager-cancel-secrets",
+			                  G_CALLBACK (provider_cancel_secrets), manager);
+			path = nm_vpn_connection_get_active_connection_path (vpn_connection);
+		}
 		g_object_unref (vpn_manager);
 	} else {
 		NMDeviceState state;
@@ -2353,7 +2935,7 @@ nm_manager_activate_connection (NMManager *manager,
 		if (state < NM_DEVICE_STATE_DISCONNECTED) {
 			g_set_error (error,
 			             NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNMANAGED_DEVICE,
-			             "%s", "Device not managed by NetworkManager");
+			             "%s", "Device not managed by NetworkManager or unavailable");
 			return NULL;
 		}
 
@@ -2369,229 +2951,145 @@ nm_manager_activate_connection (NMManager *manager,
 	return path;
 }
 
+static PendingActivation *
+nm_manager_pending_activation_find (NMManager *self,
+                                    const char *path,
+                                    NMConnectionScope scope)
+{
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	GSList *iter;
+
+	for (iter = priv->pending_activations; iter; iter = g_slist_next (iter)) {
+		PendingActivation *pending = iter->data;
+
+		if (!strcmp (pending->connection_path, path) && (pending->scope == scope))
+			return pending;
+	}
+	return NULL;
+}
+
 static void
-connection_added_default_handler (NMManager *manager,
-						    NMConnection *connection,
-						    NMConnectionScope scope)
+check_pending_ready (NMManager *self, PendingActivation *pending)
 {
-	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
-	PendingConnectionInfo *info = priv->pending_connection_info;
-	const char *path;
+	NMConnection *connection;
+	const char *path = NULL;
 	GError *error = NULL;
 
-	if (!info)
+	if (!pending->have_connection || !pending->authorized)
 		return;
 
-	if (scope != info->scope)
-		return;
+	/* Ok, we're authorized and the connection is available */
 
-	if (strcmp (info->connection_path, nm_connection_get_path (connection)))
-		return;
+	nm_manager_pending_activation_remove (self, pending);
 
-	/* Will destroy below; can't be valid during the initial activation start */
-	priv->pending_connection_info = NULL;
+	connection = nm_manager_get_connection_by_object_path (self,
+	                                                       pending->scope,
+	                                                       pending->connection_path);
+	if (!connection) {
+		error = g_error_new_literal (NM_MANAGER_ERROR,
+		                             NM_MANAGER_ERROR_UNKNOWN_CONNECTION,
+		                             "Connection could not be found.");
+		goto out;
+	}
 
-	path = nm_manager_activate_connection (manager,
+	path = nm_manager_activate_connection (self,
 	                                       connection,
-	                                       info->specific_object_path,
-	                                       info->device_path,
+	                                       pending->specific_object_path,
+	                                       pending->device_path,
 	                                       TRUE,
 	                                       &error);
-	if (path) {
-		dbus_g_method_return (info->context, path);
-		g_object_notify (G_OBJECT (manager), NM_MANAGER_ACTIVE_CONNECTIONS);
-	} else {
-		dbus_g_method_return_error (info->context, error);
+	if (!path) {
 		nm_log_warn (LOGD_CORE, "connection (%d) %s failed to activate: (%d) %s",
-		             scope, info->connection_path, error->code, error->message);
-		g_error_free (error);
-	}
+		             pending->scope, pending->connection_path, error->code, error->message);
+	} else
+		g_object_notify (G_OBJECT (pending->manager), NM_MANAGER_ACTIVE_CONNECTIONS);
 
-	pending_connection_info_destroy (info);
+out:
+	pending_activation_destroy (pending, error, path);
+	g_clear_error (&error);
 }
 
-static gboolean
-is_user_request_authorized (NMManager *manager,
-                            DBusGMethodInvocation *context,
-                            GError **error)
+static void
+connection_added_default_handler (NMManager *self,
+                                  NMConnection *connection,
+                                  NMConnectionScope scope)
 {
-	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
-	DBusConnection *connection;
-	char *sender = NULL;
-	gulong sender_uid = G_MAXULONG;
-	DBusError dbus_error;
-	char *service_owner = NULL;
-	const char *service_name;
-	gulong service_uid = G_MAXULONG;
-	gboolean success = FALSE;
-
-	/* Ensure the request to activate the user connection came from the
-	 * same session as the user settings service.  FIXME: use ConsoleKit
-	 * too.
-	 */
-	if (!priv->user_proxy) {
-		g_set_error (error, NM_MANAGER_ERROR,
-		             NM_MANAGER_ERROR_INVALID_SERVICE,
-		             "%s", "No user settings service available");
-		goto out;
-	}
-
-	sender = dbus_g_method_get_sender (context);
-	if (!sender) {
-		g_set_error (error, NM_MANAGER_ERROR,
-		             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		             "%s", "Could not determine D-Bus requestor");
-		goto out;
-	}
-
-	connection = nm_dbus_manager_get_dbus_connection (priv->dbus_mgr);
-	if (!connection) {
-		g_set_error (error, NM_MANAGER_ERROR,
-		             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		             "%s", "Could not get the D-Bus system bus");
-		goto out;
-	}
-
-	dbus_error_init (&dbus_error);
-	/* FIXME: do this async */
-	sender_uid = dbus_bus_get_unix_user (connection, sender, &dbus_error);
-	if (dbus_error_is_set (&dbus_error)) {
-		dbus_error_free (&dbus_error);
-		g_set_error (error, NM_MANAGER_ERROR,
-		             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		             "%s", "Could not determine the Unix user ID of the requestor");
-		goto out;
-	}
-
-	/* Let root activate anything.
-	 * FIXME: use a PolicyKit permission instead
-	 */
-	if (0 == sender_uid) {
-		success = TRUE;
-		goto out;
-	}
-
-	service_name = dbus_g_proxy_get_bus_name (priv->user_proxy);
-	if (!service_name) {
-		g_set_error (error, NM_MANAGER_ERROR,
-		             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		             "%s", "Could not determine user settings service name");
-		goto out;
+	PendingActivation *pending;
+
+	pending = nm_manager_pending_activation_find (self,
+	                                              nm_connection_get_path (connection),
+	                                              scope);
+	if (pending) {
+		pending->have_connection = TRUE;
+		check_pending_ready (self, pending);
 	}
+}
 
-	service_owner = nm_dbus_manager_get_name_owner (priv->dbus_mgr, service_name, NULL);
-	if (!service_owner) {
-		g_set_error (error, NM_MANAGER_ERROR,
-		             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		             "%s", "Could not determine D-Bus owner of the user settings service");
-		goto out;
-	}
+static void
+activation_auth_done (PendingActivation *pending, GError *error)
+{
+	if (error) {
+		nm_manager_pending_activation_remove (pending->manager, pending);
+		pending_activation_destroy (pending, error, NULL);
+		return;
+	} else {
+		pending->authorized = TRUE;
 
-	dbus_error_init (&dbus_error);
-	/* FIXME: do this async */
-	service_uid = dbus_bus_get_unix_user (connection, service_owner, &dbus_error);
-	if (dbus_error_is_set (&dbus_error)) {
-		dbus_error_free (&dbus_error);
-		g_set_error (error, NM_MANAGER_ERROR,
-		             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		             "%s", "Could not determine the Unix UID of the sender of the request");
-		goto out;
-	}
+		/* Now that we're authorized, if the connection hasn't shown up yet,
+		 * start a timer and wait for it.
+		 */
+		if (!pending->have_connection && !pending->timeout_id)
+			pending->timeout_id = g_timeout_add_seconds (5, wait_for_connection_expired, pending);
 
-	/* And finally, the actual UID check */
-	if (sender_uid != service_uid) {
-		g_set_error (error, NM_MANAGER_ERROR,
-		             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		             "%s", "Requestor UID does not match the UID of the user settings service");
-		goto out;
+		check_pending_ready (pending->manager, pending);
 	}
-
-	success = TRUE;
-
-out:
-	g_free (sender);
-	g_free (service_owner);
-	return success;
 }
 
 static void
-impl_manager_activate_connection (NMManager *manager,
+impl_manager_activate_connection (NMManager *self,
                                   const char *service_name,
                                   const char *connection_path,
                                   const char *device_path,
                                   const char *specific_object_path,
                                   DBusGMethodInvocation *context)
 {
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMConnectionScope scope = NM_CONNECTION_SCOPE_UNKNOWN;
-	NMConnection *connection;
+	PendingActivation *pending;
 	GError *error = NULL;
-	char *real_sop = NULL;
-	char *path = NULL;
-
-	if (!strcmp (service_name, NM_DBUS_SERVICE_USER_SETTINGS)) {
-		if (!is_user_request_authorized (manager, context, &error))
-			goto err;
 
+	if (!strcmp (service_name, NM_DBUS_SERVICE_USER_SETTINGS))
 		scope = NM_CONNECTION_SCOPE_USER;
-	} else if (!strcmp (service_name, NM_DBUS_SERVICE_SYSTEM_SETTINGS))
+	else if (!strcmp (service_name, NM_DBUS_SERVICE_SYSTEM_SETTINGS))
 		scope = NM_CONNECTION_SCOPE_SYSTEM;
 	else {
-		g_set_error (&error,
-		             NM_MANAGER_ERROR, NM_MANAGER_ERROR_INVALID_SERVICE,
-		             "%s", "Invalid settings service name");
-		goto err;
-	}
-
-	/* "/" is special-cased to NULL to get through D-Bus */
-	if (specific_object_path && strcmp (specific_object_path, "/"))
-		real_sop = g_strdup (specific_object_path);
-
-	connection = nm_manager_get_connection_by_object_path (manager, scope, connection_path);
-	if (connection) {
-		path = (char *) nm_manager_activate_connection (manager,
-		                                                connection,
-		                                                real_sop,
-		                                                device_path,
-		                                                TRUE,
-		                                                &error);
-		if (path) {
-			dbus_g_method_return (context, path);
-			g_object_notify (G_OBJECT (manager), NM_MANAGER_ACTIVE_CONNECTIONS);
-		}
-	} else {
-		PendingConnectionInfo *info;
-		NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
-
-		if (priv->pending_connection_info) {
-			pending_connection_info_destroy (priv->pending_connection_info);
-			priv->pending_connection_info = NULL;
-		}
-
-		/* Don't have the connection quite yet, probably created by
-		 * the client on-the-fly.  Defer the activation until we have it
-		 */
-
-		info = g_slice_new0 (PendingConnectionInfo);
-		info->context = context;
-		info->device_path = g_strdup (device_path);
-		info->scope = scope;
-		info->connection_path = g_strdup (connection_path);
-		info->specific_object_path = g_strdup (real_sop);
-		info->timeout_id = g_timeout_add_seconds (5, wait_for_connection_expired, manager);
-
-		// FIXME: should probably be per-device, not global to the manager
-		NM_MANAGER_GET_PRIVATE (manager)->pending_connection_info = info;
-	}
-
- err:
-	if (error) {
+		error = g_error_new_literal (NM_MANAGER_ERROR,
+		                             NM_MANAGER_ERROR_INVALID_SERVICE,
+		                             "Invalid settings service name");
 		dbus_g_method_return_error (context, error);
 		nm_log_warn (LOGD_CORE, "connection (%d) %s failed to activate: (%d) %s",
 		             scope, connection_path, error->code, error->message);
 		g_error_free (error);
+		return;
 	}
 
-	g_free (real_sop);
+	/* Need to check the caller's permissions and stuff before we can
+	 * activate the connection.
+	 */
+	pending = pending_activation_new (self,
+	                                  priv->authority,
+	                                  context,
+	                                  device_path,
+	                                  scope,
+	                                  connection_path,
+	                                  specific_object_path,
+	                                  activation_auth_done);
+	priv->pending_activations = g_slist_prepend (priv->pending_activations, pending);
+
+	if (nm_manager_get_connection_by_object_path (self, scope, connection_path))
+		pending->have_connection = TRUE;
+
+	pending_activation_check_authorized (pending, priv->dbus_mgr, priv->user_proxy);
 }
 
 gboolean
@@ -2642,15 +3140,173 @@ done:
 	return success;
 }
 
-static gboolean
-impl_manager_deactivate_connection (NMManager *manager,
-                                    const char *connection_path,
-                                    GError **error)
+static void
+deactivate_user_auth_done_cb (NMAuthChain *chain,
+                              GError *error,
+                              DBusGMethodInvocation *context,
+                              gpointer user_data)
 {
-	return nm_manager_deactivate_connection (manager,
-	                                         connection_path,
-	                                         NM_DEVICE_STATE_REASON_USER_REQUESTED,
-	                                         error);
+	NMManager *self = NM_MANAGER (user_data);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	GError *ret_error = NULL;
+	NMAuthCallResult result;
+
+	priv->auth_chains = g_slist_remove (priv->auth_chains, chain);
+
+	result = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, NM_AUTH_PERMISSION_USE_USER_CONNECTIONS));
+	ret_error = deactivate_disconnect_check_error (error, result, "Deactivate");
+	if (!ret_error) {
+		/* Everything authorized, deactivate the connection */
+		if (nm_manager_deactivate_connection (self,
+		                                      nm_auth_chain_get_data (chain, "path"),
+		                                      NM_DEVICE_STATE_REASON_USER_REQUESTED,
+		                                      &ret_error))
+			dbus_g_method_return (context);
+	}
+
+	if (ret_error)
+		dbus_g_method_return_error (context, ret_error);
+	g_clear_error (&ret_error);
+
+	nm_auth_chain_unref (chain);
+}
+
+static void
+deactivate_net_auth_done_cb (NMAuthChain *chain,
+                             GError *error,
+                             DBusGMethodInvocation *context,
+                             gpointer user_data)
+{
+	NMManager *self = NM_MANAGER (user_data);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	GError *ret_error = NULL;
+	NMAuthCallResult result;
+	const char *active_path;
+	NMConnectionScope scope;
+
+	priv->auth_chains = g_slist_remove (priv->auth_chains, chain);
+
+	result = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL));
+	ret_error = deactivate_disconnect_check_error (error, result, "Deactivate");
+	if (ret_error) {
+		dbus_g_method_return_error (context, ret_error);
+		g_error_free (ret_error);
+		goto done;
+	}
+
+	/* If it's a system connection, we're done */
+	active_path = nm_auth_chain_get_data (chain, "path");
+	scope = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "scope"));
+	if (scope == NM_CONNECTION_SCOPE_USER) {
+		NMAuthChain *user_chain;
+
+		/* It's a user connection, so we need to ensure the caller is
+		 * authorized to manipulate user connections.
+		 */
+		user_chain = nm_auth_chain_new (priv->authority, context, NULL, deactivate_user_auth_done_cb, self);
+		g_assert (user_chain);
+		priv->auth_chains = g_slist_append (priv->auth_chains, user_chain);
+
+		nm_auth_chain_set_data (user_chain, "path", g_strdup (active_path), g_free);
+		nm_auth_chain_set_data (user_chain, "scope", GUINT_TO_POINTER (scope), NULL);
+		nm_auth_chain_add_call (user_chain, NM_AUTH_PERMISSION_USE_USER_CONNECTIONS, TRUE);
+	} else {
+		if (!nm_manager_deactivate_connection (self,
+		                                       active_path,
+		                                       NM_DEVICE_STATE_REASON_USER_REQUESTED,
+		                                       &ret_error)) {
+			dbus_g_method_return_error (context, ret_error);
+			g_clear_error (&ret_error);
+		} else
+			dbus_g_method_return (context);
+	}
+
+done:
+	nm_auth_chain_unref (chain);
+}
+
+static void
+impl_manager_deactivate_connection (NMManager *self,
+                                    const char *active_path,
+                                    DBusGMethodInvocation *context)
+{
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	NMConnection *connection = NULL;
+	GError *error = NULL;
+	GSList *iter;
+	NMAuthChain *chain;
+	gulong sender_uid = G_MAXULONG;
+	NMConnectionScope scope;
+	const char *error_desc = NULL;
+
+	/* Check for device connections first */
+	for (iter = priv->devices; iter; iter = g_slist_next (iter)) {
+		NMActRequest *req;
+		const char *req_path = NULL;
+
+		req = nm_device_get_act_request (NM_DEVICE (iter->data));
+		if (req)
+			req_path = nm_act_request_get_active_connection_path (req);
+
+		if (req_path && !strcmp (active_path, req_path)) {
+			connection = nm_act_request_get_connection (req);
+			break;
+		}
+	}
+
+	/* Maybe it's a VPN */
+	if (!connection)
+		connection = nm_vpn_manager_get_connection_for_active (priv->vpn_manager, active_path);
+
+	if (!connection) {
+		error = g_error_new_literal (NM_MANAGER_ERROR,
+		                             NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE,
+		                             "The connection was not active.");
+		dbus_g_method_return_error (context, error);
+		g_error_free (error);
+		return;
+	}
+
+	/* Need to check the caller's permissions and stuff before we can
+	 * deactivate the connection.
+	 */
+	scope = nm_connection_get_scope (connection);
+	if (!check_user_authorized (priv->dbus_mgr,
+	                            priv->user_proxy,
+	                            context,
+	                            scope,
+	                            &sender_uid,
+	                            &error_desc)) {
+		error = g_error_new_literal (NM_MANAGER_ERROR,
+		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
+		                             error_desc);
+		dbus_g_method_return_error (context, error);
+		g_error_free (error);
+		return;
+	}
+
+	/* Yay for root */
+	if (0 == sender_uid) {
+		if (!nm_manager_deactivate_connection (self,
+		                                       active_path,
+		                                       NM_DEVICE_STATE_REASON_USER_REQUESTED,
+		                                       &error)) {
+			dbus_g_method_return_error (context, error);
+			g_clear_error (&error);
+		} else
+			dbus_g_method_return (context);
+
+		return;
+	}
+
+	/* Otherwise validate the user request */
+	chain = nm_auth_chain_new (priv->authority, context, NULL, deactivate_net_auth_done_cb, self);
+	g_assert (chain);
+	priv->auth_chains = g_slist_append (priv->auth_chains, chain);
+
+	nm_auth_chain_set_data (chain, "path", g_strdup (active_path), g_free);
+	nm_auth_chain_set_data (chain, "scope", GUINT_TO_POINTER (scope), NULL);
+	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL, TRUE);
 }
 
 static void
@@ -2690,13 +3346,13 @@ do_sleep_wake (NMManager *self)
 			 */
 			for (i = 0; i < RFKILL_TYPE_MAX; i++) {
 				RadioState *rstate = &priv->radio_states[i];
-				gboolean enabled = (rstate->hw_enabled && rstate->enabled);
+				gboolean enabled = radio_enabled_for_rstate (rstate);
 				RfKillType devtype = RFKILL_TYPE_UNKNOWN;
 
 				if (rstate->desc) {
-					nm_log_dbg (LOGD_RFKILL, "%s %s devices (hw_enabled %d, enabled %d)",
+					nm_log_dbg (LOGD_RFKILL, "%s %s devices (hw_enabled %d, sw_enabled %d, user_enabled %d)",
 					            enabled ? "enabling" : "disabling",
-					            rstate->desc, rstate->hw_enabled, rstate->enabled);
+					            rstate->desc, rstate->hw_enabled, rstate->sw_enabled, rstate->user_enabled);
 				}
 
 				g_object_get (G_OBJECT (device), NM_DEVICE_INTERFACE_RFKILL_TYPE, &devtype, NULL);
@@ -2719,20 +3375,31 @@ do_sleep_wake (NMManager *self)
 }
 
 static gboolean
-impl_manager_sleep (NMManager *self, gboolean do_sleep, GError **error)
+return_no_pk_error (PolkitAuthority *authority,
+                    const char *detail,
+                    DBusGMethodInvocation *context)
 {
-	NMManagerPrivate *priv;
-
-	g_return_val_if_fail (NM_IS_MANAGER (self), FALSE);
+	GError *error;
 
-	priv = NM_MANAGER_GET_PRIVATE (self);
-
-	if (priv->sleeping == do_sleep) {
-		g_set_error (error,
-		             NM_MANAGER_ERROR, NM_MANAGER_ERROR_ALREADY_ASLEEP_OR_AWAKE,
-		             "Already %s", do_sleep ? "asleep" : "awake");
+	if (!authority) {
+		error = g_error_new (NM_MANAGER_ERROR,
+		                     NM_MANAGER_ERROR_PERMISSION_DENIED,
+		                     "%s request failed: PolicyKit not initialized",
+		                     detail);
+		dbus_g_method_return_error (context, error);
+		g_error_free (error);
 		return FALSE;
 	}
+	return TRUE;
+}
+
+static void
+_internal_sleep (NMManager *self, gboolean do_sleep)
+{
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+
+	if (priv->sleeping == do_sleep)
+		return;
 
 	nm_log_info (LOGD_SUSPEND, "%s requested (sleeping: %s  enabled: %s)",
 	             do_sleep ? "sleep" : "wake",
@@ -2744,29 +3411,138 @@ impl_manager_sleep (NMManager *self, gboolean do_sleep, GError **error)
 	do_sleep_wake (self);
 
 	g_object_notify (G_OBJECT (self), NM_MANAGER_SLEEPING);
-	return TRUE;
 }
 
-static gboolean
-impl_manager_enable (NMManager *self, gboolean enable, GError **error)
+#if 0
+static void
+sleep_auth_done_cb (NMAuthChain *chain,
+                    GError *error,
+                    DBusGMethodInvocation *context,
+                    gpointer user_data)
+{
+	NMManager *self = NM_MANAGER (user_data);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	GError *ret_error;
+	NMAuthCallResult result;
+	gboolean do_sleep;
+
+	priv->auth_chains = g_slist_remove (priv->auth_chains, chain);
+
+	result = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, NM_AUTH_PERMISSION_SLEEP_WAKE));
+	if (error) {
+		nm_log_dbg (LOGD_SUSPEND, "Sleep/wake request failed: %s", error->message);
+		ret_error = g_error_new (NM_MANAGER_ERROR,
+		                         NM_MANAGER_ERROR_PERMISSION_DENIED,
+		                         "Sleep/wake request failed: %s",
+		                         error->message);
+		dbus_g_method_return_error (context, ret_error);
+		g_error_free (ret_error);
+	} else if (result != NM_AUTH_CALL_RESULT_YES) {
+		ret_error = g_error_new_literal (NM_MANAGER_ERROR,
+		                                 NM_MANAGER_ERROR_PERMISSION_DENIED,
+		                                 "Not authorized to sleep/wake");
+		dbus_g_method_return_error (context, ret_error);
+		g_error_free (ret_error);
+	} else {
+		/* Auth success */
+		do_sleep = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "sleep"));
+		_internal_sleep (self, do_sleep);
+		dbus_g_method_return (context);
+	}
+
+	nm_auth_chain_unref (chain);
+}
+#endif
+
+static void
+impl_manager_sleep (NMManager *self,
+                    gboolean do_sleep,
+                    DBusGMethodInvocation *context)
 {
 	NMManagerPrivate *priv;
+	GError *error = NULL;
+#if 0
+	NMAuthChain *chain;
+	gulong sender_uid = G_MAXULONG;
+	const char *error_desc = NULL;
+#endif
 
-	g_return_val_if_fail (NM_IS_MANAGER (self), FALSE);
+	g_return_if_fail (NM_IS_MANAGER (self));
 
 	priv = NM_MANAGER_GET_PRIVATE (self);
 
-	if (priv->net_enabled == enable) {
-		g_set_error (error,
-		             NM_MANAGER_ERROR, NM_MANAGER_ERROR_ALREADY_ENABLED_OR_DISABLED,
-		             "Already %s", enable ? "enabled" : "disabled");
-		return FALSE;
+	if (priv->sleeping == do_sleep) {
+		error = g_error_new (NM_MANAGER_ERROR,
+		                     NM_MANAGER_ERROR_ALREADY_ASLEEP_OR_AWAKE,
+		                     "Already %s", do_sleep ? "asleep" : "awake");
+		dbus_g_method_return_error (context, error);
+		g_error_free (error);
+		return;
+	}
+
+	/* Unconditionally allow the request.  Previously it was polkit protected
+	 * but unfortunately that doesn't work for short-lived processes like
+	 * pm-utils.  It uses dbus-send without --print-reply, which quits
+	 * immediately after sending the request, and NM is unable to obtain the
+	 * sender's UID as dbus-send has already dropped off the bus.  Thus NM
+	 * fails the request.  Instead, don't validate the request, but rely on
+	 * D-Bus permissions to restrict the call to root.
+	 */
+	_internal_sleep (self, do_sleep);
+	dbus_g_method_return (context);
+	return;
+
+#if 0
+	if (!nm_auth_get_caller_uid (context, priv->dbus_mgr, &sender_uid, &error_desc)) {
+		error = g_error_new_literal (NM_MANAGER_ERROR,
+		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
+		                             error_desc);
+		dbus_g_method_return_error (context, error);
+		g_error_free (error);
+		return;
+	}
+
+	/* Root doesn't need PK authentication */
+	if (0 == sender_uid) {
+		_internal_sleep (self, do_sleep);
+		dbus_g_method_return (context);
+		return;
 	}
 
+	if (!return_no_pk_error (priv->authority, "Sleep/wake", context))
+		return;
+
+	chain = nm_auth_chain_new (priv->authority, context, NULL, sleep_auth_done_cb, self);
+	g_assert (chain);
+	priv->auth_chains = g_slist_append (priv->auth_chains, chain);
+
+	nm_auth_chain_set_data (chain, "sleep", GUINT_TO_POINTER (do_sleep), NULL);
+	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_SLEEP_WAKE, TRUE);
+#endif
+}
+
+static void
+upower_sleeping_cb (DBusGProxy *proxy, gpointer user_data)
+{
+	nm_log_dbg (LOGD_SUSPEND, "Received UPower sleeping signal");
+	_internal_sleep (NM_MANAGER (user_data), TRUE);
+}
+
+static void
+upower_resuming_cb (DBusGProxy *proxy, gpointer user_data)
+{
+	nm_log_dbg (LOGD_SUSPEND, "Received UPower resuming signal");
+	_internal_sleep (NM_MANAGER (user_data), FALSE);
+}
+
+static void
+_internal_enable (NMManager *self, gboolean enable)
+{
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	GError *err = NULL;
+
 	/* Update "NetworkingEnabled" key in state file */
 	if (priv->state_file) {
-		GError *err = NULL;
-
 		if (!write_value_to_state_file (priv->state_file,
 		                                "main", "NetworkingEnabled",
 		                                G_TYPE_BOOLEAN, (gpointer) &enable,
@@ -2789,21 +3565,245 @@ impl_manager_enable (NMManager *self, gboolean enable, GError **error)
 	do_sleep_wake (self);
 
 	g_object_notify (G_OBJECT (self), NM_MANAGER_NETWORKING_ENABLED);
-	return TRUE;
+}
+
+static void
+enable_net_done_cb (NMAuthChain *chain,
+                    GError *error,
+                    DBusGMethodInvocation *context,
+                    gpointer user_data)
+{
+	NMManager *self = NM_MANAGER (user_data);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	GError *ret_error;
+	NMAuthCallResult result;
+	gboolean enable;
+
+	priv->auth_chains = g_slist_remove (priv->auth_chains, chain);
+
+	result = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK));
+	if (error) {
+		nm_log_dbg (LOGD_CORE, "Enable request failed: %s", error->message);
+		ret_error = g_error_new (NM_MANAGER_ERROR,
+		                         NM_MANAGER_ERROR_PERMISSION_DENIED,
+		                         "Enable request failed: %s",
+		                         error->message);
+		dbus_g_method_return_error (context, ret_error);
+		g_error_free (ret_error);
+	} else if (result != NM_AUTH_CALL_RESULT_YES) {
+		ret_error = g_error_new_literal (NM_MANAGER_ERROR,
+		                                 NM_MANAGER_ERROR_PERMISSION_DENIED,
+		                                 "Not authorized to enable/disable networking");
+		dbus_g_method_return_error (context, ret_error);
+		g_error_free (ret_error);
+	} else {
+		/* Auth success */
+		enable = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "enable"));
+		_internal_enable (self, enable);
+		dbus_g_method_return (context);
+	}
+
+	nm_auth_chain_unref (chain);
+}
+
+static void
+impl_manager_enable (NMManager *self,
+                     gboolean enable,
+                     DBusGMethodInvocation *context)
+{
+	NMManagerPrivate *priv;
+	NMAuthChain *chain;
+	GError *error = NULL;
+	gulong sender_uid = G_MAXULONG;
+	const char *error_desc = NULL;
+
+	g_return_if_fail (NM_IS_MANAGER (self));
+
+	priv = NM_MANAGER_GET_PRIVATE (self);
+
+	if (priv->net_enabled == enable) {
+		error = g_error_new (NM_MANAGER_ERROR,
+		                     NM_MANAGER_ERROR_ALREADY_ENABLED_OR_DISABLED,
+		                     "Already %s", enable ? "enabled" : "disabled");
+		dbus_g_method_return_error (context, error);
+		g_error_free (error);
+		return;
+	}
+
+	if (!nm_auth_get_caller_uid (context, priv->dbus_mgr, &sender_uid, &error_desc)) {
+		error = g_error_new_literal (NM_MANAGER_ERROR,
+		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
+		                             error_desc);
+		dbus_g_method_return_error (context, error);
+		g_error_free (error);
+		return;
+	}
+
+	/* Root doesn't need PK authentication */
+	if (0 == sender_uid) {
+		_internal_enable (self, enable);
+		dbus_g_method_return (context);
+		return;
+	}
+
+	if (!return_no_pk_error (priv->authority, "Enable/disable", context))
+		return;
+
+	chain = nm_auth_chain_new (priv->authority, context, NULL, enable_net_done_cb, self);
+	g_assert (chain);
+	priv->auth_chains = g_slist_append (priv->auth_chains, chain);
+
+	nm_auth_chain_set_data (chain, "enable", GUINT_TO_POINTER (enable), NULL);
+	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK, TRUE);
+}
+
+/* Permissions */
+
+static void
+user_proxy_permissions_changed_done (NMAuthChain *chain,
+                                     GError *error,
+                                     DBusGMethodInvocation *context,
+                                     gpointer user_data)
+{
+	NMManager *self = NM_MANAGER (user_data);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	gboolean authorized = FALSE;
+
+	priv->auth_chains = g_slist_remove (priv->auth_chains, chain);
+
+	if (error) {
+		nm_log_warn (LOGD_USER_SET, "User connections unavailable: (%d) %s",
+		             error->code, error->message ? error->message : "(unknown)");
+	} else
+		authorized = user_settings_authorized (self, chain);
+
+	if (authorized) {
+		/* User connections are authorized */
+		if (!priv->user_proxy)
+			user_proxy_init (self);
+	} else
+		user_proxy_cleanup (self, TRUE);
+
+	nm_auth_chain_unref (chain);
+}
+
+static void
+pk_authority_changed_cb (GObject *object, gpointer user_data)
+{
+	NMManager *self = NM_MANAGER (user_data);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	NMAuthChain *chain;
+
+	/* If the user settings service wasn't previously authorized, we wouldn't
+	 * care about it.  But it might be authorized now, so lets check.
+	 */
+	if (!priv->user_proxy)
+		user_proxy_init (self);
+	else {
+		/* Otherwise the user settings permissions could have changed so we
+		 * need to recheck them.
+		 */
+		chain = nm_auth_chain_new (priv->authority,
+		                           NULL,
+		                           priv->user_proxy,
+		                           user_proxy_permissions_changed_done,
+		                           self);
+		priv->auth_chains = g_slist_prepend (priv->auth_chains, chain);
+
+		nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_USE_USER_CONNECTIONS, FALSE);
+		nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL, FALSE);
+	}
+
+	/* Let clients know they should re-check their authorization */
+	g_signal_emit (NM_MANAGER (user_data), signals[CHECK_PERMISSIONS], 0);
+}
+
+static void
+get_perm_add_result (NMAuthChain *chain, GHashTable *results, const char *permission)
+{
+	NMAuthCallResult result;
+
+	result = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, permission));
+	if (result == NM_AUTH_CALL_RESULT_YES)
+		g_hash_table_insert (results, (char *) permission, "yes");
+	else if (result == NM_AUTH_CALL_RESULT_NO)
+		g_hash_table_insert (results, (char *) permission, "no");
+	else if (result == NM_AUTH_CALL_RESULT_AUTH)
+		g_hash_table_insert (results, (char *) permission, "auth");
+	else {
+		nm_log_dbg (LOGD_CORE, "unknown auth chain result %d", result);
+	}
+}
+
+static void
+get_permissions_done_cb (NMAuthChain *chain,
+                         GError *error,
+                         DBusGMethodInvocation *context,
+                         gpointer user_data)
+{
+	NMManager *self = NM_MANAGER (user_data);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	GError *ret_error;
+	GHashTable *results;
+
+	priv->auth_chains = g_slist_remove (priv->auth_chains, chain);
+	if (error) {
+		nm_log_dbg (LOGD_CORE, "Permissions request failed: %s", error->message);
+		ret_error = g_error_new (NM_MANAGER_ERROR,
+		                         NM_MANAGER_ERROR_PERMISSION_DENIED,
+		                         "Permissions request failed: %s",
+		                         error->message);
+		dbus_g_method_return_error (context, ret_error);
+		g_error_free (ret_error);
+	} else {
+		results = g_hash_table_new (g_str_hash, g_str_equal);
+		get_perm_add_result (chain, results, NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK);
+		get_perm_add_result (chain, results, NM_AUTH_PERMISSION_SLEEP_WAKE);
+		get_perm_add_result (chain, results, NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI);
+		get_perm_add_result (chain, results, NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN);
+		get_perm_add_result (chain, results, NM_AUTH_PERMISSION_USE_USER_CONNECTIONS);
+		get_perm_add_result (chain, results, NM_AUTH_PERMISSION_NETWORK_CONTROL);
+		dbus_g_method_return (context, results);
+		g_hash_table_destroy (results);
+	}
+
+	nm_auth_chain_unref (chain);
+}
+
+static void
+impl_manager_get_permissions (NMManager *self,
+                              DBusGMethodInvocation *context)
+{
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	NMAuthChain *chain;
+
+	if (!return_no_pk_error (priv->authority, "Permissions", context))
+		return;
+
+	chain = nm_auth_chain_new (priv->authority, context, NULL, get_permissions_done_cb, self);
+	g_assert (chain);
+	priv->auth_chains = g_slist_append (priv->auth_chains, chain);
+
+	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK, FALSE);
+	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_SLEEP_WAKE, FALSE);
+	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI, FALSE);
+	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN, FALSE);
+	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_USE_USER_CONNECTIONS, FALSE);
+	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL, FALSE);
 }
 
 /* Legacy 0.6 compatibility interface */
 
-static gboolean
-impl_manager_legacy_sleep (NMManager *manager, GError **error)
+static void
+impl_manager_legacy_sleep (NMManager *manager, DBusGMethodInvocation *context)
 {
-	return impl_manager_sleep (manager, TRUE, error);
+	return impl_manager_sleep (manager, TRUE, context);
 }
 
-static gboolean
-impl_manager_legacy_wake  (NMManager *manager, GError **error)
+static void
+impl_manager_legacy_wake  (NMManager *manager, DBusGMethodInvocation *context)
 {
-	return impl_manager_sleep (manager, FALSE, error);
+	return impl_manager_sleep (manager, FALSE, context);
 }
 
 static gboolean
@@ -2836,6 +3836,15 @@ impl_manager_set_logging (NMManager *manager,
 
 /* Connections */
 
+gboolean
+nm_manager_auto_user_connections_allowed (NMManager *self)
+{
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+
+	return    priv->user_net_perm == NM_AUTH_CALL_RESULT_YES
+	       && priv->user_con_perm == NM_AUTH_CALL_RESULT_YES;
+}
+
 static int
 connection_sort (gconstpointer pa, gconstpointer pb)
 {
@@ -2929,34 +3938,21 @@ nm_manager_start (NMManager *self)
 	/* Set initial radio enabled/disabled state */
 	for (i = 0; i < RFKILL_TYPE_MAX; i++) {
 		RadioState *rstate = &priv->radio_states[i];
-		gboolean enabled = TRUE, hw_enabled = TRUE;
+		RfKillState udev_state;
 
 		if (!rstate->desc)
 			continue;
 
-		switch (nm_udev_manager_get_rfkill_state (priv->udev_mgr, i)) {
-		case RFKILL_UNBLOCKED:
-			enabled = TRUE;
-			hw_enabled = TRUE;
-			break;
-		case RFKILL_SOFT_BLOCKED:
-			enabled = FALSE;
-			hw_enabled = TRUE;
-			break;
-		case RFKILL_HARD_BLOCKED:
-			enabled = FALSE;
-			hw_enabled = FALSE;
-			break;
-		default:
-			break;
-		}
+		udev_state = nm_udev_manager_get_rfkill_state (priv->udev_mgr, i);
+		update_rstate_from_rfkill (rstate, udev_state);
 
-		rstate->hw_enabled = hw_enabled;
-		nm_log_info (LOGD_RFKILL, "%s %s by radio killswitch; %s by state file",
-		             rstate->desc,
-		             (rstate->hw_enabled && enabled) ? "enabled" : "disabled",
-		             (rstate->enabled) ? "enabled" : "disabled");
-		manager_set_radio_enabled (self, rstate, rstate->enabled && enabled);
+		if (rstate->desc) {
+			nm_log_info (LOGD_RFKILL, "%s %s by radio killswitch; %s by state file",
+				         rstate->desc,
+				         (rstate->hw_enabled && rstate->sw_enabled) ? "enabled" : "disabled",
+				         rstate->user_enabled ? "enabled" : "disabled");
+		}
+		manager_update_radio_enabled (self, rstate);
 	}
 
 	/* Log overall networking status - enabled/disabled */
@@ -2972,12 +3968,226 @@ nm_manager_start (NMManager *self)
 	 * bus in nm_manager_name_owner_changed().
 	 */
 	if (nm_dbus_manager_name_has_owner (priv->dbus_mgr, NM_DBUS_SERVICE_USER_SETTINGS))
-		user_query_connections (self);
+		user_proxy_init (self);
 
 	nm_udev_manager_query_devices (priv->udev_mgr);
 	bluez_manager_resync_devices (self);
 }
 
+static gboolean
+handle_firmware_changed (gpointer user_data)
+{
+	NMManager *self = NM_MANAGER (user_data);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	GSList *iter;
+
+	priv->fw_changed_id = 0;
+
+	if (manager_sleeping (self))
+		return FALSE;
+
+	/* Try to re-enable devices with missing firmware */
+	for (iter = priv->devices; iter; iter = iter->next) {
+		NMDevice *candidate = NM_DEVICE (iter->data);
+		NMDeviceState state = nm_device_get_state (candidate);
+
+		if (   nm_device_get_firmware_missing (candidate)
+		    && (state == NM_DEVICE_STATE_UNAVAILABLE)) {
+			nm_log_info (LOGD_CORE, "(%s): firmware may now be available",
+			             nm_device_get_iface (candidate));
+
+			/* Re-set unavailable state to try bringing the device up again */
+			nm_device_state_changed (candidate,
+			                         NM_DEVICE_STATE_UNAVAILABLE,
+			                         NM_DEVICE_STATE_REASON_NONE);
+		}
+	}
+
+	return FALSE;
+}
+
+static void
+firmware_dir_changed (GFileMonitor *monitor,
+                      GFile *file,
+                      GFile *other_file,
+                      GFileMonitorEvent event_type,
+                      gpointer user_data)
+{
+	NMManager *self = NM_MANAGER (user_data);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+
+	switch (event_type) {
+	case G_FILE_MONITOR_EVENT_CREATED:
+	case G_FILE_MONITOR_EVENT_CHANGED:
+#if GLIB_CHECK_VERSION(2,23,4)
+	case G_FILE_MONITOR_EVENT_MOVED:
+#endif
+	case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
+	case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
+		if (!priv->fw_changed_id) {
+			priv->fw_changed_id = g_timeout_add_seconds (4, handle_firmware_changed, self);
+			nm_log_info (LOGD_CORE, "kernel firmware directory '%s' changed",
+			             KERNEL_FIRMWARE_DIR);
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+#define PERM_DENIED_ERROR "org.freedesktop.NetworkManager.PermissionDenied"
+
+static void
+prop_set_auth_done_cb (NMAuthChain *chain,
+                       GError *error,
+                       DBusGMethodInvocation *context,
+                       gpointer user_data)
+{
+	NMManager *self = NM_MANAGER (user_data);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	DBusGConnection *bus;
+	DBusConnection *dbus_connection;
+	NMAuthCallResult result;
+	DBusMessage *reply, *request;
+	const char *permission, *prop;
+	gboolean set_enabled = TRUE;
+
+	priv->auth_chains = g_slist_remove (priv->auth_chains, chain);
+
+	request = nm_auth_chain_get_data (chain, "message");
+	permission = nm_auth_chain_get_data (chain, "permission");
+	prop = nm_auth_chain_get_data (chain, "prop");
+	set_enabled = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "enabled"));
+
+	if (error) {
+		reply = dbus_message_new_error (request, PERM_DENIED_ERROR,
+		                                "Not authorized to perform this operation");
+	} else {
+		/* Caller has had a chance to obtain authorization, so we only need to
+		 * check for 'yes' here.
+		 */
+		result = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, permission));
+		if (result != NM_AUTH_CALL_RESULT_YES) {
+			reply = dbus_message_new_error (request, PERM_DENIED_ERROR,
+				                            "Not authorized to perform this operation");
+		} else {
+			g_object_set (self, prop, set_enabled, NULL);
+			reply = dbus_message_new_method_return (request);
+		}
+	}
+
+	if (reply) {
+		bus = nm_dbus_manager_get_connection (priv->dbus_mgr);
+		g_assert (bus);
+		dbus_connection = dbus_g_connection_get_connection (bus);
+		g_assert (dbus_connection);
+
+		dbus_connection_send (dbus_connection, reply, NULL);
+		dbus_message_unref (reply);
+	}
+	nm_auth_chain_unref (chain);
+}
+
+static DBusHandlerResult
+prop_filter (DBusConnection *connection,
+             DBusMessage *message,
+             void *user_data)
+{
+	NMManager *self = NM_MANAGER (user_data);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	DBusMessageIter iter;
+	DBusMessageIter sub;
+	const char *propiface = NULL;
+	const char *propname = NULL;
+	const char *sender = NULL;
+	const char *glib_propname = NULL, *permission = NULL;
+	DBusError dbus_error;
+	gulong uid = G_MAXULONG;
+	DBusMessage *reply = NULL;
+	gboolean set_enabled = FALSE;
+	NMAuthChain *chain;
+
+	/* The sole purpose of this function is to validate property accesses
+	 * on the NMManager object since dbus-glib doesn't yet give us this
+	 * functionality.
+	 */
+
+	if (!dbus_message_is_method_call (message, DBUS_INTERFACE_PROPERTIES, "Set"))
+		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+	dbus_message_iter_init (message, &iter);
+
+	/* Get the D-Bus interface of the property to set */
+	if (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_STRING)
+		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+	dbus_message_iter_get_basic (&iter, &propiface);
+	if (!propiface || strcmp (propiface, NM_DBUS_INTERFACE))
+		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+	dbus_message_iter_next (&iter);
+
+	/* Get the property name that's going to be set */
+	if (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_STRING)
+		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+	dbus_message_iter_get_basic (&iter, &propname);
+	dbus_message_iter_next (&iter);
+
+	if (!strcmp (propname, "WirelessEnabled")) {
+		glib_propname = NM_MANAGER_WIRELESS_ENABLED;
+		permission = NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI;
+	} else if (!strcmp (propname, "WwanEnabled")) {
+		glib_propname = NM_MANAGER_WWAN_ENABLED;
+		permission = NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN;
+	} else
+		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+	/* Get the new value for the property */
+	if (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_VARIANT)
+		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+	dbus_message_iter_recurse (&iter, &sub);
+	if (dbus_message_iter_get_arg_type (&sub) != DBUS_TYPE_BOOLEAN)
+		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+	dbus_message_iter_get_basic (&sub, &set_enabled);
+
+	sender = dbus_message_get_sender (message);
+	if (!sender) {
+		reply = dbus_message_new_error (message, PERM_DENIED_ERROR,
+		                                "Could not determine D-Bus requestor");
+		goto out;
+	}
+
+	dbus_error_init (&dbus_error);
+	uid = dbus_bus_get_unix_user (connection, sender, &dbus_error);
+	if (dbus_error_is_set (&dbus_error)) {
+		reply = dbus_message_new_error (message, PERM_DENIED_ERROR,
+		                                "Could not determine the user ID of the requestor");
+		dbus_error_free (&dbus_error);
+		goto out;
+	}
+
+	if (uid > 0) {
+		/* Otherwise validate the user request */
+		chain = nm_auth_chain_new_raw_message (priv->authority, message, prop_set_auth_done_cb, self);
+		g_assert (chain);
+		priv->auth_chains = g_slist_append (priv->auth_chains, chain);
+		nm_auth_chain_set_data (chain, "prop", g_strdup (glib_propname), g_free);
+		nm_auth_chain_set_data (chain, "permission", g_strdup (permission), g_free);
+		nm_auth_chain_set_data (chain, "enabled", GUINT_TO_POINTER (set_enabled), NULL);
+		nm_auth_chain_set_data (chain, "message", dbus_message_ref (message), (GDestroyNotify) dbus_message_unref);
+		nm_auth_chain_add_call (chain, permission, TRUE);
+	} else {
+		/* Yay for root */
+		g_object_set (self, glib_propname, set_enabled, NULL);
+		reply = dbus_message_new_method_return (message);
+	}
+
+out:
+	if (reply) {
+		dbus_connection_send (connection, reply, NULL);
+		dbus_message_unref (reply);
+	}
+	return DBUS_HANDLER_RESULT_HANDLED;
+}
+
 NMManager *
 nm_manager_get (const char *config_file,
                 const char *plugins,
@@ -2990,6 +4200,7 @@ nm_manager_get (const char *config_file,
 	static NMManager *singleton = NULL;
 	NMManagerPrivate *priv;
 	DBusGConnection *bus;
+	DBusConnection *dbus_connection;
 
 	if (singleton)
 		return g_object_ref (singleton);
@@ -3001,6 +4212,14 @@ nm_manager_get (const char *config_file,
 
 	bus = nm_dbus_manager_get_connection (priv->dbus_mgr);
 	g_assert (bus);
+	dbus_connection = dbus_g_connection_get_connection (bus);
+	g_assert (dbus_connection);
+
+	if (!dbus_connection_add_filter (dbus_connection, prop_filter, singleton, NULL)) {
+		nm_log_err (LOGD_CORE, "failed to register DBus connection filter");
+		g_object_unref (singleton);
+		return NULL;
+    }
 
 	priv->sys_settings = nm_sysconfig_settings_new (config_file, plugins, bus, error);
 	if (!priv->sys_settings) {
@@ -3015,8 +4234,8 @@ nm_manager_get (const char *config_file,
 
 	priv->net_enabled = initial_net_enabled;
 
-	priv->radio_states[RFKILL_TYPE_WLAN].enabled = initial_wifi_enabled;
-	priv->radio_states[RFKILL_TYPE_WWAN].enabled = initial_wwan_enabled;
+	priv->radio_states[RFKILL_TYPE_WLAN].user_enabled = initial_wifi_enabled;
+	priv->radio_states[RFKILL_TYPE_WWAN].user_enabled = initial_wwan_enabled;
 
 	g_signal_connect (priv->sys_settings, "notify::" NM_SYSCONFIG_SETTINGS_UNMANAGED_SPECS,
 	                  G_CALLBACK (system_unmanaged_devices_changed_cb), singleton);
@@ -3068,6 +4287,9 @@ dispose (GObject *object)
 {
 	NMManager *manager = NM_MANAGER (object);
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
+	GSList *iter;
+	DBusGConnection *bus;
+	DBusConnection *dbus_connection;
 
 	if (priv->disposed) {
 		G_OBJECT_CLASS (nm_manager_parent_class)->dispose (object);
@@ -3075,8 +4297,14 @@ dispose (GObject *object)
 	}
 	priv->disposed = TRUE;
 
-	pending_connection_info_destroy (priv->pending_connection_info);
-	priv->pending_connection_info = NULL;
+	for (iter = priv->pending_activations; iter; iter = g_slist_next (iter))
+		pending_activation_destroy ((PendingActivation *) iter->data, NULL, NULL);
+	g_slist_free (priv->pending_activations);
+	priv->pending_activations = NULL;
+
+	g_slist_foreach (priv->auth_chains, (GFunc) nm_auth_chain_unref, NULL);
+	g_slist_free (priv->auth_chains);
+	g_object_unref (priv->authority);
 
 	while (g_slist_length (priv->secrets_calls))
 		free_get_secrets_info ((GetSecretsInfo *) priv->secrets_calls->data);
@@ -3085,11 +4313,10 @@ dispose (GObject *object)
 		priv->devices = remove_one_device (manager,
 		                                   priv->devices,
 		                                   NM_DEVICE (priv->devices->data),
-		                                   TRUE,
-		                                   FALSE);
+		                                   TRUE);
 	}
 
-	user_destroy_connections (manager);
+	user_proxy_cleanup (manager, FALSE);
 	g_hash_table_destroy (priv->user_connections);
 	priv->user_connections = NULL;
 
@@ -3122,14 +4349,79 @@ dispose (GObject *object)
 	}
 	g_object_unref (priv->modem_manager);
 
+	/* Unregister property filter */
+	bus = nm_dbus_manager_get_connection (priv->dbus_mgr);
+	g_assert (bus);
+	dbus_connection = dbus_g_connection_get_connection (bus);
+	g_assert (dbus_connection);
+	dbus_connection_remove_filter (dbus_connection, prop_filter, manager);
 	g_object_unref (priv->dbus_mgr);
+
 	if (priv->bluez_mgr)
 		g_object_unref (priv->bluez_mgr);
 
+	if (priv->aipd_proxy)
+		g_object_unref (priv->aipd_proxy);
+
+	if (priv->upower_proxy)
+		g_object_unref (priv->upower_proxy);
+
+	if (priv->fw_monitor) {
+		if (priv->fw_monitor_id)
+			g_signal_handler_disconnect (priv->fw_monitor, priv->fw_monitor_id);
+
+		if (priv->fw_changed_id)
+			g_source_remove (priv->fw_changed_id);
+
+		g_file_monitor_cancel (priv->fw_monitor);
+		g_object_unref (priv->fw_monitor);
+	}
+
+	if (priv->timestamp_update_id) {
+		g_source_remove (priv->timestamp_update_id);
+		priv->timestamp_update_id = 0;
+	}
+
 	G_OBJECT_CLASS (nm_manager_parent_class)->dispose (object);
 }
 
 static void
+manager_radio_user_toggled (NMManager *self,
+                            RadioState *rstate,
+                            gboolean enabled)
+{
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	GError *error = NULL;
+	gboolean old_enabled, new_enabled;
+
+	if (rstate->desc) {
+		nm_log_dbg (LOGD_RFKILL, "(%s): setting radio %s by user",
+		            rstate->desc,
+		            enabled ? "enabled" : "disabled");
+	}
+
+	/* Update enabled key in state file */
+	if (priv->state_file) {
+		if (!write_value_to_state_file (priv->state_file,
+		                                "main", rstate->key,
+		                                G_TYPE_BOOLEAN, (gpointer) &enabled,
+		                                &error)) {
+			nm_log_warn (LOGD_CORE, "writing to state file %s failed: (%d) %s.",
+			             priv->state_file,
+			             error ? error->code : -1,
+			             (error && error->message) ? error->message : "unknown");
+			g_clear_error (&error);
+		}
+	}
+
+	old_enabled = radio_enabled_for_rstate (rstate);
+	rstate->user_enabled = enabled;
+	new_enabled = radio_enabled_for_rstate (rstate);
+	if (new_enabled != old_enabled)
+		manager_update_radio_enabled (self, rstate);
+}
+
+static void
 set_property (GObject *object, guint prop_id,
 			  const GValue *value, GParamSpec *pspec)
 {
@@ -3142,14 +4434,14 @@ set_property (GObject *object, guint prop_id,
 		priv->net_enabled = g_value_get_boolean (value);
 		break;
 	case PROP_WIRELESS_ENABLED:
-		manager_set_radio_enabled (NM_MANAGER (object),
-		                           &priv->radio_states[RFKILL_TYPE_WLAN],
-		                           g_value_get_boolean (value));
+		manager_radio_user_toggled (NM_MANAGER (object),
+		                            &priv->radio_states[RFKILL_TYPE_WLAN],
+		                            g_value_get_boolean (value));
 		break;
 	case PROP_WWAN_ENABLED:
-		manager_set_radio_enabled (NM_MANAGER (object),
-		                           &priv->radio_states[RFKILL_TYPE_WWAN],
-		                           g_value_get_boolean (value));
+		manager_radio_user_toggled (NM_MANAGER (object),
+		                            &priv->radio_states[RFKILL_TYPE_WWAN],
+		                            g_value_get_boolean (value));
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -3165,6 +4457,9 @@ get_property (GObject *object, guint prop_id,
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 
 	switch (prop_id) {
+	case PROP_VERSION:
+		g_value_set_string (value, VERSION);
+		break;
 	case PROP_STATE:
 		nm_manager_update_state (self);
 		g_value_set_uint (value, priv->state);
@@ -3173,13 +4468,13 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_boolean (value, priv->net_enabled);
 		break;
 	case PROP_WIRELESS_ENABLED:
-		g_value_set_boolean (value, priv->radio_states[RFKILL_TYPE_WLAN].enabled);
+		g_value_set_boolean (value, radio_enabled_for_type (self, RFKILL_TYPE_WLAN));
 		break;
 	case PROP_WIRELESS_HARDWARE_ENABLED:
 		g_value_set_boolean (value, priv->radio_states[RFKILL_TYPE_WLAN].hw_enabled);
 		break;
 	case PROP_WWAN_ENABLED:
-		g_value_set_boolean (value, priv->radio_states[RFKILL_TYPE_WWAN].enabled);
+		g_value_set_boolean (value, radio_enabled_for_type (self, RFKILL_TYPE_WWAN));
 		break;
 	case PROP_WWAN_HARDWARE_ENABLED:
 		g_value_set_boolean (value, priv->radio_states[RFKILL_TYPE_WWAN].hw_enabled);
@@ -3199,17 +4494,41 @@ get_property (GObject *object, guint prop_id,
 	}
 }
 
+static gboolean
+periodic_update_active_connection_timestamps (gpointer user_data)
+{
+	NMManager *manager = NM_MANAGER (user_data);
+	GPtrArray *active;
+	int i;
+
+	active = get_active_connections (manager, NULL);
+
+	for (i = 0; i < active->len; i++) {
+		const char *active_path = g_ptr_array_index (active, i);
+		NMActRequest *req;
+		NMDevice *device = NULL;
+
+		req = nm_manager_get_act_request_by_path (manager, active_path, &device);
+		if (device && nm_device_get_state (device) == NM_DEVICE_STATE_ACTIVATED)
+			update_active_connection_timestamp (manager, device);
+	}
+
+	return TRUE;
+}
+
 static void
 nm_manager_init (NMManager *manager)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
 	DBusGConnection *g_connection;
 	guint id, i;
+	GFile *file;
+	GError *error = NULL;
 
 	/* Initialize rfkill structures and states */
 	memset (priv->radio_states, 0, sizeof (priv->radio_states));
 
-	priv->radio_states[RFKILL_TYPE_WLAN].enabled = TRUE;
+	priv->radio_states[RFKILL_TYPE_WLAN].user_enabled = TRUE;
 	priv->radio_states[RFKILL_TYPE_WLAN].key = "WirelessEnabled";
 	priv->radio_states[RFKILL_TYPE_WLAN].prop = NM_MANAGER_WIRELESS_ENABLED;
 	priv->radio_states[RFKILL_TYPE_WLAN].hw_prop = NM_MANAGER_WIRELESS_HARDWARE_ENABLED;
@@ -3217,7 +4536,7 @@ nm_manager_init (NMManager *manager)
 	priv->radio_states[RFKILL_TYPE_WLAN].other_enabled_func = nm_manager_get_ipw_rfkill_state;
 	priv->radio_states[RFKILL_TYPE_WLAN].rtype = RFKILL_TYPE_WLAN;
 
-	priv->radio_states[RFKILL_TYPE_WWAN].enabled = TRUE;
+	priv->radio_states[RFKILL_TYPE_WWAN].user_enabled = TRUE;
 	priv->radio_states[RFKILL_TYPE_WWAN].key = "WWANEnabled";
 	priv->radio_states[RFKILL_TYPE_WWAN].prop = NM_MANAGER_WWAN_ENABLED;
 	priv->radio_states[RFKILL_TYPE_WWAN].hw_prop = NM_MANAGER_WWAN_HARDWARE_ENABLED;
@@ -3225,7 +4544,7 @@ nm_manager_init (NMManager *manager)
 	priv->radio_states[RFKILL_TYPE_WWAN].other_enabled_func = nm_manager_get_modem_enabled_state;
 	priv->radio_states[RFKILL_TYPE_WWAN].rtype = RFKILL_TYPE_WWAN;
 
-	priv->radio_states[RFKILL_TYPE_WIMAX].enabled = TRUE;
+	priv->radio_states[RFKILL_TYPE_WIMAX].user_enabled = TRUE;
 	priv->radio_states[RFKILL_TYPE_WIMAX].key = "WiMAXEnabled";
 	priv->radio_states[RFKILL_TYPE_WIMAX].prop = NULL;
 	priv->radio_states[RFKILL_TYPE_WIMAX].hw_prop = NULL;
@@ -3286,6 +4605,58 @@ nm_manager_init (NMManager *manager)
 		                             NULL);
 	} else
 		nm_log_warn (LOGD_AUTOIP4, "could not initialize avahi-autoipd D-Bus proxy");
+
+	/* upower sleep/wake handling */
+	priv->upower_proxy = dbus_g_proxy_new_for_name (g_connection,
+	                                                UPOWER_DBUS_SERVICE,
+	                                                "/org/freedesktop/UPower",
+	                                                "org.freedesktop.UPower");
+	if (priv->upower_proxy) {
+		dbus_g_proxy_add_signal (priv->upower_proxy, "Sleeping", G_TYPE_INVALID);
+		dbus_g_proxy_connect_signal (priv->upower_proxy, "Sleeping",
+		                             G_CALLBACK (upower_sleeping_cb),
+		                             manager, NULL);
+
+		dbus_g_proxy_add_signal (priv->upower_proxy, "Resuming", G_TYPE_INVALID);
+		dbus_g_proxy_connect_signal (priv->upower_proxy, "Resuming",
+		                             G_CALLBACK (upower_resuming_cb),
+		                             manager, NULL);
+	} else
+		nm_log_warn (LOGD_SUSPEND, "could not initialize UPower D-Bus proxy");
+
+	priv->authority = polkit_authority_get_sync (NULL, &error);
+	if (priv->authority) {
+		priv->auth_changed_id = g_signal_connect (priv->authority,
+		                                          "changed",
+		                                          G_CALLBACK (pk_authority_changed_cb),
+		                                          manager);
+	} else {
+		nm_log_warn (LOGD_CORE, "failed to create PolicyKit authority: (%d) %s",
+		             error ? error->code : -1,
+		             error && error->message ? error->message : "(unknown)");
+		g_clear_error (&error);
+	}
+
+	/* Monitor the firmware directory */
+	if (strlen (KERNEL_FIRMWARE_DIR)) {
+		file = g_file_new_for_path (KERNEL_FIRMWARE_DIR "/");
+		priv->fw_monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL);
+		g_object_unref (file);
+	}
+
+	if (priv->fw_monitor) {
+		priv->fw_monitor_id = g_signal_connect (priv->fw_monitor, "changed",
+		                                        G_CALLBACK (firmware_dir_changed),
+		                                        manager);
+		nm_log_info (LOGD_CORE, "monitoring kernel firmware directory '%s'.",
+		             KERNEL_FIRMWARE_DIR);
+	} else {
+		nm_log_warn (LOGD_CORE, "failed to monitor kernel firmware directory '%s'.",
+		             KERNEL_FIRMWARE_DIR);
+	}
+
+	/* Update timestamps in active connections */
+	priv->timestamp_update_id = g_timeout_add_seconds (300, (GSourceFunc) periodic_update_active_connection_timestamps, manager);
 }
 
 static void
@@ -3304,6 +4675,14 @@ nm_manager_class_init (NMManagerClass *manager_class)
 
 	/* properties */
 	g_object_class_install_property
+		(object_class, PROP_VERSION,
+		 g_param_spec_string (NM_MANAGER_VERSION,
+		                      "Version",
+		                      "NetworkManager version",
+		                      NULL,
+		                      G_PARAM_READABLE));
+
+	g_object_class_install_property
 		(object_class, PROP_STATE,
 		 g_param_spec_uint (NM_MANAGER_STATE,
 		                    "State",
@@ -3445,6 +4824,22 @@ nm_manager_class_init (NMManagerClass *manager_class)
 		              _nm_marshal_VOID__OBJECT_UINT,
 		              G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_UINT);
 
+	signals[CHECK_PERMISSIONS] =
+		g_signal_new ("check-permissions",
+		              G_OBJECT_CLASS_TYPE (object_class),
+		              G_SIGNAL_RUN_FIRST,
+		              0, NULL, NULL,
+		              g_cclosure_marshal_VOID__VOID,
+		              G_TYPE_NONE, 0);
+
+	signals[USER_PERMISSIONS_CHANGED] =
+		g_signal_new ("user-permissions-changed",
+		              G_OBJECT_CLASS_TYPE (object_class),
+		              G_SIGNAL_RUN_FIRST,
+		              0, NULL, NULL,
+		              g_cclosure_marshal_VOID__VOID,
+		              G_TYPE_NONE, 0);
+
 	/* StateChange is DEPRECATED */
 	signals[STATE_CHANGE] =
 		g_signal_new ("state-change",