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
  */
 
 #include "syshead.h"
 
 #include "buffer.h"
 #include "misc.h"
 #include "tun.h"
 #include "error.h"
 #include "thread.h"
 #include "otime.h"
 #include "plugin.h"
 #include "options.h"
 #include "manage.h"
5f31881e
 #include "crypto.h"
 #include "route.h"
73b7e698
 #include "win32.h"
6fbf66fa
 
 #include "memdbg.h"
 
0aee9ca7
 #ifdef CONFIG_FEATURE_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 */
 
a8281352
 /* contains SM_x value defined in misc.h */
 int script_method = SM_EXECVE; /* GLOBAL */
 
6fbf66fa
 /* Redefine the top level directory of the filesystem
    to restrict access to files for security */
 void
 do_chroot (const char *path)
 {
   if (path)
     {
 #ifdef HAVE_CHROOT
       const char *top = "/";
       if (chroot (path))
 	msg (M_ERR, "chroot to '%s' failed", path);
       if (openvpn_chdir (top))
 	msg (M_ERR, "cd to '%s' failed", top);
       msg (M_INFO, "chroot to '%s' and cd to '%s' succeeded", path, top);
 #else
       msg (M_FATAL, "Sorry but I can't chroot to '%s' because this operating system doesn't appear to support the chroot() system call", path);
 #endif
     }
 }
 
 /* Get/Set UID of process */
 
 bool
 get_user (const char *username, struct user_state *state)
 {
   bool ret = false;
   CLEAR (*state);
   if (username)
     {
 #if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID)
       state->pw = getpwnam (username);
       if (!state->pw)
 	msg (M_ERR, "failed to find UID for user %s", username);
       state->username = username;
       ret = true;
 #else
bb564a59
       msg (M_FATAL, "cannot get UID for user %s -- platform lacks getpwname() or setuid() system calls", username);
6fbf66fa
 #endif
     }
   return ret;
 }
 
 void
 set_user (const struct user_state *state)
 {
 #if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID)
   if (state->username && state->pw)
     {
       if (setuid (state->pw->pw_uid))
 	msg (M_ERR, "setuid('%s') failed", state->username);
       msg (M_INFO, "UID set to %s", state->username);
     }
 #endif
 }
 
 /* Get/Set GID of process */
 
 bool
 get_group (const char *groupname, struct group_state *state)
 {
   bool ret = false;
   CLEAR (*state);
   if (groupname)
     {
 #if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID)
       state->gr = getgrnam (groupname);
       if (!state->gr)
 	msg (M_ERR, "failed to find GID for group %s", groupname);
       state->groupname = groupname;
       ret = true;
 #else
bb564a59
       msg (M_FATAL, "cannot get GID for group %s -- platform lacks getgrnam() or setgid() system calls", groupname);
6fbf66fa
 #endif
     }
   return ret;
 }
 
 void
 set_group (const struct group_state *state)
 {
 #if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID)
   if (state->groupname && state->gr)
     {
       if (setgid (state->gr->gr_gid))
 	msg (M_ERR, "setgid('%s') failed", state->groupname);
       msg (M_INFO, "GID set to %s", state->groupname);
 #ifdef HAVE_SETGROUPS
       {
         gid_t gr_list[1];
 	gr_list[0] = state->gr->gr_gid;
 	if (setgroups (1, gr_list))
 	  msg (M_ERR, "setgroups('%s') failed", state->groupname);
       }
 #endif
     }
 #endif
 }
 
 /* Change process priority */
 void
 set_nice (int niceval)
 {
   if (niceval)
     {
 #ifdef HAVE_NICE
       errno = 0;
       nice (niceval);
       if (errno != 0)
 	msg (M_WARN | M_ERRNO, "WARNING: nice %d failed", niceval);
       else
 	msg (M_INFO, "nice %d succeeded", niceval);
 #else
       msg (M_WARN, "WARNING: nice %d failed (function not implemented)", niceval);
 #endif
     }
 }
 
 /*
  * 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,
 	     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);
 
   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);
 
       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);
339f2a4d
       openvpn_run_script (&argv, es, S_FATAL, "--up/--down");
5a2e9a25
       argv_reset (&argv);
6fbf66fa
     }
 
   gc_free (&gc);
 }
 
 /* Get the file we will later write our process ID to */
 void
 get_pid_file (const char* filename, struct pid_state *state)
 {
   CLEAR (*state);
   if (filename)
     {
       state->fp = fopen (filename, "w");
       if (!state->fp)
 	msg (M_ERR, "Open error on pid file %s", filename);
       state->filename = filename;
     }
 }
 
 /* Write our PID to a file */
 void
 write_pid (const struct pid_state *state)
 {
   if (state->filename && state->fp)
     {
       unsigned int pid = openvpn_getpid (); 
       fprintf(state->fp, "%u\n", pid);
       if (fclose (state->fp))
 	msg (M_ERR, "Close error on pid file %s", state->filename);
     }
 }
 
 /* Get current PID */
 unsigned int
 openvpn_getpid ()
 {
 #ifdef WIN32
   return (unsigned int) GetCurrentProcessId ();
 #else
 #ifdef HAVE_GETPID
   return (unsigned int) getpid ();
 #else
   return 0;
 #endif
 #endif
 }
 
 /* Disable paging */
 void
 do_mlockall(bool print_msg)
 {
 #ifdef HAVE_MLOCKALL
   if (mlockall (MCL_CURRENT | MCL_FUTURE))
     msg (M_WARN | M_ERRNO, "WARNING: mlockall call failed");
   else if (print_msg)
     msg (M_INFO, "mlockall call succeeded");
 #else
   msg (M_WARN, "WARNING: mlockall call failed (function not implemented)");
 #endif
 }
 
 #ifndef HAVE_DAEMON
 
 int
 daemon(int nochdir, int noclose)
 {
 #if defined(HAVE_FORK) && defined(HAVE_SETSID)
   switch (fork())
     {
     case -1:
       return (-1);
     case 0:
       break;
     default:
       openvpn_exit (OPENVPN_EXIT_STATUS_GOOD); /* exit point */
     }
 
   if (setsid() == -1)
     return (-1);
 
   if (!nochdir)
     openvpn_chdir ("/");
 
   if (!noclose)
     set_std_files_to_null (false);
 #else
   msg (M_FATAL, "Sorry but I can't become a daemon because this operating system doesn't appear to support either the daemon() or fork() system calls");
 #endif
   return (0);
 }
 
 #endif
 
 /*
  * 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
 }
 
 /*
  * Wrapper for chdir library function
  */
 int
 openvpn_chdir (const char* dir)
 {
 #ifdef HAVE_CHDIR
   return chdir (dir);
 #else
   return -1;
 #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 ENABLE_INLINE_FILES
   if (strcmp (filename, INLINE_FILE_TAG))
 #endif
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
 }
 
 /*
  * convert system() return into a success/failure value
  */
 bool
 system_ok (int stat)
 {
 #ifdef WIN32
   return stat == 0;
 #else
   return stat != -1 && WIFEXITED (stat) && WEXITSTATUS (stat) == 0;
 #endif
 }
 
 /*
  * did system() call execute the given command?
  */
 bool
 system_executed (int stat)
 {
 #ifdef WIN32
   return stat != -1;
 #else
   return stat != -1 && WEXITSTATUS (stat) != 127;
 #endif
 }
 
 /*
  * 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;
 
   if (system_ok (stat))
     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;
 }
 
339f2a4d
 
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;
339f2a4d
   static bool warn_shown = false;
5a2e9a25
 
   if (a && a->argv[0])
     {
 #if defined(ENABLE_EXECVE)
       if (openvpn_execve_allowed (flags))
 	{
a8281352
 	  if (script_method == SM_EXECVE)
 	    {
 	      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;
5a2e9a25
 
a8281352
 	      pid = fork ();
 	      if (pid == (pid_t)0) /* child side */
 		{
 		  execve (cmd, argv, envp);
 		  exit (127);
 		}
 	      else if (pid < (pid_t)0) /* fork failed */
 		;
 	      else /* parent side */
 		{
 		  if (waitpid (pid, &ret, 0) != pid)
 		    ret = -1;
 		}
 	    }
 	  else if (script_method == SM_SYSTEM)
