src/openvpn/misc.c
6fbf66fa
 /*
  *  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.
  *
564a2109
  *  Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
6fbf66fa
  *
  *  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 (see the file COPYING included with this
  *  distribution); if not, write to the Free Software Foundation, Inc.,
  *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
c110b289
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #elif defined(_MSC_VER)
 #include "config-msvc.h"
 #endif
 
6fbf66fa
 #include "syshead.h"
 
 #include "buffer.h"
 #include "misc.h"
3cf9dd88
 #include "base64.h"
6fbf66fa
 #include "tun.h"
 #include "error.h"
 #include "otime.h"
 #include "plugin.h"
 #include "options.h"
 #include "manage.h"
5f31881e
 #include "crypto.h"
 #include "route.h"
72c7b12c
 #include "console.h"
73b7e698
 #include "win32.h"
6fbf66fa
 
 #include "memdbg.h"
 
51bd56f4
 #ifdef ENABLE_IPROUTE
b4073a76
 const char *iproute_path = IPROUTE_PATH; /* GLOBAL */
0aee9ca7
 #endif
 
5a2e9a25
 /* contains an SSEC_x value defined in misc.h */
 int script_security = SSEC_BUILT_IN; /* GLOBAL */
 
6fbf66fa
 /*
  * Pass tunnel endpoint and MTU parms to a user-supplied script.
  * Used to execute the up/down script/plugins.
  */
 void
 run_up_down (const char *command,
 	     const struct plugin_list *plugins,
 	     int plugin_type,
 	     const char *arg,
a0a547a7
 	     const char *dev_type,
6fbf66fa
 	     int tun_mtu,
 	     int link_mtu,
 	     const char *ifconfig_local,
 	     const char* ifconfig_remote,
 	     const char *context,
 	     const char *signal_text,
 	     const char *script_type,
 	     struct env_set *es)
 {
   struct gc_arena gc = gc_new ();
 
   if (signal_text)
     setenv_str (es, "signal", signal_text);
   setenv_str (es, "script_context", context);
   setenv_int (es, "tun_mtu", tun_mtu);
   setenv_int (es, "link_mtu", link_mtu);
   setenv_str (es, "dev", arg);
a0a547a7
   if (dev_type)
     setenv_str (es, "dev_type", dev_type);
6fbf66fa
 
   if (!ifconfig_local)
     ifconfig_local = "";
   if (!ifconfig_remote)
     ifconfig_remote = "";
   if (!context)
     context = "";
 
   if (plugin_defined (plugins, plugin_type))
     {
5a2e9a25
       struct argv argv = argv_new ();
6fbf66fa
       ASSERT (arg);
5a2e9a25
       argv_printf (&argv,
 		   "%s %d %d %s %s %s",
 		   arg,
 		   tun_mtu, link_mtu,
 		   ifconfig_local, ifconfig_remote,
 		   context);
 
1876ccd0
       if (plugin_call (plugins, plugin_type, &argv, NULL, es) != OPENVPN_PLUGIN_FUNC_SUCCESS)
6fbf66fa
 	msg (M_FATAL, "ERROR: up/down plugin call failed");
5a2e9a25
 
       argv_reset (&argv);
6fbf66fa
     }
 
   if (command)
     {
5a2e9a25
       struct argv argv = argv_new ();
6fbf66fa
       ASSERT (arg);
       setenv_str (es, "script_type", script_type);
5a2e9a25
       argv_printf (&argv,
b8fb090c
 		  "%sc %s %d %d %s %s %s",
6fbf66fa
 		  command,
 		  arg,
 		  tun_mtu, link_mtu,
 		  ifconfig_local, ifconfig_remote,
 		  context);
5a2e9a25
       argv_msg (M_INFO, &argv);
c2533d18
       openvpn_run_script (&argv, es, S_FATAL, "--up/--down");
5a2e9a25
       argv_reset (&argv);
6fbf66fa
     }
 
   gc_free (&gc);
 }
 
659eae7b
 /* Write our PID to a file */
6fbf66fa
 void
659eae7b
 write_pid (const char *filename)
6fbf66fa
 {
   if (filename)
     {
659eae7b
       unsigned int pid = 0;
       FILE *fp = platform_fopen (filename, "w");
       if (!fp)
6fbf66fa
 	msg (M_ERR, "Open error on pid file %s", filename);
 
659eae7b
       pid = platform_getpid ();
       fprintf(fp, "%u\n", pid);
       if (fclose (fp))
 	msg (M_ERR, "Close error on pid file %s", filename);
6fbf66fa
     }
 }
 
 /*
  * Set standard file descriptors to /dev/null
  */
 void
 set_std_files_to_null (bool stdin_only)
 {
 #if defined(HAVE_DUP) && defined(HAVE_DUP2)
   int fd;
   if ((fd = open ("/dev/null", O_RDWR, 0)) != -1)
     {
       dup2 (fd, 0);
       if (!stdin_only)
 	{
 	  dup2 (fd, 1);
 	  dup2 (fd, 2);
 	}
       if (fd > 2)
 	close (fd);
     }
 #endif
 }
 
 /*
  *  dup inetd/xinetd socket descriptor and save
  */
 
 int inetd_socket_descriptor = SOCKET_UNDEFINED; /* GLOBAL */
 
 void
 save_inetd_socket_descriptor (void)
 {
   inetd_socket_descriptor = INETD_SOCKET_DESCRIPTOR;
 #if defined(HAVE_DUP) && defined(HAVE_DUP2)
   /* use handle passed by inetd/xinetd */
   if ((inetd_socket_descriptor = dup (INETD_SOCKET_DESCRIPTOR)) < 0)
     msg (M_ERR, "INETD_SOCKET_DESCRIPTOR dup(%d) failed", INETD_SOCKET_DESCRIPTOR);
   set_std_files_to_null (true);
 #endif
 }
 
 /*
  * Warn if a given file is group/others accessible.
  */
 void
 warn_if_group_others_accessible (const char* filename)
 {
1bda73a7
 #ifndef WIN32
6fbf66fa
 #ifdef HAVE_STAT
c959fc74
   if (strcmp (filename, INLINE_FILE_TAG))
6fbf66fa
     {
c959fc74
       struct stat st;
       if (stat (filename, &st))
 	{
 	  msg (M_WARN | M_ERRNO, "WARNING: cannot stat file '%s'", filename);
 	}
       else
 	{
 	  if (st.st_mode & (S_IRWXG|S_IRWXO))
 	    msg (M_WARN, "WARNING: file '%s' is group or others accessible", filename);
 	}
6fbf66fa
     }
 #endif
1bda73a7
 #endif
6fbf66fa
 }
 
 /*
  * Print an error message based on the status code returned by system().
  */
 const char *
 system_error_message (int stat, struct gc_arena *gc)
 {
   struct buffer out = alloc_buf_gc (256, gc);
 #ifdef WIN32
   if (stat == -1)
5a2e9a25
     buf_printf (&out, "external program did not execute -- ");
   buf_printf (&out, "returned error code %d", stat);
6fbf66fa
 #else
   if (stat == -1)
5a2e9a25
     buf_printf (&out, "external program fork failed");
6fbf66fa
   else if (!WIFEXITED (stat))
5a2e9a25
     buf_printf (&out, "external program did not exit normally");
6fbf66fa
   else
     {
       const int cmd_ret = WEXITSTATUS (stat);
       if (!cmd_ret)
5a2e9a25
 	buf_printf (&out, "external program exited normally");
6fbf66fa
       else if (cmd_ret == 127)
5a2e9a25
 	buf_printf (&out, "could not execute external program");
6fbf66fa
       else
5a2e9a25
 	buf_printf (&out, "external program exited with error status: %d", cmd_ret);
6fbf66fa
     }
 #endif
   return (const char *)out.data;
 }
 
 /*
5a2e9a25
  * Wrapper around openvpn_execve
6fbf66fa
  */
 bool
