diff options
author | Othmar Gsenger <otti@anytun.org> | 2008-04-12 11:38:42 +0000 |
---|---|---|
committer | Othmar Gsenger <otti@anytun.org> | 2008-04-12 11:38:42 +0000 |
commit | fffd213c8cba2135afda493d797c41c10354770e (patch) | |
tree | bb5eea1b12871d8c3fed0e687d83be3e504d11b2 /src/openvpn/misc.c | |
parent | svn cleanup (diff) |
big svn cleanup
Diffstat (limited to 'src/openvpn/misc.c')
-rw-r--r-- | src/openvpn/misc.c | 1367 |
1 files changed, 1367 insertions, 0 deletions
diff --git a/src/openvpn/misc.c b/src/openvpn/misc.c new file mode 100644 index 0000000..e190b7b --- /dev/null +++ b/src/openvpn/misc.c @@ -0,0 +1,1367 @@ +/* + * 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-2005 OpenVPN Solutions LLC <info@openvpn.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef WIN32 +#include "config-win32.h" +#else +#include "config.h" +#endif + +#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 "memdbg.h" + +/* 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, "Sorry but I can't setuid to '%s' because this operating system doesn't appear to support the 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, "Sorry but I can't setgid to '%s' because this operating system doesn't appear to support the 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 buffer cmd = alloc_buf_gc (256, &gc); + + 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), es)) + msg (M_FATAL, "ERROR: up/down plugin call failed"); + } + + if (command) + { + struct buffer cmd = alloc_buf_gc (256, &gc); + + ASSERT (arg); + + setenv_str (es, "script_type", script_type); + + buf_printf (&cmd, + "%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"); + } + + 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 +} + +/* + * 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. + */ +void +warn_if_group_others_accessible (const char* filename) +{ +#ifdef HAVE_STAT + 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 +} + +/* + * 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, "shell command did not execute -- "); + buf_printf (&out, "system() returned error code %d", stat); +#else + if (stat == -1) + buf_printf (&out, "shell command fork failed"); + else if (!WIFEXITED (stat)) + buf_printf (&out, "shell command did not exit normally"); + else + { + const int cmd_ret = WEXITSTATUS (stat); + if (!cmd_ret) + buf_printf (&out, "shell command exited normally"); + else if (cmd_ret == 127) + buf_printf (&out, "could not execute shell command"); + else + buf_printf (&out, "shell command exited with error status: %d", cmd_ret); + } +#endif + return (const char *)out.data; +} + +/* + * Run system(), exiting on error. + */ +bool +system_check (const char *command, const struct env_set *es, unsigned int flags, const char *error_message) +{ + struct gc_arena gc = gc_new (); + const int stat = openvpn_system (command, 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; +} + +/* + * 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, false, &es->list); +} + +static void +env_set_add_nolock (struct env_set *es, const char *str) +{ + remove_env_item (str, false, &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; + ASSERT (gc); + 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; +} + +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) + { + 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_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_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); + } + 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); +} + +/* + * 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; +} + +/* create a temporary filename in directory */ +const char * +create_temp_filename (const char *directory, struct gc_arena *gc) +{ + static unsigned int counter; + struct buffer fname = alloc_buf_gc (256, gc); + + mutex_lock_static (L_CREATE_TEMP); + ++counter; + mutex_unlock_static (L_CREATE_TEMP); + + buf_printf (&fname, PACKAGE "_%u_%u.tmp", + openvpn_getpid (), + counter); + + return gen_path (directory, BSTR (&fname), gc); +} + +/* + * 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, "..")) + { + struct buffer out = alloc_buf_gc (256, 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 +} + +/* + * Return the next largest power of 2 + * or u if u is a power of 2. + */ +unsigned int +adjust_power_of_2 (unsigned int u) +{ + unsigned int ret = 1; + + while (ret < u) + { + ret <<= 1; + ASSERT (ret > 0); + } + + return ret; +} + +#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 + */ + +void +get_user_pass (struct user_pass *up, + const char *auth_file, + const bool password_only, + 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")); + +#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 (!management_query_user_pass (management, up, prefix, password_only)) + msg (M_FATAL, "ERROR: could not read %s username/password from management interface", prefix); + } + else +#endif + /* + * Get username/password from standard input? + */ + 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 (!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 (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 (!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); +} + +void +purge_user_pass (struct user_pass *up, const bool force) +{ + const bool nocache = up->nocache; + if (nocache || force) + { + CLEAR (*up); + up->nocache = nocache; + } +} + +/* + * 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); +} + +/* Make arrays of strings */ + +const char ** +make_env_array (const struct env_set *es, 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) + { + e = es->list; + for (i = 0; i < n; ++i) + { + ASSERT (e); + ret[i] = e->string; + e = e->next; + } + } + + 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; +} + +void +openvpn_sleep (const int n) +{ +#ifdef ENABLE_MANAGEMENT + if (management) + { + management_event_loop_n_seconds (management, n); + return; + } +#endif + sleep (n); +} |