5a2e9a25
 	    {
a8281352
 	      ret = openvpn_system (argv_system_str (a), es, flags);
5a2e9a25
 	    }
a8281352
 	  else
5a2e9a25
 	    {
a8281352
 	      ASSERT (0);
5a2e9a25
 	    }
 	}
339f2a4d
       else if (!warn_shown && (script_security < SSEC_SCRIPTS))
5a2e9a25
 	{
51f7b848
 	  msg (M_WARN, SCRIPT_SECURITY_WARNING);
339f2a4d
           warn_shown = true;
5a2e9a25
 	}
 #else
       msg (M_WARN, "openvpn_execve: execve function not available");
 #endif
     }
   else
     {
       msg (M_WARN, "openvpn_execve: called with empty argv");
     }
 
   gc_free (&gc);
   return ret;
 }
 #endif
 
6fbf66fa
 /*
a8281352
  * Wrapper around the system() call.
  */
 int
 openvpn_system (const char *command, const struct env_set *es, unsigned int flags)
 {
 #ifdef HAVE_SYSTEM
   int ret;
 
   perf_push (PERF_SCRIPT);
 
   /*
    * add env_set to environment.
    */
   if (flags & S_SCRIPT)
     env_set_add_to_environment (es);
 
 
   /* debugging */
   dmsg (D_SCRIPT, "SYSTEM[%u] '%s'", flags, command);
   if (flags & S_SCRIPT)
     env_set_print (D_SCRIPT, es);
 
   /*
    * execute the command
    */
   ret = system (command);
 
   /* debugging */
   dmsg (D_SCRIPT, "SYSTEM return=%u", ret);
 
   /*
    * remove env_set from environment
    */
   if (flags & S_SCRIPT)
     env_set_remove_from_environment (es);
 
   perf_pop ();
   return ret;
 
 #else
   msg (M_FATAL, "Sorry but I can't execute the shell command '%s' because this operating system doesn't appear to support the system() call", command);
   return -1; /* NOTREACHED */
 #endif
 }
 
 /*
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)
 {
 #ifdef HAVE_GETTIMEOFDAY
   struct timeval tv;
 
   if (!gettimeofday (&tv, NULL))
     {
       const unsigned int seed = (unsigned int) tv.tv_sec ^ tv.tv_usec;
       srandom (seed);
     }
 #else /* HAVE_GETTIMEOFDAY */
   const time_t current = time (NULL);
   srandom ((unsigned int)current);
 #endif /* HAVE_GETTIMEOFDAY */
 }
 
 /* thread-safe strerror */
 
 const char *
 strerror_ts (int errnum, struct gc_arena *gc)
 {
 #ifdef HAVE_STRERROR
   struct buffer out = alloc_buf_gc (256, gc);
 
   mutex_lock_static (L_STRERR);
   buf_printf (&out, "%s", openvpn_strerror (errnum, gc));
   mutex_unlock_static (L_STRERR);
   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;
   mutex_lock_static (L_ENV_SET);
   ALLOC_OBJ_CLEAR_GC (es, struct env_set, gc);
   es->list = NULL;
   es->gc = gc;
   mutex_unlock_static (L_ENV_SET);
   return es;
 }
 
