about summary refs log tree commit diff
path: root/src/nm-active-connection.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nm-active-connection.c')
-rw-r--r--src/nm-active-connection.c668
1 files changed, 575 insertions, 93 deletions
diff --git a/src/nm-active-connection.c b/src/nm-active-connection.c
index 6e3a5da6..172aa614 100644
--- a/src/nm-active-connection.c
+++ b/src/nm-active-connection.c
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2008 - 2012 Red Hat, Inc.
+ * Copyright (C) 2008 - 2014 Red Hat, Inc.
  */
 
 #include <glib.h>
@@ -25,9 +25,10 @@
 #include "nm-logging.h"
 #include "nm-dbus-glib-types.h"
 #include "nm-dbus-manager.h"
-#include "nm-properties-changed-signal.h"
 #include "nm-device.h"
 #include "nm-settings-connection.h"
+#include "nm-manager-auth.h"
+#include "NetworkManagerUtils.h"
 
 #include "nm-active-connection-glue.h"
 
@@ -44,47 +45,76 @@ typedef struct {
 	char *specific_object;
 	NMDevice *device;
 
+	char *pending_activation_id;
+
 	gboolean is_default;
 	gboolean is_default6;
 	NMActiveConnectionState state;
 	gboolean vpn;
 
-	gboolean user_requested;
-	gulong user_uid;
+	NMAuthSubject *subject;
+	NMActiveConnection *master;
+	gboolean master_ready;
+
 	gboolean assumed;
-	NMDevice *master;
+
+	NMAuthChain *chain;
+	const char *wifi_shared_permission;
+	NMActiveConnectionAuthResultFunc result_func;
+	gpointer user_data1;
+	gpointer user_data2;
 } NMActiveConnectionPrivate;
 
 enum {
 	PROP_0,
 	PROP_CONNECTION,
+	PROP_ID,
 	PROP_UUID,
+	PROP_TYPE,
 	PROP_SPECIFIC_OBJECT,
 	PROP_DEVICES,
 	PROP_STATE,
 	PROP_DEFAULT,
+	PROP_IP4_CONFIG,
+	PROP_DHCP4_CONFIG,
 	PROP_DEFAULT6,
+	PROP_IP6_CONFIG,
+	PROP_DHCP6_CONFIG,
 	PROP_VPN,
 	PROP_MASTER,
 
 	PROP_INT_CONNECTION,
 	PROP_INT_DEVICE,
-	PROP_INT_USER_REQUESTED,
-	PROP_INT_USER_UID,
-	PROP_INT_ASSUMED,
+	PROP_INT_SUBJECT,
 	PROP_INT_MASTER,
+	PROP_INT_MASTER_READY,
 
 	LAST_PROP
 };
 
-enum {
-	PROPERTIES_CHANGED,
-	LAST_SIGNAL
-};
-static guint signals[LAST_SIGNAL] = { 0 };
+static void check_master_ready (NMActiveConnection *self);
+static void _device_cleanup (NMActiveConnection *self);
 
 /****************************************************************/
 
