blob: 7d06951b15fa33807401d700545cc05410d80374 [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 "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);
}