2a64816b
 void
 env_set_destroy (struct env_set *es)
 {
   mutex_lock_static (L_ENV_SET);
   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);
     }
   mutex_unlock_static (L_ENV_SET);
 }
 
6fbf66fa
 bool
 env_set_del (struct env_set *es, const char *str)
 {
   bool ret;
   ASSERT (es);
   ASSERT (str);
   mutex_lock_static (L_ENV_SET);
   ret = env_set_del_nolock (es, str);
   mutex_unlock_static (L_ENV_SET);
   return ret;
 }
 
 void
 env_set_add (struct env_set *es, const char *str)
 {
   ASSERT (es);
   ASSERT (str);
   mutex_lock_static (L_ENV_SET);
   env_set_add_nolock (es, str);
   mutex_unlock_static (L_ENV_SET);
 }
 
 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)
 	{
 	  mutex_lock_static (L_ENV_SET);
 	  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;
 	    }
 	  mutex_unlock_static (L_ENV_SET);
 	}
     }
 }
 
 void
 env_set_inherit (struct env_set *es, const struct env_set *src)
 {
   const struct env_item *e;
 
   ASSERT (es);
 
   if (src)
     {
       mutex_lock_static (L_ENV_SET);
       e = src->list;
       while (e)
 	{
 	  env_set_add_nolock (es, e->string);
 	  e = e->next;
 	}
       mutex_unlock_static (L_ENV_SET);
     }
 }
 
 void
 env_set_add_to_environment (const struct env_set *es)
 {
   if (es)
     {
       struct gc_arena gc = gc_new ();
       const struct env_item *e;
 
       mutex_lock_static (L_ENV_SET);
       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;
 	}
       mutex_unlock_static (L_ENV_SET);
       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;
 
       mutex_lock_static (L_ENV_SET);
       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;
 	}
       mutex_unlock_static (L_ENV_SET);
       gc_free (&gc);
     }
 }
 
 #ifdef HAVE_PUTENV
 
 /* companion functions to putenv */
 
 static struct env_item *global_env = NULL; /* GLOBAL */
 
 static void
 manage_env (char *str)
 {
   remove_env_item (str, true, &global_env);
   add_env_item (str, false, &global_env, NULL);
 }
 
 #endif
 
 /* 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);
 
   if (es)
     {
       if (val_tmp)
 	{
 	  const char *str = construct_name_value (name_tmp, val_tmp, &gc);
 	  env_set_add (es, str);
4e9a51d7
 	  /*msg (M_INFO, "SETENV_ES '%s'", str);*/
