diff options
author | Alberto Gonzalez Iniesta <agi@inittab.org> | 2012-11-05 16:28:10 +0100 |
---|---|---|
committer | Alberto Gonzalez Iniesta <agi@inittab.org> | 2012-11-05 16:28:10 +0100 |
commit | d213c4e5576e2fd601679e0d7b2fb1262b807111 (patch) | |
tree | 5f0cc82bd0f11fb13b385417604d04c751245a92 /src/openvpn/misc.c | |
parent | 79c8d3ef7a938f86472e549ef64e1fb820dc80c4 (diff) | |
parent | 8dd0350e1607aa30f7a043c8d5ec7a7eeb874115 (diff) |
Merge tag 'upstream/2.3_rc1'
Upstream version 2.3_rc1
Diffstat (limited to 'src/openvpn/misc.c')
-rw-r--r-- | src/openvpn/misc.c | 2089 |
1 files changed, 2089 insertions, 0 deletions
diff --git a/src/openvpn/misc.c b/src/openvpn/misc.c new file mode 100644 index 0000000..fcc8552 --- /dev/null +++ b/src/openvpn/misc.c @@ -0,0 +1,2089 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#elif defined(_MSC_VER) +#include "config-msvc.h" +#endif + +#include "syshead.h" + +#include "buffer.h" +#include "misc.h" +#include "base64.h" +#include "tun.h" +#include "error.h" +#include "otime.h" +#include "plugin.h" +#include "options.h" +#include "manage.h" +#include "crypto.h" +#include "route.h" +#include "console.h" +#include "win32.h" + +#include "memdbg.h" + +#ifdef ENABLE_IPROUTE +const char *iproute_path = IPROUTE_PATH; /* GLOBAL */ +#endif + +/* contains an SSEC_x value defined in misc.h */ +int script_security = SSEC_BUILT_IN; /* GLOBAL */ + +/* + * 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, + const char *dev_type, + 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 (dev_type) + setenv_str (es, "dev_type", dev_type); + + if (!ifconfig_local) + ifconfig_local = ""; + if (!ifconfig_remote) + ifconfig_remote = ""; + if (!context) + context = ""; + + if (plugin_defined (plugins, plugin_type)) + { + struct argv argv = argv_new (); + ASSERT (arg); + 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) + msg (M_FATAL, "ERROR: up/down plugin call failed"); + + argv_reset (&argv); + } + + if (command) + { + struct argv argv = argv_new (); + ASSERT (arg); + setenv_str (es, "script_type", script_type); + argv_printf (&argv, + "%sc %s %d %d %s %s %s", + command, + arg, + tun_mtu, link_mtu, + ifconfig_local, ifconfig_remote, + context); + argv_msg (M_INFO, &argv); + openvpn_run_script (&argv, es, S_FATAL, "--up/--down"); + argv_reset (&argv); + } + + 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 = platform_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 = platform_getpid (); + fprintf(state->fp, "%u\n", pid); + if (fclose (state->fp)) + msg (M_ERR, "Close error on pid file %s", state->filename); + } +} + +/* + * 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) +{ +#ifndef WIN32 +#ifdef HAVE_STAT + if (strcmp (filename, INLINE_FILE_TAG)) + { + 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); + } + } +#endif +#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) + buf_printf (&out, "external program did not execute -- "); + buf_printf (&out, "returned error code %d", stat); +#else + if (stat == -1) + buf_printf (&out, "external program fork failed"); + else if (!WIFEXITED (stat)) + buf_printf (&out, "external program did not exit normally"); + else + { + const int cmd_ret = WEXITSTATUS (stat); + if (!cmd_ret) + buf_printf (&out, "external program exited normally"); + else if (cmd_ret == 127) + buf_printf (&out, "could not execute external program"); + else + buf_printf (&out, "external program exited with error status: %d", cmd_ret); + } +#endif + return (const char *)out.data; +} + +/* + * Wrapper around openvpn_execve + */ +bool +openvpn_execve_check (const struct argv *a, const struct env_set *es, const unsigned int flags, const char *error_message) +{ + struct gc_arena gc = gc_new (); + const int stat = openvpn_execve (a, es, flags); + int ret = false; + + if (platform_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; +} + +bool +openvpn_execve_allowed (const unsigned int flags) +{ + if (flags & S_SCRIPT) + return script_security >= SSEC_SCRIPTS; + else + return script_security >= SSEC_BUILT_IN; +} + + +#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; + static bool warn_shown = false; + + if (a && a->argv[0]) + { +#if defined(ENABLE_FEATURE_EXECVE) + if (openvpn_execve_allowed (flags)) + { + 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; + } + } + else if (!warn_shown && (script_security < SSEC_SCRIPTS)) + { + msg (M_WARN, SCRIPT_SECURITY_WARNING); + warn_shown = true; + } +#else + msg (M_WARN, "openvpn_execve: execve function not available"); +#endif + } + else + { + msg (M_FATAL, "openvpn_execve: called with empty argv"); + } + + gc_free (&gc); + return ret; +} +#endif + +/* + * 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]) + { +#if defined(ENABLE_FEATURE_EXECVE) + 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 */ + { + close (pipe_stdout[0]); + dup2 (pipe_stdout[1],1); + execve (cmd, argv, envp); + exit (127); + } + else if (pid < (pid_t)0) /* fork failed */ + { + msg (M_ERR, "openvpn_popen: unable to fork"); + } + else /* parent side */ + { + ret=pipe_stdout[0]; + close (pipe_stdout[1]); + } + } + else { + msg (M_WARN, "openvpn_popen: unable to create stdout pipe"); + ret = -1; + } + } + 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; +} + + + +/* + * 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) +{ + return remove_env_item (str, es->gc == NULL, &es->list); +} + +static void +env_set_add_nolock (struct env_set *es, const char *str) +{ + remove_env_item (str, es->gc == NULL, &es->list); + 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; +} + +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); + } +} + +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) + { + if (env_safe_to_print (e->string)) + msg (msglevel, "ENV [%d] '%s'", i, e->string); + ++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); + } +} + +#ifdef HAVE_PUTENV + +/* companion functions to putenv */ + +static struct env_item *global_env = NULL; /* GLOBAL */ + +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 +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 +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 +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 +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 +setenv_str_safe (struct env_set *es, const char *name, const char *value) +{ + 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"); +} + +void +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); +#if DEBUG_VERBOSE_SETENV + msg (M_INFO, "SETENV_ES '%s'", str); +#endif + } + else + env_set_del (es, name_tmp); + } + else + { + char *str = construct_name_value (name_tmp, val_tmp, NULL); + if (platform_putenv(str)) + { + msg (M_WARN | M_ERRNO, "putenv('%s') failed", str); + } + } + + gc_free (&gc); +} + +/* + * 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); +} + +/* + * 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); +} + +/* return true if filename can be opened for read */ +bool +test_file (const char *filename) +{ + bool ret = false; + if (filename) + { + FILE *fp = platform_fopen (filename, "r"); + if (fp) + { + fclose (fp); + ret = true; + } + } + + dmsg (D_TEST_FILE, "TEST FILE '%s' [%d]", + filename ? filename : "UNDEF", + ret); + + return ret; +} + +#ifdef ENABLE_CRYPTO + +/* create a temporary filename in directory */ +const char * +create_temp_file (const char *directory, const char *prefix, struct gc_arena *gc) +{ + static unsigned int counter; + struct buffer fname = alloc_buf_gc (256, gc); + int fd; + const char *retfname = NULL; + unsigned int attempts = 0; + + 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. */ + fd = platform_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); + + msg (M_FATAL, "Failed to create temporary file after %i attempts", attempts); + return NULL; +} + +/* + * 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) +{ +# define n_rnd_bytes 6 + + 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; +# undef n_rnd_bytes +} + +#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; +} + +#endif + +/* + * Put a directory and filename together. + */ +const char * +gen_path (const char *directory, const char *filename, struct gc_arena *gc) +{ +#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); + + if (safe_filename + && strcmp (safe_filename, ".") + && strcmp (safe_filename, "..") +#ifdef WIN32 + && win_safe_filename (safe_filename) +#endif + ) + { + const size_t outsize = strlen(safe_filename) + (directory ? strlen (directory) : 0) + 16; + struct buffer out = alloc_buf_gc (outsize, gc); + 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; +} + +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; +} + +/* + * Get and store a username/password + */ + +bool +get_user_pass_cr (struct user_pass *up, + const char *auth_file, + const char *prefix, + const unsigned int flags, + const char *auth_challenge) +{ + struct gc_arena gc = gc_new (); + + if (!up->defined) + { + const bool from_stdin = (!auth_file || !strcmp (auth_file, "stdin")); + + if (flags & GET_USER_PASS_PREVIOUS_CREDS_FAILED) + msg (M_WARN, "Note: previous '%s' credentials failed", prefix); + +#ifdef ENABLE_MANAGEMENT + /* + * Get username/password from management interface? + */ + if (management + && ((auth_file && streq (auth_file, "management")) || (from_stdin && (flags & GET_USER_PASS_MANAGEMENT))) + && management_query_user_pass_enabled (management)) + { + const char *sc = NULL; + + if (flags & GET_USER_PASS_PREVIOUS_CREDS_FAILED) + management_auth_failure (management, prefix, "previous auth credentials failed"); + +#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)) + { + if ((flags & GET_USER_PASS_NOFATAL) != 0) + return false; + else + msg (M_FATAL, "ERROR: could not read %s username/password/ok/string from management interface", prefix); + } + } + else +#endif + /* + * 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"); + } + + /* + * Get username/password from standard input? + */ + else if (from_stdin) + { +#ifdef ENABLE_CLIENT_CR + if (auth_challenge && (flags & GET_USER_PASS_DYNAMIC_CHALLENGE)) + { + 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); + msg (M_INFO|M_NOPREFIX, "CHALLENGE: %s", ac->challenge_text); + 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"); + } + } + else +#endif + { + struct buffer user_prompt = alloc_buf_gc (128, &gc); + struct buffer pass_prompt = alloc_buf_gc (128, &gc); + + buf_printf (&user_prompt, "Enter %s Username:", prefix); + buf_printf (&pass_prompt, "Enter %s Password:", prefix); + + if (!(flags & GET_USER_PASS_PASSWORD_ONLY)) + { + 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); + } + + if (!get_console_input (BSTR (&pass_prompt), false, up->password, USER_PASS_LEN)) + msg (M_FATAL, "ERROR: could not not read %s password from stdin", prefix); + +#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"); + if (openvpn_base64_encode(up->password, strlen(up->password), &pw64) == -1 + || openvpn_base64_encode(response, strlen(response), &resp64) == -1) + 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 + } + } + 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 = 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) + { + 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); + + 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); + } + + 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); + + return true; +} + +#ifdef ENABLE_CLIENT_CR + +/* + * See management/management-notes.txt for more info on the + * the dynamic challenge/response protocol implemented here. + */ +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); + openvpn_base64_decode(work, (void*)ac->user, -1); + + /* parse challenge text */ + ac->challenge_text = string_alloc(BSTR(&b), gc); + + return ac; + } + else + return NULL; +} + +#endif + +#if AUTO_USERID + +void +get_user_pass_auto_userid (struct user_pass *up, const char *tag) +{ + 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"; + + const md_kt_t *md5_kt = md_kt_get("MD5"); + md_ctx_t ctx; + + CLEAR (*up); + buf_set_write (&buf, (uint8_t*)up->username, USER_PASS_LEN); + buf_printf (&buf, "%s", TARGET_PREFIX); + if (get_default_gateway_mac_addr (macaddr)) + { + dmsg (D_AUTO_USERID, "GUPAU: macaddr=%s", format_hex_ex (macaddr, sizeof (macaddr), 0, 1, ":", &gc)); + 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)); + } + else + { + buf_printf (&buf, "UNKNOWN"); + } + if (tag && strcmp (tag, "stdin")) + buf_printf (&buf, "-%s", tag); + up->defined = true; + gc_free (&gc); + + dmsg (D_AUTO_USERID, "GUPAU: AUTO_USERID: '%s'", up->username); +} + +#endif + +void +purge_user_pass (struct user_pass *up, const bool force) +{ + const bool nocache = up->nocache; + static bool warn_shown = false; + if (nocache || force) + { + CLEAR (*up); + up->nocache = nocache; + } + else if (!warn_shown) + { + msg (M_WARN, "WARNING: this configuration may cache passwords in memory -- use the auth-nocache option to prevent this"); + warn_shown = true; + } +} + +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); + } +} + +/* + * 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); +} + +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)); +} + +bool +env_safe_to_print (const char *str) +{ +#ifndef UNSAFE_DEBUG + if (is_password_env_var (str)) + return false; +#endif + return true; +} + +/* Make arrays of strings */ + +const char ** +make_env_array (const struct env_set *es, + const bool check_allowed, + struct gc_arena *gc) +{ + 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) + { + i = 0; + for (e = es->list; e != NULL; e = e->next) + { + if (!check_allowed || env_allowed (e->string)) + { + ASSERT (i < n); + ret[i++] = e->string; + } + } + } + + 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; +} + +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; + + buf_set_read (&buf, (const uint8_t *) str, strlen (str)); + while (buf_parse (&buf, '\n', line, sizeof (line))) + ++len; + + /* alloc return array */ + ALLOC_ARRAY_CLEAR_GC (ret, char *, len + 1, gc); + + buf_set_read (&buf, (const uint8_t *) str, strlen(str)); + 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); +} + +void +openvpn_sleep (const int n) +{ +#ifdef ENABLE_MANAGEMENT + if (management) + { + management_event_loop_n_seconds (management, n); + return; + } +#endif + sleep (n); +} + +/* + * Return the next largest power of 2 + * or u if u is a power of 2. + */ +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. + */ + +void +argv_init (struct argv *a) +{ + a->capacity = 0; + a->argc = 0; + a->argv = NULL; + a->system_str = NULL; +} + +struct argv +argv_new (void) +{ + struct argv ret; + argv_init (&ret); + return ret; +} + +void +argv_reset (struct argv *a) +{ + size_t i; + for (i = 0; i < a->argc; ++i) + free (a->argv[i]); + free (a->argv); + free (a->system_str); + argv_init (a); +} + +static void +argv_extend (struct argv *a, const size_t newcap) +{ + if (newcap > a->capacity) + { + char **newargv; + size_t i; + ALLOC_ARRAY_CLEAR (newargv, char *, newcap); + for (i = 0; i < a->argc; ++i) + newargv[i] = a->argv[i]; + free (a->argv); + a->argv = newargv; + a->capacity = newcap; + } +} + +static void +argv_grow (struct argv *a, const size_t add) +{ + const size_t newargc = a->argc + add + 1; + ASSERT (newargc > a->argc); + argv_extend (a, adjust_power_of_2 (newargc)); +} + +static void +argv_append (struct argv *a, char *str) /* str must have been malloced or be NULL */ +{ + argv_grow (a, 1); + a->argv[a->argc++] = str; +} + +static 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) + { + char *path_cp = strdup(path); /* POSIX basename() implementaions may modify its arguments */ + const char *bn = basename (path_cp); + if (bn) + { + char *ret = string_alloc (bn, NULL); + char *dot = strrchr (ret, '.'); + if (dot) + *dot = '\0'; + free(path_cp); + if (ret[0] != '\0') + return ret; + } + } + return NULL; +} + +const char * +argv_system_str (const struct argv *a) +{ + return a->system_str; +} + +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)); + r.system_str = string_alloc (a->system_str, NULL); + } + return r; +} + +struct argv +argv_insert_head (const struct argv *a, const char *head) +{ + struct argv r; + char *s; + + r = argv_clone (a, 1); + r.argv[0] = string_alloc (head, NULL); + s = r.system_str; + r.system_str = string_alloc (head, NULL); + if (s) + { + argv_system_str_append (&r, s, false); + free (s); + } + 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) + { + if (!isspace (c)) + ++termlen; + else + break; + } + else + { + if (!isspace (c)) + { + term = p; + termlen = 1; + } + } + ++p; + } + *f = p; + + if (term) + { + char *ret; + ASSERT (termlen > 0); + ret = malloc (termlen + 1); + check_malloc_return (ret); + memcpy (ret, term, termlen); + ret[termlen] = '\0'; + return ret; + } + else + return NULL; +} + +const char * +argv_str (const struct argv *a, struct gc_arena *gc, const unsigned int flags) +{ + if (a->argv) + return print_argv ((const char **)a->argv, gc, flags); + else + return ""; +} + +void +argv_msg (const int msglev, const struct argv *a) +{ + struct gc_arena gc = gc_new (); + msg (msglev, "%s", argv_str (a, &gc, 0)); + gc_free (&gc); +} + +void +argv_msg_prefix (const int msglev, const struct argv *a, const char *prefix) +{ + struct gc_arena gc = gc_new (); + msg (msglev, "%s: %s", prefix, argv_str (a, &gc, 0)); + gc_free (&gc); +} + +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)); + argv_system_str_append (a, s, true); + } + else if (!strcmp (term, "%sc")) + { + char *s = va_arg (arglist, char *); + if (s) + { + int nparms; + char *parms[MAX_PARMS+1]; + int i; + + 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); + } + else + { + argv_append (a, string_alloc ("", NULL)); + argv_system_str_append (a, "echo", false); + } + } + 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)); + argv_system_str_append (a, numstr, false); + } + 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)); + argv_system_str_append (a, numstr, false); + } + 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); + argv_system_str_append (a, combined, false); + } + } + else if (!strcmp (term, "%s%sc")) + { + char *s1 = va_arg (arglist, char *); + char *s2 = va_arg (arglist, char *); + char *combined; + char *cmd_name; + + 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); + + cmd_name = argv_extract_cmd_name (combined); + if (cmd_name) + { + argv_system_str_append (a, cmd_name, false); + free (cmd_name); + } + } + else + ASSERT (0); + free (term); + } + else + { + argv_append (a, term); + argv_system_str_append (a, term, false); + } + } + gc_free (&gc); +} + +#ifdef ARGV_TEST +void +argv_test (void) +{ + struct gc_arena gc = gc_new (); + const char *s; + + struct argv a; + + argv_init (&a); + 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");*/ + + 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");*/ + + argv_printf (&a, "%s%sc foo bar %s %s/%d %d %u", "/foo", "/bar.exe", "one two", "1.2.3.4", 24, -69, 96); + 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");*/ + + argv_printf (&a, "this is a %s test of int %d unsigned %u", "FOO", -69, 42); + s = argv_str (&a, &gc, PA_BRACKET); + printf ("PF: %s\n", s); + printf ("PF-S: %s\n", argv_system_str(&a)); + + { + struct argv b = argv_insert_head (&a, "MARK"); + s = argv_str (&b, &gc, PA_BRACKET); + printf ("PF: %s\n", s); + printf ("PF-S: %s\n", argv_system_str(&b)); + argv_reset (&b); + } + + argv_printf (&a, "%sc foo bar %d", "\"multi term\" command following \\\"spaces", 99); + s = argv_str (&a, &gc, PA_BRACKET); + printf ("PF: %s\n", s); + printf ("PF-S: %s\n", argv_system_str(&a)); + argv_reset (&a); + + s = argv_str (&a, &gc, PA_BRACKET); + printf ("PF: %s\n", s); + printf ("PF-S: %s\n", argv_system_str(&a)); + argv_reset (&a); + + argv_printf (&a, "foo bar %d", 99); + argv_printf_cat (&a, "bar %d foo %sc", 42, "nonesuch"); + argv_printf_cat (&a, "cool %s %d u %s/%d end", "frood", 4, "hello", 7); + s = argv_str (&a, &gc, PA_BRACKET); + printf ("PF: %s\n", s); + printf ("PF-S: %s\n", argv_system_str(&a)); + argv_reset (&a); + +#if 0 + { + 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); + } + } + } +#endif + + argv_reset (&a); + gc_free (&gc); +} +#endif + +/* + * Remove security-sensitive strings from control message + * so that they will not be output to log file. + */ +const char * +sanitize_control_message(const char *src, struct gc_arena *gc) +{ + char *ret = gc_malloc (strlen(src)+1, false, gc); + char *dest = ret; + bool redact = false; + int skip = 0; + + for (;;) + { + const char c = *src; + if (c == '\0') + break; + if (c == 'S' && !strncmp(src, "SESS_ID_", 8)) + { + skip = 7; + redact = true; + } + else if (c == 'e' && !strncmp(src, "echo ", 5)) + { + skip = 4; + redact = true; + } + + if (c == ',') /* end of redacted item? */ + { + skip = 0; + redact = false; + } + + if (redact) + { + if (skip > 0) + { + --skip; + *dest++ = c; + } + } + else + *dest++ = c; + + ++src; + } + *dest = '\0'; + return ret; +} + +/** + * 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)); + +} |