src/openvpn/argv.c
698e268a
 /*
  *  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.
  *
49979459
  *  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
698e268a
  *
  *  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.
  *
caa54ac3
  *  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.
698e268a
  *
  *
  *  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"
9fc0e963
 #include "integer.h"
68b97b25
 #include "env_set.h"
698e268a
 #include "options.h"
 
aed5ef40
 static void
81d882d5
 argv_init(struct argv *a)
698e268a
 {
81d882d5
     a->capacity = 0;
     a->argc = 0;
     a->argv = NULL;
698e268a
 }
 
 struct argv
81d882d5
 argv_new(void)
698e268a
 {
81d882d5
     struct argv ret;
     argv_init(&ret);
     return ret;
698e268a
 }
 
 void
81d882d5
 argv_reset(struct argv *a)
698e268a
 {
81d882d5
     size_t i;
     for (i = 0; i < a->argc; ++i)
4cd4899e
     {
81d882d5
         free(a->argv[i]);
4cd4899e
     }
81d882d5
     free(a->argv);
     argv_init(a);
698e268a
 }
 
 static void
81d882d5
 argv_extend(struct argv *a, const size_t newcap)
698e268a
 {
81d882d5
     if (newcap > a->capacity)
698e268a
     {
81d882d5
         char **newargv;
         size_t i;
         ALLOC_ARRAY_CLEAR(newargv, char *, newcap);
         for (i = 0; i < a->argc; ++i)
4cd4899e
         {
81d882d5
             newargv[i] = a->argv[i];
4cd4899e
         }
81d882d5
         free(a->argv);
         a->argv = newargv;
         a->capacity = newcap;
698e268a
     }
 }
 
 static void
81d882d5
 argv_grow(struct argv *a, const size_t add)
698e268a
 {
81d882d5
     const size_t newargc = a->argc + add + 1;
     ASSERT(newargc > a->argc);
     argv_extend(a, adjust_power_of_2(newargc));
698e268a
 }
 
 static void
81d882d5
 argv_append(struct argv *a, char *str)  /* str must have been malloced or be NULL */
698e268a
 {
81d882d5
     argv_grow(a, 1);
     a->argv[a->argc++] = str;
698e268a
 }
 
 static struct argv
81d882d5
 argv_clone(const struct argv *a, const size_t headroom)
698e268a
 {
81d882d5
     struct argv r;
     size_t i;
698e268a
 
81d882d5
     argv_init(&r);
     for (i = 0; i < headroom; ++i)
4cd4899e
     {
81d882d5
         argv_append(&r, NULL);
4cd4899e
     }
81d882d5
     if (a)
698e268a
     {
81d882d5
         for (i = 0; i < a->argc; ++i)
4cd4899e
         {
81d882d5
             argv_append(&r, string_alloc(a->argv[i], NULL));
4cd4899e
         }
698e268a
     }
81d882d5
     return r;
698e268a
 }
 
 struct argv
81d882d5
 argv_insert_head(const struct argv *a, const char *head)
698e268a
 {
81d882d5
     struct argv r;
     r = argv_clone(a, 1);
     r.argv[0] = string_alloc(head, NULL);
     return r;
698e268a
 }
 
aed5ef40
 static char *
81d882d5
 argv_term(const char **f)
698e268a
 {
81d882d5
     const char *p = *f;
     const char *term = NULL;
     size_t termlen = 0;
698e268a
 
81d882d5
     if (*p == '\0')
     {
         return NULL;
     }
698e268a
 
81d882d5
     while (true)
698e268a
     {
81d882d5
         const int c = *p;
         if (c == '\0')
698e268a
         {
             break;
         }
81d882d5
         if (term)
         {
             if (!isspace(c))
             {
                 ++termlen;
             }
             else
             {
                 break;
             }
         }
         else
698e268a
         {
81d882d5
             if (!isspace(c))
698e268a
             {
81d882d5
                 term = p;
                 termlen = 1;
698e268a
             }
         }
81d882d5
         ++p;
698e268a
     }
81d882d5
     *f = p;
698e268a
 
81d882d5
     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
698e268a
     {
81d882d5
         return NULL;
698e268a
     }
 }
 
 const char *
