blob: b799c974e52ad97a3e79674261e3ec0825d86961 [file] [log] [blame]
/*
* OpenVPN -- An application to securely tunnel IP networks
* over a single TCP/UDP port, with support for SSL/TLS-based
* session authentication and key exchange,
* packet encryption, packet authentication, and
* packet compression.
*
* Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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.
*
*
* 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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#elif defined(_MSC_VER)
#include "config-msvc.h"
#endif
#include "syshead.h"
#include "argv.h"
#include "integer.h"
#include "env_set.h"
#include "options.h"
/**
* Resizes the list of arguments struct argv can carry. This resize
* operation will only increase the size, never decrease the size.
*
* @param *a Valid pointer to a struct argv to resize
* @param newcap size_t with the new size of the argument list.
*/
static void
argv_extend(struct argv *a, const size_t newcap)
{
if (newcap > a->capacity)
{
char **newargv;
size_t i;
ALLOC_ARRAY_CLEAR_GC(newargv, char *, newcap, &a->gc);
for (i = 0; i < a->argc; ++i)
{
newargv[i] = a->argv[i];
}
a->argv = newargv;
a->capacity = newcap;
}
}
/**
* Initialise an already allocated struct argv.
* It is expected that the input argument is a valid pointer.
*
* @param *a Pointer to a struct argv to initialise
*/
static void
argv_init(struct argv *a)
{
a->capacity = 0;
a->argc = 0;
a->argv = NULL;
a->gc = gc_new();
argv_extend(a, 8);
}
/**
* Allocates a new struct argv and ensures it is initialised.
* Note that it does not return a pointer, but a struct argv directly.
*
* @returns Returns an initialised and empty struct argv.
*/
struct argv
argv_new(void)
{
struct argv ret;
argv_init(&ret);
return ret;
}
/**
* Frees all memory allocations allocated by the struct argv
* related functions.
*
* @param *a Valid pointer to a struct argv to release memory from
*/
void
argv_free(struct argv *a)
{
gc_free(&a->gc);
}
/**
* Resets the struct argv to an initial state. No memory buffers
* will be released by this call.
*
* @param *a Valid pointer to a struct argv to resize
*/
static void
argv_reset(struct argv *a)
{
if (a->argc)
{
size_t i;
for (i = 0; i < a->argc; ++i)
{
a->argv[i] = NULL;
}
a->argc = 0;
}
}
/**
* Extends an existing struct argv to carry minimum 'add' number
* of new arguments. This builds on argv_extend(), which ensures the
* new size will only be higher than the current capacity.
*
* The new size is also calculated based on the result of adjust_power_of_2().
* This approach ensures that the list does grow bulks and only when the
* current limit is reached.
*
* @param *a Valid pointer to the struct argv to extend
* @param add size_t with the number of elements to add.
*
*/
static void
argv_grow(struct argv *a, const size_t add)
{
const size_t newargc = a->argc + add + 1;
ASSERT(newargc > a->argc);
argv_extend(a, adjust_power_of_2(newargc));
}
/**
* Appends a string to to the list of arguments stored in a struct argv
* This will ensure the list size in struct argv has the needed capacity to
* store the value.
*
* @param *a struct argv where to append the new string value
* @param *str Pointer to string to append. The provided string *MUST* have
* been malloc()ed or NULL.
*/
static void
argv_append(struct argv *a, char *str)
{
argv_grow(a, 1);
a->argv[a->argc++] = str;
}
/**
* Clones a struct argv with all the contents to a new allocated struct argv.
* If 'headroom' is larger than 0, it will create a head-room in front of the
* values being copied from the source input.
*
*
* @param *source Valid pointer to the source struct argv to clone. It may
* be NULL.
* @param headroom Number of slots to leave empty in front of the slots
* copied from the source.
*
* @returns Returns a new struct argv containing a copy of the source
* struct argv, with the given headroom in front of the copy.
*
*/
static struct argv
argv_clone(const struct argv *source, const size_t headroom)
{
struct argv r;
argv_init(&r);
for (size_t i = 0; i < headroom; ++i)
{
argv_append(&r, NULL);
}
if (source)
{
for (size_t i = 0; i < source->argc; ++i)
{
argv_append(&r, string_alloc(source->argv[i], &r.gc));
}
}
return r;
}
/**
* Inserts an argument string in front of all other argument slots.
*
* @param *a Valid pointer to the struct argv to insert the argument into
* @param *head Pointer to the char * string with the argument to insert
*
* @returns Returns a new struct argv with the inserted argument in front
*/
struct argv
argv_insert_head(const struct argv *a, const char *head)
{
struct argv r;
r = argv_clone(a, 1);
r.argv[0] = string_alloc(head, &r.gc);
return r;
}
/**
* Generate a single string with all the arguments in a struct argv
* concatenated.
*
* @param *a Valid pointer to the struct argv with the arguments to list
* @param *gc Pointer to a struct gc_arena managed buffer
* @param flags Flags passed to the print_argv() function.
*
* @returns Returns a string generated by print_argv() with all the arguments
* concatenated. If the argument count is 0, it will return an empty
* string. The return string is allocated in the gc_arena managed
* buffer. If the gc_arena pointer is NULL, the returned string
* must be free()d explicitly to avoid memory leaks.
*/
const char *
argv_str(const struct argv *a, struct gc_arena *gc, const unsigned int flags)
{
return print_argv((const char **)a->argv, gc, flags);
}
/**
* Write the arguments stored in a struct argv via the msg() command.
*
* @param msglev Integer with the message level used by msg().
* @param *a Valid pointer to the struct argv with the arguments to write.
*/
void
argv_msg(const int msglev, const struct argv *a)
{
struct gc_arena gc = gc_new();
msg(msglev, "%s", argv_str(a, &gc, 0));
gc_free(&gc);
}
/**
* Similar to argv_msg() but prefixes the messages being written with a
* given string.
*
* @param msglev Integer with the message level used by msg().
* @param *a Valid pointer to the struct argv with the arguments to write
* @param *prefix Valid char * pointer to the prefix string
*
*/
void
argv_msg_prefix(const int msglev, const struct argv *a, const char *prefix)
{
struct gc_arena gc = gc_new();
msg(msglev, "%s: %s", prefix, argv_str(a, &gc, 0));
gc_free(&gc);
}
/**
* Prepares argv format string for further processing
*
* Individual argument must be separated by space. Ignores leading and
* trailing spaces. Consecutive spaces count as one. Returns prepared
* format string, with space replaced by delim and adds the number of
* arguments to the count parameter.
*
* @param *format Pointer to a the format string to process
* @param delim Char with the delimiter to use
* @param *count size_t pointer used to return the number of
* tokens (argument slots) found in the format string.
* @param *gc Pointer to a gc_arena managed buffer.
*
* @returns Returns a parsed format string (char *), together with the
* number of tokens parts found (via *count). The result string
* is allocated within the gc_arena managed buffer. If the
* gc_arena pointer is NULL, the returned string must be explicitly
* free()d to avoid memory leaks.
*/
static char *
argv_prep_format(const char *format, const char delim, size_t *count,
struct gc_arena *gc)
{
if (format == NULL)
{
return NULL;
}
bool in_token = false;
char *f = gc_malloc(strlen(format) + 1, true, gc);
for (int i = 0, j = 0; i < strlen(format); i++)
{
if (format[i] == ' ')
{
in_token = false;
continue;
}
if (!in_token)
{
(*count)++;
/*
* We don't add any delimiter to the output string if
* the string is empty; the resulting format string
* will never start with a delimiter.
*/
if (j > 0) /* Has anything been written to the output string? */
{
f[j++] = delim;
}
}
f[j++] = format[i];
in_token = true;
}
return f;
}
/**
* Create a struct argv based on a format string
*
* Instead of parsing the format string ourselves place delimiters via
* argv_prep_format() before we let libc's printf() do the parsing.
* Then split the resulting string at the injected delimiters.
*
* @param *argres Valid pointer to a struct argv where the resulting parsed
* arguments, based on the format string.
* @param *format Char* string with a printf() compliant format string
* @param arglist A va_list with the arguments to be consumed by the format
* string
*
* @returns Returns true if the parsing and processing was successfully. If
* the resulting number of arguments does not match the expected
* number of arguments (based on the format string), it is
* considered a failure, which returns false. This can happen if
* the ASCII Group Separator (GS - 0x1D) is put into the arguments
* list or format string.
*/
static bool
argv_printf_arglist(struct argv *argres, const char *format, va_list arglist)
{
const char delim = 0x1D; /* ASCII Group Separator (GS) */
bool res = false;
/*
* Prepare a format string which will be used by vsnprintf() later on.
*
* This means all space separators in the input format string will be
* replaced by the GS (0x1D), so we can split this up again after the
* the vsnprintf() call into individual arguments again which will be
* saved in the struct argv.
*
*/
size_t argc = argres->argc;
char *f = argv_prep_format(format, delim, &argc, &argres->gc);
if (f == NULL)
{
goto out;
}
/*
* Determine minimum buffer size.
*
* With C99, vsnprintf(NULL, 0, ...) will return the number of bytes
* it would have written, had the buffer been large enough.
*/
va_list tmplist;
va_copy(tmplist, arglist);
int len = vsnprintf(NULL, 0, f, tmplist);
va_end(tmplist);
if (len < 0)
{
goto out;
}
/*
* Do the actual vsnprintf() operation, which expands the format
* string with the provided arguments.
*/
size_t size = len + 1;
char *buf = gc_malloc(size, false, &argres->gc);
len = vsnprintf(buf, size, f, arglist);
if (len < 0 || len >= size)
{
goto out;
}
/*
* Split the string at the GS (0x1D) delimiters and put each elemen
* into the struct argv being returned to the caller.
*/
char *end = strchr(buf, delim);
while (end)
{
*end = '\0';
argv_append(argres, buf);
buf = end + 1;
end = strchr(buf, delim);
}
argv_append(argres, buf);
if (argres->argc != argc)
{
/* Someone snuck in a GS (0x1D), fail gracefully */
argv_reset(argres);
goto out;
}
res = true;
out:
return res;
}
/**
* printf() variant which populates a struct argv. It processes the
* format string with the provided arguments. For each space separator found
* in the format string, a new argument will be added to the resulting
* struct argv.
*
* This will always reset and ensure the result is based on a pristine
* struct argv.
*
* @param *argres Valid pointer to a struct argv where the result will be put.
* @param *format printf() compliant (char *) format string.
*
* @returns Returns true if the parsing was successful. See
* argv_printf_arglist() for more details. The parsed result will
* be put into argres.
*/
bool
argv_printf(struct argv *argres, const char *format, ...)
{
va_list arglist;
va_start(arglist, format);
argv_reset(argres);
bool res = argv_printf_arglist(argres, format, arglist);
va_end(arglist);
return res;
}
/**
* printf() inspired argv concatenation. Adds arguments to an existing
* struct argv and populets the argument slots based on the printf() based
* format string.
*
* @param *argres Valid pointer to a struct argv where the result will be put.
* @param *format printf() compliant (char *) format string.
*
* @returns Returns true if the parsing was successful. See
* argv_printf_arglist() for more details. The parsed result will
* be put into argres.
*/
bool
argv_printf_cat(struct argv *argres, const char *format, ...)
{
va_list arglist;
va_start(arglist, format);
bool res = argv_printf_arglist(argres, format, arglist);
va_end(arglist);
return res;
}
/**
* Parses a command string, tokenizes it and puts each element into a separate
* struct argv argument slot.
*
* @params *argres Valid pointer to a struct argv where the parsed result
* will be found.
* @params *cmdstr Char * based string to parse
*
*/
void
argv_parse_cmd(struct argv *argres, const char *cmdstr)
{
argv_reset(argres);
char *parms[MAX_PARMS + 1] = { 0 };
int nparms = parse_line(cmdstr, parms, MAX_PARMS, "SCRIPT-ARGV", 0,
D_ARGV_PARSE_CMD, &argres->gc);
if (nparms)
{
int i;
for (i = 0; i < nparms; ++i)
{
argv_append(argres, parms[i]);
}
}
else
{
argv_append(argres, string_alloc(cmdstr, &argres->gc));
}
}