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



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

    core: parse vpnc stdin/stdout and request secrets asynchronously
    
    Unfortunately this also requires patches to vpnc, becuase it can't
    handle input on stdin and writing config to stdin due to its use
    of getline() which requires the stdin pipe to be closed to terminate
    the config reading process.
    
    Second, it requires patches to always read input from stdin, because
    it uses getpass() which reads from /dev/tty by default.

 src/nm-vpnc-service.c |  479 +++++++++++++++++++++++++++++++++++++++----------
 1 files changed, 383 insertions(+), 96 deletions(-)
---
diff --git a/src/nm-vpnc-service.c b/src/nm-vpnc-service.c
index ec6f399..44b8117 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
@@ -47,19 +49,29 @@ static GMainLoop *loop = NULL;
 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 */
@@ -226,19 +238,105 @@ nm_vpnc_properties_validate (NMSettingVPN *s_vpn, GError **error)
 static gboolean
 nm_vpnc_secrets_validate (NMSettingVPN *s_vpn, 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) {
-               g_set_error (error,
-                            NM_VPN_PLUGIN_ERROR,
-                            NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
-                            "%s",
-                            _("No VPN secrets!"));
+       if (validate_error) {
+               g_propagate_error (error, validate_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 +350,21 @@ 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 */
+       pipe_echo_finish (&priv->out);
+       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 +383,213 @@ 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
+#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)
+{
+       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;
+
+               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 (debug) {
+               char *joined = g_strjoinv (",", (char **) hints);
+               g_message ("Requesting new secrets: '%s' (%s)", prompt, joined);
+               g_free (joined);
+       }
+
+       nm_vpn_plugin_need_secrets (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);
+
+       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;
+       }
+
+       /* 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);
+               }
+
+               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 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 gboolean
 nm_vpnc_start_vpnc_binary (NMVPNCPlugin *plugin, GError **error)
 {
-       GPid    pid;
-       const char **vpnc_binary = NULL;
-       GPtrArray *vpnc_argv;
-       GSource *vpnc_watch;
-       gint    stdin_fd;
+       NMVPNCPluginPrivate *priv = NM_VPNC_PLUGIN_GET_PRIVATE (plugin);
+       static const char *vpnc_paths[] = {
+               "/usr/sbin/vpnc",
+               "/sbin/vpnc",
+               "/usr/local/sbin/vpnc",
+               NULL
+       };
+       const char *args[5];
+       guint i = 0;
+
+       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);
 
        /* Find vpnc */
-       vpnc_binary = vpnc_binary_paths;
-       while (*vpnc_binary != NULL) {
-               if (g_file_test (*vpnc_binary, G_FILE_TEST_EXISTS))
+       while (vpnc_paths[i]) {
+               if (g_file_test (vpnc_paths[i], G_FILE_TEST_EXISTS))
                        break;
-               vpnc_binary++;
+               i++;
        }
 
-       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 (!vpnc_paths[i]) {
+               g_set_error_literal (error,
+                                    NM_VPN_PLUGIN_ERROR,
+                                    NM_VPN_PLUGIN_ERROR_LAUNCH_FAILED,
+                                    _("Could not find vpnc binary."));
+               return FALSE;
        }
 
-       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);
-
-       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);
+       args[0] = vpnc_paths[i];
+       args[1] = "--no-detach";
+       args[2] = "--interactive-stdin";
+       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,
+                                      &priv->out.fd,
+                                      &priv->err.fd,
+                                      error)) {
                g_warning ("vpnc failed to start.  error: '%s'", (*error)->message);
-               return -1;
+               return FALSE;
        }
-       g_ptr_array_free (vpnc_argv, TRUE);
-
-       g_message ("vpnc started with pid %d", pid);
+       g_message ("vpnc started with pid %d", priv->pid);
 
-       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);
+       priv->watch_id = g_child_watch_add (priv->pid, vpnc_watch_cb, plugin);
 
-       return stdin_fd;
+       /* Watch stdout and stderr */
+       pipe_setup (&priv->out, FALSE, plugin);
+       pipe_setup (&priv->err, TRUE, plugin);
+       return TRUE;
 }
 
 static inline void
@@ -532,11 +790,11 @@ 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))
@@ -544,22 +802,67 @@ real_connect (NMVPNPlugin   *plugin,
        if (!nm_vpnc_secrets_validate (s_vpn, 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;
+       write (priv->infd, &end, sizeof (end));
+       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;
+
+       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 +932,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 +958,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 *


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