[network-manager-vpnc: 1/4] core: add function and testcase to parse vpnc stdout/stderr



commit f32ee38cf68dcecf358a6a0ca97a0208f4739398
Author: Dan Williams <dcbw redhat com>
Date:   Tue Jun 25 08:48:21 2013 -0500

    core: add function and testcase to parse vpnc stdout/stderr
    
    We'll use this to run vpnc in interactive mode and handle server
    messages and input prompts during the connection, instead of all
    before connecting.

 src/Makefile.am        |   17 +++-
 src/test-vpnc-output.c |  281 ++++++++++++++++++++++++++++++++++++++++++++++++
 src/utils.c            |  166 ++++++++++++++++++++++++++++
 src/utils.h            |   35 ++++++
 4 files changed, 497 insertions(+), 2 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 3e458d6..aaa467f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -19,8 +19,9 @@ libexec_PROGRAMS = nm-vpnc-service nm-vpnc-service-vpnc-helper
 
 nm_vpnc_service_SOURCES = \
        nm-vpnc-service.c \
-       nm-vpnc-service.h
-
+       nm-vpnc-service.h \
+       utils.c \
+       utils.h
 
 nm_vpnc_service_LDADD = \
        $(DBUS_LIBS) \
@@ -36,4 +37,16 @@ nm_vpnc_service_vpnc_helper_LDADD = \
        $(GTHREAD_LIBS) \
        $(NM_LIBS)
 
+noinst_PROGRAMS = test-vpnc-output
+
+test_vpnc_output_SOURCES = \
+       test-vpnc-output.c \
+       utils.c \
+       utils.h
+
+test_vpnc_output_LDADD = $(GLIB_LIBS)
+
+check-local:
+       $(abs_builddir)/test-vpnc-output
+
 CLEANFILES = *~