5a2e9a25
 openvpn_execve_check (const struct argv *a, const struct env_set *es, const unsigned int flags, const char *error_message)
6fbf66fa
 {
   struct gc_arena gc = gc_new ();
5a2e9a25
   const int stat = openvpn_execve (a, es, flags);
6fbf66fa
   int ret = false;
 
14a131ac
   if (platform_system_ok (stat))
6fbf66fa
     ret = true;
   else
     {
       if (error_message)
 	msg (((flags & S_FATAL) ? M_FATAL : M_WARN), "%s: %s",
 	     error_message,
 	     system_error_message (stat, &gc));
     }
   gc_free (&gc);
   return ret;
 }
 
5a2e9a25
 bool
 openvpn_execve_allowed (const unsigned int flags)
 {
   if (flags & S_SCRIPT)
     return script_security >= SSEC_SCRIPTS;
   else
     return script_security >= SSEC_BUILT_IN;
 }
 
c2533d18
 
5a2e9a25
 #ifndef WIN32
 /*
  * Run execve() inside a fork().  Designed to replicate the semantics of system() but
  * in a safer way that doesn't require the invocation of a shell or the risks
  * assocated with formatting and parsing a command line.
  */
 int
 openvpn_execve (const struct argv *a, const struct env_set *es, const unsigned int flags)
 {
   struct gc_arena gc = gc_new ();
   int ret = -1;
c2533d18
   static bool warn_shown = false;
5a2e9a25
 
   if (a && a->argv[0])
     {
51bd56f4
 #if defined(ENABLE_FEATURE_EXECVE)
5a2e9a25
       if (openvpn_execve_allowed (flags))
 	{
05634736
           const char *cmd = a->argv[0];
           char *const *argv = a->argv;
           char *const *envp = (char *const *)make_env_array (es, true, &gc);
           pid_t pid;
 
           pid = fork ();
           if (pid == (pid_t)0) /* child side */
             {
               execve (cmd, argv, envp);
               exit (127);
             }
           else if (pid < (pid_t)0) /* fork failed */
             msg (M_ERR, "openvpn_execve: unable to fork");
           else /* parent side */
             {
               if (waitpid (pid, &ret, 0) != pid)
                 ret = -1;
             }
         }
c2533d18
       else if (!warn_shown && (script_security < SSEC_SCRIPTS))
5a2e9a25
 	{
51f7b848
 	  msg (M_WARN, SCRIPT_SECURITY_WARNING);
c2533d18
           warn_shown = true;
5a2e9a25
 	}
 #else
       msg (M_WARN, "openvpn_execve: execve function not available");
 #endif
     }
   else
     {
8ee56461
       msg (M_FATAL, "openvpn_execve: called with empty argv");
5a2e9a25
     }
 
   gc_free (&gc);
   return ret;
 }
 #endif
 
6fbf66fa
 /*
9449e6a9
  * Run execve() inside a fork(), duping stdout.  Designed to replicate the semantics of popen() but
  * in a safer way that doesn't require the invocation of a shell or the risks
  * assocated with formatting and parsing a command line.
  */
 int
 openvpn_popen (const struct argv *a,  const struct env_set *es)
 {
   struct gc_arena gc = gc_new ();
   int ret = -1;
   static bool warn_shown = false;
 
   if (a && a->argv[0])
     {
51bd56f4
 #if defined(ENABLE_FEATURE_EXECVE)
9449e6a9
       if (script_security >= SSEC_BUILT_IN)
 	{
 	      const char *cmd = a->argv[0];
 	      char *const *argv = a->argv;
 	      char *const *envp = (char *const *)make_env_array (es, true, &gc);
 	      pid_t pid;
 	      int pipe_stdout[2];
 
               if (pipe (pipe_stdout) == 0) {
 		      pid = fork ();
 		      if (pid == (pid_t)0) /* child side */
 			{
df8ebb21
 			  close (pipe_stdout[0]);  /* Close read end */
9449e6a9
 			  dup2 (pipe_stdout[1],1);
 			  execve (cmd, argv, envp);
 			  exit (127);
 			}
df8ebb21
 		      else if (pid > (pid_t)0) /* parent side */
9449e6a9
 			{
d886d468
                           int status = 0;
 
df8ebb21
                           close (pipe_stdout[1]); /* Close write end */
d886d468
                           waitpid(pid, &status, 0);
                           ret = pipe_stdout[0];
df8ebb21
 			}
 		      else /* fork failed */
 			{
                           close (pipe_stdout[0]);
d886d468
                           close (pipe_stdout[1]);
df8ebb21
 			  msg (M_ERR, "openvpn_popen: unable to fork %s", cmd);
9449e6a9
 			}
 	      }
 	      else {
df8ebb21
                 msg (M_WARN, "openvpn_popen: unable to create stdout pipe for %s", cmd);
                 ret = -1;
9449e6a9
 	      }
 	}
       else if (!warn_shown && (script_security < SSEC_SCRIPTS))
 	{
 	  msg (M_WARN, SCRIPT_SECURITY_WARNING);
           warn_shown = true;
 	}
 #else
       msg (M_WARN, "openvpn_popen: execve function not available");
 #endif
     }
   else
     {
       msg (M_FATAL, "openvpn_popen: called with empty argv");
     }
 
   gc_free (&gc);
   return ret;
 }
 
 
 
 /*
6fbf66fa
  * Initialize random number seed.  random() is only used
  * when "weak" random numbers are acceptable.
  * OpenSSL routines are always used when cryptographically
  * strong random numbers are required.
  */
 
 void
 init_random_seed(void)
 {
   struct timeval tv;
 
   if (!gettimeofday (&tv, NULL))
     {
       const unsigned int seed = (unsigned int) tv.tv_sec ^ tv.tv_usec;
       srandom (seed);
     }
 }
 
 /* thread-safe strerror */
 
 const char *
 strerror_ts (int errnum, struct gc_arena *gc)
 {
 #ifdef HAVE_STRERROR
   struct buffer out = alloc_buf_gc (256, gc);
 
   buf_printf (&out, "%s", openvpn_strerror (errnum, gc));
   return BSTR (&out);
 #else
   return "[error string unavailable]";
 #endif
 }
 
 /*
  * Set environmental variable (int or string).
  *
  * On Posix, we use putenv for portability,
  * and put up with its painful semantics
  * that require all the support code below.
  */
 
 /* General-purpose environmental variable set functions */
 
 static char *
 construct_name_value (const char *name, const char *value, struct gc_arena *gc)
 {
   struct buffer out;
 
   ASSERT (name);
   if (!value)
     value = "";
   out = alloc_buf_gc (strlen (name) + strlen (value) + 2, gc);
   buf_printf (&out, "%s=%s", name, value);
   return BSTR (&out);
 }
 
 bool
 deconstruct_name_value (const char *str, const char **name, const char **value, struct gc_arena *gc)
 {
   char *cp;
 
   ASSERT (str);
   ASSERT (name && value);
 
   *name = cp = string_alloc (str, gc);
   *value = NULL;
 
   while ((*cp))
     {
       if (*cp == '=' && !*value)
 	{
 	  *cp = 0;
 	  *value = cp + 1;
 	}
       ++cp;
     }
   return *name && *value;
 }
 
 static bool
 env_string_equal (const char *s1, const char *s2)
 {
   int c1, c2;
   ASSERT (s1);
   ASSERT (s2);
 
   while (true)
     {
       c1 = *s1++;
       c2 = *s2++;
       if (c1 == '=')
 	c1 = 0;
       if (c2 == '=')
 	c2 = 0;
       if (!c1 && !c2)
 	return true;
       if (c1 != c2)
 	break;
     }
   return false;
 }
 
 static bool
 remove_env_item (const char *str, const bool do_free, struct env_item **list)
 {
   struct env_item *current, *prev;
 
   ASSERT (str);
   ASSERT (list);
 
   for (current = *list, prev = NULL; current != NULL; current = current->next)
     {
       if (env_string_equal (current->string, str))
 	{
 	  if (prev)
 	    prev->next = current->next;
 	  else
 	    *list = current->next;
 	  if (do_free)
 	    {
 	      memset (current->string, 0, strlen (current->string));
 	      free (current->string);
 	      free (current);
 	    }
 	  return true;
 	}
       prev = current;
     }
   return false;
 }
 
 static void
 add_env_item (char *str, const bool do_alloc, struct env_item **list, struct gc_arena *gc)
 {
   struct env_item *item;
 
   ASSERT (str);
   ASSERT (list);
 
   ALLOC_OBJ_GC (item, struct env_item, gc);
   item->string = do_alloc ? string_alloc (str, gc): str;
   item->next = *list;
   *list = item;
 }
 
 /* struct env_set functions */
 
 static bool
 env_set_del_nolock (struct env_set *es, const char *str)
 {
2a64816b
   return remove_env_item (str, es->gc == NULL, &es->list);
6fbf66fa
 }
 
 static void
 env_set_add_nolock (struct env_set *es, const char *str)
 {
2a64816b
   remove_env_item (str, es->gc == NULL, &es->list);  
6fbf66fa
   add_env_item ((char *)str, true, &es->list, es->gc);
 }
 
 struct env_set *
 env_set_create (struct gc_arena *gc)
 {
   struct env_set *es;
   ALLOC_OBJ_CLEAR_GC (es, struct env_set, gc);
   es->list = NULL;
   es->gc = gc;
   return es;
 }
 