+static const char *
+state_to_string (NMActiveConnectionState state)
+{
+	switch (state) {
+	case NM_ACTIVE_CONNECTION_STATE_UNKNOWN:
+		return "unknown";
+	case NM_ACTIVE_CONNECTION_STATE_ACTIVATING:
+		return "activating";
+	case NM_ACTIVE_CONNECTION_STATE_ACTIVATED:
+		return "activated";
+	case NM_ACTIVE_CONNECTION_STATE_DEACTIVATING:
+		return "deactivating";
+	case NM_ACTIVE_CONNECTION_STATE_DEACTIVATED:
+		return "deactivated";
+	}
+	return "(none)";
+}
+
 NMActiveConnectionState
 nm_active_connection_get_state (NMActiveConnection *self)
 {
@@ -109,33 +139,79 @@ nm_active_connection_set_state (NMActiveConnection *self,
 	priv->state = new_state;
 	g_object_notify (G_OBJECT (self), NM_ACTIVE_CONNECTION_STATE);
 
+	check_master_ready (self);
+
 	if (   new_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED
 	    || old_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) {
 		nm_settings_connection_update_timestamp (NM_SETTINGS_CONNECTION (priv->connection),
 		                                         (guint64) time (NULL), TRUE);
 	}
 
+	if (priv->device) {
+		if (   old_state < NM_ACTIVE_CONNECTION_STATE_ACTIVATED
+		    && new_state >= NM_ACTIVE_CONNECTION_STATE_ACTIVATED &&
+		    priv->pending_activation_id)
+		{
+			nm_device_remove_pending_action (priv->device, priv->pending_activation_id, TRUE);
+			g_clear_pointer (&priv->pending_activation_id, g_free);
+		}
+	}
+
+	if (   new_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED
+	    || old_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) {
+		g_object_notify (G_OBJECT (self), NM_ACTIVE_CONNECTION_IP4_CONFIG);
+		g_object_notify (G_OBJECT (self), NM_ACTIVE_CONNECTION_DHCP4_CONFIG);
+		g_object_notify (G_OBJECT (self), NM_ACTIVE_CONNECTION_IP6_CONFIG);
+		g_object_notify (G_OBJECT (self), NM_ACTIVE_CONNECTION_DHCP6_CONFIG);
+	}
+
 	if (priv->state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) {
-		/* Device is no longer relevant when deactivated */
-		g_clear_object (&priv->device);
+		/* Device is no longer relevant when deactivated. So remove it and
+		 * emit property change notification so clients re-read the value,
+		 * which will be NULL due to conditions in get_property().
+		 */
+		_device_cleanup (self);
 		g_object_notify (G_OBJECT (self), NM_ACTIVE_CONNECTION_DEVICES);
 	}
 }
 
 const char *
-nm_active_connection_get_name (NMActiveConnection *self)
+nm_active_connection_get_id (NMActiveConnection *self)
 {
 	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), NULL);
 
 	return nm_connection_get_id (NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->connection);
 }
 
+const char *
+nm_active_connection_get_uuid (NMActiveConnection *self)
+{
+	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), NULL);
+
+	return nm_connection_get_uuid (NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->connection);
+}
+
 NMConnection *
 nm_active_connection_get_connection (NMActiveConnection *self)
 {
 	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->connection;
 }
 
+void
+nm_active_connection_set_connection (NMActiveConnection *self,
+                                     NMConnection *connection)
+{
+	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
+
+	/* Can't change connection after the ActiveConnection is exported over D-Bus */
+	g_return_if_fail (priv->path == NULL);
+	g_return_if_fail (priv->connection == NULL || !NM_IS_SETTINGS_CONNECTION (priv->connection));
+
+	if (priv->connection)
+		g_object_unref (priv->connection);
+	priv->connection = g_object_ref (connection);
+}
+
 const char *
 nm_active_connection_get_path (NMActiveConnection *self)
 {
@@ -154,6 +230,11 @@ nm_active_connection_set_specific_object (NMActiveConnection *self,
 {
 	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
 
+	/* Nothing that calls this function should be using paths from D-Bus,
+	 * where NM uses "/" to mean NULL.
+	 */
+	g_assert (g_strcmp0 (specific_object, "/") != 0);
+
 	if (g_strcmp0 (priv->specific_object, specific_object) == 0)
 		return;
 
@@ -212,14 +293,20 @@ void
 nm_active_connection_export (NMActiveConnection *self)
 {
 	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
-	NMDBusManager *dbus_mgr;
 	static guint32 counter = 0;
 
+	g_assert (priv->device || priv->vpn);
+
 	priv->path = g_strdup_printf (NM_DBUS_PATH "/ActiveConnection/%d", counter++);
-	dbus_mgr = nm_dbus_manager_get ();
-	dbus_g_connection_register_g_object (nm_dbus_manager_get_connection (dbus_mgr),
-	                                     priv->path, G_OBJECT (self));
-	g_object_unref (dbus_mgr);
+	nm_dbus_manager_register_object (nm_dbus_manager_get (), priv->path, self);
+}
+
+NMAuthSubject *
+nm_active_connection_get_subject (NMActiveConnection *self)
+{
+	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), NULL);
+
+	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->subject;
 }
 
 gboolean
@@ -227,34 +314,98 @@ nm_active_connection_get_user_requested (NMActiveConnection *self)
 {
 	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), FALSE);
 
-	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->user_requested;
+	return !nm_auth_subject_get_internal (NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->subject);
 }
 
-gulong
-nm_active_connection_get_user_uid (NMActiveConnection *self)
+NMDevice *
+nm_active_connection_get_device (NMActiveConnection *self)
 {
-	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), G_MAXULONG);
+	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), NULL);
 
-	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->user_uid;
+	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->device;
 }
 
