/* * 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-2021 OpenVPN Inc * * 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)); } }