2a64816b
 void
 env_set_destroy (struct env_set *es)
 {
   if (es && es->gc == NULL)
     {
       struct env_item *e = es->list;
       while (e)
 	{
 	  struct env_item *next = e->next;
 	  free (e->string);
 	  free (e);
 	  e = next;
 	}
       free (es);
     }
 }
 
6fbf66fa
 bool
 env_set_del (struct env_set *es, const char *str)
 {
   bool ret;
   ASSERT (es);
   ASSERT (str);
   ret = env_set_del_nolock (es, str);
   return ret;
 }
 
 void
 env_set_add (struct env_set *es, const char *str)
 {
   ASSERT (es);
   ASSERT (str);
   env_set_add_nolock (es, str);
 }
 
 void
 env_set_print (int msglevel, const struct env_set *es)
 {
   if (check_debug_level (msglevel))
     {
       const struct env_item *e;
       int i;
 
       if (es)
 	{
 	  e = es->list;
 	  i = 0;
 
 	  while (e)
 	    {
093e7eba
 	      if (env_safe_to_print (e->string))
 		msg (msglevel, "ENV [%d] '%s'", i, e->string);
6fbf66fa
 	      ++i;
 	      e = e->next;
 	    }
 	}
     }
 }
 
 void
 env_set_inherit (struct env_set *es, const struct env_set *src)
 {
   const struct env_item *e;
 
   ASSERT (es);
 
   if (src)
     {
       e = src->list;
       while (e)
 	{
 	  env_set_add_nolock (es, e->string);
 	  e = e->next;
 	}
     }
 }
 
 void
 env_set_add_to_environment (const struct env_set *es)
 {
   if (es)
     {
       struct gc_arena gc = gc_new ();
       const struct env_item *e;
 
       e = es->list;
 
       while (e)
 	{
 	  const char *name;
 	  const char *value;
 
 	  if (deconstruct_name_value (e->string, &name, &value, &gc))
 	    setenv_str (NULL, name, value);
 
 	  e = e->next;
 	}
       gc_free (&gc);
     }
 }
 
 void
 env_set_remove_from_environment (const struct env_set *es)
 {
   if (es)
     {
       struct gc_arena gc = gc_new ();
       const struct env_item *e;
 
       e = es->list;
 
       while (e)
 	{
 	  const char *name;
 	  const char *value;
 
 	  if (deconstruct_name_value (e->string, &name, &value, &gc))
 	    setenv_del (NULL, name);
 
 	  e = e->next;
 	}
       gc_free (&gc);
     }
 }
 
 /* add/modify/delete environmental strings */
 
 void
e8c1720d
 setenv_counter (struct env_set *es, const char *name, counter_type value)
 {
   char buf[64];
   openvpn_snprintf (buf, sizeof(buf), counter_format, value);
   setenv_str (es, name, buf);
 }
 
 void
6fbf66fa
 setenv_int (struct env_set *es, const char *name, int value)
 {
   char buf[64];
   openvpn_snprintf (buf, sizeof(buf), "%d", value);
   setenv_str (es, name, buf);
 }
 
 void
a783c400
 setenv_unsigned (struct env_set *es, const char *name, unsigned int value)
 {
   char buf[64];
   openvpn_snprintf (buf, sizeof(buf), "%u", value);
   setenv_str (es, name, buf);
 }
 
 void
6fbf66fa
 setenv_str (struct env_set *es, const char *name, const char *value)
 {
   setenv_str_ex (es, name, value, CC_NAME, 0, 0, CC_PRINT, 0, 0);
 }
 
 void
07d19ba7
 setenv_str_safe (struct env_set *es, const char *name, const char *value)
 {
b4073a76
   uint8_t b[64];
   struct buffer buf;
   buf_set_write (&buf, b, sizeof (b));
   if (buf_printf (&buf, "OPENVPN_%s", name))
     setenv_str (es, BSTR(&buf), value);
   else
     msg (M_WARN, "setenv_str_safe: name overflow");
07d19ba7
 }
 
 void
6fbf66fa
 setenv_del (struct env_set *es, const char *name)
 {
   ASSERT (name);
   setenv_str (es, name, NULL);
 }
 
 void
 setenv_str_ex (struct env_set *es,
 	       const char *name,
 	       const char *value,
 	       const unsigned int name_include,
 	       const unsigned int name_exclude,
 	       const char name_replace,
 	       const unsigned int value_include,
 	       const unsigned int value_exclude,
 	       const char value_replace)
 {
   struct gc_arena gc = gc_new ();
   const char *name_tmp;
   const char *val_tmp = NULL;
 
   ASSERT (name && strlen (name) > 1);
 
   name_tmp = string_mod_const (name, name_include, name_exclude, name_replace, &gc);
 
   if (value)
     val_tmp = string_mod_const (value, value_include, value_exclude, value_replace, &gc);
 
4d0fb447
   ASSERT (es);
 
   if (val_tmp)
6fbf66fa
     {
4d0fb447
       const char *str = construct_name_value (name_tmp, val_tmp, &gc);
       env_set_add (es, str);
2232f4b5
 #if DEBUG_VERBOSE_SETENV
4d0fb447
       msg (M_INFO, "SETENV_ES '%s'", str);
2232f4b5
 #endif
6fbf66fa
     }
   else
4d0fb447
     env_set_del (es, name_tmp);
6fbf66fa
 
   gc_free (&gc);
 }
 
 /*
4e9a51d7
  * Setenv functions that append an integer index to the name
  */
 static const char *
 setenv_format_indexed_name (const char *name, const int i, struct gc_arena *gc)
 {
   struct buffer out = alloc_buf_gc (strlen (name) + 16, gc);
   if (i >= 0)
     buf_printf (&out, "%s_%d", name, i);
   else
     buf_printf (&out, "%s", name);
   return BSTR (&out);
 }
 
 void
 setenv_int_i (struct env_set *es, const char *name, const int value, const int i)
 {
   struct gc_arena gc = gc_new ();
   const char *name_str = setenv_format_indexed_name (name, i, &gc);
   setenv_int (es, name_str, value);
   gc_free (&gc);
 }
 
 void
 setenv_str_i (struct env_set *es, const char *name, const char *value, const int i)
 {
   struct gc_arena gc = gc_new ();
   const char *name_str = setenv_format_indexed_name (name, i, &gc);
   setenv_str (es, name_str, value);
   gc_free (&gc);
 }
 