-gboolean
-nm_active_connection_get_assumed (NMActiveConnection *self)
+static void
+device_state_changed (NMDevice *device,
+                      NMDeviceState new_state,
+                      NMDeviceState old_state,
+                      NMDeviceStateReason reason,
+                      gpointer user_data)
 {
-	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), FALSE);
+	NMActiveConnection *self = NM_ACTIVE_CONNECTION (user_data);
+	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
 
-	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->assumed;
+	/* When already deactivated or before activation, device state changes are useless */
+	if (priv->state >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATED)
+		return;
+	if (old_state < NM_DEVICE_STATE_DISCONNECTED)
+		return;
+
+	/* Let subclasses handle the state change */
+	if (NM_ACTIVE_CONNECTION_GET_CLASS (self)->device_state_changed)
+		NM_ACTIVE_CONNECTION_GET_CLASS (self)->device_state_changed (self, device, new_state, old_state);
 }
 
-NMDevice *
-nm_active_connection_get_device (NMActiveConnection *self)
+static void
+device_master_changed (GObject *object,
+                       GParamSpec *pspec,
+                       gpointer user_data)
 {
-	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), NULL);
+	NMDevice *device = NM_DEVICE (object);
+	NMActiveConnection *self = NM_ACTIVE_CONNECTION (user_data);
+	NMActiveConnection *master;
+	NMActiveConnectionState master_state;
 
-	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->device;
+	if (NM_ACTIVE_CONNECTION (nm_device_get_act_request (device)) != self)
+		return;
+	if (!nm_device_get_master (device))
+		return;
+	g_signal_handlers_disconnect_by_func (device, G_CALLBACK (device_master_changed), self);
+
+	master = nm_active_connection_get_master (self);
+	g_assert (master);
+
+	master_state = nm_active_connection_get_state (master);
+	if (master_state >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) {
+		/* Master failed before attaching the slave */
+		if (NM_ACTIVE_CONNECTION_GET_CLASS (self)->master_failed)
+			NM_ACTIVE_CONNECTION_GET_CLASS (self)->master_failed (self);
+	}
 }
 
-NMDevice *
+gboolean
+nm_active_connection_set_device (NMActiveConnection *self, NMDevice *device)
+{
+	NMActiveConnectionPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), FALSE);
+	g_return_val_if_fail (!device || NM_IS_DEVICE (device), FALSE);
+
+	priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
+
+	if (device) {
+		g_return_val_if_fail (priv->device == NULL, FALSE);
+
+		/* Device obviously can't be its own master */
+		g_return_val_if_fail (!priv->master || device != nm_active_connection_get_device (priv->master), FALSE);
+
+		priv->device = g_object_ref (device);
+		g_object_notify (G_OBJECT (self), NM_ACTIVE_CONNECTION_INT_DEVICE);
+
+		g_signal_connect (device, "state-changed",
+		                  G_CALLBACK (device_state_changed), self);
+		g_signal_connect (device, "notify::master",
+		                  G_CALLBACK (device_master_changed), self);
+
+		if (!priv->assumed) {
+			priv->pending_activation_id = g_strdup_printf ("activation::%p", (void *)self);
+			nm_device_add_pending_action (device, priv->pending_activation_id, TRUE);
+		}
+	}
+	return TRUE;
+}
+
+NMActiveConnection *
 nm_active_connection_get_master (NMActiveConnection *self)
 {
 	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), NULL);
@@ -262,6 +413,247 @@ nm_active_connection_get_master (NMActiveConnection *self)
 	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->master;
 }
 
