/* * 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 * * 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 "options.h" static void argv_init(struct argv *a) { a->capacity = 0; a->argc = 0; a->argv = NULL; } struct argv argv_new(void) { struct argv ret; argv_init(&ret); return ret; } void argv_reset(struct argv *a) { size_t i; for (i = 0; i < a->argc; ++i) { free(a->argv[i]); } free(a->argv); argv_init(a); } static void argv_extend(struct argv *a, const size_t newcap) { if (newcap > a->capacity) { char **newargv; size_t i; ALLOC_ARRAY_CLEAR(newargv, char *, newcap); for (i = 0; i < a->argc; ++i) { newargv[i] = a->argv[i]; } free(a->argv); a->argv = newargv; a->capacity = newcap; } } 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)); } static void argv_append(struct argv *a, char *str) /* str must have been malloced or be NULL */ { argv_grow(a, 1); a->argv[a->argc++] = str; } static struct argv argv_clone(const struct argv *a, const size_t headroom) { struct argv r; size_t i; argv_init(&r); for (i = 0; i < headroom; ++i) { argv_append(&r, NULL); } if (a) { for (i = 0; i < a->argc; ++i) { argv_append(&r, string_alloc(a->argv[i], NULL)); } } return r; } 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); 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; } } 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_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); } 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); } static void argv_printf_arglist(struct argv *a, const char *format, va_list arglist) { char *term; const char *f = format; argv_extend(a, 1); /* ensure trailing NULL */ while ((term = argv_term(&f)) != NULL) { if (term[0] == '%') { 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); } else { argv_append(a, term); } } } void argv_printf(struct argv *a, const char *format, ...) { va_list arglist; argv_reset(a); va_start(arglist, format); argv_printf_arglist(a, format, 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, arglist); va_end(arglist); } void argv_parse_cmd(struct argv *a, const char *s) { int nparms; char *parms[MAX_PARMS + 1]; struct gc_arena gc = gc_new(); argv_reset(a); argv_extend(a, 1); /* ensure trailing NULL */ nparms = parse_line(s, parms, MAX_PARMS, "SCRIPT-ARGV", 0, D_ARGV_PARSE_CMD, &gc); if (nparms) { int i; for (i = 0; i < nparms; ++i) { argv_append(a, string_alloc(parms[i], NULL)); } } else { argv_append(a, string_alloc(s, NULL)); } gc_free(&gc); }