6fbf66fa
 /* return true if filename can be opened for read */
 bool
 test_file (const char *filename)
 {
   bool ret = false;
   if (filename)
     {
14a131ac
       FILE *fp = platform_fopen (filename, "r");
6fbf66fa
       if (fp)
 	{
 	  fclose (fp);
 	  ret = true;
 	}
4978dada
       else
 	{
 	  if( openvpn_errno () == EACCES ) {
 	    msg( M_WARN | M_ERRNO, "Could not access file '%s'", filename);
 	  }
 	}
6fbf66fa
     }
 
   dmsg (D_TEST_FILE, "TEST FILE '%s' [%d]",
        filename ? filename : "UNDEF",
        ret);
 
   return ret;
 }
 
9b33b5a4
 #ifdef ENABLE_CRYPTO
222f0840
 
6fbf66fa
 /* create a temporary filename in directory */
 const char *
4e1cc5f6
 create_temp_file (const char *directory, const char *prefix, struct gc_arena *gc)
6fbf66fa
 {
   static unsigned int counter;
   struct buffer fname = alloc_buf_gc (256, gc);
4e1cc5f6
   int fd;
   const char *retfname = NULL;
   unsigned int attempts = 0;
6fbf66fa
 
4e1cc5f6
   do
     {
       uint8_t rndbytes[16];
       const char *rndstr;
 
       ++attempts;
       ++counter;
 
       prng_bytes (rndbytes, sizeof rndbytes);
       rndstr = format_hex_ex (rndbytes, sizeof rndbytes, 40, 0, NULL, gc);
       buf_printf (&fname, PACKAGE "_%s_%s.tmp", prefix, rndstr);
 
       retfname = gen_path (directory, BSTR (&fname), gc);
       if (!retfname)
         {
           msg (M_FATAL, "Failed to create temporary filename and path");
           return NULL;
         }
 
       /* Atomically create the file.  Errors out if the file already
          exists.  */
14a131ac
       fd = platform_open (retfname, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR);
4e1cc5f6
       if (fd != -1)
         {
           close (fd);
           return retfname;
         }
       else if (fd == -1 && errno != EEXIST)
         {
           /* Something else went wrong, no need to retry.  */
           struct gc_arena gcerr = gc_new ();
           msg (M_FATAL, "Could not create temporary file '%s': %s",
                retfname, strerror_ts (errno, &gcerr));
           gc_free (&gcerr);
           return NULL;
         }
     }
   while (attempts < 6);
6fbf66fa
 
4e1cc5f6
   msg (M_FATAL, "Failed to create temporary file after %i attempts", attempts);
   return NULL;
6fbf66fa
 }
 
8e9666d5
 /*
7de8f3f3
  * Prepend a random string to hostname to prevent DNS caching.
8e9666d5
  * For example, foo.bar.gov would be modified to <random-chars>.foo.bar.gov.
7de8f3f3
  * Of course, this requires explicit support in the DNS server (wildcard).
8e9666d5
  */
 const char *
 hostname_randomize(const char *hostname, struct gc_arena *gc)
 {
74fce85e
 # define n_rnd_bytes 6
8e9666d5
 
7de8f3f3
   uint8_t rnd_bytes[n_rnd_bytes];
   const char *rnd_str;
   struct buffer hname = alloc_buf_gc (strlen(hostname)+sizeof(rnd_bytes)*2+4, gc);
8e9666d5
 
7de8f3f3
   prng_bytes (rnd_bytes, sizeof (rnd_bytes));
   rnd_str = format_hex_ex (rnd_bytes, sizeof (rnd_bytes), 40, 0, NULL, gc);
   buf_printf(&hname, "%s.%s", rnd_str, hostname);
   return BSTR(&hname);
74fce85e
 # undef n_rnd_bytes
8e9666d5
 }
 
 #else
 
 const char *
 hostname_randomize(const char *hostname, struct gc_arena *gc)
 {
   msg (M_WARN, "WARNING: hostname randomization disabled when crypto support is not compiled");
   return hostname;
 }
 
222f0840
 #endif
 
6fbf66fa
 /*
  * Put a directory and filename together.
  */
 const char *
 gen_path (const char *directory, const char *filename, struct gc_arena *gc)
 {
9885f57e
 #if WIN32
   const int CC_PATH_RESERVED = CC_LESS_THAN|CC_GREATER_THAN|CC_COLON|
     CC_DOUBLE_QUOTE|CC_SLASH|CC_BACKSLASH|CC_PIPE|CC_QUESTION_MARK|CC_ASTERISK;
 #else
   const int CC_PATH_RESERVED = CC_SLASH;
 #endif
   const char *safe_filename = string_mod_const (filename, CC_PRINT, CC_PATH_RESERVED, '_', gc);
6fbf66fa
 
   if (safe_filename
       && strcmp (safe_filename, ".")
73b7e698
       && strcmp (safe_filename, "..")
 #ifdef WIN32
       && win_safe_filename (safe_filename)
 #endif
       )
6fbf66fa
     {
ddad0a8c
       const size_t outsize = strlen(safe_filename) + (directory ? strlen (directory) : 0) + 16;
       struct buffer out = alloc_buf_gc (outsize, gc);
6fbf66fa
       char dirsep[2];
 
       dirsep[0] = OS_SPECIFIC_DIRSEP;
       dirsep[1] = '\0';
 
       if (directory)
 	buf_printf (&out, "%s%s", directory, dirsep);
       buf_printf (&out, "%s", safe_filename);
 
       return BSTR (&out);
     }
   else
     return NULL;
 }
 
d1dcc3e7
 bool
 absolute_pathname (const char *pathname)
 {
   if (pathname)
     {
       const int c = pathname[0];
 #ifdef WIN32
       return c == '\\' || (isalpha(c) && pathname[1] == ':' && pathname[2] == '\\');
 #else
       return c == '/';
 #endif
     }
   else
     return false;
 }
 
6fbf66fa
 /*
  * Get and store a username/password
  */
 
1d89886e
 bool
3cf9dd88
 get_user_pass_cr (struct user_pass *up,
 		  const char *auth_file,
 		  const char *prefix,
 		  const unsigned int flags,
 		  const char *auth_challenge)