diff --git a/src/test-vpnc-output.c b/src/test-vpnc-output.c
new file mode 100644
index 0000000..e722946
--- /dev/null
+++ b/src/test-vpnc-output.c
@@ -0,0 +1,281 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2013 Red Hat, Inc.
+ */
+
+#include <string.h>
+#include "utils.h"
+
+#define TEST_HEADER \
+       GString *output;\
+       guint pos = 0, num, olen, linelen;\
+       gboolean message_done = FALSE;\
+       GString *message = g_string_new (NULL);\
+       gsize consumed;\
+       const char *p;\
+\
+       output = g_string_sized_new (512);\
+       olen = strlen (o);\
+       while (pos < olen) {\
+               num = g_random_int_range (0, MIN (olen - pos, 30));\
+               if (num == 0)\
+                       num++;\
+               g_string_append_len (output, o + pos, num);\
+               pos += num;\
+               p = strchr (output->str, '\n');\
+               linelen = p ? (p - output->str) + 1 : output->len;\
+
+#define TEST_FOOTER \
+               g_string_erase (output, 0, consumed);\
+       } \
+
+#define TEST_CLEANUP \
+       g_string_free (output, TRUE);\
+       g_string_free (message, TRUE);
+
+
+static void
+test_no_prompt (const char *data, gsize dlen, gpointer user_data)
+{
+       g_assert_not_reached ();
+}
+
+static void
+test_output_simple (void)
+{
+       const char *o = \
+"vpnc version 0.5.3\n\
+   hex_test: 00010203\n\
+\n\
+S1 init_sockaddr\n\
+ [2011-06-03 11:11:12]\n\
+S3 setup_tunnel\n\
+ [2011-06-03 11:11:12]\n\
+   using interface tun0\n\
+\n\
+S4.1 create_nonce\n\
+ [2011-06-03 11:11:12]\n\
+   i_cookie: 4b3e235d 02d5dcd5\n\
+   i_nonce:\n\
+   a1d46e05 4175bbcc 6de34f7d fc374fe4 2acb8991\n\
+\n\
+S4.3 AM packet_1\n\
+ [2011-06-03 11:11:12]\n\
+\n\
+ sending: ========================>\n\
+   BEGIN_PARSE\n\
+   Recieved Packet Len: 1287\n\
+   i_cookie: 4b3e235d 02d5dcd5\n\
+   r_cookie: 00000000 00000000\n\
+   payload: 01 (ISAKMP_PAYLOAD_SA)\n\
+   isakmp_version: 10\n\
+   exchange_type: 04 (ISAKMP_EXCHANGE_AGGRESSIVE)\n\
+   flags: 00\n\
+   message_id: 00000000\n\
+   len: 00000507\n\
+   \n\
+   PARSING PAYLOAD type: 03 (ISAKMP_PAYLOAD_T)\n\
+   next_type: 03 (ISAKMP_PAYLOAD_T)\n\
+   length: 0028\n\
+   t.number: 00\n\
+   t.id: 01 (ISAKMP_IPSEC_KEY_IKE)\n\
+   t.attributes.type: 000e (IKE_ATTRIB_KEY_LENGTH)\n\
+   t.attributes.u.attr_16: 0100\n\
+   t.attributes.type: 0001 (IKE_ATTRIB_ENC)\n\
+   t.attributes.type: 000c (IKE_ATTRIB_LIFE_DURATION)\n\
+   t.attributes.u.lots.length: 0004\n\
+   t.attributes.u.lots.data: 0020c49b\n\
+   DONE PARSING PAYLOAD type: 03 (ISAKMP_PAYLOAD_T)\n\
+";
+
+       TEST_HEADER
+               /* We expect no input prompts and no server messages */
+               consumed = utils_handle_output (output, message, &message_done, test_no_prompt, NULL);
+               if (consumed)
+                       g_assert_cmpint (consumed, ==, linelen);
+               g_assert_cmpint (message->len, ==, 0);
+               g_assert (!message_done);
+       TEST_FOOTER
+
+       g_assert_cmpstr (message->str, ==, "");
+       g_assert_cmpint (message->len, ==, 0);
+
+       TEST_CLEANUP
+}
+
+static void
+test_output_message (void)
+{
+       const char *o = \
+"S5.4 xauth type check\n\
+ [2011-06-03 11:11:13]\n\
+\n\
+Wait for token to change,\n\
+then enter the new tokencode:\n\
+\n\
+S5.5 do xauth authentication\n\
+ [2011-06-03 11:11:13]\n\
+";
+       const char *expected_message = "Wait for token to change,\nthen enter the new tokencode:\n";
+
+       TEST_HEADER
+               /* We expect a server message but no input prompts */
+               consumed = utils_handle_output (output, message, &message_done, test_no_prompt, NULL);
+               if (consumed)
+                       g_assert_cmpint (consumed, ==, linelen);
+
+               if (message_done)
+                       g_assert_cmpstr (message->str, ==, expected_message);
+       TEST_FOOTER
+
+       g_assert (message_done);
+       TEST_CLEANUP
+}
+
+static void
+test_output_message_oneline (void)
+{
+       const char *o = \
+"S5.3 type-is-xauth check\n\
+ [2013-06-27 11:24:50]\n\
+\n\
+S5.4 xauth type check\n\
+ [2013-06-27 11:24:50]\n\
+Enter Username and Password.\n\
+\n\
+S5.5 do xauth reply\n\
+ [2013-06-27 11:24:50]\n\
+";
+       const char *expected_message = "Enter Username and Password.\n";
+
+       TEST_HEADER
+               /* We expect a server message but no input prompts */
+               consumed = utils_handle_output (output, message, &message_done, test_no_prompt, NULL);
+               if (consumed)
+                       g_assert_cmpint (consumed, ==, linelen);
+
+               if (message_done)
+                       g_assert_cmpstr (message->str, ==, expected_message);
+       TEST_FOOTER
+
+       g_assert (message_done);
+       TEST_CLEANUP
+}
+
+static void
+test_has_prompt (const char *data, gsize dlen, gpointer user_data)
+{
+       g_assert_cmpstr (data, ==, (const char *) user_data);
+}
+
+static void
+test_output_prompt (void)
+{
+       const char *o = \
+"S5.5 do xauth authentication\n\
+ [2011-06-03 11:11:13]\n\
+Password for VPN person 1 1 1 1: ";
+
+       const char *expected_prompt = "Password for VPN person 1 1 1 1: ";
+
+       TEST_HEADER
+               /* We expect an input prompt but no server message */
+               consumed = utils_handle_output (output, message, &message_done, test_has_prompt, (gpointer) 
expected_prompt);
+               if (consumed)
+                       g_assert_cmpint (consumed, ==, linelen);
+
+               g_assert_cmpint (message->len, ==, 0);
+               g_assert (!message_done);
+       TEST_FOOTER
+
+       TEST_CLEANUP
+}
+
+static void
+test_output_message_and_prompt (void)
+{
+       const char *o = \
+"Wait for token to change,\n\
+then enter the new tokencode:\n\
+\n\
+Password for VPN person 1 1 1 1: ";
+
+       const char *expected_prompt = "Password for VPN person 1 1 1 1: ";
+       const char *expected_message = "Wait for token to change,\nthen enter the new tokencode:\n";
+
+       TEST_HEADER
+               /* We expect an input prompt but no server message */
+               consumed = utils_handle_output (output, message, &message_done, test_has_prompt, (gpointer) 
expected_prompt);
+               if (consumed)
+                       g_assert_cmpint (consumed, ==, linelen);
+       TEST_FOOTER
+
+       g_assert_cmpstr (message->str, ==, expected_message);
+       g_assert (message_done);
+
+       TEST_CLEANUP
+}
+
+static void
+test_output_message_and_prompt_debug (void)
+{
+       const char *o = \
+"S5.3 type-is-xauth check\n\
+ [2011-06-03 11:11:13]\n\
+\n\
+S5.4 xauth type check\n\
+ [2011-06-03 11:11:13]\n\
+\n\
+Wait for token to change,\n\
+then enter the new tokencode:\n\
+\n\
+S5.5 do xauth authentication\n\
+ [2011-06-03 11:11:13]\n\
+Password for VPN person 1 1 1 1: ";
+
+       const char *expected_prompt = "Password for VPN person 1 1 1 1: ";
+       const char *expected_message = "Wait for token to change,\nthen enter the new tokencode:\n";
+
+       TEST_HEADER
+               /* We expect an input prompt but no server message */
+               consumed = utils_handle_output (output, message, &message_done, test_has_prompt, (gpointer) 
expected_prompt);
+               if (consumed)
+                       g_assert_cmpint (consumed, ==, linelen);
+       TEST_FOOTER
+
+       g_assert_cmpstr (message->str, ==, expected_message);
+       g_assert (message_done);
+
+       TEST_CLEANUP
+}
+
+int
+main (int argc, char **argv)
+{
+       g_test_init (&argc, &argv, NULL);
+
+       g_test_add_func ("/output/simple", test_output_simple);
+       g_test_add_func ("/output/message", test_output_message);
+       g_test_add_func ("/output/message-oneline", test_output_message_oneline);
+       g_test_add_func ("/output/prompt", test_output_prompt);
+       g_test_add_func ("/output/message-and-prompt", test_output_message_and_prompt);
+       g_test_add_func ("/output/message-and-prompt-debug", test_output_message_and_prompt_debug);
+
+       return g_test_run ();
+}
+
diff --git a/src/utils.c b/src/utils.c
new file mode 100644
index 0000000..fd44e2d
--- /dev/null
+++ b/src/utils.c
@@ -0,0 +1,166 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2013 Red Hat, Inc.
+ */
+
+#include <string.h>
+#include "utils.h"
+
+#define IS_EOL(a)  (a == '\r' || a == '\n')
+#define VPNC_VERSION_STR "vpnc version "
+
+/**
+ * utils_handle_output:
+ * @output: buffer containing vpnc output
+ * @server_message: buffer in which to store a message from the VPN server
+ * @server_message_done: flag which is set to %TRUE when a server message is
+ *   complete
+ * @prompt_fn: function to call when vpnc (or the server) sends a request for
+ *   passwords or more information
+ * @prompt_fn_data: pointer to pass to @prompt_fn
+ * 
+ * Parses new vpnc output to extract server messages and detect prompts for
+ * more information.  Since vpnc can print variable numbers of bytes at a time,
+ * not necessarily a complete line or block, this function should be called
+ * multiple times on the same buffer.  It will return the number of bytes which
+ * it consumed, and that number of bytes should be removed from the start of
+ * @output.  If a request for a password or username is parsed, it will call
+ * @prompt_fn with the prompt message.
+ *
+ * Returns: the number of bytes consumed, which should be removed from the
+ * start of @output.
+ **/
+gsize
+utils_handle_output (GString *output,
+                     GString *server_message,
+                     gboolean *server_message_done,
+                     PromptFn prompt_fn,
+                     gpointer prompt_fn_data)
+{
+       guint32 i;
+
+       g_return_val_if_fail (output != NULL, 0);
+       g_return_val_if_fail (server_message != NULL, 0);
+       g_return_val_if_fail (server_message_done != NULL, 0);
+       g_return_val_if_fail (prompt_fn != NULL, 0);
+
+       /* vpnc output is loosely formatted, with "blocks of interest" starting with
+        * no leading whitespace, and separated by double newlines, but unfortunately
+        * it doesn't output both newlines at the same time (one newline is printed
+        * at the end of one block and a second at the start of the next block with
+        * variable time in between) and some input prompts don't print newlines at
+        * all.
+        *
+        * S5.4 xauth type check
+        *  [2011-06-03 11:11:13]
+        *
+        * Wait for token to change,          (server message line #1)
+        * then enter the new tokencode:      (server message line #2)
+        *
+        * S5.5 do xauth authentication
+        *  [2011-06-03 11:11:13]
+        * Password for VPN person 1 1 1 1:   (waits for input without newline)
+        *    size = 42, blksz = 16, padding = 6
+        *
+        * So we can't just listen for '\n\n' or we won't react immediately to
+        * input prompts or correctly process service messages.
+        *
+        * Instead we pay attention to any lines that have no leading whitespace
+        * and do not start with "S[1 - 9]".  If the line ends with ":" it is an
+        * input prompt.  If it doesn't then we cache it and wait for the next line
+        * or newline, in which case it's a server message.
+        */
+
+       if (output->len == 0)
+               return 0;
+
+       /* Find the end of the line or the end of the string; all lines *except*
+        * prompts will be newline terminated, while prompts stop at the end of the
+        * buffer because vpnc is waiting for the input.
+        */
+       for (i = 0; i < output->len; i++) {
+               if (!output->str[i] || IS_EOL (output->str[i]))
+                       break;
+       }
+
+       /* Decide whether to stop parsing a server message, which is terminated by
+        * a single empty line or some whitespace; it looks like:
+        *
+        * <stuff>
+        *
+        * Wait for token to change,
+        * then enter the new tokencode:
+        *
+        * <more stuff>
+        */
+       if (server_message->len) {
+               if (g_ascii_isspace (output->str[0]) || IS_EOL (output->str[0]))
+                   *server_message_done = TRUE;
+       }
+
+       if (i < output->len) {
+               /* Lines starting with whitespace are debug output that we don't care
+                * about.
+                */
+               if (g_ascii_isspace (output->str[0]))
+                       return i + 1;
+       } else if (i == output->len) {
+               /* Check for a prompt; it will not begin with whitespace, and will end
+                * with a ':' and no newline, because vpnc will be waiting for the response.
+                */
+               if (!g_ascii_isspace (output->str[0]) &&
+                   (i > 2) &&
+                   (strncmp ((output->str + (i - 2)), ": ", 2) == 0)) {
+                       /* Note: if vpnc sent a server message ending with ':' but we
+                        * happened to only read up to the ':' but not the EOL, we'll
+                        * confuse the server message with an input prompt.  vpnc is not
+                        * helpful here.
+                        */
+                       prompt_fn (output->str, i, prompt_fn_data);
+                       return i;
+               }
+
+               /* No newline and no ending semicolon; probably a partial read so wait
+                * for more output
+                */
+               return 0;
+       } else
+               g_assert_not_reached ();
+
+       /* No newline at the end, wait for one */
+       if (!IS_EOL (output->str[i]))
+               return 0;
+
+       /* Ignore vpnc version debug output */
+       if (i >= strlen (VPNC_VERSION_STR) &&
+           strncmp (output->str, VPNC_VERSION_STR, strlen (VPNC_VERSION_STR)) == 0)
+               return i + 1;
+
+       /* Ignore vpnc debug messages like "S1 init_sockaddr" */
+       if (i > 2 && output->str[0] == 'S' && g_ascii_isdigit (output->str[1]))
+               return i + 1;
+
+       /* What's left is probably a server message */
+       if (*server_message_done) {
+               g_string_truncate (server_message, 0);
+               *server_message_done = FALSE;
+       }
+       g_string_append_len (server_message, output->str, i + 1);
+       return i + 1;
+}
+
diff --git a/src/utils.h b/src/utils.h
new file mode 100644
index 0000000..a2cb49a
--- /dev/null
+++ b/src/utils.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2013 Red Hat, Inc.
+ */
+
+#ifndef _UTILS_H_
+#define _UTILS_H_
+
+#include <glib.h>
+
+typedef void (*PromptFn) (const char *data, gsize len, gpointer user_data);
+
+gsize utils_handle_output (GString *output,
+                           GString *server_message,
+                           gboolean *server_message_done,
+                           PromptFn prompt_fn,
+                           gpointer prompt_fn_data);
+
+#endif  /* _UTILS_H_ */
+


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