| /* |
| * 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 "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); |
| } |