6fbf66fa
 {
   struct gc_arena gc = gc_new ();
 
   if (!up->defined)
     {
6e9373c8
       bool from_authfile = (auth_file && !streq (auth_file, "stdin"));
       bool username_from_stdin = !from_authfile;
       bool password_from_stdin = !from_authfile;
6fbf66fa
 
3cf6c932
       if (flags & GET_USER_PASS_PREVIOUS_CREDS_FAILED)
 	msg (M_WARN, "Note: previous '%s' credentials failed", prefix);
 
6fbf66fa
 #ifdef ENABLE_MANAGEMENT
       /*
3cf9dd88
        * Get username/password from management interface?
6fbf66fa
        */
       if (management
6e9373c8
 	  && ((auth_file && streq (auth_file, "management")) || (!from_authfile && (flags & GET_USER_PASS_MANAGEMENT)))
6fbf66fa
 	  && management_query_user_pass_enabled (management))
 	{
eab3e22f
 	  const char *sc = NULL;
 
3cf6c932
 	  if (flags & GET_USER_PASS_PREVIOUS_CREDS_FAILED)
 	    management_auth_failure (management, prefix, "previous auth credentials failed");
 
eab3e22f
 #ifdef ENABLE_CLIENT_CR
 	  if (auth_challenge && (flags & GET_USER_PASS_STATIC_CHALLENGE))
 	    sc = auth_challenge;
 #endif
 	  if (!management_query_user_pass (management, up, prefix, flags, sc))
1d89886e
 	    {
 	      if ((flags & GET_USER_PASS_NOFATAL) != 0)
 		return false;
 	      else
1bda73a7
 		msg (M_FATAL, "ERROR: could not read %s username/password/ok/string from management interface", prefix);
1d89886e
 	    }
6fbf66fa
 	}
       else
 #endif
       /*
dd1047f5
        * Get NEED_OK confirmation from the console
        */
       if (flags & GET_USER_PASS_NEED_OK)
 	{
 	  struct buffer user_prompt = alloc_buf_gc (128, &gc);
 
 	  buf_printf (&user_prompt, "NEED-OK|%s|%s:", prefix, up->username);
 	  
 	  if (!get_console_input (BSTR (&user_prompt), true, up->password, USER_PASS_LEN))
 	    msg (M_FATAL, "ERROR: could not read %s ok-confirmation from stdin", prefix);
 	  
 	  if (!strlen (up->password))
 	    strcpy (up->password, "ok");
 	}
6e9373c8
       else if (from_authfile)
         {
           /*
            * Try to get username/password from a file.
            */
           FILE *fp;
           char password_buf[USER_PASS_LEN] = { '\0' };
 
           warn_if_group_others_accessible (auth_file);
 
           fp = platform_fopen (auth_file, "r");
           if (!fp)
             msg (M_ERR, "Error opening '%s' auth file: %s", prefix, auth_file);
 
           if ((flags & GET_USER_PASS_PASSWORD_ONLY) == 0)
             {
               /* Read username first */
                if (fgets (up->username, USER_PASS_LEN, fp) == NULL)
                  msg (M_FATAL, "Error reading username from %s authfile: %s",
                       prefix,
                       auth_file);
              }
           chomp (up->username);
 
           if (fgets (password_buf, USER_PASS_LEN, fp) != NULL)
             {
 #ifndef ENABLE_PASSWORD_SAVE
               /*
                * Unless ENABLE_PASSWORD_SAVE is defined, don't allow sensitive passwords
                * to be read from a file.
                */
               if (flags & GET_USER_PASS_SENSITIVE)
                 msg (M_FATAL, "Sorry, '%s' password cannot be read from a file", prefix);
 #endif
               chomp (password_buf);
             }
 
           if (flags & GET_USER_PASS_PASSWORD_ONLY && !password_buf[0])
                 msg (M_FATAL, "Error reading password from %s authfile: %s", prefix, auth_file);
 
           if (password_buf[0])
             strncpy(up->password, password_buf, USER_PASS_LEN);
           else
             password_from_stdin = 1;
 
           fclose (fp);
 
           if (!(flags & GET_USER_PASS_PASSWORD_ONLY) && strlen (up->username) == 0)
             msg (M_FATAL, "ERROR: username from %s authfile '%s' is empty", prefix, auth_file);
         }
 
dd1047f5
       /*
6fbf66fa
        * Get username/password from standard input?
        */
6e9373c8
       if (username_from_stdin || password_from_stdin)
6fbf66fa
 	{
0ffd7443
 #ifndef WIN32
079e5b9c
 	  /* did we --daemon'ize before asking for passwords? */
 	  if ( !isatty(0) && !isatty(2) )
 	    { msg(M_FATAL, "neither stdin nor stderr are a tty device, can't ask for %s password.  If you used --daemon, you need to use --askpass to make passphrase-protected keys work, and you can not use --auth-nocache.", prefix ); }
0ffd7443
 #endif
079e5b9c
 
3cf9dd88
 #ifdef ENABLE_CLIENT_CR
eab3e22f
 	  if (auth_challenge && (flags & GET_USER_PASS_DYNAMIC_CHALLENGE))
984cf003
 	    {
3cf9dd88
 	      struct auth_challenge_info *ac = get_auth_challenge (auth_challenge, &gc);
 	      if (ac)
 		{
 		  char *response = (char *) gc_malloc (USER_PASS_LEN, false, &gc);
 		  struct buffer packed_resp;
 
 		  buf_set_write (&packed_resp, (uint8_t*)up->password, USER_PASS_LEN);
eab3e22f
 		  msg (M_INFO|M_NOPREFIX, "CHALLENGE: %s", ac->challenge_text);
3cf9dd88
 		  if (!get_console_input ("Response:", BOOL_CAST(ac->flags&CR_ECHO), response, USER_PASS_LEN))
 		    msg (M_FATAL, "ERROR: could not read challenge response from stdin");
 		  strncpynt (up->username, ac->user, USER_PASS_LEN);
 		  buf_printf (&packed_resp, "CRV1::%s::%s", ac->state_id, response);
 		}
 	      else
 		{
 		  msg (M_FATAL, "ERROR: received malformed challenge request from server");
 		}
6fbf66fa
 	    }
3cf9dd88
 	  else
 #endif
 	    {
 	      struct buffer user_prompt = alloc_buf_gc (128, &gc);
 	      struct buffer pass_prompt = alloc_buf_gc (128, &gc);
6fbf66fa
 
3cf9dd88
 	      buf_printf (&user_prompt, "Enter %s Username:", prefix);
 	      buf_printf (&pass_prompt, "Enter %s Password:", prefix);
 
6e9373c8
 	      if (username_from_stdin && !(flags & GET_USER_PASS_PASSWORD_ONLY))
3cf9dd88
 		{
 		  if (!get_console_input (BSTR (&user_prompt), true, up->username, USER_PASS_LEN))
 		    msg (M_FATAL, "ERROR: could not read %s username from stdin", prefix);
 		  if (strlen (up->username) == 0)
 		    msg (M_FATAL, "ERROR: %s username is empty", prefix);
 		}
 
6e9373c8
 	      if (password_from_stdin && !get_console_input (BSTR (&pass_prompt), false, up->password, USER_PASS_LEN))
3cf9dd88
 		msg (M_FATAL, "ERROR: could not not read %s password from stdin", prefix);
eab3e22f
 
 #ifdef ENABLE_CLIENT_CR
 	      if (auth_challenge && (flags & GET_USER_PASS_STATIC_CHALLENGE))
 		{
 		  char *response = (char *) gc_malloc (USER_PASS_LEN, false, &gc);
 		  struct buffer packed_resp;
 		  char *pw64=NULL, *resp64=NULL;
 
 		  msg (M_INFO|M_NOPREFIX, "CHALLENGE: %s", auth_challenge);
 		  if (!get_console_input ("Response:", BOOL_CAST(flags & GET_USER_PASS_STATIC_CHALLENGE_ECHO), response, USER_PASS_LEN))
 		    msg (M_FATAL, "ERROR: could not read static challenge response from stdin");
a4da1fe7
 		  if (openvpn_base64_encode(up->password, strlen(up->password), &pw64) == -1
 		      || openvpn_base64_encode(response, strlen(response), &resp64) == -1)
eab3e22f
 		    msg (M_FATAL, "ERROR: could not base64-encode password/static_response");
 		  buf_set_write (&packed_resp, (uint8_t*)up->password, USER_PASS_LEN);
 		  buf_printf (&packed_resp, "SCRV1:%s:%s", pw64, resp64);
 		  string_clear(pw64);
 		  free(pw64);
 		  string_clear(resp64);
 		  free(resp64);
 		}
 #endif
3cf9dd88
 	    }
6fbf66fa
 	}
 
       string_mod (up->username, CC_PRINT, CC_CRLF, 0);
       string_mod (up->password, CC_PRINT, CC_CRLF, 0);
 
       up->defined = true;
     }
 
 #if 0
   msg (M_INFO, "GET_USER_PASS %s u='%s' p='%s'", prefix, up->username, up->password);
 #endif
 
   gc_free (&gc);
1d89886e
 
   return true;
6fbf66fa
 }
 
