[network-manager-vpnc/dcbw/need-secrets: 2/3] core: parse vpnc stdin/stdout and request secrets asynchronously



commit 86f630f4c1b9ba688da55062ebe2135e0c36cdb5
Author: Dan Williams <dcbw redhat com>
Date:   Fri Jun 28 12:34:53 2013 -0500

    core: parse vpnc stdin/stdout and request secrets asynchronously
    
    Parse vpnc's output to recognize server messages and prompts for
    passwords and other information.  Unfortunately this requires two
    patches to vpnc, as vpnc (as of 0.5.3 and SVN 340-something)
    (1) expects stdin to be closed as a signal to stop parsing
    configuration which prevents writing passwords back to it, and
    (2) uses getpass() which opens /dev/tty instead of reading stdin.

 src/nm-vpnc-service.c |  602 +++++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 506 insertions(+), 96 deletions(-)
---
diff --git a/src/nm-vpnc-service.c b/src/nm-vpnc-service.c
index ec6f399..1a00dda 100644
--- a/src/nm-vpnc-service.c
+++ b/src/nm-vpnc-service.c
@@ -34,8 +34,10 @@
 #include <glib/gi18n.h>
 
 #include <nm-setting-vpn.h>
+#include <nm-utils.h>
+
 #include "nm-vpnc-service.h"
-#include "nm-utils.h"
+#include "utils.h"
 
 #if !defined(DIST_VERSION)
 # define DIST_VERSION VERSION
@@ -44,22 +46,35 @@
 static gboolean debug = FALSE;
 static GMainLoop *loop = NULL;
 
+/* TRUE if we can use vpnc's interactive mode (version 0.5.4 or greater)*/
+static gboolean use_interactive = FALSE;
+
 G_DEFINE_TYPE (NMVPNCPlugin, nm_vpnc_plugin, NM_TYPE_VPN_PLUGIN)
 
 typedef struct {
+       int fd;
+       GIOChannel *channel;
+       guint watch;
+       GString *buf;
+       gsize bufend;
+       gboolean error;
+} Pipe;
+
+typedef struct {
        GPid pid;
+       guint watch_id;
+
+       int infd;
+       Pipe out;
+       Pipe err;
+
+       GString *server_message;
+       gboolean server_message_done;
+       const char *pending_auth;
 } NMVPNCPluginPrivate;
 
 #define NM_VPNC_PLUGIN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_VPNC_PLUGIN, 
NMVPNCPluginPrivate))
 
-static const char *vpnc_binary_paths[] =
-{
-       "/usr/sbin/vpnc",
-       "/sbin/vpnc",
-       "/usr/local/sbin/vpnc",
-       NULL
-};
-
 #define NM_VPNC_HELPER_PATH            LIBEXECDIR"/nm-vpnc-service-vpnc-helper"
 #define NM_VPNC_UDP_ENCAPSULATION_PORT 0 /* random port */
 #define NM_VPNC_LOCAL_PORT_ISAKMP      0 /* random port */
@@ -224,12 +239,20 @@ nm_vpnc_properties_validate (NMSettingVPN *s_vpn, GError **error)
 }
 
 static gboolean
