diff --git a/ChangeLog b/ChangeLog index 8f4be626d5..6315318bef 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2007-09-26 Dan Williams + + * include/NetworkManagerVPN.h + - Add a NEED_AUTH state + + * src/vpn-manager/nm-vpn-connection.c + - Implement the NEED_AUTH state. First ask the VPN service plugin if + the connection needs secrets, and if so, then ask the settings + service to fill in the secrets. Then start the connection. + 2007-09-26 Dan Williams * src/vpn-manager/nm-vpn-manager.c diff --git a/include/NetworkManagerVPN.h b/include/NetworkManagerVPN.h index e637a23ff8..f1dcfe2ede 100644 --- a/include/NetworkManagerVPN.h +++ b/include/NetworkManagerVPN.h @@ -83,6 +83,7 @@ typedef enum NMVPNConnectionState { NM_VPN_CONNECTION_STATE_UNKNOWN = 0, NM_VPN_CONNECTION_STATE_PREPARE, + NM_VPN_CONNECTION_STATE_NEED_AUTH, NM_VPN_CONNECTION_STATE_CONNECT, NM_VPN_CONNECTION_STATE_IP_CONFIG_GET, NM_VPN_CONNECTION_STATE_ACTIVATED, diff --git a/src/vpn-manager/nm-vpn-connection.c b/src/vpn-manager/nm-vpn-connection.c index db499b9505..336733e76b 100644 --- a/src/vpn-manager/nm-vpn-connection.c +++ b/src/vpn-manager/nm-vpn-connection.c @@ -30,18 +30,22 @@ #include "nm-vpn-connection.h" #include "nm-dbus-manager.h" +#include "nm-manager.h" #include "NetworkManagerSystem.h" #include "nm-utils.h" #include "nm-vpn-plugin-bindings.h" static gboolean impl_vpn_connection_disconnect (NMVPNConnection *connection, GError **err); +#define CONNECTION_GET_SECRETS_CALL_TAG "get-secrets-call" + #include "nm-vpn-connection-glue.h" G_DEFINE_TYPE (NMVPNConnection, nm_vpn_connection, G_TYPE_OBJECT) typedef struct { NMConnection *connection; + gulong secrets_updated_id; NMDevice *parent_dev; char *object_path; @@ -81,13 +85,23 @@ nm_vpn_connection_set_state (NMVPNConnection *connection, priv = NM_VPN_CONNECTION_GET_PRIVATE (connection); - if (state != priv->state) { - priv->state = state; + if (state == priv->state) + return; - g_object_ref (connection); - g_signal_emit (connection, signals[STATE_CHANGED], 0, state); - g_object_unref (connection); + /* If transitioning away from the NEED_AUTH state, clean up */ + if (priv->state == NM_VPN_CONNECTION_STATE_NEED_AUTH) { + if (priv->secrets_updated_id) { + g_signal_handler_disconnect (priv->connection, + priv->secrets_updated_id); + priv->secrets_updated_id = 0; + } } + + priv->state = state; + + g_object_ref (connection); + g_signal_emit (connection, signals[STATE_CHANGED], 0, state); + g_object_unref (connection); } static void @@ -104,22 +118,24 @@ nm_vpn_connection_new (NMConnection *connection, NMDevice *parent_device) { NMVPNConnection *vpn_connection; + NMVPNConnectionPrivate *priv; + gulong id; g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL); g_return_val_if_fail (NM_IS_DEVICE (parent_device), NULL); vpn_connection = (NMVPNConnection *) g_object_new (NM_TYPE_VPN_CONNECTION, NULL); - if (vpn_connection) { - NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (vpn_connection); + if (!vpn_connection) + return NULL; - priv->connection = connection; - priv->parent_dev = g_object_ref (parent_device); + priv = NM_VPN_CONNECTION_GET_PRIVATE (vpn_connection); - priv->device_monitor = g_signal_connect (parent_device, "state-changed", - G_CALLBACK (device_state_changed), - vpn_connection); - } + priv->connection = g_object_ref (connection); + priv->parent_dev = g_object_ref (parent_device); + priv->device_monitor = g_signal_connect (parent_device, "state-changed", + G_CALLBACK (device_state_changed), + vpn_connection); return vpn_connection; } @@ -332,7 +348,6 @@ nm_vpn_connection_connect_cb (DBusGProxy *proxy, GError *err, gpointer user_data if (err) { nm_warning ("(VPN connection '%s' could not start. dbus says: '%s'.", nm_vpn_connection_get_name (connection), err->message); - g_error_free (err); nm_vpn_connection_set_state (connection, NM_VPN_CONNECTION_STATE_FAILED); } else { nm_vpn_connection_set_state (connection, NM_VPN_CONNECTION_STATE_IP_CONFIG_GET); @@ -342,29 +357,15 @@ nm_vpn_connection_connect_cb (DBusGProxy *proxy, GError *err, gpointer user_data } } -void -nm_vpn_connection_activate (NMVPNConnection *connection) +static void +really_activate (NMVPNConnection *connection) { NMVPNConnectionPrivate *priv; - NMDBusManager *dbus_mgr; g_return_if_fail (NM_IS_VPN_CONNECTION (connection)); - g_return_if_fail (nm_vpn_connection_get_state (connection) == NM_VPN_CONNECTION_STATE_PREPARE); + g_return_if_fail (nm_vpn_connection_get_state (connection) == NM_VPN_CONNECTION_STATE_NEED_AUTH); priv = NM_VPN_CONNECTION_GET_PRIVATE (connection); - dbus_mgr = nm_dbus_manager_get (); - - priv->proxy = dbus_g_proxy_new_for_name (nm_dbus_manager_get_connection (dbus_mgr), - nm_vpn_connection_get_service (connection), - NM_VPN_DBUS_PLUGIN_PATH, - NM_VPN_DBUS_PLUGIN_INTERFACE); - g_object_unref (dbus_mgr); - - /* StateChanges signal */ - dbus_g_proxy_add_signal (priv->proxy, "StateChanged", G_TYPE_UINT, G_TYPE_INVALID); - dbus_g_proxy_connect_signal (priv->proxy, "StateChanged", - G_CALLBACK (plugin_state_changed), - connection, NULL); /* Ip4Config signal */ dbus_g_object_register_marshaller (g_cclosure_marshal_VOID__BOXED, @@ -384,6 +385,33 @@ nm_vpn_connection_activate (NMVPNConnection *connection) nm_vpn_connection_set_state (connection, NM_VPN_CONNECTION_STATE_CONNECT); } +void +nm_vpn_connection_activate (NMVPNConnection *connection) +{ + NMVPNConnectionPrivate *priv; + NMDBusManager *dbus_mgr; + + g_return_if_fail (NM_IS_VPN_CONNECTION (connection)); + g_return_if_fail (nm_vpn_connection_get_state (connection) == NM_VPN_CONNECTION_STATE_PREPARE); + + priv = NM_VPN_CONNECTION_GET_PRIVATE (connection); + + dbus_mgr = nm_dbus_manager_get (); + priv->proxy = dbus_g_proxy_new_for_name (nm_dbus_manager_get_connection (dbus_mgr), + nm_vpn_connection_get_service (connection), + NM_VPN_DBUS_PLUGIN_PATH, + NM_VPN_DBUS_PLUGIN_INTERFACE); + g_object_unref (dbus_mgr); + + /* StateChanged signal */ + dbus_g_proxy_add_signal (priv->proxy, "StateChanged", G_TYPE_UINT, G_TYPE_INVALID); + dbus_g_proxy_connect_signal (priv->proxy, "StateChanged", + G_CALLBACK (plugin_state_changed), + connection, NULL); + + nm_vpn_connection_set_state (connection, NM_VPN_CONNECTION_STATE_NEED_AUTH); +} + const char * nm_vpn_connection_get_object_path (NMVPNConnection *connection) { @@ -440,12 +468,226 @@ impl_vpn_connection_disconnect (NMVPNConnection *connection, GError **err) /******************************************************************************/ +static void +clear_need_auth (NMVPNConnection *vpn_connection) +{ + NMVPNConnectionPrivate *priv; + DBusGProxy *proxy; + DBusGProxyCall *call; + NMConnection *connection; + + g_return_if_fail (vpn_connection != NULL); + + priv = NM_VPN_CONNECTION_GET_PRIVATE (vpn_connection); + if (priv->secrets_updated_id) { + g_signal_handler_disconnect (priv->connection, + priv->secrets_updated_id); + priv->secrets_updated_id = 0; + } + + g_assert (priv->connection); + + proxy = g_object_get_data (G_OBJECT (priv->connection), NM_MANAGER_CONNECTION_PROXY_TAG); + if (!proxy || !DBUS_IS_G_PROXY (proxy)) + return; + + call = g_object_get_data (G_OBJECT (vpn_connection), CONNECTION_GET_SECRETS_CALL_TAG); + if (!call) + return; + + dbus_g_proxy_cancel_call (proxy, call); + g_object_set_data (G_OBJECT (vpn_connection), CONNECTION_GET_SECRETS_CALL_TAG, NULL); +} + +static void +connection_secrets_updated_cb (NMConnection *connection, + const char *setting_name, + NMVPNConnection *self) +{ + NMVPNConnectionPrivate *priv; + + g_return_if_fail (setting_name != NULL); + g_return_if_fail (self != NULL); + g_return_if_fail (NM_IS_VPN_CONNECTION (self)); + + priv = NM_VPN_CONNECTION_GET_PRIVATE (self); + if (priv->state == NM_VPN_CONNECTION_STATE_NEED_AUTH) + really_activate (self); +} + +typedef struct GetSecretsInfo { + NMVPNConnection *vpn_connection; + char *setting_name; +} GetSecretsInfo; + +static void +free_get_secrets_info (gpointer data) +{ + GetSecretsInfo * info = (GetSecretsInfo *) data; + + g_free (info->setting_name); + if (info->vpn_connection) + g_object_unref (info->vpn_connection); + g_slice_free (GetSecretsInfo, info); +} + +static void +get_secrets_cb (DBusGProxy *proxy, DBusGProxyCall *call, gpointer user_data) +{ + GetSecretsInfo *info = (GetSecretsInfo *) user_data; + NMVPNConnectionPrivate *priv; + GError *err = NULL; + GHashTable *secrets = NULL; + NMConnection *connection; + gulong id; + + if (!info || !info->vpn_connection || !info->setting_name) + goto error; + + g_object_set_data (G_OBJECT (info->vpn_connection), CONNECTION_GET_SECRETS_CALL_TAG, NULL); + + if (!dbus_g_proxy_end_call (proxy, call, &err, + dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE), &secrets, + G_TYPE_INVALID)) { + nm_warning ("Couldn't get connection secrets: %s.", err->message); + g_error_free (err); + goto error; + } + + if (g_hash_table_size (secrets) == 0) { + // FIXME: some better way to handle invalid message? + nm_warning ("GetSecrets call returned but no secrets were found."); + goto error; + } + + priv = NM_VPN_CONNECTION_GET_PRIVATE (info->vpn_connection); + id = g_signal_connect (priv->connection, + "secrets-updated", + G_CALLBACK (connection_secrets_updated_cb), + info->vpn_connection); + priv->secrets_updated_id = id; + + nm_connection_update_secrets (priv->connection, info->setting_name, secrets); + g_hash_table_destroy (secrets); + return; + +error: + nm_vpn_connection_fail (info->vpn_connection); +} + +static gboolean +get_connection_secrets (NMVPNConnection *vpn_connection, + const char *setting_name, + gboolean request_new) +{ + NMVPNConnectionPrivate *priv; + DBusGProxy *con_proxy; + GetSecretsInfo *info = NULL; + DBusGProxyCall *call; + NMConnection *connection; + + g_return_val_if_fail (vpn_connection != NULL, FALSE); + g_return_val_if_fail (NM_IS_VPN_CONNECTION (vpn_connection), FALSE); + g_return_val_if_fail (setting_name != NULL, FALSE); + + priv = NM_VPN_CONNECTION_GET_PRIVATE (vpn_connection); + g_assert (priv->connection); + + con_proxy = g_object_get_data (G_OBJECT (connection), NM_MANAGER_CONNECTION_PROXY_TAG); + g_return_val_if_fail (con_proxy && DBUS_IS_G_PROXY (con_proxy), FALSE); + + info = g_slice_new0 (GetSecretsInfo); + g_return_val_if_fail (info != NULL, FALSE); + + info->setting_name = g_strdup (setting_name); + if (!info->setting_name) { + nm_warning ("Not enough memory to get secrets"); + goto error; + } + + info->vpn_connection = g_object_ref (vpn_connection); + + /* use ..._with_timeout to give the user time to enter secrets */ + call = dbus_g_proxy_begin_call_with_timeout (con_proxy, "GetSecrets", + get_secrets_cb, + info, + free_get_secrets_info, + G_MAXINT32, + G_TYPE_STRING, setting_name, + G_TYPE_BOOLEAN, request_new, + G_TYPE_INVALID); + if (!call) { + nm_warning ("Could not call GetSecrets"); + goto error; + } + + g_object_set_data (G_OBJECT (vpn_connection), + CONNECTION_GET_SECRETS_CALL_TAG, + call); + return TRUE; + +error: + if (info) + free_get_secrets_info (info); + return FALSE; +} + +static void +connection_need_secrets_cb (DBusGProxy *proxy, + char *setting_name, + GError *error, + gpointer user_data) +{ + NMVPNConnection *vpn_connection = NM_VPN_CONNECTION (user_data); + + g_object_set_data (G_OBJECT (vpn_connection), + CONNECTION_GET_SECRETS_CALL_TAG, + NULL); + + if (error) { + g_warning ("%s.%d: NeedSecrets failed: %d %s", + __FILE__, __LINE__, + g_quark_to_string (error->domain), error->message); + nm_vpn_connection_fail (vpn_connection); + return; + } + + if (setting_name && strlen (setting_name)) { + if (!get_connection_secrets (vpn_connection, setting_name, TRUE)) + nm_vpn_connection_fail (vpn_connection); + } else { + /* No secrets needed */ + really_activate (vpn_connection); + } +} + +static void +call_need_secrets (NMVPNConnection *vpn_connection) +{ + NMVPNConnectionPrivate *priv; + const char *setting_name; + GHashTable *settings; + + priv = NM_VPN_CONNECTION_GET_PRIVATE (vpn_connection); + settings = nm_connection_to_hash (priv->connection); + org_freedesktop_NetworkManager_VPN_Plugin_need_secrets_async (priv->proxy, + settings, + connection_need_secrets_cb, + vpn_connection); + g_hash_table_destroy (settings); +} + static void connection_state_changed (NMVPNConnection *connection, NMVPNConnectionState state) { NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection); + clear_need_auth (connection); + switch (state) { + case NM_VPN_CONNECTION_STATE_NEED_AUTH: + call_need_secrets (connection); + break; case NM_VPN_CONNECTION_STATE_DISCONNECTED: case NM_VPN_CONNECTION_STATE_FAILED: if (priv->proxy) { @@ -474,7 +716,6 @@ connection_state_changed (NMVPNConnection *connection, NMVPNConnectionState stat /* Reset routes, nameservers, and domains of the currently active device */ nm_system_device_set_from_ip4_config (priv->parent_dev); } - break; default: break; @@ -503,6 +744,12 @@ finalize (GObject *object) { NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (object); + if (priv->secrets_updated_id) { + g_signal_handler_disconnect (priv->connection, + priv->secrets_updated_id); + priv->secrets_updated_id = 0; + } + if (priv->parent_dev) { if (priv->device_monitor) g_signal_handler_disconnect (priv->parent_dev, priv->device_monitor);