blob: bc14e8c4429504e5dc8484f37db64a5766a2cd38 [file] [log] [blame]
/*
* 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-2018 OpenVPN 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; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#elif defined(_MSC_VER)
#include "config-msvc.h"
#endif
#include "syshead.h"
#include "error.h"
#include "buffer.h"
#include "misc.h"
#include "win32.h"
#include "socket.h"
#include "tun.h"
#include "otime.h"
#include "perf.h"
#include "status.h"
#include "integer.h"
#include "ps.h"
#include "mstats.h"
#if SYSLOG_CAPABILITY
#ifndef LOG_OPENVPN
#define LOG_OPENVPN LOG_DAEMON
#endif
#endif
/* Globals */
unsigned int x_debug_level; /* GLOBAL */
/* Mute state */
static int mute_cutoff; /* GLOBAL */
static int mute_count; /* GLOBAL */
static int mute_category; /* GLOBAL */
/*
* Output mode priorities are as follows:
*
* (1) --log-x overrides everything
* (2) syslog is used if --daemon or --inetd is defined and not --log-x
* (3) if OPENVPN_DEBUG_COMMAND_LINE is defined, output
* to constant logfile name.
* (4) Output to stdout.
*/
/* If true, indicates that stdin/stdout/stderr
* have been redirected due to --log */
static bool std_redir; /* GLOBAL */
/* Should messages be written to the syslog? */
static bool use_syslog; /* GLOBAL */
/* Should stdout/stderr be be parsable and always be prefixed with time
* and message flags */
static bool machine_readable_output; /* GLOBAL */
/* Should timestamps be included on messages to stdout/stderr? */
static bool suppress_timestamps; /* GLOBAL */
/* The program name passed to syslog */
#if SYSLOG_CAPABILITY
static char *pgmname_syslog; /* GLOBAL */
#endif
/* If non-null, messages should be written here (used for debugging only) */
static FILE *msgfp; /* GLOBAL */
/* If true, we forked from main OpenVPN process */
static bool forked; /* GLOBAL */
/* our default output targets */
static FILE *default_out; /* GLOBAL */
static FILE *default_err; /* GLOBAL */
void
msg_forked(void)
{
forked = true;
}
bool
set_debug_level(const int level, const unsigned int flags)
{
const int ceiling = 15;
if (level >= 0 && level <= ceiling)
{
x_debug_level = level;
return true;
}
else if (flags & SDL_CONSTRAIN)
{
x_debug_level = constrain_int(level, 0, ceiling);
return true;
}
return false;
}
bool
set_mute_cutoff(const int cutoff)
{
if (cutoff >= 0)
{
mute_cutoff = cutoff;
return true;
}
else
{
return false;
}
}
int
get_debug_level(void)
{
return x_debug_level;
}
int
get_mute_cutoff(void)
{
return mute_cutoff;
}
void
set_suppress_timestamps(bool suppressed)
{
suppress_timestamps = suppressed;
}
void
set_machine_readable_output(bool parsable)
{
machine_readable_output = parsable;
}
void
error_reset(void)
{
use_syslog = std_redir = false;
suppress_timestamps = false;
machine_readable_output = false;
x_debug_level = 1;
mute_cutoff = 0;
mute_count = 0;
mute_category = 0;
default_out = OPENVPN_MSG_FP;
default_err = OPENVPN_MSG_FP;
#ifdef OPENVPN_DEBUG_COMMAND_LINE
msgfp = fopen(OPENVPN_DEBUG_FILE, "w");
if (!msgfp)
{
openvpn_exit(OPENVPN_EXIT_STATUS_CANNOT_OPEN_DEBUG_FILE); /* exit point */
}
#else /* ifdef OPENVPN_DEBUG_COMMAND_LINE */
msgfp = NULL;
#endif
}
void
errors_to_stderr(void)
{
default_err = OPENVPN_ERROR_FP;
}
/*
* Return a file to print messages to before syslog is opened.
*/
FILE *
msg_fp(const unsigned int flags)
{
FILE *fp = msgfp;
if (!fp)
{
fp = (flags & (M_FATAL|M_USAGE_SMALL)) ? default_err : default_out;
}
if (!fp)
{
openvpn_exit(OPENVPN_EXIT_STATUS_CANNOT_OPEN_DEBUG_FILE); /* exit point */
}
return fp;
}
#define SWAP { tmp = m1; m1 = m2; m2 = tmp; }
int x_msg_line_num; /* GLOBAL */
void
x_msg(const unsigned int flags, const char *format, ...)
{
va_list arglist;
va_start(arglist, format);
x_msg_va(flags, format, arglist);
va_end(arglist);
}
void
x_msg_va(const unsigned int flags, const char *format, va_list arglist)
{
struct gc_arena gc;
#if SYSLOG_CAPABILITY
int level;
#endif
char *m1;
char *m2;
char *tmp;
int e;
const char *prefix;
const char *prefix_sep;
void usage_small(void);
#ifndef HAVE_VARARG_MACROS
/* the macro has checked this otherwise */
if (!msg_test(flags))
{
return;
}
#endif
e = openvpn_errno();
/*
* Apply muting filter.
*/
#ifndef HAVE_VARARG_MACROS
/* the macro has checked this otherwise */
if (!dont_mute(flags))
{
return;
}
#endif
gc_init(&gc);
m1 = (char *) gc_malloc(ERR_BUF_SIZE, false, &gc);
m2 = (char *) gc_malloc(ERR_BUF_SIZE, false, &gc);
vsnprintf(m1, ERR_BUF_SIZE, format, arglist);
m1[ERR_BUF_SIZE - 1] = 0; /* windows vsnprintf needs this */
if ((flags & M_ERRNO) && e)
{
openvpn_snprintf(m2, ERR_BUF_SIZE, "%s: %s (errno=%d)",
m1, strerror(e), e);
SWAP;
}
if (flags & M_OPTERR)
{
openvpn_snprintf(m2, ERR_BUF_SIZE, "Options error: %s", m1);
SWAP;
}
#if SYSLOG_CAPABILITY
if (flags & (M_FATAL|M_NONFATAL|M_USAGE_SMALL))
{
level = LOG_ERR;
}
else if (flags & M_WARN)
{
level = LOG_WARNING;
}
else
{
level = LOG_NOTICE;
}
#endif
/* set up client prefix */
if (flags & M_NOIPREFIX)
{
prefix = NULL;
}
else
{
prefix = msg_get_prefix();
}
prefix_sep = " ";
if (!prefix)
{
prefix_sep = prefix = "";
}
/* virtual output capability used to copy output to management subsystem */
if (!forked)
{
const struct virtual_output *vo = msg_get_virtual_output();
if (vo)
{
openvpn_snprintf(m2, ERR_BUF_SIZE, "%s%s%s",
prefix,
prefix_sep,
m1);
virtual_output_print(vo, flags, m2);
}
}
if (!(flags & M_MSG_VIRT_OUT))
{
if (use_syslog && !std_redir && !forked)
{
#if SYSLOG_CAPABILITY
syslog(level, "%s%s%s",
prefix,
prefix_sep,
m1);
#endif
}
else
{
FILE *fp = msg_fp(flags);
const bool show_usec = check_debug_level(DEBUG_LEVEL_USEC_TIME);
if (machine_readable_output)
{
struct timeval tv;
gettimeofday(&tv, NULL);
fprintf(fp, "%"PRIi64".%06lu %x %s%s%s%s",
(int64_t)tv.tv_sec,
(unsigned long)tv.tv_usec,
flags,
prefix,
prefix_sep,
m1,
"\n");
}
else if ((flags & M_NOPREFIX) || suppress_timestamps)
{
fprintf(fp, "%s%s%s%s",
prefix,
prefix_sep,
m1,
(flags&M_NOLF) ? "" : "\n");
}
else
{
fprintf(fp, "%s %s%s%s%s",
time_string(0, 0, show_usec, &gc),
prefix,
prefix_sep,
m1,
(flags&M_NOLF) ? "" : "\n");
}
fflush(fp);
++x_msg_line_num;
}
}
if (flags & M_FATAL)
{
msg(M_INFO, "Exiting due to fatal error");
}
if (flags & M_FATAL)
{
openvpn_exit(OPENVPN_EXIT_STATUS_ERROR); /* exit point */
}
if (flags & M_USAGE_SMALL)
{
usage_small();
}
gc_free(&gc);
}
/*
* Apply muting filter.
*/
bool
dont_mute(unsigned int flags)
{
bool ret = true;
if (mute_cutoff > 0 && !(flags & M_NOMUTE))
{
const int mute_level = DECODE_MUTE_LEVEL(flags);
if (mute_level > 0 && mute_level == mute_category)
{
if (mute_count == mute_cutoff)
{
msg(M_INFO | M_NOMUTE, "NOTE: --mute triggered...");
}
if (++mute_count > mute_cutoff)
{
ret = false;
}
}
else
{
const int suppressed = mute_count - mute_cutoff;
if (suppressed > 0)
{
msg(M_INFO | M_NOMUTE,
"%d variation(s) on previous %d message(s) suppressed by --mute",
suppressed,
mute_cutoff);
}
mute_count = 1;
mute_category = mute_level;
}
}
return ret;
}
void
assert_failed(const char *filename, int line, const char *condition)
{
if (condition)
{
msg(M_FATAL, "Assertion failed at %s:%d (%s)", filename, line, condition);
}
else
{
msg(M_FATAL, "Assertion failed at %s:%d", filename, line);
}
_exit(1);
}
/*
* Fail memory allocation. Don't use msg() because it tries
* to allocate memory as part of its operation.
*/
void
out_of_memory(void)
{
fprintf(stderr, PACKAGE_NAME ": Out of Memory\n");
exit(1);
}
void
open_syslog(const char *pgmname, bool stdio_to_null)
{
#if SYSLOG_CAPABILITY
if (!msgfp && !std_redir)
{
if (!use_syslog)
{
pgmname_syslog = string_alloc(pgmname ? pgmname : PACKAGE, NULL);
openlog(pgmname_syslog, LOG_PID, LOG_OPENVPN);
use_syslog = true;
/* Better idea: somehow pipe stdout/stderr output to msg() */
if (stdio_to_null)
{
set_std_files_to_null(false);
}
}
}
#else /* if SYSLOG_CAPABILITY */
msg(M_WARN, "Warning on use of --daemon/--inetd: this operating system lacks daemon logging features, therefore when I become a daemon, I won't be able to log status or error messages");
#endif
}
void
close_syslog(void)
{
#if SYSLOG_CAPABILITY
if (use_syslog)
{
closelog();
use_syslog = false;
if (pgmname_syslog)
{
free(pgmname_syslog);
pgmname_syslog = NULL;
}
}
#endif
}
#ifdef _WIN32
static HANDLE orig_stderr;
HANDLE
get_orig_stderr(void)
{
if (orig_stderr)
{
return orig_stderr;
}
else
{
return GetStdHandle(STD_ERROR_HANDLE);
}
}
#endif
void
redirect_stdout_stderr(const char *file, bool append)
{
#if defined(_WIN32)
if (!std_redir)
{
struct gc_arena gc = gc_new();
HANDLE log_handle;
int log_fd;
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
log_handle = CreateFileW(wide_string(file, &gc),
GENERIC_WRITE,
FILE_SHARE_READ,
&saAttr,
append ? OPEN_ALWAYS : CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
gc_free(&gc);
if (log_handle == INVALID_HANDLE_VALUE)
{
msg(M_WARN|M_ERRNO, "Warning: cannot open --log file: %s", file);
return;
}
/* append to logfile? */
if (append)
{
if (SetFilePointer(log_handle, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER)
{
msg(M_ERR, "Error: cannot seek to end of --log file: %s", file);
}
}
/* save original stderr for password prompts */
orig_stderr = GetStdHandle(STD_ERROR_HANDLE);
#if 0 /* seems not be necessary with stdout/stderr redirection below*/
/* set up for redirection */
if (!SetStdHandle(STD_OUTPUT_HANDLE, log_handle)
|| !SetStdHandle(STD_ERROR_HANDLE, log_handle))
{
msg(M_ERR, "Error: cannot redirect stdout/stderr to --log file: %s", file);
}
#endif
/* direct stdout/stderr to point to log_handle */
log_fd = _open_osfhandle((intptr_t)log_handle, _O_TEXT);
if (log_fd == -1)
{
msg(M_ERR, "Error: --log redirect failed due to _open_osfhandle failure");
}
/* open log_handle as FILE stream */
ASSERT(msgfp == NULL);
msgfp = _fdopen(log_fd, "wt");
if (msgfp == NULL)
{
msg(M_ERR, "Error: --log redirect failed due to _fdopen");
}
/* redirect C-library stdout/stderr to log file */
if (_dup2(log_fd, 1) == -1 || _dup2(log_fd, 2) == -1)
{
msg(M_WARN, "Error: --log redirect of stdout/stderr failed");
}
std_redir = true;
}
#elif defined(HAVE_DUP2)
if (!std_redir)
{
int out = open(file,
O_CREAT | O_WRONLY | (append ? O_APPEND : O_TRUNC),
S_IRUSR | S_IWUSR);
if (out < 0)
{
msg(M_WARN|M_ERRNO, "Warning: Error redirecting stdout/stderr to --log file: %s", file);
return;
}
if (dup2(out, 1) == -1)
{
msg(M_ERR, "--log file redirection error on stdout");
}
if (dup2(out, 2) == -1)
{
msg(M_ERR, "--log file redirection error on stderr");
}
if (out > 2)
{
close(out);
}
std_redir = true;
}
#else /* if defined(_WIN32) */
msg(M_WARN, "WARNING: The --log option is not supported on this OS because it lacks the dup2 function");
#endif /* if defined(_WIN32) */
}
/*
* Functions used to check return status
* of I/O operations.
*/
unsigned int x_cs_info_level; /* GLOBAL */
unsigned int x_cs_verbose_level; /* GLOBAL */
unsigned int x_cs_err_delay_ms; /* GLOBAL */
void
reset_check_status(void)
{
x_cs_info_level = 0;
x_cs_verbose_level = 0;
}
void
set_check_status(unsigned int info_level, unsigned int verbose_level)
{
x_cs_info_level = info_level;
x_cs_verbose_level = verbose_level;
}
/*
* Called after most socket or tun/tap operations, via the inline
* function check_status().
*
* Decide if we should print an error message, and see if we can
* extract any useful info from the error, such as a Path MTU hint
* from the OS.
*/
void
x_check_status(int status,
const char *description,
struct link_socket *sock,
struct tuntap *tt)
{
const int my_errno = openvpn_errno();
const char *extended_msg = NULL;
msg(x_cs_verbose_level, "%s %s returned %d",
sock ? proto2ascii(sock->info.proto, sock->info.af, true) : "",
description,
status);
if (status < 0)
{
struct gc_arena gc = gc_new();
#if EXTENDED_SOCKET_ERROR_CAPABILITY
/* get extended socket error message and possible PMTU hint from OS */
if (sock)
{
int mtu;
extended_msg = format_extended_socket_error(sock->sd, &mtu, &gc);
if (mtu > 0 && sock->mtu != mtu)
{
sock->mtu = mtu;
sock->info.mtu_changed = true;
}
}
#elif defined(_WIN32)
/* get possible driver error from TAP-Windows driver */
extended_msg = tap_win_getinfo(tt, &gc);
#endif
if (!ignore_sys_error(my_errno))
{
if (extended_msg)
{
msg(x_cs_info_level, "%s %s [%s]: %s (code=%d)", description,
sock ? proto2ascii(sock->info.proto, sock->info.af, true) : "",
extended_msg, strerror(my_errno), my_errno);
}
else
{
msg(x_cs_info_level, "%s %s: %s (code=%d)", description,
sock ? proto2ascii(sock->info.proto, sock->info.af, true) : "",
strerror(my_errno), my_errno);
}
if (x_cs_err_delay_ms)
{
platform_sleep_milliseconds(x_cs_err_delay_ms);
}
}
gc_free(&gc);
}
}
/*
* In multiclient mode, put a client-specific prefix
* before each message.
*/
const char *x_msg_prefix; /* GLOBAL */
/*
* Allow MSG to be redirected through a virtual_output object
*/
const struct virtual_output *x_msg_virtual_output; /* GLOBAL */
/*
* Exiting.
*/
void
openvpn_exit(const int status)
{
if (!forked)
{
void tun_abort();
#ifdef ENABLE_PLUGIN
void plugin_abort(void);
#endif
tun_abort();
#ifdef _WIN32
uninit_win32();
#endif
close_syslog();
#ifdef ENABLE_PLUGIN
plugin_abort();
#endif
#if PORT_SHARE
if (port_share)
{
port_share_abort(port_share);
}
#endif
#ifdef ENABLE_MEMSTATS
mstats_close();
#endif
#ifdef ABORT_ON_ERROR
if (status == OPENVPN_EXIT_STATUS_ERROR)
{
abort();
}
#endif
if (status == OPENVPN_EXIT_STATUS_GOOD)
{
perf_output_results();
}
}
exit(status);
}
/*
* Translate msg flags into a string
*/
const char *
msg_flags_string(const unsigned int flags, struct gc_arena *gc)
{
struct buffer out = alloc_buf_gc(16, gc);
if (flags == M_INFO)
{
buf_printf(&out, "I");
}
if (flags & M_FATAL)
{
buf_printf(&out, "F");
}
if (flags & M_NONFATAL)
{
buf_printf(&out, "N");
}
if (flags & M_WARN)
{
buf_printf(&out, "W");
}
if (flags & M_DEBUG)
{
buf_printf(&out, "D");
}
return BSTR(&out);
}
#ifdef ENABLE_DEBUG
void
crash(void)
{
char *null = NULL;
*null = 0;
}
#endif
#ifdef _WIN32
const char *
strerror_win32(DWORD errnum, struct gc_arena *gc)
{
/*
* This code can be omitted, though often the Windows
* WSA error messages are less informative than the
* Posix equivalents.
*/
#if 1
switch (errnum)
{
/*
* When the TAP-Windows driver returns STATUS_UNSUCCESSFUL, this code
* gets returned to user space.
*/
case ERROR_GEN_FAILURE:
return "General failure (ERROR_GEN_FAILURE)";
case ERROR_IO_PENDING:
return "I/O Operation in progress (ERROR_IO_PENDING)";
case WSA_IO_INCOMPLETE:
return "I/O Operation in progress (WSA_IO_INCOMPLETE)";
case WSAEINTR:
return "Interrupted system call (WSAEINTR)";
case WSAEBADF:
return "Bad file number (WSAEBADF)";
case WSAEACCES:
return "Permission denied (WSAEACCES)";
case WSAEFAULT:
return "Bad address (WSAEFAULT)";
case WSAEINVAL:
return "Invalid argument (WSAEINVAL)";
case WSAEMFILE:
return "Too many open files (WSAEMFILE)";
case WSAEWOULDBLOCK:
return "Operation would block (WSAEWOULDBLOCK)";
case WSAEINPROGRESS:
return "Operation now in progress (WSAEINPROGRESS)";
case WSAEALREADY:
return "Operation already in progress (WSAEALREADY)";
case WSAEDESTADDRREQ:
return "Destination address required (WSAEDESTADDRREQ)";
case WSAEMSGSIZE:
return "Message too long (WSAEMSGSIZE)";
case WSAEPROTOTYPE:
return "Protocol wrong type for socket (WSAEPROTOTYPE)";
case WSAENOPROTOOPT:
return "Bad protocol option (WSAENOPROTOOPT)";
case WSAEPROTONOSUPPORT:
return "Protocol not supported (WSAEPROTONOSUPPORT)";
case WSAESOCKTNOSUPPORT:
return "Socket type not supported (WSAESOCKTNOSUPPORT)";
case WSAEOPNOTSUPP:
return "Operation not supported on socket (WSAEOPNOTSUPP)";
case WSAEPFNOSUPPORT:
return "Protocol family not supported (WSAEPFNOSUPPORT)";
case WSAEAFNOSUPPORT:
return "Address family not supported by protocol family (WSAEAFNOSUPPORT)";
case WSAEADDRINUSE:
return "Address already in use (WSAEADDRINUSE)";
case WSAENETDOWN:
return "Network is down (WSAENETDOWN)";
case WSAENETUNREACH:
return "Network is unreachable (WSAENETUNREACH)";
case WSAENETRESET:
return "Net dropped connection or reset (WSAENETRESET)";
case WSAECONNABORTED:
return "Software caused connection abort (WSAECONNABORTED)";
case WSAECONNRESET:
return "Connection reset by peer (WSAECONNRESET)";
case WSAENOBUFS:
return "No buffer space available (WSAENOBUFS)";
case WSAEISCONN:
return "Socket is already connected (WSAEISCONN)";
case WSAENOTCONN:
return "Socket is not connected (WSAENOTCONN)";
case WSAETIMEDOUT:
return "Connection timed out (WSAETIMEDOUT)";
case WSAECONNREFUSED:
return "Connection refused (WSAECONNREFUSED)";
case WSAELOOP:
return "Too many levels of symbolic links (WSAELOOP)";
case WSAENAMETOOLONG:
return "File name too long (WSAENAMETOOLONG)";
case WSAEHOSTDOWN:
return "Host is down (WSAEHOSTDOWN)";
case WSAEHOSTUNREACH:
return "No Route to Host (WSAEHOSTUNREACH)";
case WSAENOTEMPTY:
return "Directory not empty (WSAENOTEMPTY)";
case WSAEPROCLIM:
return "Too many processes (WSAEPROCLIM)";
case WSAEUSERS:
return "Too many users (WSAEUSERS)";
case WSAEDQUOT:
return "Disc Quota Exceeded (WSAEDQUOT)";
case WSAESTALE:
return "Stale NFS file handle (WSAESTALE)";
case WSASYSNOTREADY:
return "Network SubSystem is unavailable (WSASYSNOTREADY)";
case WSAVERNOTSUPPORTED:
return "WINSOCK DLL Version out of range (WSAVERNOTSUPPORTED)";
case WSANOTINITIALISED:
return "Successful WSASTARTUP not yet performed (WSANOTINITIALISED)";
case WSAEREMOTE:
return "Too many levels of remote in path (WSAEREMOTE)";
case WSAHOST_NOT_FOUND:
return "Host not found (WSAHOST_NOT_FOUND)";
default:
break;
}
#endif /* if 1 */
/* format a windows error message */
{
char message[256];
struct buffer out = alloc_buf_gc(256, gc);
const int status = FormatMessage(
FORMAT_MESSAGE_IGNORE_INSERTS
| FORMAT_MESSAGE_FROM_SYSTEM
| FORMAT_MESSAGE_ARGUMENT_ARRAY,
NULL,
errnum,
0,
message,
sizeof(message),
NULL);
if (!status)
{
buf_printf(&out, "[Unknown Win32 Error]");
}
else
{
char *cp;
for (cp = message; *cp != '\0'; ++cp)
{
if (*cp == '\n' || *cp == '\r')
{
*cp = ' ';
}
}
buf_printf(&out, "%s", message);
}
return BSTR(&out);
}
}
#endif /* ifdef _WIN32 */