/* * OpenVPN -- An application to securely tunnel IP networks * over a single TCP/UDP port, with support for SSL/TLS-based * session authentication and key exchange, * packet encryption, packet authentication, and * packet compression. * * Copyright (C) 2002-2010 OpenVPN Technologies, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (see the file COPYING included with this * distribution); if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #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 () { 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 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_ts (e, &gc), 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, "%lu.%06lu %x %s%s%s%s", 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) { msg (M_FATAL, "Assertion failed at %s:%d", filename, line); } /* * 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 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 () { #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 msg (M_WARN, "WARNING: The --log option is not supported on this OS because it lacks the dup2 function"); #endif } /* * 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 () { 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_ts (my_errno, &gc), my_errno); else msg (x_cs_info_level, "%s %s: %s (code=%d)", description, sock ? proto2ascii (sock->info.proto, sock->info.af, true) : "", strerror_ts (my_errno, &gc), 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 /* 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