From 5a2e9a2587372aeb4b74fa1aadf53283ed7cae10 Mon Sep 17 00:00:00 2001 From: james Date: Sat, 26 Jul 2008 07:27:03 +0000 Subject: Completely revamped the system for calling external programs and scripts: * All external programs and scripts are now called by execve() on unix and CreateProcess on Windows. * The system() function is no longer used. * Argument lists for external programs and scripts are now built by the new argv_printf function which natively outputs to string arrays (i.e. char *argv[] lists), never truncates its output, and eliminates the security issues inherent in formatting and parsing command lines, and dealing with argument quoting. * The --script-security directive has been added to offer policy controls on OpenVPN's execution of external programs and scripts. Also added a new plugin example (openvpn/plugin/examples/log.c) that logs information to stdout for every plugin method called by OpenVPN. git-svn-id: http://svn.openvpn.net/projects/openvpn/branches/BETA21/openvpn@3122 e7ae566f-a301-0410-adde-c780ea21d3b5 --- buffer.c | 132 ++++++++++++++--------- buffer.h | 4 + configure.ac | 3 +- debug/valgrind-suppress | 18 ++++ init.c | 7 +- lladdr.c | 34 +++--- misc.c | 275 ++++++++++++++++++++++++++++++++---------------- misc.h | 26 +++-- multi.c | 65 ++++++------ openvpn.8 | 36 +++++++ openvpn.c | 4 + options.c | 19 ++++ plugin.c | 19 ++-- plugin.h | 4 +- plugin/examples/log.c | 184 ++++++++++++++++++++++++++++++++ route.c | 159 ++++++++++++++++------------ socket.c | 34 ++++-- socket.h | 1 + ssl.c | 40 ++++--- syshead.h | 8 ++ tun.c | 254 +++++++++++++++++++++++++------------------- win32.c | 177 +++++++++++++++++++++++++++++++ win32.h | 15 +++ 23 files changed, 1098 insertions(+), 420 deletions(-) create mode 100644 plugin/examples/log.c diff --git a/buffer.c b/buffer.c index c7eab07..b62d4b8 100644 --- a/buffer.c +++ b/buffer.c @@ -240,6 +240,14 @@ argv_init (struct argv *a) a->argv = NULL; } +struct argv +argv_new (void) +{ + struct argv ret; + argv_init (&ret); + return ret; +} + void argv_reset (struct argv *a) { @@ -266,6 +274,23 @@ argv_argc (const char *format) return argc; } +struct argv +argv_insert_head (const struct argv *a, const char *head) +{ + struct argv r; + size_t i; + + r.argc = (a ? a->argc : 0) + 1; + ALLOC_ARRAY_CLEAR (r.argv, char *, r.argc + 1); + r.argv[0] = string_alloc (head, NULL); + if (a) + { + for (i = 0; i < a->argc; ++i) + r.argv[i+1] = string_alloc (a->argv[i], NULL); + } + return r; +} + char * argv_term (const char **f) { @@ -323,6 +348,22 @@ argv_str (const struct argv *a, struct gc_arena *gc, const unsigned int flags) 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, ...) { @@ -373,7 +414,10 @@ argv_printf_arglist (struct argv *a, const char *format, const unsigned int flag { if (!strcmp (term, "%s")) { - a->argv[argc++] = string_alloc (va_arg (arglist, char *), NULL); + char *s = va_arg (arglist, char *); + if (!s) + s = ""; + a->argv[argc++] = string_alloc (s, NULL); } else if (!strcmp (term, "%d")) { @@ -387,6 +431,41 @@ argv_printf_arglist (struct argv *a, const char *format, const unsigned int flag openvpn_snprintf (numstr, sizeof (numstr), "%u", va_arg (arglist, unsigned int)); a->argv[argc++] = string_alloc (numstr, NULL); } + else if (!strcmp (term, "%s/%d")) + { + char numstr[64]; + char *s = va_arg (arglist, char *); + + if (!s) + s = ""; + + openvpn_snprintf (numstr, sizeof (numstr), "%d", va_arg (arglist, int)); + + { + const size_t len = strlen(s) + strlen(numstr) + 2; + char *combined = (char *) malloc (len); + check_malloc_return (combined); + + strcpy (combined, s); + strcat (combined, "/"); + strcat (combined, numstr); + a->argv[argc++] = combined; + } + } + else if (!strcmp (term, "%s%s")) + { + char *s1 = va_arg (arglist, char *); + char *s2 = va_arg (arglist, char *); + char *combined; + + if (!s1) s1 = ""; + if (!s2) s2 = ""; + combined = (char *) malloc (strlen(s1) + strlen(s2) + 1); + check_malloc_return (combined); + strcpy (combined, s1); + strcat (combined, s2); + a->argv[argc++] = combined; + } else ASSERT (0); free (term); @@ -399,57 +478,6 @@ argv_printf_arglist (struct argv *a, const char *format, const unsigned int flag ASSERT (argc == a->argc); } -#ifdef ARGV_TEST -void -argv_test (void) -{ - struct gc_arena gc = gc_new (); - char line[512]; - const char *s; - - struct argv a; - argv_init (&a); - argv_printf (&a, "this is a %s test of int %d unsigned %u", "FOO", -69, 42); - s = argv_str (&a, &gc, PA_BRACKET); - argv_reset (&a); - printf ("%s\n", s); - - argv_init (&a); - argv_printf (&a, "foo bar %d", 99); - s = argv_str (&a, &gc, PA_BRACKET); - argv_reset (&a); - printf ("%s\n", s); - - argv_init (&a); - s = argv_str (&a, &gc, PA_BRACKET); - argv_reset (&a); - printf ("%s\n", s); - - argv_init (&a); - argv_printf (&a, "foo bar %d", 99); - argv_printf_cat (&a, "bar %d foo", 42); - argv_printf_cat (&a, "cool %s %d u", "frood", 4); - s = argv_str (&a, &gc, PA_BRACKET); - argv_reset (&a); - printf ("%s\n", s); - - 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); - } - } - gc_free (&gc); -} -#endif - /* * write a string to the end of a buffer that was * truncated by buf_printf diff --git a/buffer.h b/buffer.h index 3190229..577041c 100644 --- a/buffer.h +++ b/buffer.h @@ -234,10 +234,14 @@ int openvpn_snprintf(char *str, size_t size, const char *format, ...) * to execve. */ void argv_init (struct argv *a); +struct argv argv_new (void); void argv_reset (struct argv *a); size_t argv_argc (const char *format); char *argv_term (const char **f); const char *argv_str (const struct argv *a, struct gc_arena *gc, const unsigned int flags); +struct argv argv_insert_head (const struct argv *a, const char *head); +void argv_msg (const int msglev, const struct argv *a); +void argv_msg_prefix (const int msglev, const struct argv *a, const char *prefix); #define APA_CAT (1<<0) /* concatentate onto existing struct argv list */ void argv_printf_arglist (struct argv *a, const char *format, const unsigned int flags, va_list arglist); diff --git a/configure.ac b/configure.ac index 4da3e85..da53407 100644 --- a/configure.ac +++ b/configure.ac @@ -462,7 +462,8 @@ AC_CHECK_FUNCS(daemon chroot getpwnam setuid nice system getpid dup dup2 dnl getpass strerror syslog openlog mlockall getgrnam setgid dnl setgroups stat flock readv writev setsockopt getsockopt dnl setsid chdir putenv getpeername unlink dnl - poll chsize ftruncate sendmsg recvmsg getsockname) + poll chsize ftruncate sendmsg recvmsg getsockname dnl + execve) AC_CACHE_SAVE if test "${WIN32}" = "yes"; then diff --git a/debug/valgrind-suppress b/debug/valgrind-suppress index a94c61a..c62e569 100644 --- a/debug/valgrind-suppress +++ b/debug/valgrind-suppress @@ -392,6 +392,24 @@ fun:main } +{ + + Memcheck:Cond + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libc-2.5.so + obj:/lib/libdl-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libdl-2.5.so + fun:dlsym + fun:libdl_resolve_symbol + fun:plugin_list_init + fun:init_plugins + fun:main +} + { Memcheck:Cond diff --git a/init.c b/init.c index ebdc9f3..6f762c9 100644 --- a/init.c +++ b/init.c @@ -370,7 +370,7 @@ init_port_share (struct context *c) bool init_static (void) { - configure_path (); + /* configure_path (); */ #if defined(USE_CRYPTO) && defined(DMALLOC) openssl_dmalloc_init (); @@ -921,8 +921,11 @@ do_route (const struct options *options, if (options->route_script) { + struct argv argv = argv_new (); setenv_str (es, "script_type", "route-up"); - system_check (options->route_script, es, S_SCRIPT, "Route script failed"); + argv_printf (&argv, "%s", options->route_script); + openvpn_execve_check (&argv, es, S_SCRIPT, "Route script failed"); + argv_reset (&argv); } #ifdef WIN32 diff --git a/lladdr.c b/lladdr.c index ad28dcf..7aefdba 100644 --- a/lladdr.c +++ b/lladdr.c @@ -9,7 +9,7 @@ int set_lladdr(const char *ifname, const char *lladdr, const struct env_set *es) { - char cmd[256]; + struct argv argv = argv_new (); int r; if (!ifname || !lladdr) @@ -17,37 +17,45 @@ int set_lladdr(const char *ifname, const char *lladdr, #if defined(TARGET_LINUX) #ifdef CONFIG_FEATURE_IPROUTE - openvpn_snprintf (cmd, sizeof (cmd), + argv_printf (&argv, "%s link set addr %s dev %s", iproute_path, lladdr, ifname); #else - openvpn_snprintf (cmd, sizeof (cmd), - IFCONFIG_PATH " %s hw ether %s", + argv_printf (&argv, + "%s %s hw ether %s", + IFCONFIG_PATH, ifname, lladdr); #endif #elif defined(TARGET_SOLARIS) - openvpn_snprintf (cmd, sizeof (cmd), - IFCONFIG_PATH " %s ether %s", + argv_printf (&argv, + "%s %s ether %s", + IFCONFIG_PATH, ifname, lladdr); #elif defined(TARGET_OPENBSD) - openvpn_snprintf (cmd, sizeof (cmd), - IFCONFIG_PATH " %s lladdr %s", + argv_printf (&argv, + "%s %s lladdr %s", + IFCONFIG_PATH, ifname, lladdr); #elif defined(TARGET_DARWIN) - openvpn_snprintf (cmd, sizeof (cmd), - IFCONFIG_PATH " %s lladdr %s", + argv_printf (&argv, + "%s %s lladdr %s", + IFCONFIG_PATH, ifname, lladdr); #elif defined(TARGET_FREEBSD) - openvpn_snprintf (cmd, sizeof (cmd), - IFCONFIG_PATH " %s ether %s", + argv_printf (&argv, + "%s %s ether %s", + IFCONFIG_PATH, ifname, lladdr); #else msg (M_WARN, "Sorry, but I don't know how to configure link layer addresses on this operating system."); return -1; #endif - r = system_check (cmd, es, M_WARN, "ERROR: Unable to set link layer address."); + argv_msg (M_INFO, &argv); + r = openvpn_execve_check (&argv, es, M_WARN, "ERROR: Unable to set link layer address."); if (r) msg (M_INFO, "TUN/TAP link layer address set to %s", lladdr); + + argv_reset (&argv); return r; } diff --git a/misc.c b/misc.c index 744cfc9..5b7cf3e 100644 --- a/misc.c +++ b/misc.c @@ -43,6 +43,9 @@ const char *iproute_path = IPROUTE_PATH; #endif +/* contains an SSEC_x value defined in misc.h */ +int script_security = SSEC_BUILT_IN; /* GLOBAL */ + /* Redefine the top level directory of the filesystem to restrict access to files for security */ void @@ -196,38 +199,36 @@ run_up_down (const char *command, if (plugin_defined (plugins, plugin_type)) { - struct buffer cmd = alloc_buf_gc (256, &gc); - + struct argv argv = argv_new (); ASSERT (arg); - - buf_printf (&cmd, - "\"%s\" %d %d %s %s %s", - arg, - tun_mtu, link_mtu, - ifconfig_local, ifconfig_remote, - context); - - if (plugin_call (plugins, plugin_type, BSTR (&cmd), NULL, es) != OPENVPN_PLUGIN_FUNC_SUCCESS) + 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 buffer cmd = alloc_buf_gc (256, &gc); - + struct argv argv = argv_new (); ASSERT (arg); - setenv_str (es, "script_type", script_type); - - buf_printf (&cmd, - "%s \"%s\" %d %d %s %s %s", + argv_printf (&argv, + "%s %s %d %d %s %s %s", command, arg, tun_mtu, link_mtu, ifconfig_local, ifconfig_remote, context); - msg (M_INFO, "%s", BSTR (&cmd)); - system_check (BSTR (&cmd), es, S_SCRIPT|S_FATAL, "script failed"); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, es, S_SCRIPT|S_FATAL, "script failed"); + argv_reset (&argv); } gc_free (&gc); @@ -374,59 +375,6 @@ save_inetd_socket_descriptor (void) #endif } -/* - * Wrapper around the system() call. - */ -int -openvpn_system (const char *command, const struct env_set *es, unsigned int flags) -{ -#ifdef HAVE_SYSTEM - int ret; - - /* - * We need to bracket this code by mutex because system() doesn't - * accept an environment list, so we have to use the process-wide - * list which is shared between all threads. - */ - mutex_lock_static (L_SYSTEM); - 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 (); - mutex_unlock_static (L_SYSTEM); - 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 -} - /* * Warn if a given file is group/others accessible. */ @@ -489,35 +437,35 @@ 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, "shell command did not execute -- "); - buf_printf (&out, "system() returned error code %d", stat); + buf_printf (&out, "external program did not execute -- "); + buf_printf (&out, "returned error code %d", stat); #else if (stat == -1) - buf_printf (&out, "shell command fork failed"); + buf_printf (&out, "external program fork failed"); else if (!WIFEXITED (stat)) - buf_printf (&out, "shell command did not exit normally"); + buf_printf (&out, "external program did not exit normally"); else { const int cmd_ret = WEXITSTATUS (stat); if (!cmd_ret) - buf_printf (&out, "shell command exited normally"); + buf_printf (&out, "external program exited normally"); else if (cmd_ret == 127) - buf_printf (&out, "could not execute shell command"); + buf_printf (&out, "could not execute external program"); else - buf_printf (&out, "shell command exited with error status: %d", cmd_ret); + buf_printf (&out, "external program exited with error status: %d", cmd_ret); } #endif return (const char *)out.data; } /* - * Run system(), exiting on error. + * Wrapper around openvpn_execve */ bool -system_check (const char *command, const struct env_set *es, unsigned int flags, const char *error_message) +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_system (command, es, flags); + const int stat = openvpn_execve (a, es, flags); int ret = false; if (system_ok (stat)) @@ -533,6 +481,69 @@ system_check (const char *command, const struct env_set *es, unsigned int flags, 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; + + if (a && a->argv[0]) + { +#if defined(ENABLE_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 */ + ; + else /* parent side */ + { + if (waitpid (pid, &ret, 0) != pid) + ret = -1; + } + } + else + { + msg (M_WARN, "openvpn_execve: external program may not be called due to setting of --script-security level"); + } +#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 + /* * Initialize random number seed. random() is only used * when "weak" random numbers are acceptable. @@ -1479,11 +1490,23 @@ 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 (strncmp (str, "password", 8) == 0) + if (is_password_env_var (str)) return false; #endif return true; @@ -1492,7 +1515,9 @@ env_safe_to_print (const char *str) /* Make arrays of strings */ const char ** -make_env_array (const struct env_set *es, struct gc_arena *gc) +make_env_array (const struct env_set *es, + const bool check_allowed, + struct gc_arena *gc) { char **ret = NULL; struct env_item *e = NULL; @@ -1511,12 +1536,14 @@ make_env_array (const struct env_set *es, struct gc_arena *gc) /* fill return array */ if (es) { - e = es->list; - for (i = 0; i < n; ++i) + i = 0; + for (e = es->list; e != NULL; e = e->next) { - ASSERT (e); - ret[i] = e->string; - e = e->next; + if (!check_allowed || env_allowed (e->string)) + { + ASSERT (i < n); + ret[i++] = e->string; + } } } @@ -1631,6 +1658,7 @@ openvpn_sleep (const int n) sleep (n); } +#if 0 /* * Configure PATH. On Windows, sometimes PATH is not set correctly * by default. @@ -1672,3 +1700,72 @@ configure_path (void) } #endif } +#endif + +#ifdef ARGV_TEST +void +argv_test (void) +{ + struct gc_arena gc = gc_new (); + char line[512]; + const char *s; + + struct argv a; + argv_init (&a); + +#ifdef WIN32 + argv_printf (&a, "%s foo bar %s", "c:\\src\\test\\jyargs.exe", "foo bar"); + //argv_printf (&a, "%s %s %s", "c:\\src\\test files\\batargs.bat", "foo", "bar"); +#else + argv_printf (&a, "./myechox foo bar"); +#endif + + argv_msg_prefix (M_INFO, &a, "ARGV"); + 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 ("%s\n", s); + + { + struct argv b = argv_insert_head (&a, "MARK"); + s = argv_str (&b, &gc, PA_BRACKET); + argv_reset (&b); + printf ("%s\n", s); + } + + argv_printf (&a, "foo bar %d", 99); + s = argv_str (&a, &gc, PA_BRACKET); + argv_reset (&a); + printf ("%s\n", s); + + s = argv_str (&a, &gc, PA_BRACKET); + argv_reset (&a); + printf ("%s\n", s); + + argv_printf (&a, "foo bar %d", 99); + argv_printf_cat (&a, "bar %d foo", 42); + argv_printf_cat (&a, "cool %s %d u %s/%d end", "frood", 4, "hello", 7); + s = argv_str (&a, &gc, PA_BRACKET); + printf ("%s\n", s); + +#if 0 + 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 diff --git a/misc.h b/misc.h index 5343c02..87cdb31 100644 --- a/misc.h +++ b/misc.h @@ -117,17 +117,15 @@ void warn_if_group_others_accessible(const char* filename); #define S_SCRIPT (1<<0) #define S_FATAL (1<<1) -/* wrapper around the system() call. */ -int openvpn_system (const char *command, const struct env_set *es, unsigned int flags); - -/* interpret the status code returned by system() */ +/* interpret the status code returned by system()/execve() */ bool system_ok(int); int system_executed (int stat); const char *system_error_message (int, struct gc_arena *gc); -/* run system() with error check, return true if success, - false if error, exit if error and fatal==true */ -bool system_check (const char *command, const struct env_set *es, unsigned int flags, const char *error_message); +/* wrapper around the execve() call */ +int openvpn_execve (const struct argv *a, const struct env_set *es, const unsigned int flags); +bool openvpn_execve_check (const struct argv *a, const struct env_set *es, const unsigned int flags, const char *error_message); +bool openvpn_execve_allowed (const unsigned int flags); #ifdef HAVE_STRERROR /* a thread-safe version of strerror */ @@ -184,7 +182,10 @@ void env_set_remove_from_environment (const struct env_set *es); /* Make arrays of strings */ -const char **make_env_array (const struct env_set *es, struct gc_arena *gc); +const char **make_env_array (const struct env_set *es, + const bool check_allowed, + struct gc_arena *gc); + const char **make_arg_array (const char *first, const char *parms, struct gc_arena *gc); const char **make_extended_arg_array (char **p, struct gc_arena *gc); @@ -271,6 +272,9 @@ const char *safe_print (const char *str, struct gc_arena *gc); /* returns true if environmental variable safe to print to log */ bool env_safe_to_print (const char *str); +/* returns true if environmental variable may be passed to an external program */ +bool env_allowed (const char *str); + /* * A sleep function that services the management layer for n * seconds rather than doing nothing. @@ -290,4 +294,10 @@ void get_user_pass_auto_userid (struct user_pass *up, const char *tag); extern const char *iproute_path; #endif +#define SSEC_NONE 0 /* strictly no calling of external programs */ +#define SSEC_BUILT_IN 1 /* only call built-in programs such as ifconfig, route, netsh, etc.*/ +#define SSEC_SCRIPTS 2 /* allow calling of built-in programs and user-defined scripts */ +#define SSEC_PW_ENV 3 /* allow calling of built-in programs and user-defined scripts that may receive a password as an environmental variable */ +extern int script_security; /* GLOBAL */ + #endif diff --git a/multi.c b/multi.c index c5bbcfd..5f70b6f 100644 --- a/multi.c +++ b/multi.c @@ -85,36 +85,33 @@ learn_address_script (const struct multi_context *m, if (plugin_defined (plugins, OPENVPN_PLUGIN_LEARN_ADDRESS)) { - struct buffer cmd = alloc_buf_gc (256, &gc); - - buf_printf (&cmd, "\"%s\" \"%s\"", - op, - mroute_addr_print (addr, &gc)); + struct argv argv = argv_new (); + argv_printf (&argv, "%s %s", + op, + mroute_addr_print (addr, &gc)); if (mi) - buf_printf (&cmd, " \"%s\"", tls_common_name (mi->context.c2.tls_multi, false)); - - if (plugin_call (plugins, OPENVPN_PLUGIN_LEARN_ADDRESS, BSTR (&cmd), NULL, es) != OPENVPN_PLUGIN_FUNC_SUCCESS) + argv_printf_cat (&argv, "%s", tls_common_name (mi->context.c2.tls_multi, false)); + if (plugin_call (plugins, OPENVPN_PLUGIN_LEARN_ADDRESS, &argv, NULL, es) != OPENVPN_PLUGIN_FUNC_SUCCESS) { msg (M_WARN, "WARNING: learn-address plugin call failed"); ret = false; } + argv_reset (&argv); } if (m->top.options.learn_address_script) { - struct buffer cmd = alloc_buf_gc (256, &gc); - + struct argv argv = argv_new (); setenv_str (es, "script_type", "learn-address"); - - buf_printf (&cmd, "%s \"%s\" \"%s\"", - m->top.options.learn_address_script, - op, - mroute_addr_print (addr, &gc)); + argv_printf (&argv, "%s %s %s", + m->top.options.learn_address_script, + op, + mroute_addr_print (addr, &gc)); if (mi) - buf_printf (&cmd, " \"%s\"", tls_common_name (mi->context.c2.tls_multi, false)); - - if (!system_check (BSTR (&cmd), es, S_SCRIPT, "WARNING: learn-address command failed")) + argv_printf_cat (&argv, "%s", tls_common_name (mi->context.c2.tls_multi, false)); + if (!openvpn_execve_check (&argv, es, S_SCRIPT, "WARNING: learn-address command failed")) ret = false; + argv_reset (&argv); } gc_free (&gc); @@ -474,16 +471,11 @@ multi_client_disconnect_script (struct multi_context *m, if (mi->context.options.client_disconnect_script) { - struct gc_arena gc = gc_new (); - struct buffer cmd = alloc_buf_gc (256, &gc); - + struct argv argv = argv_new (); setenv_str (mi->context.c2.es, "script_type", "client-disconnect"); - - buf_printf (&cmd, "%s", mi->context.options.client_disconnect_script); - - system_check (BSTR (&cmd), mi->context.c2.es, S_SCRIPT, "client-disconnect command failed"); - - gc_free (&gc); + argv_printf (&argv, "%s", mi->context.options.client_disconnect_script); + openvpn_execve_check (&argv, mi->context.c2.es, S_SCRIPT, "client-disconnect command failed"); + argv_reset (&argv); } #ifdef MANAGEMENT_DEF_AUTH if (management) @@ -1523,11 +1515,11 @@ multi_connection_established (struct multi_context *m, struct multi_instance *mi /* deprecated callback, use a file for passing back return info */ if (plugin_defined (mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT)) { + struct argv argv = argv_new (); const char *dc_file = create_temp_filename (mi->context.options.tmp_dir, "cc", &gc); - + argv_printf (&argv, "%s", dc_file); delete_file (dc_file); - - if (plugin_call (mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT, dc_file, NULL, mi->context.c2.es) != OPENVPN_PLUGIN_FUNC_SUCCESS) + if (plugin_call (mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT, &argv, NULL, mi->context.c2.es) != OPENVPN_PLUGIN_FUNC_SUCCESS) { msg (M_WARN, "WARNING: client-connect plugin call failed"); cc_succeeded = false; @@ -1537,6 +1529,7 @@ multi_connection_established (struct multi_context *m, struct multi_instance *mi multi_client_connect_post (m, mi, dc_file, option_permissions_mask, &option_types_found); ++cc_succeeded_count; } + argv_reset (&argv); } /* V2 callback, use a plugin_return struct for passing back return info */ @@ -1566,7 +1559,7 @@ multi_connection_established (struct multi_context *m, struct multi_instance *mi */ if (mi->context.options.client_connect_script && cc_succeeded) { - struct buffer cmd = alloc_buf_gc (256, &gc); + struct argv argv = argv_new (); const char *dc_file = NULL; setenv_str (mi->context.c2.es, "script_type", "client-connect"); @@ -1575,17 +1568,19 @@ multi_connection_established (struct multi_context *m, struct multi_instance *mi delete_file (dc_file); - buf_printf (&cmd, "%s %s", - mi->context.options.client_connect_script, - dc_file); + argv_printf (&argv, "%s %s", + mi->context.options.client_connect_script, + dc_file); - if (system_check (BSTR (&cmd), mi->context.c2.es, S_SCRIPT, "client-connect command failed")) + if (openvpn_execve_check (&argv, mi->context.c2.es, S_SCRIPT, "client-connect command failed")) { multi_client_connect_post (m, mi, dc_file, option_permissions_mask, &option_types_found); ++cc_succeeded_count; } else cc_succeeded = false; + + argv_reset (&argv); } /* diff --git a/openvpn.8 b/openvpn.8 index c406014..b9f0016 100644 --- a/openvpn.8 +++ b/openvpn.8 @@ -252,6 +252,7 @@ openvpn \- secure IP tunnel daemon. [\ \fB\-\-route\-up\fR\ \fIcmd\fR\ ] [\ \fB\-\-route\fR\ \fInetwork\ [netmask]\ [gateway]\ [metric]\fR\ ] [\ \fB\-\-rport\fR\ \fIport\fR\ ] +[\ \fB\-\-script\-security\fR\ \fIlevel\fR\ ] [\ \fB\-\-secret\fR\ \fIfile\ [direction]\fR\ ] [\ \fB\-\-secret\fR\ \fIfile\fR\ ] [\ \fB\-\-server\-bridge\fR\ \fIgateway\ netmask\ pool\-start\-IP\ pool\-end\-IP\fR\ ] @@ -300,6 +301,7 @@ openvpn \- secure IP tunnel daemon. [\ \fB\-\-user\fR\ \fIuser\fR\ ] [\ \fB\-\-username\-as\-common\-name\fR\ ] [\ \fB\-\-verb\fR\ \fIn\fR\ ] +[\ \fB\-\-win\-sys\fR\ \fIpath|'env'\fR\ ] [\ \fB\-\-writepid\fR\ \fIfile\fR\ ] .in -4 .ti +4 @@ -1998,6 +2000,24 @@ is a safety precaution to prevent a LD_PRELOAD style attack from a malicious or compromised server. .\"********************************************************* .TP +.B --script-security level +This directive offers policy-level control over OpenVPN's usage of external programs +and scripts. Lower values are more restrictive, higher values are more permissive. Settings for +.B level: + +.B 0 -- +Strictly no calling of external programs. +.br +.B 1 -- +(Default) Only call built-in executables such as ifconfig, ip, route, or netsh. +.br +.B 2 -- +Allow calling of built-in executables and user-defined scripts. +.br +.B 3 -- +Allow passwords to be passed to scripts via environmental variables (potentially unsafe). +.\"********************************************************* +.TP .B --disable-occ Don't output a warning message if option inconsistencies are detected between peers. An example of an option inconsistency would be where one peer uses @@ -4481,6 +4501,22 @@ Optional group to be owner of this tunnel. .SS Windows-Specific Options: .\"********************************************************* .TP +.B --win-sys path|'env' +Set the Windows system directory pathname to use when looking for system +executables such as +.B route.exe +and +.B netsh.exe. +By default, if this directive is +not specified, the pathname will be set to "C:\\WINDOWS" + +The special string +.B 'env' +indicates that the pathname should be read from the +.B SystemRoot +environmental variable. +.\"********************************************************* +.TP .B --ip-win32 method When using .B --ifconfig diff --git a/openvpn.c b/openvpn.c index f089fb2..fb8afe6 100644 --- a/openvpn.c +++ b/openvpn.c @@ -27,6 +27,7 @@ #include "init.h" #include "forward.h" #include "multi.h" +#include "win32.h" #include "memdbg.h" @@ -131,6 +132,9 @@ main (int argc, char *argv[]) /* initialize environmental variable store */ c.es = env_set_create (NULL); +#ifdef WIN32 + env_set_add_win32 (c.es); +#endif #ifdef ENABLE_MANAGEMENT /* initialize management subsystem */ diff --git a/options.c b/options.c index 197a058..3a53c31 100644 --- a/options.c +++ b/options.c @@ -189,6 +189,10 @@ static const char usage_message[] = " flag to add a direct route to DHCP server, bypassing tunnel.\n" " Add 'bypass-dns' flag to similarly bypass tunnel for DNS.\n" "--setenv name value : Set a custom environmental variable to pass to script.\n" + "--script-security level : 0 -- strictly no calling of external programs\n" + " 1 -- (default) only call built-ins such as ifconfig\n" + " 2 -- allow calling of built-ins and scripts\n" + " 3 -- allow password to be passed to scripts via env\n" "--shaper n : Restrict output to peer to n bytes per second.\n" "--keepalive n m : Helper option for setting timeouts in server mode. Send\n" " ping once every n seconds, restart if ping not received\n" @@ -536,6 +540,8 @@ static const char usage_message[] = #ifdef WIN32 "\n" "Windows Specific:\n" + "--win-sys path|'env' : Pathname of Windows system directory, C:\\WINDOWS by default.\n" + " If specified as 'env', read the pathname from SystemRoot env var.\n" "--ip-win32 method : When using --ifconfig on Windows, set TAP-Win32 adapter\n" " IP address using method = manual, netsh, ipapi,\n" " dynamic, or adaptive (default = adaptive).\n" @@ -4249,6 +4255,11 @@ add_option (struct options *options, VERIFY_PERMISSION (OPT_P_SETENV); setenv_str_safe (es, p[1], p[2] ? p[2] : ""); } + else if (streq (p[0], "script-security") && p[1]) + { + VERIFY_PERMISSION (OPT_P_GENERAL); + script_security = atoi (p[1]); + } else if (streq (p[0], "mssfix")) { VERIFY_PERMISSION (OPT_P_GENERAL); @@ -4618,6 +4629,14 @@ add_option (struct options *options, } #endif #ifdef WIN32 + else if (streq (p[0], "win-sys") && p[1]) + { + VERIFY_PERMISSION (OPT_P_GENERAL); + if (streq (p[1], "env")) + set_win_sys_path_via_env (es); + else + set_win_sys_path (p[1], es); + } else if (streq (p[0], "route-method") && p[1]) { VERIFY_PERMISSION (OPT_P_ROUTE_EXTRAS); diff --git a/plugin.c b/plugin.c index 432ed23..eac301e 100644 --- a/plugin.c +++ b/plugin.c @@ -327,7 +327,7 @@ static int plugin_call_item (const struct plugin *p, void *per_client_context, const int type, - const char *args, + const struct argv *av, struct openvpn_plugin_string_list **retlist, const char **envp) { @@ -340,18 +340,18 @@ plugin_call_item (const struct plugin *p, if (p->plugin_handle && (p->plugin_type_mask & OPENVPN_PLUGIN_MASK (type))) { struct gc_arena gc = gc_new (); - const char **argv = make_arg_array (p->so_pathname, args, &gc); + struct argv a = argv_insert_head (av, p->so_pathname); dmsg (D_PLUGIN_DEBUG, "PLUGIN_CALL: PRE type=%s", plugin_type_name (type)); - plugin_show_args_env (D_PLUGIN_DEBUG, argv, envp); + plugin_show_args_env (D_PLUGIN_DEBUG, (const char **)a.argv, envp); /* * Call the plugin work function */ if (p->func2) - status = (*p->func2)(p->plugin_handle, type, argv, envp, per_client_context, retlist); + status = (*p->func2)(p->plugin_handle, type, (const char **)a.argv, envp, per_client_context, retlist); else if (p->func1) - status = (*p->func1)(p->plugin_handle, type, argv, envp); + status = (*p->func1)(p->plugin_handle, type, (const char **)a.argv, envp); else ASSERT (0); @@ -366,6 +366,7 @@ plugin_call_item (const struct plugin *p, status, p->so_pathname); + argv_reset (&a); gc_free (&gc); } return status; @@ -482,7 +483,7 @@ plugin_common_open (struct plugin_common *pc, int i; const char **envp; - envp = make_env_array (es, &gc); + envp = make_env_array (es, false, &gc); if (pr) plugin_return_init (pr); @@ -540,7 +541,7 @@ plugin_list_open (struct plugin_list *pl, int plugin_call (const struct plugin_list *pl, const int type, - const char *args, + const struct argv *av, struct plugin_return *pr, struct env_set *es) { @@ -560,14 +561,14 @@ plugin_call (const struct plugin_list *pl, mutex_lock_static (L_PLUGIN); setenv_del (es, "script_type"); - envp = make_env_array (es, &gc); + envp = make_env_array (es, false, &gc); for (i = 0; i < n; ++i) { const int status = plugin_call_item (&pl->common->plugins[i], pl->per_client.per_client_context[i], type, - args, + av, pr ? &pr->list[i] : NULL, envp); switch (status) diff --git a/plugin.h b/plugin.h index 8e46eeb..61c3645 100644 --- a/plugin.h +++ b/plugin.h @@ -116,7 +116,7 @@ struct plugin_list *plugin_list_inherit (const struct plugin_list *src); int plugin_call (const struct plugin_list *pl, const int type, - const char *args, + const struct argv *av, struct plugin_return *pr, struct env_set *es); @@ -168,7 +168,7 @@ plugin_defined (const struct plugin_list *pl, const int type) static inline int plugin_call (const struct plugin_list *pl, const int type, - const char *args, + const struct argv *av, struct plugin_return *pr, struct env_set *es) { diff --git a/plugin/examples/log.c b/plugin/examples/log.c new file mode 100644 index 0000000..31cbb8c --- /dev/null +++ b/plugin/examples/log.c @@ -0,0 +1,184 @@ +/* + * 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-2008 Telethra, Inc. + * + * 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 + */ + +/* + * This plugin is similar to simple.c, except it also logs extra information + * to stdout for every plugin method called by OpenVPN. + * + * See the README file for build instructions. + */ + +#include +#include +#include + +#include "openvpn-plugin.h" + +/* + * Our context, where we keep our state. + */ +struct plugin_context { + const char *username; + const char *password; +}; + +/* + * Given an environmental variable name, search + * the envp array for its value, returning it + * if found or NULL otherwise. + */ +static const char * +get_env (const char *name, const char *envp[]) +{ + if (envp) + { + int i; + const int namelen = strlen (name); + for (i = 0; envp[i]; ++i) + { + if (!strncmp (envp[i], name, namelen)) + { + const char *cp = envp[i] + namelen; + if (*cp == '=') + return cp + 1; + } + } + } + return NULL; +} + +OPENVPN_EXPORT openvpn_plugin_handle_t +openvpn_plugin_open_v1 (unsigned int *type_mask, const char *argv[], const char *envp[]) +{ + struct plugin_context *context; + + /* + * Allocate our context + */ + context = (struct plugin_context *) calloc (1, sizeof (struct plugin_context)); + + /* + * Set the username/password we will require. + */ + context->username = "foo"; + context->password = "bar"; + + /* + * Which callbacks to intercept. + */ + *type_mask = + OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_UP) | + OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_DOWN) | + OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_ROUTE_UP) | + OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_IPCHANGE) | + OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_TLS_VERIFY) | + OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY) | + OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_CLIENT_CONNECT_V2) | + OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_CLIENT_DISCONNECT) | + OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_LEARN_ADDRESS) | + OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_TLS_FINAL); + + return (openvpn_plugin_handle_t) context; +} + +void +show (const int type, const char *argv[], const char *envp[]) +{ + size_t i; + switch (type) + { + case OPENVPN_PLUGIN_UP: + printf ("OPENVPN_PLUGIN_UP\n"); + break; + case OPENVPN_PLUGIN_DOWN: + printf ("OPENVPN_PLUGIN_DOWN\n"); + break; + case OPENVPN_PLUGIN_ROUTE_UP: + printf ("OPENVPN_PLUGIN_ROUTE_UP\n"); + break; + case OPENVPN_PLUGIN_IPCHANGE: + printf ("OPENVPN_PLUGIN_IPCHANGE\n"); + break; + case OPENVPN_PLUGIN_TLS_VERIFY: + printf ("OPENVPN_PLUGIN_TLS_VERIFY\n"); + break; + case OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY: + printf ("OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY\n"); + break; + case OPENVPN_PLUGIN_CLIENT_CONNECT_V2: + printf ("OPENVPN_PLUGIN_CLIENT_CONNECT_V2\n"); + break; + case OPENVPN_PLUGIN_CLIENT_DISCONNECT: + printf ("OPENVPN_PLUGIN_CLIENT_DISCONNECT\n"); + break; + case OPENVPN_PLUGIN_LEARN_ADDRESS: + printf ("OPENVPN_PLUGIN_LEARN_ADDRESS\n"); + break; + case OPENVPN_PLUGIN_TLS_FINAL: + printf ("OPENVPN_PLUGIN_TLS_FINAL\n"); + break; + default: + printf ("OPENVPN_PLUGIN_?\n"); + break; + } + + printf ("ARGV\n"); + for (i = 0; argv[i] != NULL; ++i) + printf ("%d '%s'\n", (int)i, argv[i]); + + printf ("ENVP\n"); + for (i = 0; envp[i] != NULL; ++i) + printf ("%d '%s'\n", (int)i, envp[i]); +} + +OPENVPN_EXPORT int +openvpn_plugin_func_v1 (openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[]) +{ + struct plugin_context *context = (struct plugin_context *) handle; + + show (type, argv, envp); + + /* check entered username/password against what we require */ + if (type == OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY) + { + /* get username/password from envp string array */ + const char *username = get_env ("username", envp); + const char *password = get_env ("password", envp); + + if (username && !strcmp (username, context->username) + && password && !strcmp (password, context->password)) + return OPENVPN_PLUGIN_FUNC_SUCCESS; + else + return OPENVPN_PLUGIN_FUNC_ERROR; + } + else + return OPENVPN_PLUGIN_FUNC_SUCCESS; +} + +OPENVPN_EXPORT void +openvpn_plugin_close_v1 (openvpn_plugin_handle_t handle) +{ + struct plugin_context *context = (struct plugin_context *) handle; + free (context); +} diff --git a/route.c b/route.c index 2d9e85a..47a6013 100644 --- a/route.c +++ b/route.c @@ -34,6 +34,7 @@ #include "misc.h" #include "socket.h" #include "manage.h" +#include "win32.h" #include "memdbg.h" @@ -743,7 +744,7 @@ void add_route (struct route *r, const struct tuntap *tt, unsigned int flags, const struct env_set *es) { struct gc_arena gc; - struct buffer buf; + struct argv argv; const char *network; const char *netmask; const char *gateway; @@ -753,7 +754,7 @@ add_route (struct route *r, const struct tuntap *tt, unsigned int flags, const s return; gc_init (&gc); - buf = alloc_buf_gc (256, &gc); + argv_init (&argv); network = print_in_addr_t (r->network, 0, &gc); netmask = print_in_addr_t (r->netmask, 0, &gc); @@ -771,35 +772,38 @@ add_route (struct route *r, const struct tuntap *tt, unsigned int flags, const s #if defined(TARGET_LINUX) #ifdef CONFIG_FEATURE_IPROUTE - buf_printf (&buf, "%s route add %s/%d via %s", + argv_printf (&argv, "%s route add %s/%d via %s", iproute_path, network, count_netmask_bits(netmask), gateway); if (r->metric_defined) - buf_printf (&buf, " metric %d", r->metric); + argv_printf_cat (&argv, "metric %d", r->metric); #else - buf_printf (&buf, ROUTE_PATH " add -net %s netmask %s gw %s", + argv_printf (&argv, "%s add -net %s netmask %s gw %s", + ROUTE_PATH, network, netmask, gateway); if (r->metric_defined) - buf_printf (&buf, " metric %d", r->metric); + argv_printf_cat (&argv, "metric %d", r->metric); #endif /*CONFIG_FEATURE_IPROUTE*/ - msg (D_ROUTE, "%s", BSTR (&buf)); - status = system_check (BSTR (&buf), es, 0, "ERROR: Linux route add command failed"); + argv_msg (D_ROUTE, &argv); + status = openvpn_execve_check (&argv, es, 0, "ERROR: Linux route add command failed"); #elif defined (WIN32) - buf_printf (&buf, ROUTE_PATH " ADD %s MASK %s %s", - network, - netmask, - gateway); + argv_printf (&argv, "%s%s ADD %s MASK %s %s", + get_win_sys_path(), + WIN_ROUTE_PATH_SUFFIX, + network, + netmask, + gateway); if (r->metric_defined) - buf_printf (&buf, " METRIC %d", r->metric); + argv_printf_cat (&argv, "METRIC %d", r->metric); - msg (D_ROUTE, "%s", BSTR (&buf)); + argv_msg (D_ROUTE, &argv); if ((flags & ROUTE_METHOD_MASK) == ROUTE_METHOD_IPAPI) { @@ -809,7 +813,7 @@ add_route (struct route *r, const struct tuntap *tt, unsigned int flags, const s else if ((flags & ROUTE_METHOD_MASK) == ROUTE_METHOD_EXE) { netcmd_semaphore_lock (); - status = system_check (BSTR (&buf), es, 0, "ERROR: Windows route add command failed"); + status = openvpn_execve_check (&argv, es, 0, "ERROR: Windows route add command failed"); netcmd_semaphore_release (); } else if ((flags & ROUTE_METHOD_MASK) == ROUTE_METHOD_ADAPTIVE) @@ -820,7 +824,7 @@ add_route (struct route *r, const struct tuntap *tt, unsigned int flags, const s { msg (D_ROUTE, "Route addition fallback to route.exe"); netcmd_semaphore_lock (); - status = system_check (BSTR (&buf), es, 0, "ERROR: Windows route add command failed [adaptive]"); + status = openvpn_execve_check (&argv, es, 0, "ERROR: Windows route add command failed [adaptive]"); netcmd_semaphore_release (); } } @@ -833,88 +837,93 @@ add_route (struct route *r, const struct tuntap *tt, unsigned int flags, const s /* example: route add 192.0.2.32 -netmask 255.255.255.224 somegateway */ - buf_printf (&buf, ROUTE_PATH " add"); + argv_printf (&argv, "%s add", + ROUTE_PATH); #if 0 if (r->metric_defined) - buf_printf (&buf, " -rtt %d", r->metric); + argv_printf_cat (&argv, "-rtt %d", r->metric); #endif - buf_printf (&buf, " %s -netmask %s %s", + argv_printf_cat (&argv, "%s -netmask %s %s", network, netmask, gateway); - msg (D_ROUTE, "%s", BSTR (&buf)); - status = system_check (BSTR (&buf), es, 0, "ERROR: Solaris route add command failed"); + argv_msg (D_ROUTE, &argv); + status = openvpn_execve_check (&argv, es, 0, "ERROR: Solaris route add command failed"); #elif defined(TARGET_FREEBSD) - buf_printf (&buf, ROUTE_PATH " add"); + argv_printf (&argv, "%s add", + ROUTE_PATH); #if 0 if (r->metric_defined) - buf_printf (&buf, " -rtt %d", r->metric); + argv_printf_cat (&argv, "-rtt %d", r->metric); #endif - buf_printf (&buf, " -net %s %s %s", + argv_printf_cat (&argv, "-net %s %s %s", network, gateway, netmask); - msg (D_ROUTE, "%s", BSTR (&buf)); - status = system_check (BSTR (&buf), es, 0, "ERROR: FreeBSD route add command failed"); + argv_msg (D_ROUTE, &argv); + status = openvpn_execve_check (&argv, es, 0, "ERROR: FreeBSD route add command failed"); #elif defined(TARGET_DRAGONFLY) - buf_printf (&buf, ROUTE_PATH " add"); + argv_printf (&argv, "%s add", + ROUTE_PATH); #if 0 if (r->metric_defined) - buf_printf (&buf, " -rtt %d", r->metric); + argv_printf_cat (&argv, "-rtt %d", r->metric); #endif - buf_printf (&buf, " -net %s %s %s", + argv_printf_cat (&argv, "-net %s %s %s", network, gateway, netmask); - msg (D_ROUTE, "%s", BSTR (&buf)); - status = system_check (BSTR (&buf), es, 0, "ERROR: DragonFly route add command failed"); + argv_msg (D_ROUTE, &argv); + status = openvpn_execve_check (&argv, es, 0, "ERROR: DragonFly route add command failed"); #elif defined(TARGET_DARWIN) - buf_printf (&buf, ROUTE_PATH " add"); + argv_printf (&argv, "%s add", + ROUTE_PATH); #if 0 if (r->metric_defined) - buf_printf (&buf, " -rtt %d", r->metric); + argv_printf_cat (&argv, "-rtt %d", r->metric); #endif - buf_printf (&buf, " -net %s %s %s", + argv_printf_cat (&argv, "-net %s %s %s", network, gateway, netmask); - msg (D_ROUTE, "%s", BSTR (&buf)); - status = system_check (BSTR (&buf), es, 0, "ERROR: OS X route add command failed"); + argv_msg (D_ROUTE, &argv); + status = openvpn_execve_check (&argv, es, 0, "ERROR: OS X route add command failed"); #elif defined(TARGET_OPENBSD) || defined(TARGET_NETBSD) - buf_printf (&buf, ROUTE_PATH " add"); + argv_printf (&argv, "%s add", + ROUTE_PATH); #if 0 if (r->metric_defined) - buf_printf (&buf, " -rtt %d", r->metric); + argv_printf_cat (&argv, "-rtt %d", r->metric); #endif - buf_printf (&buf, " -net %s %s -netmask %s", + argv_printf_cat (&argv, "-net %s %s -netmask %s", network, gateway, netmask); - msg (D_ROUTE, "%s", BSTR (&buf)); - status = system_check (BSTR (&buf), es, 0, "ERROR: OpenBSD/NetBSD route add command failed"); + argv_msg (D_ROUTE, &argv); + status = openvpn_execve_check (&argv, es, 0, "ERROR: OpenBSD/NetBSD route add command failed"); #else msg (M_FATAL, "Sorry, but I don't know how to do 'route' commands on this operating system. Try putting your routes in a --route-up script"); @@ -922,6 +931,7 @@ add_route (struct route *r, const struct tuntap *tt, unsigned int flags, const s done: r->defined = status; + argv_reset (&argv); gc_free (&gc); } @@ -929,7 +939,7 @@ static void delete_route (const struct route *r, const struct tuntap *tt, unsigned int flags, const struct env_set *es) { struct gc_arena gc; - struct buffer buf; + struct argv argv; const char *network; const char *netmask; const char *gateway; @@ -938,37 +948,40 @@ delete_route (const struct route *r, const struct tuntap *tt, unsigned int flags return; gc_init (&gc); + argv_init (&argv); - buf = alloc_buf_gc (256, &gc); network = print_in_addr_t (r->network, 0, &gc); netmask = print_in_addr_t (r->netmask, 0, &gc); gateway = print_in_addr_t (r->gateway, 0, &gc); #if defined(TARGET_LINUX) #ifdef CONFIG_FEATURE_IPROUTE - buf_printf (&buf, "%s route del %s/%d", + argv_printf (&argv, "%s route del %s/%d", iproute_path, network, count_netmask_bits(netmask)); #else - buf_printf (&buf, ROUTE_PATH " del -net %s netmask %s", + argv_printf (&argv, "%s del -net %s netmask %s", + ROUTE_PATH, network, netmask); #endif /*CONFIG_FEATURE_IPROUTE*/ if (r->metric_defined) - buf_printf (&buf, " metric %d", r->metric); - msg (D_ROUTE, "%s", BSTR (&buf)); - system_check (BSTR (&buf), es, 0, "ERROR: Linux route delete command failed"); + argv_printf_cat (&argv, "metric %d", r->metric); + argv_msg (D_ROUTE, &argv); + openvpn_execve_check (&argv, es, 0, "ERROR: Linux route delete command failed"); #elif defined (WIN32) - buf_printf (&buf, ROUTE_PATH " DELETE %s MASK %s %s", - network, - netmask, - gateway); + argv_printf (&argv, "%s%s DELETE %s MASK %s %s", + get_win_sys_path(), + WIN_ROUTE_PATH_SUFFIX, + network, + netmask, + gateway); - msg (D_ROUTE, "%s", BSTR (&buf)); + argv_msg (D_ROUTE, &argv); if ((flags & ROUTE_METHOD_MASK) == ROUTE_METHOD_IPAPI) { @@ -978,7 +991,7 @@ delete_route (const struct route *r, const struct tuntap *tt, unsigned int flags else if ((flags & ROUTE_METHOD_MASK) == ROUTE_METHOD_EXE) { netcmd_semaphore_lock (); - system_check (BSTR (&buf), es, 0, "ERROR: Windows route delete command failed"); + openvpn_execve_check (&argv, es, 0, "ERROR: Windows route delete command failed"); netcmd_semaphore_release (); } else if ((flags & ROUTE_METHOD_MASK) == ROUTE_METHOD_ADAPTIVE) @@ -989,7 +1002,7 @@ delete_route (const struct route *r, const struct tuntap *tt, unsigned int flags { msg (D_ROUTE, "Route deletion fallback to route.exe"); netcmd_semaphore_lock (); - system_check (BSTR (&buf), es, 0, "ERROR: Windows route delete command failed [adaptive]"); + openvpn_execve_check (&argv, es, 0, "ERROR: Windows route delete command failed [adaptive]"); netcmd_semaphore_release (); } } @@ -1000,58 +1013,64 @@ delete_route (const struct route *r, const struct tuntap *tt, unsigned int flags #elif defined (TARGET_SOLARIS) - buf_printf (&buf, ROUTE_PATH " delete %s -netmask %s %s", + argv_printf (&argv, "%s delete %s -netmask %s %s", + ROUTE_PATH, network, netmask, gateway); - msg (D_ROUTE, "%s", BSTR (&buf)); - system_check (BSTR (&buf), es, 0, "ERROR: Solaris route delete command failed"); + argv_msg (D_ROUTE, &argv); + openvpn_execve_check (&argv, es, 0, "ERROR: Solaris route delete command failed"); #elif defined(TARGET_FREEBSD) - buf_printf (&buf, ROUTE_PATH " delete -net %s %s %s", + argv_printf (&argv, "%s delete -net %s %s %s", + ROUTE_PATH, network, gateway, netmask); - msg (D_ROUTE, "%s", BSTR (&buf)); - system_check (BSTR (&buf), es, 0, "ERROR: FreeBSD route delete command failed"); + argv_msg (D_ROUTE, &argv); + openvpn_execve_check (&argv, es, 0, "ERROR: FreeBSD route delete command failed"); #elif defined(TARGET_DRAGONFLY) - buf_printf (&buf, ROUTE_PATH " delete -net %s %s %s", + argv_printf (&argv, "%s delete -net %s %s %s", + ROUTE_PATH, network, gateway, netmask); - msg (D_ROUTE, "%s", BSTR (&buf)); - system_check (BSTR (&buf), es, 0, "ERROR: DragonFly route delete command failed"); + argv_msg (D_ROUTE, &argv); + openvpn_execve_check (&argv, es, 0, "ERROR: DragonFly route delete command failed"); #elif defined(TARGET_DARWIN) - buf_printf (&buf, ROUTE_PATH " delete -net %s %s %s", + argv_printf (&argv, "%s delete -net %s %s %s", + ROUTE_PATH, network, gateway, netmask); - msg (D_ROUTE, "%s", BSTR (&buf)); - system_check (BSTR (&buf), es, 0, "ERROR: OS X route delete command failed"); + argv_msg (D_ROUTE, &argv); + openvpn_execve_check (&argv, es, 0, "ERROR: OS X route delete command failed"); #elif defined(TARGET_OPENBSD) || defined(TARGET_NETBSD) - buf_printf (&buf, ROUTE_PATH " delete -net %s %s -netmask %s", + argv_printf (&argv, "%s delete -net %s %s -netmask %s", + ROUTE_PATH, network, gateway, netmask); - msg (D_ROUTE, "%s", BSTR (&buf)); - system_check (BSTR (&buf), es, 0, "ERROR: OpenBSD/NetBSD route delete command failed"); + argv_msg (D_ROUTE, &argv); + openvpn_execve_check (&argv, es, 0, "ERROR: OpenBSD/NetBSD route delete command failed"); #else msg (M_FATAL, "Sorry, but I don't know how to do 'route' commands on this operating system. Try putting your routes in a --route-up script"); #endif + argv_reset (&argv); gc_free (&gc); } diff --git a/socket.c b/socket.c index 135fb0e..c1b16ad 100644 --- a/socket.c +++ b/socket.c @@ -1480,6 +1480,22 @@ setenv_trusted (struct env_set *es, const struct link_socket_info *info) setenv_link_socket_actual (es, "trusted", &info->lsa->actual, SA_IP_PORT); } +static void +ipchange_fmt (const bool include_cmd, struct argv *argv, const struct link_socket_info *info, struct gc_arena *gc) +{ + const char *ip = print_sockaddr_ex (&info->lsa->actual.dest, NULL, 0, gc); + const char *port = print_sockaddr_ex (&info->lsa->actual.dest, NULL, PS_DONT_SHOW_ADDR|PS_SHOW_PORT, gc); + if (include_cmd) + argv_printf (argv, "%s %s %s", + info->ipchange_command, + ip, + port); + else + argv_printf (argv, "%s %s", + ip, + port); +} + void link_socket_connection_initiated (const struct buffer *buf, struct link_socket_info *info, @@ -1508,20 +1524,21 @@ link_socket_connection_initiated (const struct buffer *buf, /* Process --ipchange plugin */ if (plugin_defined (info->plugins, OPENVPN_PLUGIN_IPCHANGE)) { - const char *addr_ascii = print_sockaddr_ex (&info->lsa->actual.dest, " ", PS_SHOW_PORT, &gc); - if (plugin_call (info->plugins, OPENVPN_PLUGIN_IPCHANGE, addr_ascii, NULL, es) != OPENVPN_PLUGIN_FUNC_SUCCESS) + struct argv argv = argv_new (); + ipchange_fmt (false, &argv, info, &gc); + if (plugin_call (info->plugins, OPENVPN_PLUGIN_IPCHANGE, &argv, NULL, es) != OPENVPN_PLUGIN_FUNC_SUCCESS) msg (M_WARN, "WARNING: ipchange plugin call failed"); + argv_reset (&argv); } /* Process --ipchange option */ if (info->ipchange_command) { - struct buffer out = alloc_buf_gc (256, &gc); + struct argv argv = argv_new (); setenv_str (es, "script_type", "ipchange"); - buf_printf (&out, "%s %s", - info->ipchange_command, - print_sockaddr_ex (&info->lsa->actual.dest, " ", PS_SHOW_PORT, &gc)); - system_check (BSTR (&out), es, S_SCRIPT, "ip-change command failed"); + ipchange_fmt (true, &argv, info, &gc); + openvpn_execve_check (&argv, es, S_SCRIPT, "ip-change command failed"); + argv_reset (&argv); } gc_free (&gc); @@ -1791,7 +1808,8 @@ print_sockaddr_ex (const struct openvpn_sockaddr *addr, const int port = ntohs (addr->sa.sin_port); mutex_lock_static (L_INET_NTOA); - buf_printf (&out, "%s", (addr_defined (addr) ? inet_ntoa (addr->sa.sin_addr) : "[undef]")); + if (!(flags & PS_DONT_SHOW_ADDR)) + buf_printf (&out, "%s", (addr_defined (addr) ? inet_ntoa (addr->sa.sin_addr) : "[undef]")); mutex_unlock_static (L_INET_NTOA); if (((flags & PS_SHOW_PORT) || (addr_defined (addr) && (flags & PS_SHOW_PORT_IF_DEFINED))) diff --git a/socket.h b/socket.h index 3d25f5e..9084fa0 100644 --- a/socket.h +++ b/socket.h @@ -325,6 +325,7 @@ void link_socket_close (struct link_socket *sock); #define PS_SHOW_PORT_IF_DEFINED (1<<0) #define PS_SHOW_PORT (1<<1) #define PS_SHOW_PKTINFO (1<<2) +#define PS_DONT_SHOW_ADDR (1<<3) const char *print_sockaddr_ex (const struct openvpn_sockaddr *addr, const char* separator, diff --git a/ssl.c b/ssl.c index 55a3830..9318b8a 100644 --- a/ssl.c +++ b/ssl.c @@ -544,6 +544,7 @@ verify_callback (int preverify_ok, X509_STORE_CTX * ctx) struct tls_session *session; const struct tls_options *opt; const int max_depth = 8; + struct argv argv = argv_new (); /* get the tls_session pointer */ ssl = X509_STORE_CTX_get_ex_data (ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); @@ -689,16 +690,13 @@ verify_callback (int preverify_ok, X509_STORE_CTX * ctx) /* call --tls-verify plug-in(s) */ if (plugin_defined (opt->plugins, OPENVPN_PLUGIN_TLS_VERIFY)) { - char command[256]; - struct buffer out; int ret; - buf_set_write (&out, (uint8_t*)command, sizeof (command)); - buf_printf (&out, "%d %s", - ctx->error_depth, - subject); + argv_printf (&argv, "%d %s", + ctx->error_depth, + subject); - ret = plugin_call (opt->plugins, OPENVPN_PLUGIN_TLS_VERIFY, command, NULL, opt->es); + ret = plugin_call (opt->plugins, OPENVPN_PLUGIN_TLS_VERIFY, &argv, NULL, opt->es); if (ret == OPENVPN_PLUGIN_FUNC_SUCCESS) { @@ -716,19 +714,16 @@ verify_callback (int preverify_ok, X509_STORE_CTX * ctx) /* run --tls-verify script */ if (opt->verify_command) { - char command[256]; - struct buffer out; int ret; setenv_str (opt->es, "script_type", "tls-verify"); - buf_set_write (&out, (uint8_t*)command, sizeof (command)); - buf_printf (&out, "%s %d %s", - opt->verify_command, - ctx->error_depth, - subject); - dmsg (D_TLS_DEBUG, "TLS: executing verify command: %s", command); - ret = openvpn_system (command, opt->es, S_SCRIPT); + argv_printf (&argv, "%s %d %s", + opt->verify_command, + ctx->error_depth, + subject); + argv_msg_prefix (D_TLS_DEBUG, &argv, "TLS: executing verify command"); + ret = openvpn_execve (&argv, opt->es, S_SCRIPT); if (system_ok (ret)) { @@ -738,7 +733,7 @@ verify_callback (int preverify_ok, X509_STORE_CTX * ctx) else { if (!system_executed (ret)) - msg (M_ERR, "Verify command failed to execute: %s", command); + argv_msg_prefix (M_ERR, &argv, "Verify command failed to execute"); msg (D_HANDSHAKE, "VERIFY SCRIPT ERROR: depth=%d, %s", ctx->error_depth, subject); goto err; /* Reject connection */ @@ -801,11 +796,13 @@ verify_callback (int preverify_ok, X509_STORE_CTX * ctx) session->verified = true; free (subject); + argv_reset (&argv); return 1; /* Accept connection */ err: ERR_clear_error (); free (subject); + argv_reset (&argv); return 0; /* Reject connection */ } @@ -2901,7 +2898,7 @@ static bool verify_user_pass_script (struct tls_session *session, const struct user_pass *up) { struct gc_arena gc = gc_new (); - struct buffer cmd = alloc_buf_gc (256, &gc); + struct argv argv = argv_new (); const char *tmp_file = ""; int retval; bool ret = false; @@ -2940,16 +2937,16 @@ verify_user_pass_script (struct tls_session *session, const struct user_pass *up setenv_untrusted (session); /* format command line */ - buf_printf (&cmd, "%s %s", session->opt->auth_user_pass_verify_script, tmp_file); + argv_printf (&argv, "%s %s", session->opt->auth_user_pass_verify_script, tmp_file); /* call command */ - retval = openvpn_system (BSTR (&cmd), session->opt->es, S_SCRIPT); + retval = openvpn_execve (&argv, session->opt->es, S_SCRIPT); /* test return status of command */ if (system_ok (retval)) ret = true; else if (!system_executed (retval)) - msg (D_TLS_ERRORS, "TLS Auth Error: user-pass-verify script failed to execute: %s", BSTR (&cmd)); + argv_msg_prefix (D_TLS_ERRORS, &argv, "TLS Auth Error: user-pass-verify script failed to execute"); if (!session->opt->auth_user_pass_verify_script_via_file) setenv_del (session->opt->es, "password"); @@ -2963,6 +2960,7 @@ verify_user_pass_script (struct tls_session *session, const struct user_pass *up if (strlen (tmp_file) > 0) delete_file (tmp_file); + argv_reset (&argv); gc_free (&gc); return ret; } diff --git a/syshead.h b/syshead.h index da07a71..e87814e 100644 --- a/syshead.h +++ b/syshead.h @@ -447,6 +447,14 @@ socket_defined (const socket_descriptor_t sd) */ #define USE_64_BIT_COUNTERS +/* + * Should we enable the use of execve() for calling subprocesses, + * instead of system()? + */ +#if defined(HAVE_EXECVE) && defined(HAVE_FORK) +#define ENABLE_EXECVE +#endif + /* * Do we have point-to-multipoint capability? */ diff --git a/tun.c b/tun.c index c1494d9..c6916ef 100644 --- a/tun.c +++ b/tun.c @@ -39,6 +39,7 @@ #include "socket.h" #include "manage.h" #include "route.h" +#include "win32.h" #include "memdbg.h" @@ -534,7 +535,9 @@ do_ifconfig (struct tuntap *tt, const char *ifconfig_local = NULL; const char *ifconfig_remote_netmask = NULL; const char *ifconfig_broadcast = NULL; - char command_line[256]; + struct argv argv; + + argv_init (&argv); /* * We only handle TUN/TAP devices here, not --dev null devices. @@ -570,31 +573,31 @@ do_ifconfig (struct tuntap *tt, /* * Set the MTU for the device */ - openvpn_snprintf (command_line, sizeof (command_line), + argv_printf (&argv, "%s link set dev %s up mtu %d", iproute_path, actual, tun_mtu ); - msg (M_INFO, "%s", command_line); - system_check (command_line, es, S_FATAL, "Linux ip link set failed"); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, es, S_FATAL, "Linux ip link set failed"); if (tun) { /* * Set the address for the device */ - openvpn_snprintf (command_line, sizeof (command_line), + argv_printf (&argv, "%s addr add dev %s local %s peer %s", iproute_path, actual, ifconfig_local, ifconfig_remote_netmask ); - msg (M_INFO, "%s", command_line); - system_check (command_line, es, S_FATAL, "Linux ip addr add failed"); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, es, S_FATAL, "Linux ip addr add failed"); } else { - openvpn_snprintf (command_line, sizeof (command_line), + argv_printf (&argv, "%s addr add dev %s %s/%d broadcast %s", iproute_path, actual, @@ -602,30 +605,32 @@ do_ifconfig (struct tuntap *tt, count_netmask_bits(ifconfig_remote_netmask), ifconfig_broadcast ); - msg (M_INFO, "%s", command_line); - system_check (command_line, es, S_FATAL, "Linux ip addr add failed"); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, es, S_FATAL, "Linux ip addr add failed"); } tt->did_ifconfig = true; #else if (tun) - openvpn_snprintf (command_line, sizeof (command_line), - IFCONFIG_PATH " %s %s pointopoint %s mtu %d", + argv_printf (&argv, + "%s %s %s pointopoint %s mtu %d", + IFCONFIG_PATH, actual, ifconfig_local, ifconfig_remote_netmask, tun_mtu ); else - openvpn_snprintf (command_line, sizeof (command_line), - IFCONFIG_PATH " %s %s netmask %s mtu %d broadcast %s", + argv_printf (&argv, + "%s %s %s netmask %s mtu %d broadcast %s", + IFCONFIG_PATH, actual, ifconfig_local, ifconfig_remote_netmask, tun_mtu, ifconfig_broadcast ); - msg (M_INFO, "%s", command_line); - system_check (command_line, es, S_FATAL, "Linux ifconfig failed"); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, es, S_FATAL, "Linux ifconfig failed"); tt->did_ifconfig = true; #endif /*CONFIG_FEATURE_IPROUTE*/ @@ -638,28 +643,30 @@ do_ifconfig (struct tuntap *tt, */ if (tun) { - openvpn_snprintf (command_line, sizeof (command_line), - IFCONFIG_PATH " %s %s %s mtu %d up", + argv_printf (&argv, + "%s %s %s %s mtu %d up", + IFCONFIG_PATH, actual, ifconfig_local, ifconfig_remote_netmask, tun_mtu ); - msg (M_INFO, "%s", command_line); - if (!system_check (command_line, es, 0, "Solaris ifconfig phase-1 failed")) + argv_msg (M_INFO, &argv); + if (!openvpn_execve_check (&argv, es, 0, "Solaris ifconfig phase-1 failed")) solaris_error_close (tt, es, actual); - openvpn_snprintf (command_line, sizeof (command_line), - IFCONFIG_PATH " %s netmask 255.255.255.255", + argv_printf (&argv, + "%s %s netmask 255.255.255.255", + IFCONFIG_PATH, actual ); } else no_tap_ifconfig (); - msg (M_INFO, "%s", command_line); - if (!system_check (command_line, es, 0, "Solaris ifconfig phase-2 failed")) + argv_msg (M_INFO, &argv); + if (!openvpn_execve_check (&argv, es, 0, "Solaris ifconfig phase-2 failed")) solaris_error_close (tt, es, actual); tt->did_ifconfig = true; @@ -672,45 +679,50 @@ do_ifconfig (struct tuntap *tt, * (if it exists), and re-ifconfig. Let me know if you know a better way. */ - openvpn_snprintf (command_line, sizeof (command_line), - IFCONFIG_PATH " %s destroy", + argv_printf (&argv, + "%s %s destroy", + IFCONFIG_PATH, actual); - msg (M_INFO, "%s", command_line); - system_check (command_line, es, 0, NULL); - openvpn_snprintf (command_line, sizeof (command_line), - IFCONFIG_PATH " %s create", + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, es, 0, NULL); + argv_printf (&argv, + "%s %s create", + IFCONFIG_PATH, actual); - msg (M_INFO, "%s", command_line); - system_check (command_line, es, 0, NULL); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, es, 0, NULL); msg (M_INFO, "NOTE: Tried to delete pre-existing tun/tap instance -- No Problem if failure"); /* example: ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 netmask 255.255.255.255 up */ if (tun) - openvpn_snprintf (command_line, sizeof (command_line), - IFCONFIG_PATH " %s %s %s mtu %d netmask 255.255.255.255 up", + argv_printf (&argv, + "%s %s %s %s mtu %d netmask 255.255.255.255 up", + IFCONFIG_PATH, actual, ifconfig_local, ifconfig_remote_netmask, tun_mtu ); else - openvpn_snprintf (command_line, sizeof (command_line), - IFCONFIG_PATH " %s %s netmask %s mtu %d broadcast %s link0", + argv_printf (&argv, + "%s %s %s netmask %s mtu %d broadcast %s link0", + IFCONFIG_PATH, actual, ifconfig_local, ifconfig_remote_netmask, tun_mtu, ifconfig_broadcast ); - msg (M_INFO, "%s", command_line); - system_check (command_line, es, S_FATAL, "OpenBSD ifconfig failed"); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, es, S_FATAL, "OpenBSD ifconfig failed"); tt->did_ifconfig = true; #elif defined(TARGET_NETBSD) if (tun) - openvpn_snprintf (command_line, sizeof (command_line), - IFCONFIG_PATH " %s %s %s mtu %d netmask 255.255.255.255 up", + argv_printf (&argv, + "%s %s %s %s mtu %d netmask 255.255.255.255 up", + IFCONFIG_PATH, actual, ifconfig_local, ifconfig_remote_netmask, @@ -722,16 +734,17 @@ do_ifconfig (struct tuntap *tt, * so we don't need the "link0" extra parameter to specify we want to do * tunneling at the ethernet level */ - openvpn_snprintf (command_line, sizeof (command_line), - IFCONFIG_PATH " %s %s netmask %s mtu %d broadcast %s", + argv_printf (&argv, + "%s %s %s netmask %s mtu %d broadcast %s", + IFCONFIG_PATH, actual, ifconfig_local, ifconfig_remote_netmask, tun_mtu, ifconfig_broadcast ); - msg (M_INFO, "%s", command_line); - system_check (command_line, es, S_FATAL, "NetBSD ifconfig failed"); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, es, S_FATAL, "NetBSD ifconfig failed"); tt->did_ifconfig = true; #elif defined(TARGET_DARWIN) @@ -740,18 +753,20 @@ do_ifconfig (struct tuntap *tt, * Darwin (i.e. Mac OS X) seems to exhibit similar behaviour to OpenBSD... */ - openvpn_snprintf (command_line, sizeof (command_line), - IFCONFIG_PATH " %s delete", + argv_printf (&argv, + "%s %s delete", + IFCONFIG_PATH, actual); - msg (M_INFO, "%s", command_line); - system_check (command_line, es, 0, NULL); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, es, 0, NULL); msg (M_INFO, "NOTE: Tried to delete pre-existing tun/tap instance -- No Problem if failure"); /* example: ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 netmask 255.255.255.255 up */ if (tun) - openvpn_snprintf (command_line, sizeof (command_line), - IFCONFIG_PATH " %s %s %s mtu %d netmask 255.255.255.255 up", + argv_printf (&argv, + "%s %s %s %s mtu %d netmask 255.255.255.255 up", + IFCONFIG_PATH, actual, ifconfig_local, ifconfig_remote_netmask, @@ -760,8 +775,9 @@ do_ifconfig (struct tuntap *tt, else { if (tt->topology == TOP_SUBNET) - openvpn_snprintf (command_line, sizeof (command_line), - IFCONFIG_PATH " %s %s %s netmask %s mtu %d up", + argv_printf (&argv, + "%s %s %s %s netmask %s mtu %d up", + IFCONFIG_PATH, actual, ifconfig_local, ifconfig_local, @@ -769,16 +785,17 @@ do_ifconfig (struct tuntap *tt, tun_mtu ); else - openvpn_snprintf (command_line, sizeof (command_line), - IFCONFIG_PATH " %s %s netmask %s mtu %d up", + argv_printf (&argv, + "%s %s %s netmask %s mtu %d up", + IFCONFIG_PATH, actual, ifconfig_local, ifconfig_remote_netmask, tun_mtu ); } - msg (M_INFO, "%s", command_line); - system_check (command_line, es, S_FATAL, "Mac OS X ifconfig failed"); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, es, S_FATAL, "Mac OS X ifconfig failed"); tt->did_ifconfig = true; /* Add a network route for the local tun interface */ @@ -797,8 +814,9 @@ do_ifconfig (struct tuntap *tt, /* example: ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 netmask 255.255.255.255 up */ if (tun) - openvpn_snprintf (command_line, sizeof (command_line), - IFCONFIG_PATH " %s %s %s mtu %d netmask 255.255.255.255 up", + argv_printf (&argv, + "%s %s %s %s mtu %d netmask 255.255.255.255 up", + IFCONFIG_PATH, actual, ifconfig_local, ifconfig_remote_netmask, @@ -806,8 +824,9 @@ do_ifconfig (struct tuntap *tt, ); else { if (tt->topology == TOP_SUBNET) - openvpn_snprintf (command_line, sizeof (command_line), - IFCONFIG_PATH " %s %s %s netmask %s mtu %d up", + argv_printf (&argv, + "%s %s %s %s netmask %s mtu %d up", + IFCONFIG_PATH, actual, ifconfig_local, ifconfig_local, @@ -815,8 +834,9 @@ do_ifconfig (struct tuntap *tt, tun_mtu ); else - openvpn_snprintf (command_line, sizeof (command_line), - IFCONFIG_PATH " %s %s netmask %s mtu %d up", + argv_printf (&argv, + "%s %s %s netmask %s mtu %d up", + IFCONFIG_PATH, actual, ifconfig_local, ifconfig_remote_netmask, @@ -824,8 +844,8 @@ do_ifconfig (struct tuntap *tt, ); } - msg (M_INFO, "%s", command_line); - system_check (command_line, es, S_FATAL, "FreeBSD ifconfig failed"); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, es, S_FATAL, "FreeBSD ifconfig failed"); tt->did_ifconfig = true; /* Add a network route for the local tun interface */ @@ -882,6 +902,7 @@ do_ifconfig (struct tuntap *tt, #else msg (M_FATAL, "Sorry, but I don't know how to do 'ifconfig' commands on this operating system. You should ifconfig your TUN/TAP device manually or use an --up script."); #endif + argv_reset (&argv); } gc_free (&gc); } @@ -1216,13 +1237,14 @@ close_tun (struct tuntap *tt) { if (tt->type != DEV_TYPE_NULL && tt->did_ifconfig) { - char command_line[256]; + struct argv argv; struct gc_arena gc = gc_new (); + argv_init (&argv); #ifdef CONFIG_FEATURE_IPROUTE if (is_tun_p2p (tt)) { - openvpn_snprintf (command_line, sizeof (command_line), + argv_printf (&argv, "%s addr del dev %s local %s peer %s", iproute_path, tt->actual_name, @@ -1232,7 +1254,7 @@ close_tun (struct tuntap *tt) } else { - openvpn_snprintf (command_line, sizeof (command_line), + argv_printf (&argv, "%s addr del dev %s %s/%d", iproute_path, tt->actual_name, @@ -1241,15 +1263,17 @@ close_tun (struct tuntap *tt) ); } #else - openvpn_snprintf (command_line, sizeof (command_line), - IFCONFIG_PATH " %s 0.0.0.0", + argv_printf (&argv, + "%s %s 0.0.0.0", + IFCONFIG_PATH, tt->actual_name ); #endif - msg (M_INFO, "%s", command_line); - system_check (command_line, NULL, 0, "Linux ip addr del failed"); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, NULL, 0, "Linux ip addr del failed"); + argv_reset (&argv); gc_free (&gc); } close_tun_generic (tt); @@ -1471,16 +1495,19 @@ close_tun (struct tuntap *tt) static void solaris_error_close (struct tuntap *tt, const struct env_set *es, const char *actual) { - char command_line[256]; + struct argv argv; + argv_init (&argv); - openvpn_snprintf (command_line, sizeof (command_line), - IFCONFIG_PATH " %s unplumb", + argv_printf (&argv, + "%s %s unplumb", + IFCONFIG_PATH, actual); - msg (M_INFO, "%s", command_line); - system_check (command_line, es, 0, "Solaris ifconfig unplumb failed"); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, es, 0, "Solaris ifconfig unplumb failed"); close_tun (tt); msg (M_FATAL, "Solaris ifconfig failed"); + argv_reset (&argv); } int @@ -3275,7 +3302,7 @@ dhcp_renew (const struct tuntap *tt) */ static void -netsh_command (const char *cmd, int n) +netsh_command (const struct argv *a, int n) { int i; for (i = 0; i < n; ++i) @@ -3283,8 +3310,8 @@ netsh_command (const char *cmd, int n) bool status; openvpn_sleep (1); netcmd_semaphore_lock (); - msg (M_INFO, "NETSH: %s", cmd); - status = system_check (cmd, NULL, 0, "ERROR: netsh command failed"); + argv_msg_prefix (M_INFO, a, "NETSH"); + status = openvpn_execve_check (a, NULL, 0, "ERROR: netsh command failed"); netcmd_semaphore_release (); if (status) return; @@ -3376,7 +3403,7 @@ netsh_ifconfig_options (const char *type, const bool test_first) { struct gc_arena gc = gc_new (); - struct buffer out = alloc_buf_gc (256, &gc); + struct argv argv = argv_new (); bool delete_first = false; /* first check if we should delete existing DNS/WINS settings from TAP interface */ @@ -3391,11 +3418,12 @@ netsh_ifconfig_options (const char *type, /* delete existing DNS/WINS settings from TAP interface */ if (delete_first) { - buf_init (&out, 0); - buf_printf (&out, "netsh interface ip delete %s \"%s\" all", - type, - flex_name); - netsh_command (BSTR(&out), 2); + argv_printf (&argv, "%s%s interface ip delete %s %s all", + get_win_sys_path(), + NETSH_PATH_SUFFIX, + type, + flex_name); + netsh_command (&argv, 2); } /* add new DNS/WINS settings to TAP interface */ @@ -3407,15 +3435,16 @@ netsh_ifconfig_options (const char *type, if (delete_first || !test_first || !ip_addr_member_of (addr_list[i], current)) { const char *fmt = count ? - "netsh interface ip add %s \"%s\" %s" - : "netsh interface ip set %s \"%s\" static %s"; - - buf_init (&out, 0); - buf_printf (&out, fmt, - type, - flex_name, - print_in_addr_t (addr_list[i], 0, &gc)); - netsh_command (BSTR(&out), 2); + "%s%s interface ip add %s %s %s" + : "%s%s interface ip set %s %s static %s"; + + argv_printf (&argv, fmt, + get_win_sys_path(), + NETSH_PATH_SUFFIX, + type, + flex_name, + print_in_addr_t (addr_list[i], 0, &gc)); + netsh_command (&argv, 2); ++count; } @@ -3429,6 +3458,7 @@ netsh_ifconfig_options (const char *type, } } + argv_reset (&argv); gc_free (&gc); } @@ -3458,7 +3488,7 @@ netsh_ifconfig (const struct tuntap_options *to, const unsigned int flags) { struct gc_arena gc = gc_new (); - struct buffer out = alloc_buf_gc (256, &gc); + struct argv argv = argv_new (); const IP_ADAPTER_INFO *ai = NULL; const IP_PER_ADAPTER_INFO *pai = NULL; @@ -3482,14 +3512,15 @@ netsh_ifconfig (const struct tuntap_options *to, else { /* example: netsh interface ip set address my-tap static 10.3.0.1 255.255.255.0 */ - buf_init (&out, 0); - buf_printf (&out, - "netsh interface ip set address \"%s\" static %s %s", - flex_name, - print_in_addr_t (ip, 0, &gc), - print_in_addr_t (netmask, 0, &gc)); - - netsh_command (BSTR(&out), 4); + argv_printf (&argv, + "%s%s interface ip set address %s static %s %s", + get_win_sys_path(), + NETSH_PATH_SUFFIX, + flex_name, + print_in_addr_t (ip, 0, &gc), + print_in_addr_t (netmask, 0, &gc)); + + netsh_command (&argv, 4); } } @@ -3517,6 +3548,7 @@ netsh_ifconfig (const struct tuntap_options *to, BOOL_CAST (flags & NI_TEST_FIRST)); } + argv_reset (&argv); gc_free (&gc); } @@ -3524,17 +3556,19 @@ static void netsh_enable_dhcp (const struct tuntap_options *to, const char *actual_name) { - struct gc_arena gc = gc_new (); - struct buffer out = alloc_buf_gc (256, &gc); + struct argv argv; + argv_init (&argv); /* example: netsh interface ip set address my-tap dhcp */ - buf_printf (&out, - "netsh interface ip set address \"%s\" dhcp", - actual_name); + argv_printf (&argv, + "%s%s interface ip set address %s dhcp", + get_win_sys_path(), + NETSH_PATH_SUFFIX, + actual_name); - netsh_command (BSTR(&out), 4); + netsh_command (&argv, 4); - gc_free (&gc); + argv_reset (&argv); } /* diff --git a/win32.c b/win32.c index 516bf8e..ec9247e 100644 --- a/win32.c +++ b/win32.c @@ -35,6 +35,7 @@ #include "mtu.h" #include "sig.h" #include "win32.h" +#include "misc.h" #include "memdbg.h" @@ -69,6 +70,11 @@ struct window_title window_title; /* GLOBAL*/ struct semaphore netcmd_semaphore; /* GLOBAL */ +/* + * Windows system pathname such as c:\windows + */ +static char *win_sys_path = NULL; /* GLOBAL */ + void init_win32 (void) { @@ -100,6 +106,7 @@ uninit_win32 (void) window_title_restore (&window_title); win32_signal_close (&win32_signal); WSACleanup (); + free (win_sys_path); } void @@ -816,4 +823,174 @@ win_safe_filename (const char *fn) return true; } +/* + * Service functions for openvpn_execve + */ + +static char * +env_block (const struct env_set *es) +{ + if (es) + { + struct env_item *e; + char *ret; + char *p; + size_t nchars = 1; + + for (e = es->list; e != NULL; e = e->next) + nchars += strlen (e->string) + 1; + + ret = (char *) malloc (nchars); + check_malloc_return (ret); + + p = ret; + for (e = es->list; e != NULL; e = e->next) + { + if (env_allowed (e->string)) + { + strcpy (p, e->string); + p += strlen (e->string) + 1; + } + } + *p = '\0'; + return ret; + } + else + return NULL; +} + +static char * +cmd_line (const struct argv *a) +{ + size_t nchars = 1; + size_t maxlen = 0; + size_t i; + struct buffer buf; + char *work = NULL; + + if (!a) + return NULL; + + for (i = 0; i < a->argc; ++i) + { + const char *arg = a->argv[i]; + const size_t len = strlen (arg); + nchars += len + 3; + if (len > maxlen) + maxlen = len; + } + + work = (char *) malloc (maxlen + 1); + check_malloc_return (work); + buf = alloc_buf (nchars); + + for (i = 0; i < a->argc; ++i) + { + const char *arg = a->argv[i]; + strcpy (work, arg); + string_mod (work, CC_PRINT, CC_DOUBLE_QUOTE|CC_CRLF, '_'); + if (i) + buf_printf (&buf, " "); + if (string_class (work, CC_ANY, CC_SPACE)) + buf_printf (&buf, "%s", work); + else + buf_printf (&buf, "\"%s\"", work); + } + + free (work); + return BSTR(&buf); +} + +/* + * Attempt to simulate fork/execve on Windows + */ +int +openvpn_execve (const struct argv *a, const struct env_set *es, const unsigned int flags) +{ + int ret = -1; + if (a && a->argv[0]) + { + if (openvpn_execve_allowed (flags)) + { + STARTUPINFO start_info; + PROCESS_INFORMATION proc_info; + + char *env = env_block (es); + char *cl = cmd_line (a); + char *cmd = a->argv[0]; + + CLEAR (start_info); + CLEAR (proc_info); + + /* fill in STARTUPINFO struct */ + GetStartupInfo(&start_info); + start_info.cb = sizeof(start_info); + start_info.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW; + start_info.wShowWindow = SW_HIDE; + start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + start_info.hStdOutput = start_info.hStdError = GetStdHandle(STD_OUTPUT_HANDLE); + + if (CreateProcess (cmd, cl, NULL, NULL, FALSE, 0, env, NULL, &start_info, &proc_info)) + { + DWORD exit_status = 0; + CloseHandle (proc_info.hThread); + WaitForSingleObject (proc_info.hProcess, INFINITE); + if (GetExitCodeProcess (proc_info.hProcess, &exit_status)) + ret = (int)exit_status; + else + msg (M_WARN|M_ERRNO, "openvpn_execve: GetExitCodeProcess %s failed", cmd); + CloseHandle (proc_info.hProcess); + } + else + { + msg (M_WARN|M_ERRNO, "openvpn_execve: CreateProcess %s failed", cmd); + } + free (cl); + free (env); + } + else + { + msg (M_WARN, "openvpn_execve: external program may not be called due to setting of --script-security level"); + } + } + else + { + msg (M_WARN, "openvpn_execve: called with empty argv"); + } + return ret; +} + +char * +get_win_sys_path (void) +{ + ASSERT (win_sys_path); + return win_sys_path; +} + +void +set_win_sys_path (const char *newpath, struct env_set *es) +{ + free (win_sys_path); + win_sys_path = string_alloc (newpath, NULL); + setenv_str (es, SYS_PATH_ENV_VAR_NAME, win_sys_path); /* route.exe needs this */ +} + +void +set_win_sys_path_via_env (struct env_set *es) +{ + char buf[256]; + DWORD status = GetEnvironmentVariable (SYS_PATH_ENV_VAR_NAME, buf, sizeof(buf)); + if (!status) + msg (M_ERR, "Cannot find environmental variable %s", SYS_PATH_ENV_VAR_NAME); + if (status > sizeof (buf) - 1) + msg (M_FATAL, "String overflow attempting to read environmental variable %s", SYS_PATH_ENV_VAR_NAME); + set_win_sys_path (buf, es); +} + +void +env_set_add_win32 (struct env_set *es) +{ + set_win_sys_path (DEFAULT_WIN_SYS_PATH, es); +} + #endif diff --git a/win32.h b/win32.h index 8cf8af2..b243bf2 100644 --- a/win32.h +++ b/win32.h @@ -28,6 +28,12 @@ #include "mtu.h" +/* location of executables */ +#define SYS_PATH_ENV_VAR_NAME "SystemRoot" /* environmental variable name that normally contains the system path */ +#define DEFAULT_WIN_SYS_PATH "C:\\WINDOWS" /* --win-sys default value */ +#define NETSH_PATH_SUFFIX "\\system32\\netsh.exe" +#define WIN_ROUTE_PATH_SUFFIX "\\system32\\route.exe" + /* * Win32-specific OpenVPN code, targetted at the mingw * development environment. @@ -250,5 +256,14 @@ bool init_security_attributes_allow_all (struct security_attributes *obj); /* return true if filename is safe to be used on Windows */ bool win_safe_filename (const char *fn); +/* add constant environmental variables needed by Windows */ +struct env_set; +void env_set_add_win32 (struct env_set *es); + +/* get and set the current windows system path */ +void set_win_sys_path (const char *newpath, struct env_set *es); +void set_win_sys_path_via_env (struct env_set *es); +char *get_win_sys_path (void); + #endif #endif -- cgit