6fbf66fa
 	}
       else
 	env_set_del (es, name_tmp);
     }
   else
     {
 #if defined(WIN32)
       {
 	/*msg (M_INFO, "SetEnvironmentVariable '%s' '%s'", name_tmp, val_tmp ? val_tmp : "NULL");*/
 	if (!SetEnvironmentVariable (name_tmp, val_tmp))
 	  msg (M_WARN | M_ERRNO, "SetEnvironmentVariable failed, name='%s', value='%s'",
 	       name_tmp,
 	       val_tmp ? val_tmp : "NULL");
       }
 #elif defined(HAVE_PUTENV)
       {
 	char *str = construct_name_value (name_tmp, val_tmp, NULL);
 	int status;
 
 	mutex_lock_static (L_PUTENV);
 	status = putenv (str);
 	/*msg (M_INFO, "PUTENV '%s'", str);*/
 	if (!status)
 	  manage_env (str);
 	mutex_unlock_static (L_PUTENV);
 	if (status)
 	  msg (M_WARN | M_ERRNO, "putenv('%s') failed", str);
       }
 #endif
     }
 
   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
  * taken from busybox networking/ifupdown.c
  */
 unsigned int
 count_bits(unsigned int a)
 {
   unsigned int result;
   result = (a & 0x55) + ((a >> 1) & 0x55);
   result = (result & 0x33) + ((result >> 2) & 0x33);
   return((result & 0x0F) + ((result >> 4) & 0x0F));
 }
 
 int
 count_netmask_bits(const char *dotted_quad)
 {
   unsigned int result, a, b, c, d;
   /* Found a netmask...  Check if it is dotted quad */
   if (sscanf(dotted_quad, "%u.%u.%u.%u", &a, &b, &c, &d) != 4)
     return -1;
   result = count_bits(a);
   result += count_bits(b);
   result += count_bits(c);
   result += count_bits(d);
   return ((int)result);
 }
 
 /*
  * Go to sleep for n milliseconds.
  */
 void
 sleep_milliseconds (unsigned int n)
 {
 #ifdef WIN32
   Sleep (n);
 #else
   struct timeval tv;
   tv.tv_sec = n / 1000;
   tv.tv_usec = (n % 1000) * 1000;
   select (0, NULL, NULL, NULL, &tv);
 #endif
 }
 
 /*
  * Go to sleep indefinitely.
  */
 void
 sleep_until_signal (void)
 {
 #ifdef WIN32
   ASSERT (0);
 #else
   select (0, NULL, NULL, NULL, NULL);
 #endif
 }
 
 /* return true if filename can be opened for read */
 bool
 test_file (const char *filename)
 {
   bool ret = false;
   if (filename)
     {
       FILE *fp = fopen (filename, "r");
       if (fp)
 	{
 	  fclose (fp);
 	  ret = true;
 	}
     }
 
   dmsg (D_TEST_FILE, "TEST FILE '%s' [%d]",
        filename ? filename : "UNDEF",
        ret);
 
   return ret;
 }
 
