On Tue, 2014-08-19 at 11:06 -0500, David Woodhouse wrote:
Sitting on a crappy hotel network this week, I'm annoyed by how often I have to reauthenticate to my VPN server. I'd *really* like the secrets to be cached so that it can just *work* when I fall off the wireless and rejoin. It doesn't look *so* hard to do this with a hack in nm-openconnect-service itself. When we connect, remember the secrets locally (and disable the "quit" signal so we don't exit on idle). If we later get a need_secrets() call, and if we already have them locally (although they won't be in the settings dictionary that we're sent), then just return FALSE. We'll get a connect() call, and we can use the cached secrets to make the connection. If the connection *fails* then invalidate the cached secrets. That kind of works, but it's ugly. And at the very least, we'd really like to differentiate between a disconnect call which happens because the physical network went down, and a disconnect call triggered by explicit user request. We'd probably want to sign off and terminate (and forget) the session on the latter, but keep it alive and cached in the former case. But really, rather than hacking this up in the vpn service itself, perhaps what I want here is a new secret flag called something like NM_SETTING_SECRET_SAVED_NON_PERSISTENT — which allows the secret to be stored, but only in *memory* for the lifetime of a given session. And then they could be automatically discarded when the user explicitly requests a disconnect, etc. Thoughts?
(Full citation preserved since I'm not sure the original got through. Yay for crappy hotel networks) Here's my hack, now that it's actually working: diff --git a/src/nm-openconnect-service-openconnect-helper.c b/src/nm-openconnect-service-openconnect-helper.c index 2fa6852..7ad5e75 100644 --- a/src/nm-openconnect-service-openconnect-helper.c +++ b/src/nm-openconnect-service-openconnect-helper.c @@ -620,6 +620,19 @@ main (int argc, char *argv[]) if (val) g_hash_table_insert (ip6config, NM_VPN_PLUGIN_IP6_CONFIG_DNS, val); + /* Session timeout */ + tmp = getenv ("CISCO_CSTP_OPTIONS"); + while (tmp) { + if (!strncmp(tmp, "X-CSTP-Session-Timeout=", 23)) { + val = uint_to_gvalue (strtol (tmp + 23, NULL, 10)); + g_hash_table_insert(config, "openconnect_session_timeout", val); + break; + } + tmp = strchr(tmp, '\n'); + if (tmp) + tmp++; + } + /* Routes */ val = get_ip6_routes (); if (val) { diff --git a/src/nm-openconnect-service.c b/src/nm-openconnect-service.c index 7dcf964..d23640d 100644 --- a/src/nm-openconnect-service.c +++ b/src/nm-openconnect-service.c @@ -54,6 +54,12 @@ G_DEFINE_TYPE (NMOPENCONNECTPlugin, nm_openconnect_plugin, NM_TYPE_VPN_PLUGIN) typedef struct { + char *uuid; + char *cookie; + char *gateway; + char *gwcert; + time_t cookie_start; + unsigned long cookie_validity; GPid pid; char *tun_name; } NMOPENCONNECTPluginPrivate; @@ -103,6 +109,7 @@ static ValidProperty valid_secrets[] = { { NULL, G_TYPE_NONE, 0, 0 } }; + static uid_t tun_owner; static gid_t tun_group; static gboolean debug = FALSE; @@ -114,6 +121,7 @@ typedef struct ValidateInfo { gboolean have_items; } ValidateInfo; + static void validate_one_property (const char *key, const char *value, gpointer user_data) { @@ -387,6 +395,8 @@ nm_openconnect_start_openconnect_binary (NMOPENCONNECTPlugin *plugin, /* The actual gateway to use (after redirection) comes from the auth dialog, so it's in the secrets hash not the properties */ props_vpn_gw = nm_setting_vpn_get_secret (s_vpn, NM_OPENCONNECT_KEY_GATEWAY); + if (!props_vpn_gw || !strlen (props_vpn_gw) ) + props_vpn_gw = priv->gateway; if (!props_vpn_gw || !strlen (props_vpn_gw) ) { g_set_error (error, NM_VPN_PLUGIN_ERROR, @@ -397,6 +407,8 @@ nm_openconnect_start_openconnect_binary (NMOPENCONNECTPlugin *plugin, } props_cookie = nm_setting_vpn_get_secret (s_vpn, NM_OPENCONNECT_KEY_COOKIE); + if (!props_cookie || !strlen (props_cookie) ) + props_cookie = priv->cookie; if (!props_cookie || !strlen (props_cookie)) { g_set_error (error, NM_VPN_PLUGIN_ERROR, @@ -406,6 +418,8 @@ nm_openconnect_start_openconnect_binary (NMOPENCONNECTPlugin *plugin, return -1; } props_gwcert = nm_setting_vpn_get_secret (s_vpn, NM_OPENCONNECT_KEY_GWCERT); + if (!props_gwcert || !strlen (props_gwcert) ) + props_gwcert = priv->gwcert; props_cacert = nm_setting_vpn_get_data_item (s_vpn, NM_OPENCONNECT_KEY_CACERT); props_mtu = nm_setting_vpn_get_data_item (s_vpn, NM_OPENCONNECT_KEY_MTU); @@ -452,6 +466,9 @@ nm_openconnect_start_openconnect_binary (NMOPENCONNECTPlugin *plugin, g_ptr_array_add (openconnect_argv, NULL); + /* Only allow the cookie reuse when connections are successful */ + priv->cookie_validity = 0; + if (!g_spawn_async_with_pipes (NULL, (char **) openconnect_argv->pdata, NULL, G_SPAWN_DO_NOT_REAP_CHILD, openconnect_drop_child_privs, priv->tun_name, @@ -507,16 +524,34 @@ real_connect (NMVPNPlugin *plugin, } static gboolean +cached_secrets_valid (NMVPNPlugin *plugin, NMSettingVPN *s_vpn, const char *uuid) +{ + NMOPENCONNECTPluginPrivate *priv = NM_OPENCONNECT_PLUGIN_GET_PRIVATE (plugin); + + if (!uuid || !priv->uuid || !priv->cookie_validity || strcmp(priv->uuid, uuid)) + return FALSE; + + /* Only use cached cookie if it's valid for at least an hour */ + if ((priv->cookie_start + priv->cookie_validity) > time(NULL) + 3600) + return TRUE; + + return FALSE; +} + +static gboolean real_need_secrets (NMVPNPlugin *plugin, NMConnection *connection, char **setting_name, GError **error) { + NMOPENCONNECTPluginPrivate *priv = NM_OPENCONNECT_PLUGIN_GET_PRIVATE (plugin); NMSettingVPN *s_vpn; + const char *gateway, *cookie, *gwcert, *uuid; g_return_val_if_fail (NM_IS_VPN_PLUGIN (plugin), FALSE); g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE); + uuid = nm_connection_get_uuid (connection); s_vpn = NM_SETTING_VPN (nm_connection_get_setting (connection, NM_TYPE_SETTING_VPN)); if (!s_vpn) { g_set_error (error, @@ -530,18 +565,26 @@ real_need_secrets (NMVPNPlugin *plugin, /* We just need the WebVPN cookie, and the final IP address of the gateway (after HTTP redirects, which do happen). All the certificate/SecurID nonsense can be handled for us, in the user's context, by auth-dialog */ - if (!nm_setting_vpn_get_secret (s_vpn, NM_OPENCONNECT_KEY_GATEWAY)) { - *setting_name = NM_SETTING_VPN_SETTING_NAME; - return TRUE; - } - if (!nm_setting_vpn_get_secret (s_vpn, NM_OPENCONNECT_KEY_COOKIE)) { - *setting_name = NM_SETTING_VPN_SETTING_NAME; - return TRUE; - } - if (!nm_setting_vpn_get_secret (s_vpn, NM_OPENCONNECT_KEY_GWCERT)) { + gateway = nm_setting_vpn_get_secret (s_vpn, NM_OPENCONNECT_KEY_GATEWAY); + cookie = nm_setting_vpn_get_secret (s_vpn, NM_OPENCONNECT_KEY_COOKIE); + gwcert = nm_setting_vpn_get_secret (s_vpn, NM_OPENCONNECT_KEY_GWCERT); + if (!gateway || !cookie || !gwcert) { + if (cached_secrets_valid(plugin, s_vpn, uuid)) + return FALSE; + *setting_name = NM_SETTING_VPN_SETTING_NAME; return TRUE; } + + g_free(priv->uuid); + priv->uuid = g_strdup(uuid); + g_free(priv->cookie); + priv->cookie = g_strdup(cookie); + g_free(priv->gwcert); + priv->gwcert = g_strdup(gwcert); + g_free(priv->gateway); + priv->gateway = g_strdup(gateway); + priv->cookie_start = time(NULL); return FALSE; } @@ -629,6 +672,18 @@ quit_mainloop (NMOPENCONNECTPlugin *plugin, gpointer user_data) g_main_loop_quit ((GMainLoop *) user_data); } +static void got_config (NMOPENCONNECTPlugin *plugin, GHashTable *config, + gpointer user_data) +{ + NMOPENCONNECTPluginPrivate *priv = NM_OPENCONNECT_PLUGIN_GET_PRIVATE (plugin); + GValue *val = g_hash_table_lookup (config, "openconnect_session_timeout"); + + if (!val) + return; + + priv->cookie_validity = g_value_get_uint(val); +} + int main (int argc, char *argv[]) { NMOPENCONNECTPlugin *plugin; @@ -684,6 +739,8 @@ int main (int argc, char *argv[]) if (!persist) g_signal_connect (plugin, "quit", G_CALLBACK (quit_mainloop), loop); + g_signal_connect (plugin, "config", G_CALLBACK (got_config), plugin); + setup_signals (); g_main_loop_run (loop); -- dwmw2
Attachment:
smime.p7s
Description: S/MIME cryptographic signature