From 26bb4c740b12cf3f606f657103a1695c23f6b72f Mon Sep 17 00:00:00 2001 From: james Date: Wed, 23 Jul 2008 19:51:27 +0000 Subject: Added argv_x functions to buffer.[ch] to be used to safely build up argv strings for execve without the possibility of truncation or misinterpretation of mid-argument spacing. git-svn-id: http://svn.openvpn.net/projects/openvpn/branches/BETA21/openvpn@3107 e7ae566f-a301-0410-adde-c780ea21d3b5 --- buffer.c | 224 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ buffer.h | 33 ++++++++++ init.c | 8 +++ 3 files changed, 265 insertions(+) diff --git a/buffer.c b/buffer.c index 498861e..c7eab07 100644 --- a/buffer.c +++ b/buffer.c @@ -226,6 +226,230 @@ int openvpn_snprintf(char *str, size_t size, const char *format, ...) return ret; } +/* + * A printf-like function (that only recognizes a subset of standard printf + * format operators) that prints arguments to an argv list instead + * of a standard string. This is used to build up argv arrays for passing + * to execve. + */ + +void +argv_init (struct argv *a) +{ + a->argc = 0; + a->argv = NULL; +} + +void +argv_reset (struct argv *a) +{ + size_t i; + for (i = 0; i < a->argc; ++i) + free (a->argv[i]); + free (a->argv); + a->argc = 0; + a->argv = NULL; +} + +size_t +argv_argc (const char *format) +{ + char *term; + const char *f = format; + size_t argc = 0; + + while ((term = argv_term (&f)) != NULL) + { + ++argc; + free (term); + } + return argc; +} + +char * +argv_term (const char **f) +{ + const char *p = *f; + const char *term = NULL; + size_t termlen = 0; + + if (*p == '\0') + return NULL; + + while (true) + { + const int c = *p; + if (c == '\0') + break; + if (term) + { + if (!isspace (c)) + ++termlen; + else + break; + } + else + { + if (!isspace (c)) + { + term = p; + termlen = 1; + } + } + ++p; + } + *f = p; + + if (term) + { + char *ret; + ASSERT (termlen > 0); + ret = malloc (termlen + 1); + check_malloc_return (ret); + memcpy (ret, term, termlen); + ret[termlen] = '\0'; + return ret; + } + else + return NULL; +} + +const char * +argv_str (const struct argv *a, struct gc_arena *gc, const unsigned int flags) +{ + if (a->argv) + return print_argv ((const char **)a->argv, gc, flags); + else + return ""; +} + +void +argv_printf (struct argv *a, const char *format, ...) +{ + va_list arglist; + va_start (arglist, format); + argv_printf_arglist (a, format, 0, arglist); + va_end (arglist); + } + +void +argv_printf_cat (struct argv *a, const char *format, ...) +{ + va_list arglist; + va_start (arglist, format); + argv_printf_arglist (a, format, APA_CAT, arglist); + va_end (arglist); +} + +void +argv_printf_arglist (struct argv *a, const char *format, const unsigned int flags, va_list arglist) +{ + char *term; + const char *f = format; + size_t argc = 0; + + if (flags & APA_CAT) + { + char **old_argv = a->argv; + size_t i; + argc = a->argc; + a->argc += argv_argc (format); + ALLOC_ARRAY_CLEAR (a->argv, char *, a->argc + 1); + for (i = 0; i < argc; ++i) + a->argv[i] = old_argv[i]; + free (old_argv); + } + else + { + argv_reset (a); + a->argc = argv_argc (format); + ALLOC_ARRAY_CLEAR (a->argv, char *, a->argc + 1); + } + + while ((term = argv_term (&f)) != NULL) + { + ASSERT (argc < a->argc); + if (term[0] == '%') + { + if (!strcmp (term, "%s")) + { + a->argv[argc++] = string_alloc (va_arg (arglist, char *), NULL); + } + else if (!strcmp (term, "%d")) + { + char numstr[64]; + openvpn_snprintf (numstr, sizeof (numstr), "%d", va_arg (arglist, int)); + a->argv[argc++] = string_alloc (numstr, NULL); + } + else if (!strcmp (term, "%u")) + { + char numstr[64]; + openvpn_snprintf (numstr, sizeof (numstr), "%u", va_arg (arglist, unsigned int)); + a->argv[argc++] = string_alloc (numstr, NULL); + } + else + ASSERT (0); + free (term); + } + else + { + a->argv[argc++] = term; + } + } + ASSERT (argc == a->argc); +} + +#ifdef ARGV_TEST +void +argv_test (void) +{ + struct gc_arena gc = gc_new (); + char line[512]; + const char *s; + + struct argv a; + argv_init (&a); + argv_printf (&a, "this is a %s test of int %d unsigned %u", "FOO", -69, 42); + s = argv_str (&a, &gc, PA_BRACKET); + argv_reset (&a); + printf ("%s\n", s); + + argv_init (&a); + argv_printf (&a, "foo bar %d", 99); + s = argv_str (&a, &gc, PA_BRACKET); + argv_reset (&a); + printf ("%s\n", s); + + argv_init (&a); + s = argv_str (&a, &gc, PA_BRACKET); + argv_reset (&a); + printf ("%s\n", s); + + argv_init (&a); + argv_printf (&a, "foo bar %d", 99); + argv_printf_cat (&a, "bar %d foo", 42); + argv_printf_cat (&a, "cool %s %d u", "frood", 4); + s = argv_str (&a, &gc, PA_BRACKET); + argv_reset (&a); + printf ("%s\n", s); + + while (fgets (line, sizeof(line), stdin) != NULL) + { + char *term; + const char *f = line; + int i = 0; + + while ((term = argv_term (&f)) != NULL) + { + printf ("[%d] '%s'\n", i, term); + ++i; + free (term); + } + } + gc_free (&gc); +} +#endif + /* * write a string to the end of a buffer that was * truncated by buf_printf diff --git a/buffer.h b/buffer.h index 7de3363..3190229 100644 --- a/buffer.h +++ b/buffer.h @@ -56,6 +56,12 @@ struct buffer #endif }; +/* used by argv_x functions */ +struct argv { + size_t argc; + char **argv; +}; + /* for garbage collection */ struct gc_entry @@ -221,6 +227,33 @@ int openvpn_snprintf(char *str, size_t size, const char *format, ...) #endif ; +/* + * A printf-like function (that only recognizes a subset of standard printf + * format operators) that prints arguments to an argv list instead + * of a standard string. This is used to build up argv arrays for passing + * to execve. + */ +void argv_init (struct argv *a); +void argv_reset (struct argv *a); +size_t argv_argc (const char *format); +char *argv_term (const char **f); +const char *argv_str (const struct argv *a, struct gc_arena *gc, const unsigned int flags); + +#define APA_CAT (1<<0) /* concatentate onto existing struct argv list */ +void argv_printf_arglist (struct argv *a, const char *format, const unsigned int flags, va_list arglist); + +void argv_printf (struct argv *a, const char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 2, 3))) +#endif + ; + +void argv_printf_cat (struct argv *a, const char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 2, 3))) +#endif + ; + /* * remove/add trailing characters */ diff --git a/init.c b/init.c index 61b23e3..ebdc9f3 100644 --- a/init.c +++ b/init.c @@ -465,6 +465,14 @@ init_static (void) return false; #endif +#ifdef ARGV_TEST + { + void argv_test (void); + argv_test (); + return false; + } +#endif + return true; } -- cgit