222f0840
 #ifdef USE_CRYPTO
 
6fbf66fa
 /* create a temporary filename in directory */
 const char *
cb042f8e
 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);
cb042f8e
   int fd;
   const char *retfname = NULL;
   unsigned int attempts = 0;
6fbf66fa
 
cb042f8e
   do
     {
       uint8_t rndbytes[16];
       const char *rndstr;
 
       ++attempts;
       mutex_lock_static (L_CREATE_TEMP);
       ++counter;
       mutex_unlock_static (L_CREATE_TEMP);
 
       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.  */
       fd = open (retfname, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR);
       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
 
cb042f8e
   msg (M_FATAL, "Failed to create temporary file after %i attempts", attempts);
   return NULL;
6fbf66fa
 }
 
8e9666d5
 /*
  * Add a random string to first DNS label of hostname to prevent DNS caching.
  * For example, foo.bar.gov would be modified to <random-chars>.foo.bar.gov.
  * Of course, this requires explicit support in the DNS server.
  */
 const char *
 hostname_randomize(const char *hostname, struct gc_arena *gc)
 {
74fce85e
 # define n_rnd_bytes 6
8e9666d5
 
   char *hst = string_alloc(hostname, gc);
   char *dot = strchr(hst, '.');
 
   if (dot)
     {
       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);
 
       *dot++ = '\0';
       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-0x%s.%s", hst, rnd_str, dot);
       return BSTR(&hname);
     }
   else
     return hostname;
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)
 {
   const char *safe_filename = string_mod_const (filename, CC_ALNUM|CC_UNDERBAR|CC_DASH|CC_DOT|CC_AT, 0, '_', gc);
 
   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;
 }
 
 /* delete a file, return true if succeeded */
 bool
 delete_file (const char *filename)
 {
 #if defined(WIN32)
   return (DeleteFile (filename) != 0);
 #elif defined(HAVE_UNLINK)
   return (unlink (filename) == 0);
 #else
   return false;
 #endif
 }
 
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
 #ifdef HAVE_GETPASS
 
 static FILE *
 open_tty (const bool write)
 {
   FILE *ret;
   ret = fopen ("/dev/tty", write ? "w" : "r");
   if (!ret)
     ret = write ? stderr : stdin;
   return ret;
 }
 
 static void
 close_tty (FILE *fp)
 {
   if (fp != stderr && fp != stdin)
     fclose (fp);
 }
 
 #endif
 
 /*
  * Get input from console
  */
 bool
 get_console_input (const char *prompt, const bool echo, char *input, const int capacity)
 {
   bool ret = false;
   ASSERT (prompt);
   ASSERT (input);
   ASSERT (capacity > 0);
   input[0] = '\0';
 
 #if defined(WIN32)
   return get_console_input_win32 (prompt, echo, input, capacity);
 #elif defined(HAVE_GETPASS)
   if (echo)
     {
       FILE *fp;
 
       fp = open_tty (true);
       fprintf (fp, "%s", prompt);
       fflush (fp);
       close_tty (fp);
 
       fp = open_tty (false);
       if (fgets (input, capacity, fp) != NULL)
 	{
 	  chomp (input);
 	  ret = true;
 	}
       close_tty (fp);
     }
   else
     {
       char *gp = getpass (prompt);
       if (gp)
 	{
 	  strncpynt (input, gp, capacity);
 	  memset (gp, 0, strlen (gp));
 	  ret = true;
 	}
     }
 #else
   msg (M_FATAL, "Sorry, but I can't get console input on this OS");
 #endif
   return ret;
 }
 
 /*
  * Get and store a username/password
  */
 