+/**
+ * nm_active_connection_get_master_ready:
+ * @self: the #NMActiveConnection
+ *
+ * Returns: %TRUE if the connection has a master connection, and that
+ * master connection is ready to accept slaves.  Otherwise %FALSE.
+ */
+gboolean
+nm_active_connection_get_master_ready (NMActiveConnection *self)
+{
+	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), FALSE);
+
+	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->master_ready;
+}
+
+static void
+check_master_ready (NMActiveConnection *self)
+{
+	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
+	NMActiveConnectionState master_state = NM_ACTIVE_CONNECTION_STATE_UNKNOWN;
+
+	if (priv->state != NM_ACTIVE_CONNECTION_STATE_ACTIVATING) {
+		nm_log_dbg (LOGD_DEVICE, "(%p): not signalling master-ready (not activating)", self);
+		return;
+	}
+	if (!priv->master) {
+		nm_log_dbg (LOGD_DEVICE, "(%p): not signalling master-ready (no master)", self);
+		return;
+	}
+	if (priv->master_ready) {
+		nm_log_dbg (LOGD_DEVICE, "(%p): not signalling master-ready (already signaled)", self);
+		return;
+	}
+
+	/* ActiveConnetions don't enter the ACTIVATING state until they have a
+	 * NMDevice in PREPARE or higher states, so the master active connection's
+	 * device will be ready to accept slaves when the master is in ACTIVATING
+	 * or higher states.
+	 */
+	master_state = nm_active_connection_get_state (priv->master);
+	nm_log_dbg (LOGD_DEVICE, "(%p): master ActiveConnection [%p] state now '%s' (%d)",
+	            self, priv->master, state_to_string (master_state), master_state);
+
+	if (   master_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATING
+	    || master_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) {
+		nm_log_dbg (LOGD_DEVICE, "(%p): signalling master-ready", self);
+
+		priv->master_ready = TRUE;
+		g_object_notify (G_OBJECT (self), NM_ACTIVE_CONNECTION_INT_MASTER_READY);
+
+		/* Also notify clients to recheck the exported 'master' property to
+		 * ensure that if the master connection was created without a device
+		 * that we notify clients when the master device is known.
+		 */
+		g_object_notify (G_OBJECT (self), NM_ACTIVE_CONNECTION_MASTER);
+	}
+}
+
+static void
+master_state_cb (NMActiveConnection *master,
+                 GParamSpec *pspec,
+                 gpointer user_data)
+{
+	NMActiveConnection *self = NM_ACTIVE_CONNECTION (user_data);
+	NMActiveConnectionState master_state = nm_active_connection_get_state (master);
+
+	check_master_ready (self);
+
+	nm_log_dbg (LOGD_DEVICE, "(%p): master ActiveConnection [%p] state now '%s' (%d)",
+	            self, master, state_to_string (master_state), master_state);
+
+	if (master_state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATING &&
+	    nm_active_connection_get_device (master) == NULL) {
+		/* Master failed without ever creating its device */
+		if (NM_ACTIVE_CONNECTION_GET_CLASS (self)->master_failed)
+			NM_ACTIVE_CONNECTION_GET_CLASS (self)->master_failed (self);
+	}
+}
+
+/**
+ * nm_active_connection_set_master:
+ * @self: the #NMActiveConnection
+ * @master: if the activation depends on another device (ie, bond or bridge
+ * master to which this device will be enslaved) pass the #NMActiveConnection
+ * that this activation request is a child of
+ *
+ * Sets the master active connection of @self.
+ */
+void
+nm_active_connection_set_master (NMActiveConnection *self, NMActiveConnection *master)
+{
+	NMActiveConnectionPrivate *priv;
+
+	g_return_if_fail (NM_IS_ACTIVE_CONNECTION (self));
+	g_return_if_fail (NM_IS_ACTIVE_CONNECTION (master));
+
+	priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
+
+	/* Master is write-once, and must be set before exporting the object */
+	g_return_if_fail (priv->master == NULL);
+	g_return_if_fail (priv->path == NULL);
+	if (priv->device) {
+		/* Note, the master ActiveConnection may not yet have a device */
+		g_return_if_fail (priv->device != nm_active_connection_get_device (master));
+	}
+
+	nm_log_dbg (LOGD_DEVICE, "(%p): master ActiveConnection is [%p] %s",
+	            self, master, nm_active_connection_get_id (master));
+
+	priv->master = g_object_ref (master);
+	g_signal_connect (priv->master,
+	                  "notify::" NM_ACTIVE_CONNECTION_STATE,
+	                  (GCallback) master_state_cb,
+	                  self);
+
+	check_master_ready (self);
+}
+
+void
+nm_active_connection_set_assumed (NMActiveConnection *self, gboolean assumed)
+{
+	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
+
+	g_return_if_fail (priv->assumed == FALSE);
+	priv->assumed = assumed;
+
+	if (priv->pending_activation_id) {
+		nm_device_remove_pending_action (priv->device, priv->pending_activation_id, TRUE);
+		g_clear_pointer (&priv->pending_activation_id, g_free);
+	}
+}
+
+gboolean
+nm_active_connection_get_assumed (NMActiveConnection *self)
+{
+	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->assumed;
+}
+
+/****************************************************************/
+
+static void
+auth_done (NMAuthChain *chain,
+           GError *error,
+           DBusGMethodInvocation *unused,
+           gpointer user_data)
+{
+	NMActiveConnection *self = NM_ACTIVE_CONNECTION (user_data);
+	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
+	NMAuthCallResult result;
+
+	g_assert (priv->chain == chain);
+	g_assert (priv->result_func != NULL);
+
+	/* Must stay alive over the callback */
+	g_object_ref (self);
+
+	if (error) {
+		priv->result_func (self, FALSE, error->message, priv->user_data1, priv->user_data2);
+		goto done;
+	}
+
+	/* Caller has had a chance to obtain authorization, so we only need to
+	 * check for 'yes' here.
+	 */
+	result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL);
+	if (result != NM_AUTH_CALL_RESULT_YES) {
+		priv->result_func (self,
+		                   FALSE,
+		                   "Not authorized to control networking.",
+		                   priv->user_data1,
+		                   priv->user_data2);
+		goto done;
+	}
+
+	if (priv->wifi_shared_permission) {
+		result = nm_auth_chain_get_result (chain, priv->wifi_shared_permission);
+		if (result != NM_AUTH_CALL_RESULT_YES) {
+			priv->result_func (self,
+			                   FALSE,
+			                   "Not authorized to share connections via wifi.",
+			                   priv->user_data1,
+			                   priv->user_data2);
+			goto done;
+		}
+	}
+
+	/* Otherwise authorized and available to activate */
+	priv->result_func (self, TRUE, NULL, priv->user_data1, priv->user_data2);
+
+done:
+	nm_auth_chain_unref (chain);
+	priv->chain = NULL;
+	priv->result_func = NULL;
+	priv->user_data1 = NULL;
+	priv->user_data2 = NULL;
+
+	g_object_unref (self);
+}
+
+/**
+ * nm_active_connection_authorize:
+ * @self: the #NMActiveConnection
+ * @result_func: function to be called on success or error
+ * @user_data1: pointer passed to @result_func
+ * @user_data2: additional pointer passed to @result_func
+ *
+ * Checks whether the subject that initiated the active connection (read from
+ * the #NMActiveConnection::subject property) is authorized to complete this
+ * activation request.
+ */
+void
+nm_active_connection_authorize (NMActiveConnection *self,
+                                NMActiveConnectionAuthResultFunc result_func,
+                                gpointer user_data1,
+                                gpointer user_data2)
+{
+	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
+	const char *wifi_permission = NULL;
+
+	g_return_if_fail (result_func != NULL);
+	g_return_if_fail (priv->chain == NULL);
+
+	priv->chain = nm_auth_chain_new_subject (priv->subject, NULL, auth_done, self);
+	g_assert (priv->chain);
+
+	/* Check that the subject is allowed to use networking at all */
+	nm_auth_chain_add_call (priv->chain, NM_AUTH_PERMISSION_NETWORK_CONTROL, TRUE);
+
+	/* Shared wifi connections require special permissions too */
+	wifi_permission = nm_utils_get_shared_wifi_permission (priv->connection);
+	if (wifi_permission) {
+		priv->wifi_shared_permission = wifi_permission;
+		nm_auth_chain_add_call (priv->chain, wifi_permission, TRUE);
+	}
+
+	/* Wait for authorization */
+	priv->result_func = result_func;
+	priv->user_data1 = user_data1;
+	priv->user_data2 = user_data2;
+}
+
 /****************************************************************/
 
 static void