3cf9dd88
 #ifdef ENABLE_CLIENT_CR
 
 /*
eab3e22f
  * See management/management-notes.txt for more info on the
  * the dynamic challenge/response protocol implemented here.
3cf9dd88
  */
 struct auth_challenge_info *
 get_auth_challenge (const char *auth_challenge, struct gc_arena *gc)
 {
   if (auth_challenge)
     {
       struct auth_challenge_info *ac;
       const int len = strlen (auth_challenge);
       char *work = (char *) gc_malloc (len+1, false, gc);
       char *cp;
 
       struct buffer b;
       buf_set_read (&b, (const uint8_t *)auth_challenge, len);
 
       ALLOC_OBJ_CLEAR_GC (ac, struct auth_challenge_info, gc);
 
       /* parse prefix */
       if (!buf_parse(&b, ':', work, len))
 	return NULL;
       if (strcmp(work, "CRV1"))
 	return NULL;
 
       /* parse flags */
       if (!buf_parse(&b, ':', work, len))
 	return NULL;
       for (cp = work; *cp != '\0'; ++cp)
 	{
 	  const char c = *cp;
 	  if (c == 'E')
 	    ac->flags |= CR_ECHO;
 	  else if (c == 'R')
 	    ac->flags |= CR_RESPONSE;
 	}
       
       /* parse state ID */
       if (!buf_parse(&b, ':', work, len))
 	return NULL;
       ac->state_id = string_alloc(work, gc);
 
       /* parse user name */
       if (!buf_parse(&b, ':', work, len))
 	return NULL;
       ac->user = (char *) gc_malloc (strlen(work)+1, true, gc);
a4da1fe7
       openvpn_base64_decode(work, (void*)ac->user, -1);
3cf9dd88
 
       /* parse challenge text */
       ac->challenge_text = string_alloc(BSTR(&b), gc);
 
       return ac;
     }
   else
     return NULL;
 }
 
 #endif
 
5f31881e
 #if AUTO_USERID
 
 void
70f4f82a
 get_user_pass_auto_userid (struct user_pass *up, const char *tag)
5f31881e
 {
   struct gc_arena gc = gc_new ();
   struct buffer buf;
   uint8_t macaddr[6];
   static uint8_t digest [MD5_DIGEST_LENGTH];
   static const uint8_t hashprefix[] = "AUTO_USERID_DIGEST";
 
d5f44617
   const md_kt_t *md5_kt = md_kt_get("MD5");
   md_ctx_t ctx;
 
5f31881e
   CLEAR (*up);
   buf_set_write (&buf, (uint8_t*)up->username, USER_PASS_LEN);
51bd56f4
   buf_printf (&buf, "%s", TARGET_PREFIX);
5f31881e
   if (get_default_gateway_mac_addr (macaddr))
     {
       dmsg (D_AUTO_USERID, "GUPAU: macaddr=%s", format_hex_ex (macaddr, sizeof (macaddr), 0, 1, ":", &gc));
d5f44617
       md_ctx_init(&ctx, md5_kt);
       md_ctx_update(&ctx, hashprefix, sizeof (hashprefix) - 1);
       md_ctx_update(&ctx, macaddr, sizeof (macaddr));
       md_ctx_final(&ctx, digest);
       md_ctx_cleanup(&ctx)
       buf_printf(&buf, "%s", format_hex_ex (digest, sizeof (digest), 0, 256, " ", &gc));
5f31881e
     }
   else
     {
       buf_printf (&buf, "UNKNOWN");
     }
70f4f82a
   if (tag && strcmp (tag, "stdin"))
     buf_printf (&buf, "-%s", tag);
5f31881e
   up->defined = true;
   gc_free (&gc);
 
   dmsg (D_AUTO_USERID, "GUPAU: AUTO_USERID: '%s'", up->username);
 }
 
 #endif
 
6fbf66fa
 void
 purge_user_pass (struct user_pass *up, const bool force)
 {
   const bool nocache = up->nocache;
6f94529f
   static bool warn_shown = false;
6fbf66fa
   if (nocache || force)
     {
       CLEAR (*up);
       up->nocache = nocache;
     }
6f94529f
   else if (!warn_shown)
70899be8
     {
       msg (M_WARN, "WARNING: this configuration may cache passwords in memory -- use the auth-nocache option to prevent this");
6f94529f
       warn_shown = true;
70899be8
     }
6fbf66fa
 }
 
0db046f2
 void
 set_auth_token (struct user_pass *up, const char *token)
 {
   if (token && strlen(token) && up && up->defined && !up->nocache)
     {
       CLEAR (up->password);
       strncpynt (up->password, token, USER_PASS_LEN);
     }
 }
 
6fbf66fa
 /*
  * Process string received by untrusted peer before
  * printing to console or log file.
  *
  * Assumes that string has been null terminated.
  */
 const char *
 safe_print (const char *str, struct gc_arena *gc)
 {
   return string_mod_const (str, CC_PRINT, CC_CRLF, '.', gc);
 }
 
5a2e9a25
 static bool
 is_password_env_var (const char *str)
 {
   return (strncmp (str, "password", 8) == 0);
 }
 
 bool
 env_allowed (const char *str)
 {
   return (script_security >= SSEC_PW_ENV || !is_password_env_var (str));
 }
 
093e7eba
 bool
 env_safe_to_print (const char *str)
 {
 #ifndef UNSAFE_DEBUG
5a2e9a25
   if (is_password_env_var (str))
093e7eba
     return false;
 #endif
   return true;
 }
 
6fbf66fa
 /* Make arrays of strings */
 
 const char **
5a2e9a25
 make_env_array (const struct env_set *es,
 		const bool check_allowed,
 		struct gc_arena *gc)
6fbf66fa
 {
   char **ret = NULL;
   struct env_item *e = NULL;
   int i = 0, n = 0;
 
   /* figure length of es */
   if (es)
     {
       for (e = es->list; e != NULL; e = e->next)
 	++n;
     }
 
   /* alloc return array */
   ALLOC_ARRAY_CLEAR_GC (ret, char *, n+1, gc);
 
   /* fill return array */
   if (es)
     {
5a2e9a25
       i = 0;
       for (e = es->list; e != NULL; e = e->next)
6fbf66fa
 	{
5a2e9a25
 	  if (!check_allowed || env_allowed (e->string))
 	    {
 	      ASSERT (i < n);
 	      ret[i++] = e->string;
 	    }
6fbf66fa
 	}
     }
 
   ret[i] = NULL;
   return (const char **)ret;
 }
 
 const char **
 make_arg_array (const char *first, const char *parms, struct gc_arena *gc)
 {
   char **ret = NULL;
   int base = 0;
   const int max_parms = MAX_PARMS + 2;
   int n = 0;
 
   /* alloc return array */
   ALLOC_ARRAY_CLEAR_GC (ret, char *, max_parms, gc);
 
   /* process first parameter, if provided */
   if (first)
     {
       ret[base++] = string_alloc (first, gc);
     }
 
   if (parms)
     {
       n = parse_line (parms, &ret[base], max_parms - base - 1, "make_arg_array", 0, M_WARN, gc);
       ASSERT (n >= 0 && n + base + 1 <= max_parms);
     }
   ret[base + n] = NULL;
 
   return (const char **)ret;
 }
 