1d89886e
 bool
6fbf66fa
 get_user_pass (struct user_pass *up,
 	       const char *auth_file,
 	       const char *prefix,
 	       const unsigned int flags)
 {
   struct gc_arena gc = gc_new ();
 
   if (!up->defined)
     {
       const bool from_stdin = (!auth_file || !strcmp (auth_file, "stdin"));
 
3cf6c932
       if (flags & GET_USER_PASS_PREVIOUS_CREDS_FAILED)
 	msg (M_WARN, "Note: previous '%s' credentials failed", prefix);
 
6fbf66fa
 #ifdef ENABLE_MANAGEMENT
       /*
        * Get username/password from standard input?
        */
       if (management
 	  && ((auth_file && streq (auth_file, "management")) || (from_stdin && (flags & GET_USER_PASS_MANAGEMENT)))
 	  && management_query_user_pass_enabled (management))
 	{
3cf6c932
 	  if (flags & GET_USER_PASS_PREVIOUS_CREDS_FAILED)
 	    management_auth_failure (management, prefix, "previous auth credentials failed");
 
984cf003
 	  if (!management_query_user_pass (management, up, prefix, flags))
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");
 	}
 	  
       /*
6fbf66fa
        * Get username/password from standard input?
        */
dd1047f5
       else if (from_stdin)
6fbf66fa
 	{
 	  struct buffer user_prompt = alloc_buf_gc (128, &gc);
 	  struct buffer pass_prompt = alloc_buf_gc (128, &gc);
 
dd1047f5
 	  buf_printf (&user_prompt, "Enter %s Username:", prefix);
 	  buf_printf (&pass_prompt, "Enter %s Password:", prefix);
 
 	  if (!(flags & GET_USER_PASS_PASSWORD_ONLY))
984cf003
 	    {
dd1047f5
 	      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);
6fbf66fa
 	    }
 
 	  if (!get_console_input (BSTR (&pass_prompt), false, up->password, USER_PASS_LEN))
dd1047f5
 	    msg (M_FATAL, "ERROR: could not not read %s password from stdin", prefix);
6fbf66fa
 	}
       else
 	{
 	  /*
 	   * Get username/password from a file.
 	   */
 	  FILE *fp;
       
 #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
 
 	  warn_if_group_others_accessible (auth_file);
 
 	  fp = fopen (auth_file, "r");
 	  if (!fp)
 	    msg (M_ERR, "Error opening '%s' auth file: %s", prefix, auth_file);
 
984cf003
 	  if (flags & GET_USER_PASS_PASSWORD_ONLY)
6fbf66fa
 	    {
 	      if (fgets (up->password, USER_PASS_LEN, fp) == NULL)
 		msg (M_FATAL, "Error reading password from %s authfile: %s",
 		     prefix,
 		     auth_file);
 	    }
 	  else
 	    {
 	      if (fgets (up->username, USER_PASS_LEN, fp) == NULL
 		  || fgets (up->password, USER_PASS_LEN, fp) == NULL)
 		msg (M_FATAL, "Error reading username and password (must be on two consecutive lines) from %s authfile: %s",
 		     prefix,
 		     auth_file);
 	    }
       
 	  fclose (fp);
       
 	  chomp (up->username);
 	  chomp (up->password);
       
984cf003
 	  if (!(flags & GET_USER_PASS_PASSWORD_ONLY) && strlen (up->username) == 0)
6fbf66fa
 	    msg (M_FATAL, "ERROR: username from %s authfile '%s' is empty", prefix, auth_file);
 	}
 
       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
 }
 