@@ -270,10 +662,18 @@ nm_active_connection_init (NMActiveConnection *self)
 }
 
 static void
+constructed (GObject *object)
+{
+	G_OBJECT_CLASS (nm_active_connection_parent_class)->constructed (object);
+	g_assert (NM_ACTIVE_CONNECTION_GET_PRIVATE (object)->subject);
+}
+
+static void
 set_property (GObject *object, guint prop_id,
 			  const GValue *value, GParamSpec *pspec)
 {
 	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (object);
+	const char *tmp;
 
 	switch (prop_id) {
 	case PROP_INT_CONNECTION:
@@ -281,28 +681,19 @@ set_property (GObject *object, guint prop_id,
 		priv->connection = g_value_dup_object (value);
 		break;
 	case PROP_INT_DEVICE:
-		g_warn_if_fail (priv->device == NULL);
-		priv->device = g_value_dup_object (value);
-		if (priv->device)
-			g_warn_if_fail (priv->device != priv->master);
+		nm_active_connection_set_device (NM_ACTIVE_CONNECTION (object), g_value_get_object (value));
 		break;
-	case PROP_INT_USER_REQUESTED:
-		priv->user_requested = g_value_get_boolean (value);
-		break;
-	case PROP_INT_USER_UID:
-		priv->user_uid = g_value_get_ulong (value);
-		break;
-	case PROP_INT_ASSUMED:
-		priv->assumed = g_value_get_boolean (value);
+	case PROP_INT_SUBJECT:
+		priv->subject = g_value_dup_object (value);
 		break;
 	case PROP_INT_MASTER:
-		g_warn_if_fail (priv->master == NULL);
-		priv->master = g_value_dup_object (value);
-		if (priv->master)
-			g_warn_if_fail (priv->master != priv->device);
+		nm_active_connection_set_master (NM_ACTIVE_CONNECTION (object), g_value_get_object (value));
 		break;
 	case PROP_SPECIFIC_OBJECT:
-		priv->specific_object = g_value_dup_boxed (value);
+		tmp = g_value_get_boxed (value);
+		/* NM uses "/" to mean NULL */
+		if (g_strcmp0 (tmp, "/") != 0)
+			priv->specific_object = g_value_dup_boxed (value);
 		break;
 	case PROP_DEFAULT:
 		priv->is_default = g_value_get_boolean (value);
@@ -327,20 +718,27 @@ get_property (GObject *object, guint prop_id,
 {
 	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (object);
 	GPtrArray *devices;
+	NMDevice *master_device = NULL;
 
 	switch (prop_id) {
 	case PROP_CONNECTION:
 		g_value_set_boxed (value, nm_connection_get_path (priv->connection));
 		break;
+	case PROP_ID:
+		g_value_set_string (value, nm_connection_get_id (priv->connection));
+		break;
 	case PROP_UUID:
 		g_value_set_string (value, nm_connection_get_uuid (priv->connection));
 		break;
+	case PROP_TYPE:
+		g_value_set_string (value, nm_connection_get_connection_type (priv->connection));
+		break;
 	case PROP_SPECIFIC_OBJECT:
 		g_value_set_boxed (value, priv->specific_object ? priv->specific_object : "/");
 		break;
 	case PROP_DEVICES:
 		devices = g_ptr_array_sized_new (1);
-		if (priv->device)
+		if (priv->device && priv->state < NM_ACTIVE_CONNECTION_STATE_DEACTIVATED)
 			g_ptr_array_add (devices, g_strdup (nm_device_get_path (priv->device)));
 		g_value_take_boxed (value, devices);
 		break;
@@ -350,14 +748,35 @@ get_property (GObject *object, guint prop_id,
 	case PROP_DEFAULT:
 		g_value_set_boolean (value, priv->is_default);
 		break;
+	case PROP_IP4_CONFIG:
+		/* The IP and DHCP config properties may be overridden by a subclass */
+		g_value_set_boxed (value, "/");
+		break;
+	case PROP_DHCP4_CONFIG:
+		g_value_set_boxed (value, "/");
+		break;
 	case PROP_DEFAULT6:
 		g_value_set_boolean (value, priv->is_default6);
 		break;
+	case PROP_IP6_CONFIG:
+		g_value_set_boxed (value, "/");
+		break;
+	case PROP_DHCP6_CONFIG:
+		g_value_set_boxed (value, "/");
+		break;
 	case PROP_VPN:
 		g_value_set_boolean (value, priv->vpn);
 		break;
 	case PROP_MASTER:
-		g_value_set_boxed (value, priv->master ? nm_device_get_path (priv->master) : "/");
+		if (priv->master)
+			master_device = nm_active_connection_get_device (priv->master);
+		g_value_set_boxed (value, master_device ? nm_device_get_path (master_device) : "/");
+		break;
+	case PROP_INT_SUBJECT:
+		g_value_set_object (value, priv->subject);
+		break;
+	case PROP_INT_MASTER_READY:
+		g_value_set_boolean (value, priv->master_ready);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -366,9 +785,33 @@ get_property (GObject *object, guint prop_id,
 }
 
 static void
+_device_cleanup (NMActiveConnection *self)
+{
+	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
+
+	if (priv->device) {
+		g_signal_handlers_disconnect_by_func (priv->device, G_CALLBACK (device_state_changed), self);
+		g_signal_handlers_disconnect_by_func (priv->device, G_CALLBACK (device_master_changed), self);
+	}
+
+	if (priv->pending_activation_id) {
+		nm_device_remove_pending_action (priv->device, priv->pending_activation_id, TRUE);
+		g_clear_pointer (&priv->pending_activation_id, g_free);
+	}
+
+	g_clear_object (&priv->device);
+}
+
+static void
 dispose (GObject *object)
 {
-	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (object);
+	NMActiveConnection *self = NM_ACTIVE_CONNECTION (object);
+	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
+
+	if (priv->chain) {
+		nm_auth_chain_unref (priv->chain);
+		priv->chain = NULL;
+	}
 
 	g_free (priv->path);
 	priv->path = NULL;
@@ -376,22 +819,31 @@ dispose (GObject *object)
 	priv->specific_object = NULL;
 
 	g_clear_object (&priv->connection);
-	g_clear_object (&priv->device);
+
+	_device_cleanup (self);
+
+	if (priv->master) {
+		g_signal_handlers_disconnect_by_func (priv->master,
+		                                      (GCallback) master_state_cb,
+		                                      self);
+	}
 	g_clear_object (&priv->master);
+	g_clear_object (&priv->subject);
 
 	G_OBJECT_CLASS (nm_active_connection_parent_class)->dispose (object);
 }
 
 static void
-nm_active_connection_class_init (NMActiveConnectionClass *vpn_class)
+nm_active_connection_class_init (NMActiveConnectionClass *ac_class)
 {
-	GObjectClass *object_class = G_OBJECT_CLASS (vpn_class);
+	GObjectClass *object_class = G_OBJECT_CLASS (ac_class);
 
-	g_type_class_add_private (vpn_class, sizeof (NMActiveConnectionPrivate));
+	g_type_class_add_private (ac_class, sizeof (NMActiveConnectionPrivate));
 
 	/* virtual methods */
 	object_class->get_property = get_property;
 	object_class->set_property = set_property;
+	object_class->constructed = constructed;
 	object_class->dispose = dispose;
 
 	/* D-Bus exported properties */
@@ -402,6 +854,13 @@ nm_active_connection_class_init (NMActiveConnectionClass *vpn_class)
 		                    DBUS_TYPE_G_OBJECT_PATH,
 		                    G_PARAM_READABLE));
 
+	g_object_class_install_property (object_class, PROP_ID,
+		g_param_spec_string (NM_ACTIVE_CONNECTION_ID,
+		                     "Connection ID",
+		                     "Connection ID",
+		                     NULL,
+		                     G_PARAM_READABLE));
+
 	g_object_class_install_property (object_class, PROP_UUID,
 		g_param_spec_string (NM_ACTIVE_CONNECTION_UUID,
 		                     "Connection UUID",
@@ -409,6 +868,13 @@ nm_active_connection_class_init (NMActiveConnectionClass *vpn_class)
 		                     NULL,
 		                     G_PARAM_READABLE));
 
+	g_object_class_install_property (object_class, PROP_TYPE,
+		g_param_spec_string (NM_ACTIVE_CONNECTION_TYPE,
+		                     "Connection Type",
+		                     "Connection Type",
+		                     NULL,
+		                     G_PARAM_READABLE));
+
 	g_object_class_install_property (object_class, PROP_SPECIFIC_OBJECT,
 		g_param_spec_boxed (NM_ACTIVE_CONNECTION_SPECIFIC_OBJECT,
 		                    "Specific object",
@@ -439,6 +905,20 @@ nm_active_connection_class_init (NMActiveConnectionClass *vpn_class)
 		                      FALSE,
 		                      G_PARAM_READWRITE));
 
+	g_object_class_install_property (object_class, PROP_IP4_CONFIG,
+		 g_param_spec_boxed (NM_ACTIVE_CONNECTION_IP4_CONFIG,
+		                     "IP4 Config",
+		                     "IP4 Config",
+		                     DBUS_TYPE_G_OBJECT_PATH,
+		                     G_PARAM_READABLE));
+
+	g_object_class_install_property (object_class, PROP_DHCP4_CONFIG,
+		 g_param_spec_boxed (NM_ACTIVE_CONNECTION_DHCP4_CONFIG,
+		                     "DHCP4 Config",
+		                     "DHCP4 Config",
+		                     DBUS_TYPE_G_OBJECT_PATH,
+		                     G_PARAM_READABLE));
+
 	g_object_class_install_property (object_class, PROP_DEFAULT6,
 		g_param_spec_boolean (NM_ACTIVE_CONNECTION_DEFAULT6,
 		                      "Default6",
@@ -446,6 +926,20 @@ nm_active_connection_class_init (NMActiveConnectionClass *vpn_class)
 		                      FALSE,
 		                      G_PARAM_READWRITE));
 
+	g_object_class_install_property (object_class, PROP_IP6_CONFIG,
+		 g_param_spec_boxed (NM_ACTIVE_CONNECTION_IP6_CONFIG,
+		                     "IP6 Config",
+		                     "IP6 Config",
+		                     DBUS_TYPE_G_OBJECT_PATH,
+		                     G_PARAM_READABLE));
+
+	g_object_class_install_property (object_class, PROP_DHCP6_CONFIG,
+		 g_param_spec_boxed (NM_ACTIVE_CONNECTION_DHCP6_CONFIG,
+		                     "DHCP6 Config",
+		                     "DHCP6 Config",
+		                     DBUS_TYPE_G_OBJECT_PATH,
+		                     G_PARAM_READABLE));
+
 	g_object_class_install_property (object_class, PROP_VPN,
 		g_param_spec_boolean (NM_ACTIVE_CONNECTION_VPN,
 		                      "VPN",
@@ -466,49 +960,37 @@ nm_active_connection_class_init (NMActiveConnectionClass *vpn_class)
 		                     "Internal Connection",
 		                     "Internal connection",
 		                     NM_TYPE_CONNECTION,
-		                     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | NM_PROPERTY_PARAM_NO_EXPORT));
+		                     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
 	g_object_class_install_property (object_class, PROP_INT_DEVICE,
 		g_param_spec_object (NM_ACTIVE_CONNECTION_INT_DEVICE,
 		                     "Internal device",
 		                     "Internal device",
 		                     NM_TYPE_DEVICE,
-		                     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | NM_PROPERTY_PARAM_NO_EXPORT));
+		                     G_PARAM_READWRITE));
 
-	g_object_class_install_property (object_class, PROP_INT_USER_REQUESTED,
-		g_param_spec_boolean (NM_ACTIVE_CONNECTION_INT_USER_REQUESTED,
-		                      "User requested",
-		                      "User requested",
-		                      FALSE,
-		                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | NM_PROPERTY_PARAM_NO_EXPORT));
-
-	g_object_class_install_property (object_class, PROP_INT_USER_UID,
-		g_param_spec_ulong (NM_ACTIVE_CONNECTION_INT_USER_UID,
-		                    "User UID",
-		                    "User UID (if user requested)",
-		                    0, G_MAXULONG, 0,
-		                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | NM_PROPERTY_PARAM_NO_EXPORT));
-
-	g_object_class_install_property (object_class, PROP_INT_ASSUMED,
-		g_param_spec_boolean (NM_ACTIVE_CONNECTION_INT_ASSUMED,
-		                      "Assumed",
-		                      "Assumed",
-		                      FALSE,
-		                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | NM_PROPERTY_PARAM_NO_EXPORT));
+	g_object_class_install_property (object_class, PROP_INT_SUBJECT,
+		g_param_spec_object (NM_ACTIVE_CONNECTION_INT_SUBJECT,
+		                     "Subject",
+		                     "Subject",
+		                     NM_TYPE_AUTH_SUBJECT,
+		                     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
 	g_object_class_install_property (object_class, PROP_INT_MASTER,
 		g_param_spec_object (NM_ACTIVE_CONNECTION_INT_MASTER,
-		                     "Internal master device",
-		                     "Internal device",
-		                     NM_TYPE_DEVICE,
-		                     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | NM_PROPERTY_PARAM_NO_EXPORT));
-
-	/* Signals */
-	signals[PROPERTIES_CHANGED] = 
-		nm_properties_changed_signal_new (object_class,
-		                                  G_STRUCT_OFFSET (NMActiveConnectionClass, properties_changed));
-
-	dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (vpn_class),
-									 &dbus_glib_nm_active_connection_object_info);
+		                     "Internal master active connection",
+		                     "Internal active connection",
+		                     NM_TYPE_ACTIVE_CONNECTION,
+		                     G_PARAM_READWRITE));
+
+	g_object_class_install_property (object_class, PROP_INT_MASTER_READY,
+		g_param_spec_boolean (NM_ACTIVE_CONNECTION_INT_MASTER_READY,
+		                      "Internal master active connection ready for slaves",
+		                      "Internal active connection ready",
+		                      FALSE, G_PARAM_READABLE));
+
+	nm_dbus_manager_register_exported_type (nm_dbus_manager_get (),
+	                                        G_TYPE_FROM_CLASS (ac_class),
+	                                        &dbus_glib_nm_active_connection_object_info);
 }