eadf16a6
 static const char **
 make_inline_array (const char *str, struct gc_arena *gc)
 {
   char line[OPTION_LINE_SIZE];
   struct buffer buf;
   int len = 0;
   char **ret = NULL;
   int i = 0;
 
e1447acc
   buf_set_read (&buf, (const uint8_t *) str, strlen (str));
eadf16a6
   while (buf_parse (&buf, '\n', line, sizeof (line)))
     ++len;
 
   /* alloc return array */
   ALLOC_ARRAY_CLEAR_GC (ret, char *, len + 1, gc);
 
e1447acc
   buf_set_read (&buf, (const uint8_t *) str, strlen(str));
eadf16a6
   while (buf_parse (&buf, '\n', line, sizeof (line)))
     {
       chomp (line);
       ASSERT (i < len);
       ret[i] = string_alloc (skip_leading_whitespace (line), gc);
       ++i;
     }  
   ASSERT (i <= len);
   ret[i] = NULL;
   return (const char **)ret;
 }
 
 static const char **
 make_arg_copy (char **p, struct gc_arena *gc)
 {
   char **ret = NULL;
   const int len = string_array_len ((const char **)p);
   const int max_parms = len + 1;
   int i;
 
   /* alloc return array */
   ALLOC_ARRAY_CLEAR_GC (ret, char *, max_parms, gc);
 
   for (i = 0; i < len; ++i)
     ret[i] = p[i];
 
   return (const char **)ret;
 }
 
 const char **
 make_extended_arg_array (char **p, struct gc_arena *gc)
 {
   const int argc = string_array_len ((const char **)p);
   if (!strcmp (p[0], INLINE_FILE_TAG) && argc == 2)
     return make_inline_array (p[1], gc);
   else
   if (argc == 0)
     return make_arg_array (NULL, NULL, gc);
   else if (argc == 1)
     return make_arg_array (p[0], NULL, gc);
   else if (argc == 2)
     return make_arg_array (p[0], p[1], gc);
   else
     return make_arg_copy (p, gc);
 }
 
6fbf66fa
 void
 openvpn_sleep (const int n)
 {
 #ifdef ENABLE_MANAGEMENT
   if (management)
     {
       management_event_loop_n_seconds (management, n);
       return;
     }
 #endif
   sleep (n);
 }
c67d59cd
 
 /*
b8fb090c
  * Return the next largest power of 2
  * or u if u is a power of 2.
c67d59cd
  */
b8fb090c
 size_t
 adjust_power_of_2 (size_t u)
 {
   size_t ret = 1;
 
   while (ret < u)
     {
       ret <<= 1;
       ASSERT (ret > 0);
     }
 
   return ret;
 }
 
 /*
  * 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.
  */
 
c67d59cd
 void
b8fb090c
 argv_init (struct argv *a)
c67d59cd
 {
b8fb090c
   a->capacity = 0;
   a->argc = 0;
   a->argv = NULL;
a8281352
   a->system_str = NULL;
b8fb090c
 }
 
 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);
a8281352
   free (a->system_str);
b8fb090c
   argv_init (a);
 }
 
 static void
 argv_extend (struct argv *a, const size_t newcap)
 {
   if (newcap > a->capacity)
c67d59cd
     {
b8fb090c
       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;
 }
 
a8281352
 static void
 argv_system_str_append (struct argv *a, const char *str, const bool enquote)
 {
   if (str)
     {
       char *newstr;
 
       /* compute length of new system_str */
       size_t l = strlen (str) + 1; /* space for new string plus trailing '\0' */
       if (a->system_str)
 	l += strlen (a->system_str) + 1; /* space for existing string + space (" ") separator */
       if (enquote)
 	l += 2; /* space for two quotes */
 
       /* build new system_str */
       newstr = (char *) malloc (l);
       newstr[0] = '\0';
       check_malloc_return (newstr);
       if (a->system_str)
 	{
 	  strcpy (newstr, a->system_str);
 	  strcat (newstr, " ");
 	}
       if (enquote)
 	strcat (newstr, "\"");
       strcat (newstr, str);
       if (enquote)
 	strcat (newstr, "\"");
       free (a->system_str);
       a->system_str = newstr;
     }
 }
 
 static char *
 argv_extract_cmd_name (const char *path)
 {
   if (path)
     {
ddc7692d
       char *path_cp = string_alloc(path, NULL); /* POSIX basename() implementaions may modify its arguments */
ec302f70
       const char *bn = basename (path_cp);
a8281352
       if (bn)
 	{
 	  char *ret = string_alloc (bn, NULL);
 	  char *dot = strrchr (ret, '.');
 	  if (dot)
 	    *dot = '\0';
ec302f70
 	  free(path_cp);
a8281352
 	  if (ret[0] != '\0')
 	    return ret;
 	}
     }
   return NULL;
 }
 
 const char *
 argv_system_str (const struct argv *a)
 {
   return a->system_str;
 }
 
b8fb090c
 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));
7bb9f5aa
       r.system_str = string_alloc (a->system_str, NULL);
b8fb090c
     }
   return r;
 }
 
 struct argv
 argv_insert_head (const struct argv *a, const char *head)
 {
   struct argv r;
a8281352
   char *s;
b8fb090c
 
   r = argv_clone (a, 1);
   r.argv[0] = string_alloc (head, NULL);
a8281352
   s = r.system_str;
   r.system_str = string_alloc (head, NULL);
   if (s)
     {
       argv_system_str_append (&r, s, false);
       free (s);
     }
b8fb090c
   return r;
 }
 
 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)
c67d59cd
 	{
b8fb090c
 	  if (!isspace (c))
 	    ++termlen;
 	  else
 	    break;
c67d59cd
 	}
b8fb090c
       else
 	{
 	  if (!isspace (c))
 	    {
 	      term = p;
 	      termlen = 1;
 	    }
 	}
       ++p;
c67d59cd
     }
b8fb090c
   *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);
 }
 
 void
 argv_printf (struct argv *a, const char *format, ...)
 {
   va_list arglist;
   va_start (arglist, format);
   argv_printf_arglist (a, format, 0, 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, APA_CAT, arglist);
   va_end (arglist);
 }
 
 void
 argv_printf_arglist (struct argv *a, const char *format, const unsigned int flags, va_list arglist)
 {
   struct gc_arena gc = gc_new ();
   char *term;
   const char *f = format;
 
   if (!(flags & APA_CAT))
     argv_reset (a);
   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));
a8281352
 	      argv_system_str_append (a, s, true);
b8fb090c
 	    }
 	  else if (!strcmp (term, "%sc"))
 	    {
 	      char *s = va_arg (arglist, char *);
 	      if (s)
 		{
 		  int nparms;
 		  char *parms[MAX_PARMS+1];
 		  int i;
 
a8281352
 		  nparms = parse_line (s, parms, MAX_PARMS, "SCRIPT-ARGV", 0, D_ARGV_PARSE_CMD, &gc);
 		  if (nparms)
 		    {
 		      for (i = 0; i < nparms; ++i)
 			argv_append (a, string_alloc (parms[i], NULL));
 		    }
 		  else
 		    argv_append (a, string_alloc (s, NULL));
 
 		  argv_system_str_append (a, s, false);
b8fb090c
 		}
 	      else
a8281352
 		{
 		  argv_append (a, string_alloc ("", NULL));
 		  argv_system_str_append (a, "echo", false);
 		}
b8fb090c
 	    }
 	  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));
a8281352
 	      argv_system_str_append (a, numstr, false);
b8fb090c
 	    }
 	  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));
a8281352
 	      argv_system_str_append (a, numstr, false);
b8fb090c
 	    }
 	  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);
a8281352
 		argv_system_str_append (a, combined, false);
b8fb090c
 	      }
 	    }
a8281352
 	  else if (!strcmp (term, "%s%sc"))
b8fb090c
 	    {
 	      char *s1 = va_arg (arglist, char *);
 	      char *s2 = va_arg (arglist, char *);
 	      char *combined;
a8281352
 	      char *cmd_name;
b8fb090c
 
 	      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);
a8281352
 
 	      cmd_name = argv_extract_cmd_name (combined);
 	      if (cmd_name)
 		{
 		  argv_system_str_append (a, cmd_name, false);
 		  free (cmd_name);
 		}
