libnm-glib-vpn: add support for interactive secrets requests

There are three additions to the D-Bus interface for VPN plugins as
part of this patch:

1) ConnectInteractive(): called by NM instead of Connect() to let
the plugin know that it can request additional secrets during the
connection process using SecretsRequired

2) SecretsRequired: a new signal emitted by the plugin to indicate
to NetworkManager that additional secrets are required to connect;
can only be called if NetworkManager initiated the connection by
calling the ConnectInteractive() method

3) NewSecrets(): a new method of the plugin that NetworkManager calls
when new secrets requested by the SecretsRequired signal have been
retrieved from secret agents

We need new methods because agents need to be aware of the hints that
the VPN plugins may send with the SecretsRequired signal (detailing
the specific secrets that are required) and at this time, not all
agents support passing those hints to the VPN plugin authentication
dialogs.
This commit is contained in:
Dan Williams 2013-06-18 09:32:53 -05:00
parent eacd4cf8f9
commit cc924d8bab
4 changed files with 258 additions and 17 deletions

View file

@ -5,9 +5,11 @@
<tp:docstring>
This interface is provided by plugins providing VPN services to the NetworkManager daemon.
</tp:docstring>
<method name="Connect">
<tp:docstring>
Tells the plugin to connect.
Tells the plugin to connect. Interactive secrets requests (eg, emitting
the SecretsRequired signal) are not allowed.
</tp:docstring>
<annotation name="org.freedesktop.DBus.GLib.CSymbol" value="impl_vpn_plugin_connect"/>
<arg name="connection" type="a{sa{sv}}" direction="in" tp:type="String_String_Variant_Map_Map">
@ -21,6 +23,35 @@
<tp:error name="org.freedesktop.NetworkManager.VPN.Error.StoppingInProgress"/>
<tp:error name="org.freedesktop.NetworkManager.VPN.Error.BadArguments"/>
<tp:error name="org.freedesktop.NetworkManager.VPN.Error.LaunchFailed"/>
<tp:error name="org.freedesktop.NetworkManager.VPN.Error.WrongState"/>
</tp:possible-errors>
</method>
<method name="ConnectInteractive">
<tp:docstring>
Tells the plugin to connect, allowing interactive secrets requests (eg
the plugin is allowed to emit the SecretsRequired signal if the VPN
service indicates that it needs additional secrets during the connect
process).
</tp:docstring>
<annotation name="org.freedesktop.DBus.GLib.CSymbol" value="impl_vpn_plugin_connect_interactive"/>
<arg name="connection" type="a{sa{sv}}" direction="in" tp:type="String_String_Variant_Map_Map">
<tp:docstring>
Describes the connection to be established.
</tp:docstring>
</arg>
<arg name="details" type="a{sv}" direction="in" tp:type="String_Variant_Map">
<tp:docstring>
Additional details about the Connect process.
</tp:docstring>
</arg>
<tp:possible-errors>
<tp:error name="org.freedesktop.NetworkManager.VPN.Error.StartingInProgress"/>
<tp:error name="org.freedesktop.NetworkManager.VPN.Error.AlreadyStarted"/>
<tp:error name="org.freedesktop.NetworkManager.VPN.Error.StoppingInProgress"/>
<tp:error name="org.freedesktop.NetworkManager.VPN.Error.BadArguments"/>
<tp:error name="org.freedesktop.NetworkManager.VPN.Error.LaunchFailed"/>
<tp:error name="org.freedesktop.NetworkManager.VPN.Error.WrongState"/>
</tp:possible-errors>
</method>
@ -122,6 +153,47 @@
</arg>
</signal>
<signal name="SecretsRequired">
<tp:docstring>
Emitted during an ongoing ConnectInteractive() request when the plugin
has determined that new secrets are required. NetworkManager will then
call the NewSecrets() method with a connection hash including the new
secrets.
</tp:docstring>
<arg name="message" type="s" direction="out">
<tp:docstring>
Informational message, if any, about the request. For example, if
a second PIN is required, could indicate to the user to wait for
the token code to change until entering the next PIN.
</tp:docstring>
</arg>
<arg name="secrets" type="as" direction="out">
<tp:docstring>
Array of strings of VPN secret names which the plugin thinks
secrets may be required for, or other VPN-specific data to be
processed by the VPN's front-end.
</tp:docstring>
</arg>
</signal>
<method name="NewSecrets">
<tp:docstring>
Called in response to a SecretsRequired signal to deliver updated secrets
or other information to the plugin.
</tp:docstring>
<annotation name="org.freedesktop.DBus.GLib.CSymbol" value="impl_vpn_plugin_new_secrets"/>
<arg name="connection" type="a{sa{sv}}" direction="in" tp:type="String_String_Variant_Map_Map">
<tp:docstring>
Describes the connection including the new secrets.
</tp:docstring>
</arg>
<tp:possible-errors>
<tp:error name="org.freedesktop.NetworkManager.VPN.Error.WrongState"/>
<tp:error name="org.freedesktop.NetworkManager.VPN.Error.BadArguments"/>
<tp:error name="org.freedesktop.NetworkManager.VPN.Error.LaunchFailed"/>
</tp:possible-errors>
</method>
<signal name="Config">
<tp:docstring>
The plugin obtained generic configuration information.

