mirror of
https://github.com/OpenVPN/openvpn.git
synced 2024-09-20 12:02:28 +02:00
0db046f253
pushed by server, and that is used to offer a temporary session token to clients that can be used in place of a password on subsequent credential challenges. This accomplishes the security benefit of preventing caching of the real password while offering most of the advantages of password caching, i.e. not forcing the user to re-enter credentials for every TLS renegotiation or network hiccup. auth-token does two things: 1. if password caching is enabled, the token replaces the previous password, and 2. if the management interface is active, the token is output to it: >PASSWORD:Auth-Token:<token> Also made a minor change to HALT/RESTART processing when password caching is enabled. When client receives a HALT or RESTART message, and if the message text contains a flags block (i.e. [FFF]:message), if flag 'P' (preserve auth) is present in flags, don't purge the Auth password. Otherwise do purge the Auth password. Version 2.1.3o git-svn-id: http://svn.openvpn.net/projects/openvpn/branches/BETA21/openvpn@7088 e7ae566f-a301-0410-adde-c780ea21d3b5
2410 lines
52 KiB
C
2410 lines
52 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-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program (see the file COPYING included with this
|
|
* distribution); if not, write to the Free Software Foundation, Inc.,
|
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "syshead.h"
|
|
|
|
#include "buffer.h"
|
|
#include "misc.h"
|
|
#include "base64.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_execve_check (&argv, es, S_SCRIPT|S_FATAL, "script failed");
|
|
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;
|
|
|
|
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
|
|
{
|
|
msg (M_WARN, SCRIPT_SECURITY_WARNING);
|
|
}
|
|
#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_filename (const char *directory, const char *prefix, 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);
|
|
|
|
{
|
|
uint8_t rndbytes[16];
|
|
const char *rndstr;
|
|
|
|
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);
|
|
}
|
|
|
|
return gen_path (directory, BSTR (&fname), gc);
|
|
}
|
|
|
|
/*
|
|
* Add a random string to first DNS label of hostname to prevent DNS caching.
|
|
* For example, foo.bar.gov would be modified to <random-chars>.foo.bar.gov.
|
|
* Of course, this requires explicit support in the DNS server.
|
|
*/
|
|
const char *
|
|
hostname_randomize(const char *hostname, struct gc_arena *gc)
|
|
{
|
|
# define n_rnd_bytes 6
|
|
|
|
char *hst = string_alloc(hostname, gc);
|
|
char *dot = strchr(hst, '.');
|
|
|
|
if (dot)
|
|
{
|
|
uint8_t rnd_bytes[n_rnd_bytes];
|
|
const char *rnd_str;
|
|
struct buffer hname = alloc_buf_gc (strlen(hostname)+sizeof(rnd_bytes)*2+4, gc);
|
|
|
|
*dot++ = '\0';
|
|
prng_bytes (rnd_bytes, sizeof (rnd_bytes));
|
|
rnd_str = format_hex_ex (rnd_bytes, sizeof (rnd_bytes), 40, 0, NULL, gc);
|
|
buf_printf(&hname, "%s-0x%s.%s", hst, rnd_str, dot);
|
|
return BSTR(&hname);
|
|
}
|
|
else
|
|
return hostname;
|
|
# undef n_rnd_bytes
|
|
}
|
|
|
|
#else
|
|
|
|
const char *
|
|
hostname_randomize(const char *hostname, struct gc_arena *gc)
|
|
{
|
|
msg (M_WARN, "WARNING: hostname randomization disabled when crypto support is not compiled");
|
|
return hostname;
|
|
}
|
|
|
|
#endif
|
|
|
|
/*
|
|
* Put a directory and filename together.
|
|
*/
|
|
const char *
|
|
gen_path (const char *directory, const char *filename, struct gc_arena *gc)
|
|
{
|
|
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_cr (struct user_pass *up,
|
|
const char *auth_file,
|
|
const char *prefix,
|
|
const unsigned int flags,
|
|
const char *auth_challenge)
|
|
{
|
|
struct gc_arena gc = gc_new ();
|
|
|
|
if (!up->defined)
|
|
{
|
|
const bool from_stdin = (!auth_file || !strcmp (auth_file, "stdin"));
|
|
|
|
if (flags & GET_USER_PASS_PREVIOUS_CREDS_FAILED)
|
|
msg (M_WARN, "Note: previous '%s' credentials failed", prefix);
|
|
|
|
#ifdef ENABLE_MANAGEMENT
|
|
/*
|
|
* Get username/password from management interface?
|
|
*/
|
|
if (management
|
|
&& ((auth_file && streq (auth_file, "management")) || (from_stdin && (flags & GET_USER_PASS_MANAGEMENT)))
|
|
&& management_query_user_pass_enabled (management))
|
|
{
|
|
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)
|
|
{
|
|
#ifdef ENABLE_CLIENT_CR
|
|
if (auth_challenge)
|
|
{
|
|
struct auth_challenge_info *ac = get_auth_challenge (auth_challenge, &gc);
|
|
if (ac)
|
|
{
|
|
char *response = (char *) gc_malloc (USER_PASS_LEN, false, &gc);
|
|
struct buffer packed_resp;
|
|
|
|
buf_set_write (&packed_resp, (uint8_t*)up->password, USER_PASS_LEN);
|
|
msg (M_INFO, "CHALLENGE: %s", ac->challenge_text);
|
|
if (!get_console_input ("Response:", BOOL_CAST(ac->flags&CR_ECHO), response, USER_PASS_LEN))
|
|
msg (M_FATAL, "ERROR: could not read challenge response from stdin");
|
|
strncpynt (up->username, ac->user, USER_PASS_LEN);
|
|
buf_printf (&packed_resp, "CRV1::%s::%s", ac->state_id, response);
|
|
}
|
|
else
|
|
{
|
|
msg (M_FATAL, "ERROR: received malformed challenge request from server");
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
struct buffer user_prompt = alloc_buf_gc (128, &gc);
|
|
struct buffer pass_prompt = alloc_buf_gc (128, &gc);
|
|
|
|
buf_printf (&user_prompt, "Enter %s Username:", prefix);
|
|
buf_printf (&pass_prompt, "Enter %s Password:", prefix);
|
|
|
|
if (!(flags & GET_USER_PASS_PASSWORD_ONLY))
|
|
{
|
|
if (!get_console_input (BSTR (&user_prompt), true, up->username, USER_PASS_LEN))
|
|
msg (M_FATAL, "ERROR: could not read %s username from stdin", prefix);
|
|
if (strlen (up->username) == 0)
|
|
msg (M_FATAL, "ERROR: %s username is empty", prefix);
|
|
}
|
|
|
|
if (!get_console_input (BSTR (&pass_prompt), false, up->password, USER_PASS_LEN))
|
|
msg (M_FATAL, "ERROR: could not not read %s password from stdin", prefix);
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
#ifdef ENABLE_CLIENT_CR
|
|
|
|
/*
|
|
* Parse a challenge message returned along with AUTH_FAILED.
|
|
* The message is formatted as such:
|
|
*
|
|
* CRV1:<flags>:<state_id>:<username_base64>:<challenge_text>
|
|
*
|
|
* flags: a series of optional, comma-separated flags:
|
|
* E : echo the response when the user types it
|
|
* R : a response is required
|
|
*
|
|
* state_id: an opaque string that should be returned to the server
|
|
* along with the response.
|
|
*
|
|
* username_base64 : the username formatted as base64
|
|
*
|
|
* challenge_text : the challenge text to be shown to the user
|
|
*
|
|
* Example challenge:
|
|
*
|
|
* CRV1:R,E:Om01u7Fh4LrGBS7uh0SWmzwabUiGiW6l:Y3Ix:Please enter token PIN
|
|
*
|
|
* After showing the challenge_text and getting a response from the user
|
|
* (if R flag is specified), the client should submit the following
|
|
* auth creds back to the OpenVPN server:
|
|
*
|
|
* Username: [username decoded from username_base64]
|
|
* Password: CRV1::<state_id>::<response_text>
|
|
*
|
|
* Where state_id is taken from the challenge request and response_text
|
|
* is what the user entered in response to the challenge_text.
|
|
* If the R flag is not present, response_text may be the empty
|
|
* string.
|
|
*
|
|
* Example response (suppose the user enters "8675309" for the token PIN):
|
|
*
|
|
* Username: cr1 ("Y3Ix" base64 decoded)
|
|
* Password: CRV1::Om01u7Fh4LrGBS7uh0SWmzwabUiGiW6l::8675309
|
|
*/
|
|
struct auth_challenge_info *
|
|
get_auth_challenge (const char *auth_challenge, struct gc_arena *gc)
|
|
{
|
|
if (auth_challenge)
|
|
{
|
|
struct auth_challenge_info *ac;
|
|
const int len = strlen (auth_challenge);
|
|
char *work = (char *) gc_malloc (len+1, false, gc);
|
|
char *cp;
|
|
|
|
struct buffer b;
|
|
buf_set_read (&b, (const uint8_t *)auth_challenge, len);
|
|
|
|
ALLOC_OBJ_CLEAR_GC (ac, struct auth_challenge_info, gc);
|
|
|
|
/* parse prefix */
|
|
if (!buf_parse(&b, ':', work, len))
|
|
return NULL;
|
|
if (strcmp(work, "CRV1"))
|
|
return NULL;
|
|
|
|
/* parse flags */
|
|
if (!buf_parse(&b, ':', work, len))
|
|
return NULL;
|
|
for (cp = work; *cp != '\0'; ++cp)
|
|
{
|
|
const char c = *cp;
|
|
if (c == 'E')
|
|
ac->flags |= CR_ECHO;
|
|
else if (c == 'R')
|
|
ac->flags |= CR_RESPONSE;
|
|
}
|
|
|
|
/* parse state ID */
|
|
if (!buf_parse(&b, ':', work, len))
|
|
return NULL;
|
|
ac->state_id = string_alloc(work, gc);
|
|
|
|
/* parse user name */
|
|
if (!buf_parse(&b, ':', work, len))
|
|
return NULL;
|
|
ac->user = (char *) gc_malloc (strlen(work)+1, true, gc);
|
|
base64_decode(work, (void*)ac->user, -1);
|
|
|
|
/* parse challenge text */
|
|
ac->challenge_text = string_alloc(BSTR(&b), gc);
|
|
|
|
return ac;
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
#endif
|
|
|
|
#if AUTO_USERID
|
|
|
|
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;
|
|
if (nocache || force)
|
|
{
|
|
CLEAR (*up);
|
|
up->nocache = nocache;
|
|
}
|
|
else
|
|
{
|
|
msg (M_WARN, "WARNING: this configuration may cache passwords in memory -- use the auth-nocache option to prevent this");
|
|
}
|
|
}
|
|
|
|
void
|
|
set_auth_token (struct user_pass *up, const char *token)
|
|
{
|
|
if (token && strlen(token) && up && up->defined && !up->nocache)
|
|
{
|
|
CLEAR (up->password);
|
|
strncpynt (up->password, token, USER_PASS_LEN);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Process string received by untrusted peer before
|
|
* printing to console or log file.
|
|
*
|
|
* Assumes that string has been null terminated.
|
|
*/
|
|
const char *
|
|
safe_print (const char *str, struct gc_arena *gc)
|
|
{
|
|
return string_mod_const (str, CC_PRINT, CC_CRLF, '.', gc);
|
|
}
|
|
|
|
static bool
|
|
is_password_env_var (const char *str)
|
|
{
|
|
return (strncmp (str, "password", 8) == 0);
|
|
}
|
|
|
|
bool
|
|
env_allowed (const char *str)
|
|
{
|
|
return (script_security >= SSEC_PW_ENV || !is_password_env_var (str));
|
|
}
|
|
|
|
bool
|
|
env_safe_to_print (const char *str)
|
|
{
|
|
#ifndef UNSAFE_DEBUG
|
|
if (is_password_env_var (str))
|
|
return false;
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
/* Make arrays of strings */
|
|
|
|
const char **
|
|
make_env_array (const struct env_set *es,
|
|
const bool check_allowed,
|
|
struct gc_arena *gc)
|
|
{
|
|
char **ret = NULL;
|
|
struct env_item *e = NULL;
|
|
int i = 0, n = 0;
|
|
|
|
/* figure length of es */
|
|
if (es)
|
|
{
|
|
for (e = es->list; e != NULL; e = e->next)
|
|
++n;
|
|
}
|
|
|
|
/* alloc return array */
|
|
ALLOC_ARRAY_CLEAR_GC (ret, char *, n+1, gc);
|
|
|
|
/* fill return array */
|
|
if (es)
|
|
{
|
|
i = 0;
|
|
for (e = es->list; e != NULL; e = e->next)
|
|
{
|
|
if (!check_allowed || env_allowed (e->string))
|
|
{
|
|
ASSERT (i < n);
|
|
ret[i++] = e->string;
|
|
}
|
|
}
|
|
}
|
|
|
|
ret[i] = NULL;
|
|
return (const char **)ret;
|
|
}
|
|
|
|
const char **
|
|
make_arg_array (const char *first, const char *parms, struct gc_arena *gc)
|
|
{
|
|
char **ret = NULL;
|
|
int base = 0;
|
|
const int max_parms = MAX_PARMS + 2;
|
|
int n = 0;
|
|
|
|
/* alloc return array */
|
|
ALLOC_ARRAY_CLEAR_GC (ret, char *, max_parms, gc);
|
|
|
|
/* process first parameter, if provided */
|
|
if (first)
|
|
{
|
|
ret[base++] = string_alloc (first, gc);
|
|
}
|
|
|
|
if (parms)
|
|
{
|
|
n = parse_line (parms, &ret[base], max_parms - base - 1, "make_arg_array", 0, M_WARN, gc);
|
|
ASSERT (n >= 0 && n + base + 1 <= max_parms);
|
|
}
|
|
ret[base + n] = NULL;
|
|
|
|
return (const char **)ret;
|
|
}
|
|
|
|
#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;
|
|
}
|
|
|
|
/*
|
|
* Remove SESS_ID_x strings (i.e. auth tokens) from control message
|
|
* strings so that they will not be output to log file.
|
|
*/
|
|
const char *
|
|
sanitize_control_message(const char *str, struct gc_arena *gc)
|
|
{
|
|
char *ret = gc_malloc (strlen(str)+1, false, gc);
|
|
char *cp = ret;
|
|
bool redact = false;
|
|
|
|
strcpy(ret, str);
|
|
for (;;)
|
|
{
|
|
const char c = *cp;
|
|
if (c == '\0')
|
|
break;
|
|
if (c == 'S' && !strncmp(cp, "SESS_ID_", 8))
|
|
{
|
|
cp += 7;
|
|
redact = true;
|
|
}
|
|
else
|
|
{
|
|
if (c == ',') /* end of session id? */
|
|
redact = false;
|
|
if (redact)
|
|
*cp = '_';
|
|
}
|
|
++cp;
|
|
}
|
|
return ret;
|
|
}
|