diff options
Diffstat (limited to 'src/openvpn/argv.c')
-rw-r--r-- | src/openvpn/argv.c | 536 |
1 files changed, 340 insertions, 196 deletions
diff --git a/src/openvpn/argv.c b/src/openvpn/argv.c index 7d06951..b799c97 100644 --- a/src/openvpn/argv.c +++ b/src/openvpn/argv.c @@ -37,16 +37,55 @@ #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) { @@ -55,36 +94,51 @@ argv_new(void) 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_reset(struct argv *a) +argv_free(struct argv *a) { - size_t i; - for (i = 0; i < a->argc; ++i) - { - free(a->argv[i]); - } - free(a->argv); - argv_init(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_extend(struct argv *a, const size_t newcap) +argv_reset(struct argv *a) { - if (newcap > a->capacity) + if (a->argc) { - char **newargv; size_t i; - ALLOC_ARRAY_CLEAR(newargv, char *, newcap); for (i = 0; i < a->argc; ++i) { - newargv[i] = a->argv[i]; + a->argv[i] = NULL; } - free(a->argv); - a->argv = newargv; - a->capacity = newcap; + 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) { @@ -93,114 +147,100 @@ argv_grow(struct argv *a, const size_t add) 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) /* str must have been malloced or be NULL */ +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 *a, const size_t headroom) +argv_clone(const struct argv *source, const size_t headroom) { struct argv r; - size_t i; - argv_init(&r); - for (i = 0; i < headroom; ++i) + + for (size_t i = 0; i < headroom; ++i) { argv_append(&r, NULL); } - if (a) + if (source) { - for (i = 0; i < a->argc; ++i) + for (size_t i = 0; i < source->argc; ++i) { - argv_append(&r, string_alloc(a->argv[i], NULL)); + 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, NULL); + r.argv[0] = string_alloc(head, &r.gc); return r; } -static 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; - } -} - +/** + * 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) { - if (a->argv) - { - return print_argv((const char **)a->argv, gc, flags); - } - else - { - return ""; - } + 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) { @@ -209,6 +249,15 @@ argv_msg(const int msglev, const struct argv *a) 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) { @@ -217,144 +266,239 @@ argv_msg_prefix(const int msglev, const struct argv *a, const char *prefix) gc_free(&gc); } -static void -argv_printf_arglist(struct argv *a, const char *format, va_list arglist) +/** + * 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) { - char *term; - const char *f = format; - - argv_extend(a, 1); /* ensure trailing NULL */ + if (format == NULL) + { + return NULL; + } - while ((term = argv_term(&f)) != 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 (term[0] == '%') + if (format[i] == ' ') { - if (!strcmp(term, "%s")) - { - char *s = va_arg(arglist, char *); - if (!s) - { - s = ""; - } - argv_append(a, string_alloc(s, NULL)); - } - else if (!strcmp(term, "%d")) - { - char numstr[64]; - openvpn_snprintf(numstr, sizeof(numstr), "%d", va_arg(arglist, int)); - argv_append(a, string_alloc(numstr, NULL)); - } - else if (!strcmp(term, "%u")) - { - char numstr[64]; - openvpn_snprintf(numstr, sizeof(numstr), "%u", va_arg(arglist, unsigned int)); - argv_append(a, string_alloc(numstr, NULL)); - } - else if (!strcmp(term, "%lu")) - { - char numstr[64]; - openvpn_snprintf(numstr, sizeof(numstr), "%lu", - va_arg(arglist, unsigned long)); - argv_append(a, string_alloc(numstr, NULL)); - } - else if (!strcmp(term, "%s/%d")) - { - char numstr[64]; - char *s = va_arg(arglist, char *); - - if (!s) - { - s = ""; - } - - openvpn_snprintf(numstr, sizeof(numstr), "%d", va_arg(arglist, int)); - - { - const size_t len = strlen(s) + strlen(numstr) + 2; - char *combined = (char *) malloc(len); - check_malloc_return(combined); - - strcpy(combined, s); - strcat(combined, "/"); - strcat(combined, numstr); - argv_append(a, combined); - } - } - else if (!strcmp(term, "%s%sc")) - { - char *s1 = va_arg(arglist, char *); - char *s2 = va_arg(arglist, char *); - char *combined; - - if (!s1) - { - s1 = ""; - } - if (!s2) - { - s2 = ""; - } - combined = (char *) malloc(strlen(s1) + strlen(s2) + 1); - check_malloc_return(combined); - strcpy(combined, s1); - strcat(combined, s2); - argv_append(a, combined); - } - else - { - ASSERT(0); - } - free(term); + in_token = false; + continue; } - else + + if (!in_token) { - argv_append(a, term); + (*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; } -void -argv_printf(struct argv *a, const char *format, ...) +/** + * 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; - argv_reset(a); va_start(arglist, format); - argv_printf_arglist(a, format, arglist); + + argv_reset(argres); + bool res = argv_printf_arglist(argres, format, arglist); va_end(arglist); + return res; } -void -argv_printf_cat(struct argv *a, const char *format, ...) +/** + * 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); - argv_printf_arglist(a, format, arglist); + 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 *a, const char *s) +argv_parse_cmd(struct argv *argres, const char *cmdstr) { - int nparms; - char *parms[MAX_PARMS + 1]; - struct gc_arena gc = gc_new(); - - argv_reset(a); - argv_extend(a, 1); /* ensure trailing NULL */ + argv_reset(argres); - nparms = parse_line(s, parms, MAX_PARMS, "SCRIPT-ARGV", 0, D_ARGV_PARSE_CMD, &gc); + 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(a, string_alloc(parms[i], NULL)); + argv_append(argres, parms[i]); } } else { - argv_append(a, string_alloc(s, NULL)); + argv_append(argres, string_alloc(cmdstr, &argres->gc)); } - - gc_free(&gc); } |