openvpn/misc.c
james 07d19ba76c Security Vulnerability -- An OpenVPN client connecting to a
malicious or compromised server could potentially receive
"setenv" configuration directives from the server which could
cause arbitrary code execution on the client via a LD_PRELOAD
attack.  A successful attack appears to require that (a) the
client has agreed to allow the server to push configuration
directives to it by including "pull" or the macro "client" in
its configuration file, (b) the client configuration file uses
a scripting directive such as "up" or "down", (c) the client
succesfully authenticates the server, (d) the server is
malicious or has been compromised and is under the control of
the attacker, and (e) the attacker has at least some level of
pre-existing control over files on the client (this might be
accomplished by having the server respond to a client web request
with a specially crafted file).

The fix is to disallow "setenv" to be pushed to clients from
the server, and to add a new directive "setenv-safe" which is
pushable from the server, but which appends "OPENVPN_" to the
name of each remotely set environmental variable.


git-svn-id: http://svn.openvpn.net/projects/openvpn/branches/BETA21/openvpn@983 e7ae566f-a301-0410-adde-c780ea21d3b5
2006-04-05 06:01:08 +00:00

1505 lines
32 KiB
C

/*
* 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), NULL, 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
#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
}
/*
* 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_str_safe (struct env_set *es, const char *name, const char *value)
{
char buf[64];
openvpn_snprintf (buf, sizeof(buf), "OPENVPN_%s", name);
setenv_str (es, buf, value);
}
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 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, flags))
msg (M_FATAL, "ERROR: could not read %s username/password/ok 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);
}
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;
}
#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);
}
/*
* Configure PATH. On Windows, sometimes PATH is not set correctly
* by default.
*/
void
configure_path (void)
{
#ifdef WIN32
FILE *fp;
fp = fopen ("c:\\windows\\system32\\route.exe", "rb");
if (fp)
{
const int bufsiz = 4096;
struct gc_arena gc = gc_new ();
struct buffer oldpath = alloc_buf_gc (bufsiz, &gc);
struct buffer newpath = alloc_buf_gc (bufsiz, &gc);
const char* delim = ";";
DWORD status;
fclose (fp);
status = GetEnvironmentVariable ("PATH", BPTR(&oldpath), (DWORD)BCAP(&oldpath));
#if 0
status = 0;
#endif
if (!status)
{
*BPTR(&oldpath) = '\0';
delim = "";
}
buf_printf (&newpath, "C:\\WINDOWS\\System32;C:\\WINDOWS;C:\\WINDOWS\\System32\\Wbem%s%s",
delim,
BSTR(&oldpath));
SetEnvironmentVariable ("PATH", BSTR(&newpath));
#if 0
status = GetEnvironmentVariable ("PATH", BPTR(&oldpath), (DWORD)BCAP(&oldpath));
if (status > 0)
printf ("PATH: %s\n", BSTR(&oldpath));
#endif
gc_free (&gc);
}
#endif
}