/* * 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. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (see the file COPYING included with this * distribution); if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "syshead.h" #include "buffer.h" #include "misc.h" #include "tun.h" #include "error.h" #include "thread.h" #include "otime.h" #include "plugin.h" #include "options.h" #include "manage.h" #include "crypto.h" #include "route.h" #include "win32.h" #include "memdbg.h" #ifdef CONFIG_FEATURE_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 */ /* contains SM_x value defined in misc.h */ int script_method = SM_EXECVE; /* GLOBAL */ /* Redefine the top level directory of the filesystem to restrict access to files for security */ void do_chroot (const char *path) { if (path) { #ifdef HAVE_CHROOT const char *top = "/"; if (chroot (path)) msg (M_ERR, "chroot to '%s' failed", path); if (openvpn_chdir (top)) msg (M_ERR, "cd to '%s' failed", top); msg (M_INFO, "chroot to '%s' and cd to '%s' succeeded", path, top); #else msg (M_FATAL, "Sorry but I can't chroot to '%s' because this operating system doesn't appear to support the chroot() system call", path); #endif } } /* Get/Set UID of process */ bool get_user (const char *username, struct user_state *state) { bool ret = false; CLEAR (*state); if (username) { #if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID) state->pw = getpwnam (username); if (!state->pw) msg (M_ERR, "failed to find UID for user %s", username); state->username = username; ret = true; #else msg (M_FATAL, "cannot get UID for user %s -- platform lacks getpwname() or setuid() system calls", username); #endif } return ret; } void set_user (const struct user_state *state) { #if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID) if (state->username && state->pw) { if (setuid (state->pw->pw_uid)) msg (M_ERR, "setuid('%s') failed", state->username); msg (M_INFO, "UID set to %s", state->username); } #endif } /* Get/Set GID of process */ bool get_group (const char *groupname, struct group_state *state) { bool ret = false; CLEAR (*state); if (groupname) { #if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID) state->gr = getgrnam (groupname); if (!state->gr) msg (M_ERR, "failed to find GID for group %s", groupname); state->groupname = groupname; ret = true; #else msg (M_FATAL, "cannot get GID for group %s -- platform lacks getgrnam() or setgid() system calls", groupname); #endif } return ret; } void set_group (const struct group_state *state) { #if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID) if (state->groupname && state->gr) { if (setgid (state->gr->gr_gid)) msg (M_ERR, "setgid('%s') failed", state->groupname); msg (M_INFO, "GID set to %s", state->groupname); #ifdef HAVE_SETGROUPS { gid_t gr_list[1]; gr_list[0] = state->gr->gr_gid; if (setgroups (1, gr_list)) msg (M_ERR, "setgroups('%s') failed", state->groupname); } #endif } #endif } /* Change process priority */ void set_nice (int niceval) { if (niceval) { #ifdef HAVE_NICE errno = 0; nice (niceval); if (errno != 0) msg (M_WARN | M_ERRNO, "WARNING: nice %d failed", niceval); else msg (M_INFO, "nice %d succeeded", niceval); #else msg (M_WARN, "WARNING: nice %d failed (function not implemented)", niceval); #endif } } /* * Pass tunnel endpoint and MTU parms to a user-supplied script. * Used to execute the up/down script/plugins. */ void run_up_down (const char *command, const struct plugin_list *plugins, int plugin_type, const char *arg, int tun_mtu, int link_mtu, const char *ifconfig_local, const char* ifconfig_remote, const char *context, const char *signal_text, const char *script_type, struct env_set *es) { struct gc_arena gc = gc_new (); if (signal_text) setenv_str (es, "signal", signal_text); setenv_str (es, "script_context", context); setenv_int (es, "tun_mtu", tun_mtu); setenv_int (es, "link_mtu", link_mtu); setenv_str (es, "dev", arg); if (!ifconfig_local) ifconfig_local = ""; if (!ifconfig_remote) ifconfig_remote = ""; if (!context) context = ""; if (plugin_defined (plugins, plugin_type)) { 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 = fopen (filename, "w"); if (!state->fp) msg (M_ERR, "Open error on pid file %s", filename); state->filename = filename; } } /* Write our PID to a file */ void write_pid (const struct pid_state *state) { if (state->filename && state->fp) { unsigned int pid = openvpn_getpid (); fprintf(state->fp, "%u\n", pid); if (fclose (state->fp)) msg (M_ERR, "Close error on pid file %s", state->filename); } } /* Get current PID */ unsigned int openvpn_getpid () { #ifdef WIN32 return (unsigned int) GetCurrentProcessId (); #else #ifdef HAVE_GETPID return (unsigned int) getpid (); #else return 0; #endif #endif } /* Disable paging */ void do_mlockall(bool print_msg) { #ifdef HAVE_MLOCKALL if (mlockall (MCL_CURRENT | MCL_FUTURE)) msg (M_WARN | M_ERRNO, "WARNING: mlockall call failed"); else if (print_msg) msg (M_INFO, "mlockall call succeeded"); #else msg (M_WARN, "WARNING: mlockall call failed (function not implemented)"); #endif } #ifndef HAVE_DAEMON int daemon(int nochdir, int noclose) { #if defined(HAVE_FORK) && defined(HAVE_SETSID) switch (fork()) { case -1: return (-1); case 0: break; default: openvpn_exit (OPENVPN_EXIT_STATUS_GOOD); /* exit point */ } if (setsid() == -1) return (-1); if (!nochdir) openvpn_chdir ("/"); if (!noclose) set_std_files_to_null (false); #else msg (M_FATAL, "Sorry but I can't become a daemon because this operating system doesn't appear to support either the daemon() or fork() system calls"); #endif return (0); } #endif /* * Set standard file descriptors to /dev/null */ void set_std_files_to_null (bool stdin_only) { #if defined(HAVE_DUP) && defined(HAVE_DUP2) int fd; if ((fd = open ("/dev/null", O_RDWR, 0)) != -1) { dup2 (fd, 0); if (!stdin_only) { dup2 (fd, 1); dup2 (fd, 2); } if (fd > 2) close (fd); } #endif } /* * Wrapper for chdir library function */ int openvpn_chdir (const char* dir) { #ifdef HAVE_CHDIR return chdir (dir); #else return -1; #endif } /* * dup inetd/xinetd socket descriptor and save */ int inetd_socket_descriptor = SOCKET_UNDEFINED; /* GLOBAL */ void save_inetd_socket_descriptor (void) { inetd_socket_descriptor = INETD_SOCKET_DESCRIPTOR; #if defined(HAVE_DUP) && defined(HAVE_DUP2) /* use handle passed by inetd/xinetd */ if ((inetd_socket_descriptor = dup (INETD_SOCKET_DESCRIPTOR)) < 0) msg (M_ERR, "INETD_SOCKET_DESCRIPTOR dup(%d) failed", INETD_SOCKET_DESCRIPTOR); set_std_files_to_null (true); #endif } /* * Warn if a given file is group/others accessible. */ void warn_if_group_others_accessible (const char* filename) { #ifndef WIN32 #ifdef HAVE_STAT #if ENABLE_INLINE_FILES if (strcmp (filename, INLINE_FILE_TAG)) #endif { 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 } /* * convert system() return into a success/failure value */ bool system_ok (int stat) { #ifdef WIN32 return stat == 0; #else return stat != -1 && WIFEXITED (stat) && WEXITSTATUS (stat) == 0; #endif } /* * did system() call execute the given command? */ bool system_executed (int stat) { #ifdef WIN32 return stat != -1; #else return stat != -1 && WEXITSTATUS (stat) != 127; #endif } /* * Print an error message based on the status code returned by system(). */ const char * system_error_message (int stat, struct gc_arena *gc) { struct buffer out = alloc_buf_gc (256, gc); #ifdef WIN32 if (stat == -1) 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 (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_EXECVE) if (openvpn_execve_allowed (flags)) { if (script_method == SM_EXECVE) { const char *cmd = a->argv[0]; char *const *argv = a->argv; char *const *envp = (char *const *)make_env_array (es, true, &gc); pid_t pid; pid = fork (); if (pid == (pid_t)0) /* child side */ { execve (cmd, argv, envp); exit (127); } else if (pid < (pid_t)0) /* fork failed */ ; else /* parent side */ { if (waitpid (pid, &ret, 0) != pid) ret = -1; } } else if (script_method == SM_SYSTEM) { ret = openvpn_system (argv_system_str (a), es, flags); } else { ASSERT (0); } } 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_WARN, "openvpn_execve: called with empty argv"); } gc_free (&gc); return ret; } #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; perf_push (PERF_SCRIPT); /* * add env_set to environment. */ if (flags & S_SCRIPT) env_set_add_to_environment (es); /* debugging */ dmsg (D_SCRIPT, "SYSTEM[%u] '%s'", flags, command); if (flags & S_SCRIPT) env_set_print (D_SCRIPT, es); /* * execute the command */ ret = system (command); /* debugging */ dmsg (D_SCRIPT, "SYSTEM return=%u", ret); /* * remove env_set from environment */ if (flags & S_SCRIPT) env_set_remove_from_environment (es); perf_pop (); return ret; #else msg (M_FATAL, "Sorry but I can't execute the shell command '%s' because this operating system doesn't appear to support the system() call", command); return -1; /* NOTREACHED */ #endif } /* * Initialize random number seed. random() is only used * when "weak" random numbers are acceptable. * OpenSSL routines are always used when cryptographically * strong random numbers are required. */ void init_random_seed(void) { #ifdef HAVE_GETTIMEOFDAY struct timeval tv; if (!gettimeofday (&tv, NULL)) { const unsigned int seed = (unsigned int) tv.tv_sec ^ tv.tv_usec; srandom (seed); } #else /* HAVE_GETTIMEOFDAY */ const time_t current = time (NULL); srandom ((unsigned int)current); #endif /* HAVE_GETTIMEOFDAY */ } /* thread-safe strerror */ const char * strerror_ts (int errnum, struct gc_arena *gc) { #ifdef HAVE_STRERROR struct buffer out = alloc_buf_gc (256, gc); mutex_lock_static (L_STRERR); buf_printf (&out, "%s", openvpn_strerror (errnum, gc)); mutex_unlock_static (L_STRERR); return BSTR (&out); #else return "[error string unavailable]"; #endif } /* * Set environmental variable (int or string). * * On Posix, we use putenv for portability, * and put up with its painful semantics * that require all the support code below. */ /* General-purpose environmental variable set functions */ static char * construct_name_value (const char *name, const char *value, struct gc_arena *gc) { struct buffer out; ASSERT (name); if (!value) value = ""; out = alloc_buf_gc (strlen (name) + strlen (value) + 2, gc); buf_printf (&out, "%s=%s", name, value); return BSTR (&out); } bool deconstruct_name_value (const char *str, const char **name, const char **value, struct gc_arena *gc) { char *cp; ASSERT (str); ASSERT (name && value); *name = cp = string_alloc (str, gc); *value = NULL; while ((*cp)) { if (*cp == '=' && !*value) { *cp = 0; *value = cp + 1; } ++cp; } return *name && *value; } static bool env_string_equal (const char *s1, const char *s2) { int c1, c2; ASSERT (s1); ASSERT (s2); while (true) { c1 = *s1++; c2 = *s2++; if (c1 == '=') c1 = 0; if (c2 == '=') c2 = 0; if (!c1 && !c2) return true; if (c1 != c2) break; } return false; } static bool remove_env_item (const char *str, const bool do_free, struct env_item **list) { struct env_item *current, *prev; ASSERT (str); ASSERT (list); for (current = *list, prev = NULL; current != NULL; current = current->next) { if (env_string_equal (current->string, str)) { if (prev) prev->next = current->next; else *list = current->next; if (do_free) { memset (current->string, 0, strlen (current->string)); free (current->string); free (current); } return true; } prev = current; } return false; } static void add_env_item (char *str, const bool do_alloc, struct env_item **list, struct gc_arena *gc) { struct env_item *item; ASSERT (str); ASSERT (list); ALLOC_OBJ_GC (item, struct env_item, gc); item->string = do_alloc ? string_alloc (str, gc): str; item->next = *list; *list = item; } /* struct env_set functions */ static bool env_set_del_nolock (struct env_set *es, const char *str) { 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; mutex_lock_static (L_ENV_SET); ALLOC_OBJ_CLEAR_GC (es, struct env_set, gc); es->list = NULL; es->gc = gc; mutex_unlock_static (L_ENV_SET); return es; } void env_set_destroy (struct env_set *es) { mutex_lock_static (L_ENV_SET); if (es && es->gc == NULL) { struct env_item *e = es->list; while (e) { struct env_item *next = e->next; free (e->string); free (e); e = next; } free (es); } mutex_unlock_static (L_ENV_SET); } bool env_set_del (struct env_set *es, const char *str) { bool ret; ASSERT (es); ASSERT (str); mutex_lock_static (L_ENV_SET); ret = env_set_del_nolock (es, str); mutex_unlock_static (L_ENV_SET); return ret; } void env_set_add (struct env_set *es, const char *str) { ASSERT (es); ASSERT (str); mutex_lock_static (L_ENV_SET); env_set_add_nolock (es, str); mutex_unlock_static (L_ENV_SET); } void env_set_print (int msglevel, const struct env_set *es) { if (check_debug_level (msglevel)) { const struct env_item *e; int i; if (es) { mutex_lock_static (L_ENV_SET); e = es->list; i = 0; while (e) { if (env_safe_to_print (e->string)) msg (msglevel, "ENV [%d] '%s'", i, e->string); ++i; e = e->next; } mutex_unlock_static (L_ENV_SET); } } } void env_set_inherit (struct env_set *es, const struct env_set *src) { const struct env_item *e; ASSERT (es); if (src) { mutex_lock_static (L_ENV_SET); e = src->list; while (e) { env_set_add_nolock (es, e->string); e = e->next; } mutex_unlock_static (L_ENV_SET); } } void env_set_add_to_environment (const struct env_set *es) { if (es) { struct gc_arena gc = gc_new (); const struct env_item *e; mutex_lock_static (L_ENV_SET); e = es->list; while (e) { const char *name; const char *value; if (deconstruct_name_value (e->string, &name, &value, &gc)) setenv_str (NULL, name, value); e = e->next; } mutex_unlock_static (L_ENV_SET); gc_free (&gc); } } void env_set_remove_from_environment (const struct env_set *es) { if (es) { struct gc_arena gc = gc_new (); const struct env_item *e; mutex_lock_static (L_ENV_SET); e = es->list; while (e) { const char *name; const char *value; if (deconstruct_name_value (e->string, &name, &value, &gc)) setenv_del (NULL, name); e = e->next; } mutex_unlock_static (L_ENV_SET); gc_free (&gc); } } #ifdef HAVE_PUTENV /* companion functions to putenv */ static struct env_item *global_env = NULL; /* GLOBAL */ static void manage_env (char *str) { remove_env_item (str, true, &global_env); add_env_item (str, false, &global_env, NULL); } #endif /* add/modify/delete environmental strings */ void 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); /*msg (M_INFO, "SETENV_ES '%s'", str);*/ } else env_set_del (es, name_tmp); } else { #if defined(WIN32) { /*msg (M_INFO, "SetEnvironmentVariable '%s' '%s'", name_tmp, val_tmp ? val_tmp : "NULL");*/ if (!SetEnvironmentVariable (name_tmp, val_tmp)) msg (M_WARN | M_ERRNO, "SetEnvironmentVariable failed, name='%s', value='%s'", name_tmp, val_tmp ? val_tmp : "NULL"); } #elif defined(HAVE_PUTENV) { char *str = construct_name_value (name_tmp, val_tmp, NULL); int status; mutex_lock_static (L_PUTENV); status = putenv (str); /*msg (M_INFO, "PUTENV '%s'", str);*/ if (!status) manage_env (str); mutex_unlock_static (L_PUTENV); if (status) msg (M_WARN | M_ERRNO, "putenv('%s') failed", str); } #endif } gc_free (&gc); } /* * 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); } /* * Go to sleep for n milliseconds. */ void sleep_milliseconds (unsigned int n) { #ifdef WIN32 Sleep (n); #else struct timeval tv; tv.tv_sec = n / 1000; tv.tv_usec = (n % 1000) * 1000; select (0, NULL, NULL, NULL, &tv); #endif } /* * Go to sleep indefinitely. */ void sleep_until_signal (void) { #ifdef WIN32 ASSERT (0); #else select (0, NULL, NULL, NULL, NULL); #endif } /* return true if filename can be opened for read */ bool test_file (const char *filename) { bool ret = false; if (filename) { FILE *fp = fopen (filename, "r"); if (fp) { fclose (fp); ret = true; } } dmsg (D_TEST_FILE, "TEST FILE '%s' [%d]", filename ? filename : "UNDEF", ret); return ret; } #ifdef USE_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; mutex_lock_static (L_CREATE_TEMP); ++counter; mutex_unlock_static (L_CREATE_TEMP); prng_bytes (rndbytes, sizeof rndbytes); rndstr = format_hex_ex (rndbytes, sizeof rndbytes, 40, 0, NULL, gc); buf_printf (&fname, PACKAGE "_%s_%s.tmp", prefix, rndstr); retfname = gen_path (directory, BSTR (&fname), gc); if (!retfname) { msg (M_FATAL, "Failed to create temporary filename and path"); return NULL; } /* Atomically create the file. Errors out if the file already exists. */ fd = open (retfname, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR); if (fd != -1) { close (fd); return retfname; } else if (fd == -1 && errno != EEXIST) { /* Something else went wrong, no need to retry. */ struct gc_arena gcerr = gc_new (); msg (M_FATAL, "Could not create temporary file '%s': %s", retfname, strerror_ts (errno, &gcerr)); gc_free (&gcerr); return NULL; } } while (attempts < 6); 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 .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) { const char *safe_filename = string_mod_const (filename, CC_ALNUM|CC_UNDERBAR|CC_DASH|CC_DOT|CC_AT, 0, '_', gc); if (safe_filename && strcmp (safe_filename, ".") && 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; } /* delete a file, return true if succeeded */ bool delete_file (const char *filename) { #if defined(WIN32) return (DeleteFile (filename) != 0); #elif defined(HAVE_UNLINK) return (unlink (filename) == 0); #else return false; #endif } 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; } #ifdef HAVE_GETPASS static FILE * open_tty (const bool write) { FILE *ret; ret = fopen ("/dev/tty", write ? "w" : "r"); if (!ret) ret = write ? stderr : stdin; return ret; } static void close_tty (FILE *fp) { if (fp != stderr && fp != stdin) fclose (fp); } #endif /* * Get input from console */ bool get_console_input (const char *prompt, const bool echo, char *input, const int capacity) { bool ret = false; ASSERT (prompt); ASSERT (input); ASSERT (capacity > 0); input[0] = '\0'; #if defined(WIN32) return get_console_input_win32 (prompt, echo, input, capacity); #elif defined(HAVE_GETPASS) if (echo) { FILE *fp; fp = open_tty (true); fprintf (fp, "%s", prompt); fflush (fp); close_tty (fp); fp = open_tty (false); if (fgets (input, capacity, fp) != NULL) { chomp (input); ret = true; } close_tty (fp); } else { char *gp = getpass (prompt); if (gp) { strncpynt (input, gp, capacity); memset (gp, 0, strlen (gp)); ret = true; } } #else msg (M_FATAL, "Sorry, but I can't get console input on this OS"); #endif return ret; } /* * Get and store a username/password */ bool get_user_pass (struct user_pass *up, const char *auth_file, const char *prefix, const unsigned int flags) { struct gc_arena gc = gc_new (); if (!up->defined) { const bool from_stdin = (!auth_file || !strcmp (auth_file, "stdin")); if (flags & GET_USER_PASS_PREVIOUS_CREDS_FAILED) msg (M_WARN, "Note: previous '%s' credentials failed", prefix); #ifdef ENABLE_MANAGEMENT /* * Get username/password from standard input? */ if (management && ((auth_file && streq (auth_file, "management")) || (from_stdin && (flags & GET_USER_PASS_MANAGEMENT))) && management_query_user_pass_enabled (management)) { if (flags & GET_USER_PASS_PREVIOUS_CREDS_FAILED) management_auth_failure (management, prefix, "previous auth credentials failed"); if (!management_query_user_pass (management, up, prefix, flags)) { 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) { 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); } else { /* * Get username/password from a file. */ FILE *fp; #ifndef ENABLE_PASSWORD_SAVE /* * Unless ENABLE_PASSWORD_SAVE is defined, don't allow sensitive passwords * to be read from a file. */ if (flags & GET_USER_PASS_SENSITIVE) msg (M_FATAL, "Sorry, '%s' password cannot be read from a file", prefix); #endif warn_if_group_others_accessible (auth_file); fp = fopen (auth_file, "r"); if (!fp) msg (M_ERR, "Error opening '%s' auth file: %s", prefix, auth_file); 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; } #if AUTO_USERID static const char * get_platform_prefix (void) { #if defined(TARGET_LINUX) return "L"; #elif defined(TARGET_SOLARIS) return "S"; #elif defined(TARGET_OPENBSD) return "O"; #elif defined(TARGET_DARWIN) return "M"; #elif defined(TARGET_NETBSD) return "N"; #elif defined(TARGET_FREEBSD) return "F"; #elif defined(WIN32) return "W"; #else return "X"; #endif } void get_user_pass_auto_userid (struct user_pass *up, const char *tag) { struct gc_arena gc = gc_new (); MD5_CTX ctx; struct buffer buf; uint8_t macaddr[6]; static uint8_t digest [MD5_DIGEST_LENGTH]; static const uint8_t hashprefix[] = "AUTO_USERID_DIGEST"; CLEAR (*up); buf_set_write (&buf, (uint8_t*)up->username, USER_PASS_LEN); buf_printf (&buf, "%s", get_platform_prefix ()); if (get_default_gateway_mac_addr (macaddr)) { dmsg (D_AUTO_USERID, "GUPAU: macaddr=%s", format_hex_ex (macaddr, sizeof (macaddr), 0, 1, ":", &gc)); MD5_Init (&ctx); MD5_Update (&ctx, hashprefix, sizeof (hashprefix) - 1); MD5_Update (&ctx, macaddr, sizeof (macaddr)); MD5_Final (digest, &ctx); buf_printf (&buf, "%s", format_hex_ex (digest, sizeof (digest), 0, 256, " ", &gc)); } else { buf_printf (&buf, "UNKNOWN"); } 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; } } /* * 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; } #if ENABLE_INLINE_FILES static const char ** make_inline_array (const char *str, struct gc_arena *gc) { char line[OPTION_LINE_SIZE]; struct buffer buf; int len = 0; char **ret = NULL; int i = 0; 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; } #endif static const char ** make_arg_copy (char **p, struct gc_arena *gc) { char **ret = NULL; const int len = string_array_len ((const char **)p); const int max_parms = len + 1; int i; /* alloc return array */ ALLOC_ARRAY_CLEAR_GC (ret, char *, max_parms, gc); for (i = 0; i < len; ++i) ret[i] = p[i]; return (const char **)ret; } const char ** make_extended_arg_array (char **p, struct gc_arena *gc) { const int argc = string_array_len ((const char **)p); #if ENABLE_INLINE_FILES if (!strcmp (p[0], INLINE_FILE_TAG) && argc == 2) return make_inline_array (p[1], gc); else #endif if (argc == 0) return make_arg_array (NULL, NULL, gc); else if (argc == 1) return make_arg_array (p[0], NULL, gc); else if (argc == 2) return make_arg_array (p[0], p[1], gc); else return make_arg_copy (p, gc); } 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) { const char *bn = openvpn_basename (path); if (bn) { char *ret = string_alloc (bn, NULL); char *dot = strrchr (ret, '.'); if (dot) *dot = '\0'; if (ret[0] != '\0') return ret; } } return NULL; } const char * argv_system_str (const struct argv *a) { return a->system_str; } 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 const char * openvpn_basename (const char *path) { const char *ret; const int dirsep = OS_SPECIFIC_DIRSEP; if (path) { ret = strrchr (path, dirsep); if (ret && *ret) ++ret; else ret = path; if (*ret) return ret; } return NULL; }