-nm_vpnc_secrets_validate (NMSettingVPN *s_vpn, GError **error)
+nm_vpnc_secrets_validate (NMSettingVPN *s_vpn,
+                          gboolean allow_missing,
+                          GError **error)
 {
-       ValidateInfo info = { &valid_secrets[0], error, FALSE };
+       GError *validate_error = NULL;
+       ValidateInfo info = { &valid_secrets[0], &validate_error, FALSE };
 
        nm_setting_vpn_foreach_secret (s_vpn, validate_one_property, &info);
-       if (!info.have_items) {
+       if (validate_error) {
+               g_propagate_error (error, validate_error);
+               return FALSE;
+       }
+
+       if (allow_missing == FALSE && !info.have_items) {
                g_set_error (error,
                             NM_VPN_PLUGIN_ERROR,
                             NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
@@ -238,7 +261,96 @@ nm_vpnc_secrets_validate (NMSettingVPN *s_vpn, GError **error)
                return FALSE;
        }
 
-       return *error ? FALSE : TRUE;
+       return TRUE;
+}
+
+static gboolean
+ensure_killed (gpointer data)
+{
+       int pid = GPOINTER_TO_INT (data);
+
+       if (kill (pid, 0) == 0)
+               kill (pid, SIGKILL);
+       waitpid (pid, NULL, 0);
+
+       return FALSE;
+}
+
+static void
+pipe_cleanup (Pipe *pipe)
+{
+       if (pipe->channel) {
+               g_source_remove (pipe->watch);
+               pipe->watch = 0;
+               g_io_channel_shutdown (pipe->channel, FALSE, NULL);
+               g_io_channel_unref (pipe->channel);
+               pipe->channel = NULL;
+       }
+       if (pipe->fd >= 0) {
+               close (pipe->fd);
+               pipe->fd = -1;
+       }
+       if (pipe->buf) {
+               g_string_free (pipe->buf, TRUE);
+               pipe->buf = NULL;
+       }
+}
+
+static void
+pipe_echo_finish (Pipe *pipe)
+{
+       GIOStatus status;
+       gsize bytes_read;
+       char buf[512];
+
+       do {
+               bytes_read = 0;
+               status = g_io_channel_read_chars (pipe->channel,
+                                                 buf,
+                                                 sizeof (buf),
+                                                 &bytes_read,
+                                                 NULL);
+               if (bytes_read) {
+                       fprintf (pipe->error ? stderr : stdout, "%.*s", (int) bytes_read, buf);
+                       fflush (pipe->error ? stderr : stdout);
+               }
+       } while (status == G_IO_STATUS_NORMAL);
+}
+
+static void
+vpnc_cleanup (NMVPNCPlugin *self, gboolean killit)
+{
+       NMVPNCPluginPrivate *priv = NM_VPNC_PLUGIN_GET_PRIVATE (self);
+
+       if (priv->infd >= 0) {
+               close (priv->infd);
+               priv->infd = -1;
+       }
+
+       pipe_cleanup (&priv->out);
+       pipe_cleanup (&priv->err);
+       g_string_truncate (priv->server_message, 0);
+       priv->server_message_done = FALSE;
+
+       if (priv->watch_id) {
+               g_source_remove (priv->watch_id);
+               priv->watch_id = 0;
+       }
+
+       if (priv->pid) {
+               if (killit) {
+                       /* Try giving it some time to disconnect cleanly */
+                       if (kill (priv->pid, SIGTERM) == 0)
+                               g_timeout_add (2000, ensure_killed, GINT_TO_POINTER (priv->pid));
+                       else
+                               kill (priv->pid, SIGKILL);
+                       g_message ("Terminated vpnc daemon with PID %d.", priv->pid);
+               } else {
+                       /* Already quit, just reap the child */
+                       waitpid (priv->pid, NULL, WNOHANG);
+               }
+               priv->pid = 0;
+       }
 }
 
 static void
@@ -252,17 +364,23 @@ vpnc_watch_cb (GPid pid, gint status, gpointer user_data)
                error = WEXITSTATUS (status);
                if (error != 0)
                        g_warning ("vpnc exited with error code %d", error);
-       }
-       else if (WIFSTOPPED (status))
+       } else if (WIFSTOPPED (status))
                g_warning ("vpnc stopped unexpectedly with signal %d", WSTOPSIG (status));
        else if (WIFSIGNALED (status))
                g_warning ("vpnc died with signal %d", WTERMSIG (status));
        else
                g_warning ("vpnc died from an unknown cause");
 
-       /* Reap child if needed. */
-       waitpid (priv->pid, NULL, WNOHANG);
-       priv->pid = 0;
+       priv->watch_id = 0;
+
+       /* Grab any remaining output, if any */
+       if (priv->out.channel)
+               pipe_echo_finish (&priv->out);
+       if (priv->err.channel)
+               pipe_echo_finish (&priv->err);
+
+       priv->infd = -1;
+       vpnc_cleanup (plugin, FALSE);
 
        /* Must be after data->state is set since signals use data->state */
        switch (error) {
@@ -281,57 +399,223 @@ vpnc_watch_cb (GPid pid, gint status, gpointer user_data)
        nm_vpn_plugin_set_state (NM_VPN_PLUGIN (plugin), NM_VPN_SERVICE_STATE_STOPPED);
 }
 