5f31881e
 #if AUTO_USERID
 
 static const char *
 get_platform_prefix (void)
 {
 #if defined(TARGET_LINUX)
   return "L";
 #elif defined(TARGET_SOLARIS)
   return "S";
 #elif defined(TARGET_OPENBSD)
   return "O";
 #elif defined(TARGET_DARWIN)
   return "M";
 #elif defined(TARGET_NETBSD)
   return "N";
 #elif defined(TARGET_FREEBSD)
   return "F";
 #elif defined(WIN32)
   return "W";
 #else
   return "X";
 #endif
 }
 
 void
70f4f82a
 get_user_pass_auto_userid (struct user_pass *up, const char *tag)
5f31881e
 {
   struct gc_arena gc = gc_new ();
   MD5_CTX ctx;
   struct buffer buf;
   uint8_t macaddr[6];
   static uint8_t digest [MD5_DIGEST_LENGTH];
   static const uint8_t hashprefix[] = "AUTO_USERID_DIGEST";
 
   CLEAR (*up);
   buf_set_write (&buf, (uint8_t*)up->username, USER_PASS_LEN);
   buf_printf (&buf, "%s", get_platform_prefix ());
   if (get_default_gateway_mac_addr (macaddr))
     {
       dmsg (D_AUTO_USERID, "GUPAU: macaddr=%s", format_hex_ex (macaddr, sizeof (macaddr), 0, 1, ":", &gc));
       MD5_Init (&ctx);
       MD5_Update (&ctx, hashprefix, sizeof (hashprefix) - 1);
       MD5_Update (&ctx, macaddr, sizeof (macaddr));
       MD5_Final (digest, &ctx);
       buf_printf (&buf, "%s", format_hex_ex (digest, sizeof (digest), 0, 256, " ", &gc));
     }
   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;
793e2baf
   static bool warn_shown = false;
6fbf66fa
   if (nocache || force)
     {
       CLEAR (*up);
       up->nocache = nocache;
     }
793e2baf
   else if (!warn_shown)
70899be8
     {
       msg (M_WARN, "WARNING: this configuration may cache passwords in memory -- use the auth-nocache option to prevent this");
793e2baf
       warn_shown = true;
70899be8
     }
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
 #if ENABLE_INLINE_FILES
 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;
 }
 #endif
 
 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 ENABLE_INLINE_FILES
   if (!strcmp (p[0], INLINE_FILE_TAG) && argc == 2)
     return make_inline_array (p[1], gc);
   else
 #endif
   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)
     {
       const char *bn = openvpn_basename (path);
       if (bn)
 	{
 	  char *ret = string_alloc (bn, NULL);
 	  char *dot = strrchr (ret, '.');
 	  if (dot)
 	    *dot = '\0';
 	  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));
   //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));
   //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));
b8fb090c
   //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
 
 const char *
 openvpn_basename (const char *path)
 {
   const char *ret;
   const int dirsep = OS_SPECIFIC_DIRSEP;
 
   if (path)
     {
       ret = strrchr (path, dirsep);
       if (ret && *ret)
 	++ret;
       else
 	ret = path;
       if (*ret)
 	return ret;
     }
   return NULL;
 }