b8fb090c
 	    }
 	  else
 	    ASSERT (0);
 	  free (term);
 	}
       else
 	{
 	  argv_append (a, term);
a8281352
 	  argv_system_str_append (a, term, false);
b8fb090c
 	}
     }
   gc_free (&gc);
c67d59cd
 }
5a2e9a25
 
 #ifdef ARGV_TEST
 void
 argv_test (void)
 {
   struct gc_arena gc = gc_new ();
   const char *s;
 
   struct argv a;
a8281352
 
5a2e9a25
   argv_init (&a);
a8281352
   argv_printf (&a, "%sc foo bar %s", "c:\\\\src\\\\test\\\\jyargs.exe", "foo bar");
   argv_msg_prefix (M_INFO, &a, "ARGV");
   msg (M_INFO, "ARGV-S: %s", argv_system_str(&a));
cc8dd144
   /*openvpn_execve_check (&a, NULL, 0, "command failed");*/
5a2e9a25
 
a8281352
   argv_printf (&a, "%sc %s %s", "c:\\\\src\\\\test files\\\\batargs.bat", "foo", "bar");  
   argv_msg_prefix (M_INFO, &a, "ARGV");
   msg (M_INFO, "ARGV-S: %s", argv_system_str(&a));
cc8dd144
   /*openvpn_execve_check (&a, NULL, 0, "command failed");*/
5a2e9a25
 
a8281352
   argv_printf (&a, "%s%sc foo bar %s %s/%d %d %u", "/foo", "/bar.exe", "one two", "1.2.3.4", 24, -69, 96);
5a2e9a25
   argv_msg_prefix (M_INFO, &a, "ARGV");
a8281352
   msg (M_INFO, "ARGV-S: %s", argv_system_str(&a));
cc8dd144
   /*openvpn_execve_check (&a, NULL, 0, "command failed");*/
5a2e9a25
 
   argv_printf (&a, "this is a %s test of int %d unsigned %u", "FOO", -69, 42);
   s = argv_str (&a, &gc, PA_BRACKET);
a8281352
   printf ("PF: %s\n", s);
   printf ("PF-S: %s\n", argv_system_str(&a));
5a2e9a25
 
   {
     struct argv b = argv_insert_head (&a, "MARK");
     s = argv_str (&b, &gc, PA_BRACKET);
a8281352
     printf ("PF: %s\n", s);
     printf ("PF-S: %s\n", argv_system_str(&b));
5a2e9a25
     argv_reset (&b);
   }
 
b8fb090c
   argv_printf (&a, "%sc foo bar %d", "\"multi term\" command      following \\\"spaces", 99);
5a2e9a25
   s = argv_str (&a, &gc, PA_BRACKET);
a8281352
   printf ("PF: %s\n", s);
   printf ("PF-S: %s\n", argv_system_str(&a));
5a2e9a25
   argv_reset (&a);
 
   s = argv_str (&a, &gc, PA_BRACKET);
a8281352
   printf ("PF: %s\n", s);
   printf ("PF-S: %s\n", argv_system_str(&a));
5a2e9a25
   argv_reset (&a);
 
   argv_printf (&a, "foo bar %d", 99);
b8fb090c
   argv_printf_cat (&a, "bar %d foo %sc", 42, "nonesuch");
5a2e9a25
   argv_printf_cat (&a, "cool %s %d u %s/%d end", "frood", 4, "hello", 7);
   s = argv_str (&a, &gc, PA_BRACKET);
a8281352
   printf ("PF: %s\n", s);
   printf ("PF-S: %s\n", argv_system_str(&a));
   argv_reset (&a);
5a2e9a25
 
 #if 0
b8fb090c
   {
     char line[512];
     while (fgets (line, sizeof(line), stdin) != NULL)
       {
 	char *term;
 	const char *f = line;
 	int i = 0;
 
 	while ((term = argv_term (&f)) != NULL) 
 	  {
 	    printf ("[%d] '%s'\n", i, term);
 	    ++i;
 	    free (term);
 	  }
       }
   }
5a2e9a25
 #endif
 
   argv_reset (&a);
   gc_free (&gc);
 }
 #endif
a8281352
 
0db046f2
 /*
a296f99b
  * Remove security-sensitive strings from control message
  * so that they will not be output to log file.
0db046f2
  */
 const char *
a296f99b
 sanitize_control_message(const char *src, struct gc_arena *gc)
0db046f2
 {
a296f99b
   char *ret = gc_malloc (strlen(src)+1, false, gc);
   char *dest = ret;
0db046f2
   bool redact = false;
a296f99b
   int skip = 0;
0db046f2
 
   for (;;)
     {
a296f99b
       const char c = *src;
0db046f2
       if (c == '\0')
 	  break;
a296f99b
       if (c == 'S' && !strncmp(src, "SESS_ID_", 8))
0db046f2
 	{
a296f99b
 	  skip = 7;
0db046f2
 	  redact = true;
 	}
a296f99b
       else if (c == 'e' && !strncmp(src, "echo ", 5))
429ab795
 	{
a296f99b
 	  skip = 4;
429ab795
 	  redact = true;
 	}
a296f99b
 
       if (c == ',') /* end of redacted item? */
0db046f2
 	{
a296f99b
 	  skip = 0;
 	  redact = false;
0db046f2
 	}
a296f99b
 
       if (redact)
 	{
 	  if (skip > 0)
 	    {
 	      --skip;
 	      *dest++ = c;
 	    }
 	}
       else
 	*dest++ = c;
 
       ++src;
0db046f2
     }
a296f99b
   *dest = '\0';
0db046f2
   return ret;
 }
e7412ca3
 
 /**
  * Will set or query for a global compat flag.  To modify the compat flags
  * the COMPAT_FLAG_SET must be bitwise ORed together with the flag to set.
  * If no "operator" flag is given it defaults to COMPAT_FLAG_QUERY,
  * which returns the flag state.
  *
  * @param  flag  Flag to be set/queried for bitwise ORed with the operator flag
  * @return Returns 0 if the flag is not set, otherwise the 'flag' value is returned
  */
 bool
 compat_flag (unsigned int flag)
 {
   static unsigned int compat_flags = 0;
 
   if (flag & COMPAT_FLAG_SET)
     compat_flags |= (flag >> 1);
 
   return (compat_flags & (flag >> 1));
 
 }
46e02127
 
 #if P2MP_SERVER
 
 /* helper to parse peer_info received from multi client, validate
  * (this is untrusted data) and put into environment
  */
 bool
 validate_peer_info_line(char *line)
 {
   uint8_t c;
   int state = 0;
   while (*line)
     {
       c = *line;
       switch (state)
 	{
 	case 0:
 	case 1:
 	  if (c == '=' && state == 1)
 	    state = 2;
 	  else if (isalnum(c) || c == '_')
 	    state = 1;
 	  else
 	    return false;
 	case 2:
 	  /* after the '=', replace non-printable or shell meta with '_' */
 	  if (!isprint(c) || isspace(c) ||
 	       c == '$' || c == '(' || c == '`' )
 	    *line = '_';
 	}
       line++;
     }
   return (state == 2);
 }
 
 void
 output_peer_info_env (struct env_set *es, const char * peer_info)
 {
   char line[256];
   struct buffer buf;
   buf_set_read (&buf, (const uint8_t *) peer_info, strlen(peer_info));
   while (buf_parse (&buf, '\n', line, sizeof (line)))
     {
       chomp (line);
       if (validate_peer_info_line(line) &&
             (strncmp(line, "IV_", 3) == 0 || strncmp(line, "UV_", 3) == 0) )
 	{
 	  msg (M_INFO, "peer info: %s", line);
 	  env_set_add(es, line);
 	}
       else
 	msg (M_WARN, "validation failed on peer_info line received from client");
     }
 }
 
 #endif /* P2MP_SERVER */