81d882d5
 argv_str(const struct argv *a, struct gc_arena *gc, const unsigned int flags)
698e268a
 {
81d882d5
     if (a->argv)
     {
         return print_argv((const char **)a->argv, gc, flags);
     }
     else
     {
         return "";
     }
698e268a
 }
 
 void
81d882d5
 argv_msg(const int msglev, const struct argv *a)
698e268a
 {
81d882d5
     struct gc_arena gc = gc_new();
     msg(msglev, "%s", argv_str(a, &gc, 0));
     gc_free(&gc);
698e268a
 }
 
 void
81d882d5
 argv_msg_prefix(const int msglev, const struct argv *a, const char *prefix)
698e268a
 {
81d882d5
     struct gc_arena gc = gc_new();
     msg(msglev, "%s: %s", prefix, argv_str(a, &gc, 0));
     gc_free(&gc);
698e268a
 }
 
aed5ef40
 static void
81d882d5
 argv_printf_arglist(struct argv *a, const char *format, va_list arglist)
698e268a
 {
81d882d5
     char *term;
     const char *f = format;
698e268a
 
81d882d5
     argv_extend(a, 1); /* ensure trailing NULL */
698e268a
 
81d882d5
     while ((term = argv_term(&f)) != NULL)
698e268a
     {
81d882d5
         if (term[0] == '%')
698e268a
         {
81d882d5
             if (!strcmp(term, "%s"))
698e268a
             {
81d882d5
                 char *s = va_arg(arglist, char *);
                 if (!s)
                 {
                     s = "";
                 }
                 argv_append(a, string_alloc(s, NULL));
698e268a
             }
81d882d5
             else if (!strcmp(term, "%d"))
698e268a
             {
81d882d5
                 char numstr[64];
                 openvpn_snprintf(numstr, sizeof(numstr), "%d", va_arg(arglist, int));
                 argv_append(a, string_alloc(numstr, NULL));
698e268a
             }
81d882d5
             else if (!strcmp(term, "%u"))
698e268a
             {
81d882d5
                 char numstr[64];
                 openvpn_snprintf(numstr, sizeof(numstr), "%u", va_arg(arglist, unsigned int));
                 argv_append(a, string_alloc(numstr, NULL));
698e268a
             }
e38d3a00
             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));
             }
81d882d5
             else if (!strcmp(term, "%s/%d"))
698e268a
             {
81d882d5
                 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);
                 }
698e268a
             }
81d882d5
             else if (!strcmp(term, "%s%sc"))
698e268a
             {
81d882d5
                 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);
698e268a
             }
81d882d5
             else
             {
                 ASSERT(0);
             }
             free(term);
698e268a
         }
81d882d5
         else
698e268a
         {
81d882d5
             argv_append(a, term);
698e268a
         }
     }
 }
aed5ef40
 
 void
81d882d5
 argv_printf(struct argv *a, const char *format, ...)
aed5ef40
 {
81d882d5
     va_list arglist;
     argv_reset(a);
     va_start(arglist, format);
     argv_printf_arglist(a, format, arglist);
     va_end(arglist);
 }
aed5ef40
 
 void
81d882d5
 argv_printf_cat(struct argv *a, const char *format, ...)
aed5ef40
 {
81d882d5
     va_list arglist;
     va_start(arglist, format);
     argv_printf_arglist(a, format, arglist);
     va_end(arglist);
aed5ef40
 }
25360912
 
 void
81d882d5
 argv_parse_cmd(struct argv *a, const char *s)
25360912
 {
81d882d5
     int nparms;
     char *parms[MAX_PARMS + 1];
     struct gc_arena gc = gc_new();
25360912
 
81d882d5
     argv_reset(a);
     argv_extend(a, 1); /* ensure trailing NULL */
25360912
 
81d882d5
     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)
4cd4899e
         {
81d882d5
             argv_append(a, string_alloc(parms[i], NULL));
4cd4899e
         }
81d882d5
     }
     else
25360912
     {
81d882d5
         argv_append(a, string_alloc(s, NULL));
25360912
     }
 
81d882d5
     gc_free(&gc);
25360912
 }