View file

@ -7,6 +7,7 @@ global:
nm_vpn_plugin_get_connection;
nm_vpn_plugin_get_state;
nm_vpn_plugin_get_type;
nm_vpn_plugin_secrets_required;
nm_vpn_plugin_set_ip4_config;
nm_vpn_plugin_set_login_banner;
nm_vpn_plugin_set_state;

View file

@ -31,13 +31,22 @@
static gboolean impl_vpn_plugin_connect (NMVPNPlugin *plugin,
GHashTable *connection,
GError **err);
GError **error);
static gboolean impl_vpn_plugin_connect_interactive (NMVPNPlugin *plugin,
GHashTable *connection,
GHashTable *details,
GError **error);
static gboolean impl_vpn_plugin_need_secrets (NMVPNPlugin *plugin,
GHashTable *connection,
char **service_name,
GError **err);
static gboolean impl_vpn_plugin_new_secrets (NMVPNPlugin *plugin,
GHashTable *connection,
GError **err);
static gboolean impl_vpn_plugin_disconnect (NMVPNPlugin *plugin,
GError **err);
@ -74,6 +83,7 @@ typedef struct {
guint connect_timer;
guint quit_timer;
guint fail_stop_id;
gboolean interactive;
gboolean got_config;
gboolean has_ip4, got_ip4;
@ -93,6 +103,7 @@ enum {
LOGIN_BANNER,
FAILURE,
QUIT,
SECRETS_REQUIRED,
LAST_SIGNAL
};
@ -275,6 +286,16 @@ fail_stop (gpointer data)
return FALSE;
}
static void
schedule_fail_stop (NMVPNPlugin *plugin)
{
NMVPNPluginPrivate *priv = NM_VPN_PLUGIN_GET_PRIVATE (plugin);
if (priv->fail_stop_id)
g_source_remove (priv->fail_stop_id);
priv->fail_stop_id = g_idle_add (fail_stop, plugin);
}
void
nm_vpn_plugin_set_config (NMVPNPlugin *plugin,
GHashTable *config)
@ -395,12 +416,26 @@ connect_timer_removed (gpointer data)
NM_VPN_PLUGIN_GET_PRIVATE (data)->connect_timer = 0;
}
static gboolean
impl_vpn_plugin_connect (NMVPNPlugin *plugin,
GHashTable *properties,
GError **error)
static void
connect_timer_start (NMVPNPlugin *plugin)
{
NMVPNPluginPrivate *priv = NM_VPN_PLUGIN_GET_PRIVATE (plugin);
priv->connect_timer = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT,
60,
connect_timer_expired,
plugin,
connect_timer_removed);
}
static gboolean
_connect_generic (NMVPNPlugin *plugin,
GHashTable *properties,
GHashTable *details,
GError **error)
{
NMVPNPluginPrivate *priv = NM_VPN_PLUGIN_GET_PRIVATE (plugin);
NMVPNPluginClass *vpn_class = NM_VPN_PLUGIN_GET_CLASS (plugin);
NMConnection *connection;
gboolean success = FALSE;
GError *local = NULL;
@ -424,27 +459,52 @@ impl_vpn_plugin_connect (NMVPNPlugin *plugin,
nm_vpn_plugin_set_state (plugin, NM_VPN_SERVICE_STATE_STARTING);
success = NM_VPN_PLUGIN_GET_CLASS (plugin)->connect (plugin, connection, error);
priv->interactive = FALSE;
if (details && !vpn_class->connect_interactive) {
g_set_error_literal (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_GENERAL,
"Plugin does not implement ConnectInteractive()");
return FALSE;
}
if (details) {
priv->interactive = TRUE;
success = vpn_class->connect_interactive (plugin, connection, details, error);
} else
success = vpn_class->connect (plugin, connection, error);
if (success) {
/* Add a timer to make sure we do not wait indefinitely for the successful connect. */
priv->connect_timer = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT,
60,
connect_timer_expired,
plugin,
connect_timer_removed);
connect_timer_start (plugin);
} else {
/* Stop the plugin from an idle handler so that the Connect
* method return gets sent before the STOP StateChanged signal.
*/
if (priv->fail_stop_id)
g_source_remove (priv->fail_stop_id);
priv->fail_stop_id = g_idle_add (fail_stop, plugin);
schedule_fail_stop (plugin);
}
g_object_unref (connection);
return success;
}
static gboolean
impl_vpn_plugin_connect (NMVPNPlugin *plugin,
GHashTable *connection,
GError **error)
{
return _connect_generic (plugin, connection, NULL, error);
}
static gboolean
impl_vpn_plugin_connect_interactive (NMVPNPlugin *plugin,
GHashTable *connection,
GHashTable *details,
GError **error)
{
return _connect_generic (plugin, connection, details, error);
}
/***************************************************************/
static gboolean
impl_vpn_plugin_need_secrets (NMVPNPlugin *plugin,
GHashTable *properties,
@ -499,6 +559,94 @@ out:
return ret;
}
static gboolean
impl_vpn_plugin_new_secrets (NMVPNPlugin *plugin,
GHashTable *properties,
GError **error)
{
NMVPNPluginPrivate *priv = NM_VPN_PLUGIN_GET_PRIVATE (plugin);
NMConnection *connection;
GError *local = NULL;
gboolean success;
if (priv->state != NM_VPN_SERVICE_STATE_STARTING) {
g_set_error (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_WRONG_STATE,
"Could not accept new secrets: wrong plugin state %d",
priv->state);
return FALSE;
}
connection = nm_connection_new_from_hash (properties, &local);
if (!connection) {
g_set_error (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
"Invalid connection: (%d) %s",
local->code, local->message);
g_clear_error (&local);
return FALSE;
}
if (!NM_VPN_PLUGIN_GET_CLASS (plugin)->new_secrets) {
g_set_error_literal (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_GENERAL,
"Could not accept new secrets: plugin cannot process interactive secrets");
g_object_unref (connection);
return FALSE;
}
success = NM_VPN_PLUGIN_GET_CLASS (plugin)->new_secrets (plugin, connection, error);
if (success) {
/* Add a timer to make sure we do not wait indefinitely for the successful connect. */
connect_timer_start (plugin);
} else {
/* Stop the plugin from and idle handler so that the NewSecrets
* method return gets sent before the STOP StateChanged signal.
*/
schedule_fail_stop (plugin);
}
g_object_unref (connection);
return success;
}
/**
* nm_vpn_plugin_secrets_required:
* @plugin: the #NMVPNPlugin
* @message: an information message about why secrets are required, if any
* @hints: VPN specific secret names for required new secrets
*
* Called by VPN plugin implementations to signal to NetworkManager that secrets
* are required during the connection process. This signal may be used to
* request new secrets when the secrets originally provided by NetworkManager
* are insufficient, or the VPN process indicates that it needs additional
* information to complete the request.
*
* Since: 0.9.10
*/
void
nm_vpn_plugin_secrets_required (NMVPNPlugin *plugin,
const char *message,
const char **hints)
{
NMVPNPluginPrivate *priv = NM_VPN_PLUGIN_GET_PRIVATE (plugin);
/* Plugin must be able to accept the new secrets if it calls this method */
g_return_if_fail (NM_VPN_PLUGIN_GET_CLASS (plugin)->new_secrets);
/* Plugin cannot call this method if NetworkManager didn't originally call
* ConnectInteractive().
*/
g_return_if_fail (priv->interactive == TRUE);
/* Cancel the connect timer since secrets might take a while. It'll
* get restarted when the secrets come back via NewSecrets().
*/
if (priv->connect_timer)
g_source_remove (priv->connect_timer);
g_signal_emit (plugin, signals[SECRETS_REQUIRED], 0, message, hints);
}
/***************************************************************/
static gboolean
impl_vpn_plugin_disconnect (NMVPNPlugin *plugin,
GError **err)
@ -835,6 +983,14 @@ nm_vpn_plugin_class_init (NMVPNPluginClass *plugin_class)
G_TYPE_NONE, 1,
G_TYPE_UINT);
signals[SECRETS_REQUIRED] =
g_signal_new ("secrets-required",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
0, NULL, NULL,
NULL,
G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRV);
signals[CONFIG] =
g_signal_new ("config",
G_OBJECT_CLASS_TYPE (object_class),

View file

@ -121,11 +121,19 @@ typedef struct {
void (*ip6_config) (NMVPNPlugin *plugin,
GHashTable *config);
/* more methods */
gboolean (*new_secrets) (NMVPNPlugin *plugin,
NMConnection *connection,
GError **error);
gboolean (*connect_interactive) (NMVPNPlugin *plugin,
NMConnection *connection,
GHashTable *details,
GError **error);
/* Padding for future expansion */
void (*_reserved1) (void);
void (*_reserved2) (void);
void (*_reserved3) (void);
void (*_reserved4) (void);
} NMVPNPluginClass;
GType nm_vpn_plugin_get_type (void);
@ -137,6 +145,10 @@ NMVPNServiceState nm_vpn_plugin_get_state (NMVPNPlugin *plugin);
void nm_vpn_plugin_set_state (NMVPNPlugin *plugin,
NMVPNServiceState state);
void nm_vpn_plugin_secrets_required (NMVPNPlugin *plugin,
const char *message,
const char **hints);
void nm_vpn_plugin_set_login_banner (NMVPNPlugin *plugin,
const char *banner);