-static gint
-nm_vpnc_start_vpnc_binary (NMVPNCPlugin *plugin, GError **error)
+#define XAUTH_USERNAME_PROMPT "Enter username for "
+#define XAUTH_PASSWORD_PROMPT "Enter password for "
+#define IPSEC_SECRET_PROMPT   "Enter IPSec secret for "
+#define ASYNC_ANSWER_PROMPT   "Answer for VPN "
+#define ASYNC_PASSCODE_PROMPT "Passcode for VPN "
+#define ASYNC_PASSWORD_PROMPT "Password for VPN "
+
+static void
+vpnc_prompt (const char *data, gsize dlen, gpointer user_data)
 {
-       GPid    pid;
-       const char **vpnc_binary = NULL;
-       GPtrArray *vpnc_argv;
-       GSource *vpnc_watch;
-       gint    stdin_fd;
+       NMVPNCPlugin *plugin = NM_VPNC_PLUGIN (user_data);
+       NMVPNCPluginPrivate *priv = NM_VPNC_PLUGIN_GET_PRIVATE (plugin);
+       const char *hints[2] = { NULL, NULL };
+       char *prompt;
+
+       g_warn_if_fail (priv->pending_auth == NULL);
+       priv->pending_auth = NULL;
+
+       prompt = g_strndup (data, dlen);
+
+       g_debug ("vpnc requested input: '%s'", prompt);
+
+       if (g_str_has_prefix (prompt, XAUTH_USERNAME_PROMPT)) {
+               /* Username */
+               hints[0] = NM_VPNC_KEY_XAUTH_USER;
+       } else if (g_str_has_prefix (prompt, XAUTH_PASSWORD_PROMPT) ||
+                  g_str_has_prefix (prompt, ASYNC_PASSWORD_PROMPT)) {
+               /* User password */
+               hints[0] = NM_VPNC_KEY_XAUTH_PASSWORD;
+       } else if (g_str_has_prefix (prompt, IPSEC_SECRET_PROMPT)) {
+               /* Group password */
+               hints[0] = NM_VPNC_KEY_SECRET;
+       } else if (g_str_has_prefix (prompt, ASYNC_ANSWER_PROMPT)) {
+               /* FIXME: add new secret item for this? */
+               hints[0] = NM_VPNC_KEY_XAUTH_PASSWORD;
+       } else if (g_str_has_prefix (prompt, ASYNC_PASSCODE_PROMPT)) {
+               /* FIXME: add new secret item for this? */
+               hints[0] = NM_VPNC_KEY_XAUTH_PASSWORD;
+       } else {
+               g_warning ("Unhandled vpnc request '%s'", prompt);
+               g_free (prompt);
+               prompt = NULL;
 
-       /* Find vpnc */
-       vpnc_binary = vpnc_binary_paths;
-       while (*vpnc_binary != NULL) {
-               if (g_file_test (*vpnc_binary, G_FILE_TEST_EXISTS))
-                       break;
-               vpnc_binary++;
+               nm_vpn_plugin_failure (NM_VPN_PLUGIN (plugin), NM_VPN_PLUGIN_FAILURE_LOGIN_FAILED);
+               nm_vpn_plugin_set_state (NM_VPN_PLUGIN (plugin), NM_VPN_SERVICE_STATE_STOPPED);
+               return;
        }
 
-       if (!*vpnc_binary) {
-               g_set_error (error,
-                            NM_VPN_PLUGIN_ERROR,
-                            NM_VPN_PLUGIN_ERROR_LAUNCH_FAILED,
-                            "%s",
-                            _("Could not find vpnc binary."));
-               return -1;
+       if (debug) {
+               char *joined = g_strjoinv (",", (char **) hints);
+               g_message ("Requesting new secrets: '%s' (%s)", prompt, joined);
+               g_free (joined);
        }
 
-       vpnc_argv = g_ptr_array_new ();
-       g_ptr_array_add (vpnc_argv, (gpointer) (*vpnc_binary));
-       g_ptr_array_add (vpnc_argv, (gpointer) "--non-inter");
-       g_ptr_array_add (vpnc_argv, (gpointer) "--no-detach");
-       g_ptr_array_add (vpnc_argv, (gpointer) "-");
-       g_ptr_array_add (vpnc_argv, NULL);
+       nm_vpn_plugin_secrets_required (NM_VPN_PLUGIN (plugin),
+                                       priv->server_message->len ? priv->server_message->str : prompt,
+                                       (const char **) hints);
+       g_string_truncate (priv->server_message, 0);
+       g_free (prompt);
 
-       if (!g_spawn_async_with_pipes (NULL, (char **) vpnc_argv->pdata, NULL,
-                                                        G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, 
&stdin_fd,
-                                                        NULL, NULL, error)) {
-               g_ptr_array_free (vpnc_argv, TRUE);
-               g_warning ("vpnc failed to start.  error: '%s'", (*error)->message);
-               return -1;
+       priv->pending_auth = hints[0];
+}
+
+static gboolean
+data_available (GIOChannel *source,
+                GIOCondition condition,
+                gpointer data)
+{
+       NMVPNCPlugin *plugin = NM_VPNC_PLUGIN (data);
+       NMVPNCPluginPrivate *priv = NM_VPNC_PLUGIN_GET_PRIVATE (plugin);
+       GError *error = NULL;
+       Pipe *pipe;
+       gsize bytes_read = 0;
+       GIOStatus status;
+
+       if (condition & G_IO_ERR) {
+               g_warning ("Unexpected vpnc pipe error");
+               return TRUE;
        }
-       g_ptr_array_free (vpnc_argv, TRUE);
 
-       g_message ("vpnc started with pid %d", pid);
+       /* Figure out which pipe we're using */
+       if (source == priv->out.channel)
+               pipe = &priv->out;
+       else if (source == priv->err.channel)
+               pipe = &priv->err;
+       else
+               g_assert_not_reached ();
+
+       do {
+               gsize consumed = 0;
+               char buf[512];
+
+               status = g_io_channel_read_chars (source,
+                                                 buf,
+                                                 sizeof (buf),
+                                                 &bytes_read,
+                                                 &error);
+               if (status == G_IO_STATUS_ERROR) {
+                       if (error)
+                               g_warning ("vpnc read error: %s", error->message);
+                       g_clear_error (&error);
+               }
 
-       NM_VPNC_PLUGIN_GET_PRIVATE (plugin)->pid = pid;
-       vpnc_watch = g_child_watch_source_new (pid);
-       g_source_set_callback (vpnc_watch, (GSourceFunc) vpnc_watch_cb, plugin, NULL);
-       g_source_attach (vpnc_watch, NULL);
-       g_source_unref (vpnc_watch);
+               if (bytes_read) {
+                       g_string_append_len (pipe->buf, buf, bytes_read);
+
+                       do {
+                               consumed = utils_handle_output (pipe->buf,
+                                                               priv->server_message,
+                                                               &priv->server_message_done,
+                                                               vpnc_prompt,
+                                                               plugin);
+                               if (consumed) {
+                                       /* Log all output to the console */
+                                       fprintf (pipe->error ? stderr : stdout, "%.*s", (int) consumed, 
pipe->buf->str);
+                                       fflush (pipe->error ? stderr : stdout);
+
+                                       /* If output was handled, clear the buffer */
+                                       g_string_erase (pipe->buf, 0, consumed);
+                               }
+                       } while (consumed);
+               }
+       } while (bytes_read);
 
-       return stdin_fd;
+       return TRUE;
+}
+
+static void
+pipe_setup (Pipe *pipe, gboolean is_stderr, gpointer user_data)
+{
+       GIOFlags flags = 0;
+
+       pipe->error = is_stderr;
+       pipe->buf = g_string_sized_new (512);
+
+       pipe->channel = g_io_channel_unix_new (pipe->fd);
+       g_io_channel_set_encoding (pipe->channel, NULL, NULL);
+       flags = g_io_channel_get_flags (pipe->channel);
+       g_io_channel_set_flags (pipe->channel, flags | G_IO_FLAG_NONBLOCK, NULL);
+       g_io_channel_set_buffered (pipe->channel, FALSE);
+
+       pipe->watch = g_io_add_watch (pipe->channel,
+                                     G_IO_IN | G_IO_ERR | G_IO_PRI,
+                                     data_available,
+                                     user_data);
+}
+
+static const char *
+find_vpnc (void)
+{
+       static const char *vpnc_paths[] = {
+               "/usr/sbin/vpnc",
+               "/sbin/vpnc",
+               "/usr/local/sbin/vpnc",
+               NULL
+       };
+       guint i;
+
+       /* Find vpnc */
+       for (i = 0; vpnc_paths[i]; i++) {
+               if (g_file_test (vpnc_paths[i], G_FILE_TEST_EXISTS))
+                       return vpnc_paths[i];
+       }
+       return NULL;
+}
+
+static gboolean
+nm_vpnc_start_vpnc_binary (NMVPNCPlugin *plugin, GError **error)
+{
+       NMVPNCPluginPrivate *priv = NM_VPNC_PLUGIN_GET_PRIVATE (plugin);
+       const char *vpnc_path;
+       const char *args[5];
+
+       g_return_val_if_fail (priv->pid == 0, FALSE);
+       g_return_val_if_fail (priv->infd == -1, FALSE);
+       g_return_val_if_fail (priv->out.fd == -1, FALSE);
+       g_return_val_if_fail (priv->err.fd == -1, FALSE);
+
+
+       vpnc_path = find_vpnc ();
+       if (!vpnc_path) {
+               g_set_error_literal (error,
+                                    NM_VPN_PLUGIN_ERROR,
+                                    NM_VPN_PLUGIN_ERROR_LAUNCH_FAILED,
+                                    _("Could not find vpnc binary."));
+               return FALSE;
+       }
+
+       args[0] = vpnc_path;
+       args[1] = "--no-detach";
+       args[2] = use_interactive ? "--interactive-stdin" : "--non-inter";
+       args[3] = "-";
+       args[4] = NULL;
+       if (!g_spawn_async_with_pipes (NULL,
+                                      (char **) args,
+                                      NULL,
+                                      G_SPAWN_DO_NOT_REAP_CHILD,
+                                      NULL,
+                                      NULL,
+                                      &priv->pid,
+                                      &priv->infd,
+                                      use_interactive ? &priv->out.fd : NULL,
+                                      use_interactive ? &priv->err.fd : NULL,
+                                      error)) {
+               g_warning ("vpnc failed to start.  error: '%s'", (*error)->message);
+               return FALSE;
+       }
+       g_message ("vpnc started with pid %d", priv->pid);
+
+       priv->watch_id = g_child_watch_add (priv->pid, vpnc_watch_cb, plugin);
+
+       if (use_interactive) {
+               /* Watch stdout and stderr */
+               pipe_setup (&priv->out, FALSE, plugin);
+               pipe_setup (&priv->err, TRUE, plugin);
+       }
+       return TRUE;
 }
 
 static inline void
@@ -532,34 +816,93 @@ real_connect (NMVPNPlugin   *plugin,
               NMConnection  *connection,
               GError       **error)
 {
+       NMVPNCPluginPrivate *priv = NM_VPNC_PLUGIN_GET_PRIVATE (plugin);
        NMSettingVPN *s_vpn;
-       gint vpnc_fd = -1;
-       gboolean success = FALSE;
+       char end[] = { 0x1A };
 
-       s_vpn = NM_SETTING_VPN (nm_connection_get_setting (connection, NM_TYPE_SETTING_VPN));
+       s_vpn = nm_connection_get_setting_vpn (connection);
        g_assert (s_vpn);
 
        if (!nm_vpnc_properties_validate (s_vpn, error))
                goto out;
-       if (!nm_vpnc_secrets_validate (s_vpn, error))
+       if (!nm_vpnc_secrets_validate (s_vpn, use_interactive, error))
                goto out;
 
-       vpnc_fd = nm_vpnc_start_vpnc_binary (NM_VPNC_PLUGIN (plugin), error);
-       if (vpnc_fd < 0)
+       if (!nm_vpnc_start_vpnc_binary (NM_VPNC_PLUGIN (plugin), error))
                goto out;
 
        if (getenv ("NM_VPNC_DUMP_CONNECTION") || debug)
                nm_connection_dump (connection);
 
-       if (!nm_vpnc_config_write (vpnc_fd, s_vpn, error))
+       if (!nm_vpnc_config_write (priv->infd, s_vpn, error))
                goto out;
 
-       success = TRUE;
+       if (use_interactive)
+               write (priv->infd, &end, sizeof (end));
+       else {
+               close (priv->infd);
+               priv->infd = -1;
+       }
+
+       return TRUE;
 
 out:
-       if (vpnc_fd >= 0)
-               close (vpnc_fd);
-       return success;
+       vpnc_cleanup (NM_VPNC_PLUGIN (plugin), TRUE);
+       return FALSE;
+}
+
+static gboolean
+real_new_secrets (NMVPNPlugin *plugin,
+                  NMConnection *connection,
+                  GError **error)
+{
+       NMVPNCPluginPrivate *priv = NM_VPNC_PLUGIN_GET_PRIVATE (plugin);
+       NMSettingVPN *s_vpn;
+       const char *secret;
+
+       if (!use_interactive) {
+               g_set_error_literal (error,
+                                    NM_VPN_PLUGIN_ERROR,
+                                    NM_VPN_PLUGIN_ERROR_GENERAL,
+                                    _("Could not use new secrets as interactive mode is disabled."));
+               return FALSE;
+       }
+
+       s_vpn = NM_SETTING_VPN (nm_connection_get_setting (connection, NM_TYPE_SETTING_VPN));
+       if (!s_vpn) {
+               g_set_error_literal (error,
+                                    NM_VPN_PLUGIN_ERROR,
+                                    NM_VPN_PLUGIN_ERROR_CONNECTION_INVALID,
+                                    _("Could not process the request because the VPN connection settings 
were invalid."));
+               return FALSE;
+       }
+
+       if (!priv->pending_auth) {
+               g_set_error_literal (error,
+                                    NM_VPN_PLUGIN_ERROR,
+                                    NM_VPN_PLUGIN_ERROR_CONNECTION_INVALID,
+                                    _("Could not process the request because no pending authentication is 
required."));
+               return FALSE;
+       }
+
+       if (debug)
+               g_message ("VPN received new secrets; sending to '%s' vpnc stdin", priv->pending_auth);
+
+       secret = nm_setting_vpn_get_secret (s_vpn, priv->pending_auth);
+       if (!secret) {
+               g_set_error (error,
+                            NM_VPN_PLUGIN_ERROR,
+                            NM_VPN_PLUGIN_ERROR_CONNECTION_INVALID,
+                            _("Could not process the request because the requested info '%s' was not 
provided."),
+                            priv->pending_auth);
+               return FALSE;
+       }
+
+       /* Ignoring secret flags here; if vpnc requested the item, we must provide it */
+       write_config_option (priv->infd, "%s\n", secret);
+
+       priv->pending_auth = NULL;
+       return TRUE;
 }
 
 static NMSettingSecretFlags
@@ -629,38 +972,21 @@ real_need_secrets (NMVPNPlugin *plugin,
 }
 
 static gboolean
-ensure_killed (gpointer data)
+real_disconnect (NMVPNPlugin *plugin, GError **error)
 {
-       int pid = GPOINTER_TO_INT (data);
-
-       if (kill (pid, 0) == 0)
-               kill (pid, SIGKILL);
-
-       return FALSE;
-}
-
-static gboolean
-real_disconnect (NMVPNPlugin   *plugin,
-                         GError       **err)
-{
-       NMVPNCPluginPrivate *priv = NM_VPNC_PLUGIN_GET_PRIVATE (plugin);
-
-       if (priv->pid) {
-               if (kill (priv->pid, SIGTERM) == 0)
-                       g_timeout_add (2000, ensure_killed, GINT_TO_POINTER (priv->pid));
-               else
-                       kill (priv->pid, SIGKILL);
-
-               g_message ("Terminated vpnc daemon with PID %d.", priv->pid);
-               priv->pid = 0;
-       }
-
+       vpnc_cleanup (NM_VPNC_PLUGIN (plugin), TRUE);
        return TRUE;
 }
 
 static void
 nm_vpnc_plugin_init (NMVPNCPlugin *plugin)
 {
+       NMVPNCPluginPrivate *priv = NM_VPNC_PLUGIN_GET_PRIVATE (plugin);
+
+       priv->infd = -1;
+       priv->out.fd = -1;
+       priv->err.fd = -1;
+       priv->server_message = g_string_sized_new (30);
 }
 
 static void
@@ -672,9 +998,10 @@ nm_vpnc_plugin_class_init (NMVPNCPluginClass *vpnc_class)
        g_type_class_add_private (object_class, sizeof (NMVPNCPluginPrivate));
 
        /* virtual methods */
-       parent_class->connect    = real_connect;
+       parent_class->connect = real_connect;
        parent_class->need_secrets = real_need_secrets;
        parent_class->disconnect = real_disconnect;
+       parent_class->new_secrets = real_new_secrets;
 }
 
 NMVPNCPlugin *
@@ -712,12 +1039,89 @@ quit_mainloop (NMVPNCPlugin *plugin, gpointer user_data)
        g_main_loop_quit ((GMainLoop *) user_data);
 }
 
+
+#define VPNC_VERSION_STR "vpnc version "
+
+static char *
+vpnc_check_version (void)
+{
+       const char *vpnc_path;
+       const char *argv[3];
+       GError *error = NULL;
+       char *output = NULL, *p, *j;
+       char **lines = NULL, **iter, **versions = NULL;
+       char *version = NULL;
+
+       vpnc_path = find_vpnc ();
+       if (!vpnc_path) {
+               g_warning ("Failed to find vpnc for version check");
+               return NULL;
+       }
+
+       argv[0] = vpnc_path;
+       argv[1] = "--version";
+       argv[2] = NULL;
+       if (!g_spawn_sync ("/", (char **) argv, NULL, G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, &output, NULL, 
NULL, &error)) {
+               g_warning ("Failed to start vpnc for version check: %s", error->message);
+               g_error_free (error);
+               return NULL;
+       }
+
+       /* look for a version higher than 0.5.3 */
+       lines = g_strsplit_set (output, "\n", -1);
+       for (iter = lines; iter && *iter; iter++) {
+               if (g_str_has_prefix (*iter, VPNC_VERSION_STR)) {
+                       long int num;
+
+                       j = p = *iter + strlen (VPNC_VERSION_STR);
+                       /* Stop at the first non-number or non-dot */
+                       while (*j && (g_ascii_isdigit (*j) || *j == '.'))
+                               j++;
+                       *j = '\0';
+                       version = g_strdup (p);
+
+                       versions = g_strsplit_set (p, ".", -1);
+                       if (!versions || g_strv_length (versions) < 3)
+                               break;
+
+                       /* Fail if major version < 0 */
+                       errno = 0;
+                       num = strtol (versions[0], NULL, 10);
+                       if (errno || num < 0 || num > 1000)
+                               break;
+
+                       /* Fail if minor version < 5 */
+                       errno = 0;
+                       num = strtol (versions[1], NULL, 10);
+                       if (errno || num < 5 || num > 1000)
+                               break;
+
+                       /* Fail if micro version < 4 */
+                       errno = 0;
+                       num = strtol (versions[2], NULL, 10);
+                       if (errno || num < 4 || num > 1000)
+                               break;
+
+                       use_interactive = TRUE;
+                       break;
+               }
+       }
+
+       if (versions)
+               g_strfreev (versions);
+       if (lines)
+               g_strfreev (lines);
+       g_free (output);
+       return version;
+}
+
 int
 main (int argc, char *argv[])
 {
        NMVPNCPlugin *plugin;
        gboolean persist = FALSE;
        GOptionContext *opt_ctx = NULL;
+       char *version;
 
        GOptionEntry options[] = {
                { "persist", 0, 0, G_OPTION_ARG_NONE, &persist, N_("Don't quit when VPN connection 
terminates"), NULL },
@@ -749,11 +1153,16 @@ main (int argc, char *argv[])
        g_option_context_parse (opt_ctx, &argc, &argv, NULL);
        g_option_context_free (opt_ctx);
 
+       version = vpnc_check_version ();
+
        if (getenv ("VPNC_DEBUG"))
                debug = TRUE;
 
-       if (debug)
+       if (debug) {
                g_message ("nm-vpnc-service (version " DIST_VERSION ") starting...");
+               g_message ("   vpnc version '%s'", version ? version : "(unknown)");
+               g_message ("   vpnc interactive mode is %s", use_interactive ? "enabled" : "disabled");
+       }
 
        if (system ("/sbin/modprobe tun") == -1)
                exit (EXIT_FAILURE);
@@ -772,6 +1181,7 @@ main (int argc, char *argv[])
 
        g_main_loop_unref (loop);
        g_object_unref (plugin);
+       g_free (version);
 
        exit (EXIT_SUCCESS);
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]