/* * 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 * * 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 "buffer.h" #include "error.h" #include "misc.h" #include "win32.h" #include "memdbg.h" #include "platform.h" /* Redefine the top level directory of the filesystem * to restrict access to files for security */ void platform_chroot(const char *path) { if (path) { #ifdef HAVE_CHROOT const char *top = "/"; if (chroot(path)) { msg(M_ERR, "chroot to '%s' failed", path); } if (platform_chdir(top)) { msg(M_ERR, "cd to '%s' failed", top); } msg(M_INFO, "chroot to '%s' and cd to '%s' succeeded", path, top); #else /* ifdef HAVE_CHROOT */ 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 platform_user_get(const char *username, struct platform_state_user *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 /* if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID) */ msg(M_FATAL, "cannot get UID for user %s -- platform lacks getpwname() or setuid() system calls", username); #endif } return ret; } void platform_user_set(const struct platform_state_user *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 platform_group_get(const char *groupname, struct platform_state_group *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 /* if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID) */ msg(M_FATAL, "cannot get GID for group %s -- platform lacks getgrnam() or setgid() system calls", groupname); #endif } return ret; } void platform_group_set(const struct platform_state_group *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 platform_nice(int niceval) { if (niceval) { #ifdef HAVE_NICE errno = 0; if (nice(niceval) < 0 && errno != 0) { msg(M_WARN | M_ERRNO, "WARNING: nice %d failed", niceval); } else { msg(M_INFO, "nice %d succeeded", niceval); } #else /* ifdef HAVE_NICE */ msg(M_WARN, "WARNING: nice %d failed (function not implemented)", niceval); #endif } } /* Get current PID */ unsigned int platform_getpid(void) { #ifdef _WIN32 return (unsigned int) GetCurrentProcessId(); #else #ifdef HAVE_GETPID return (unsigned int) getpid(); #else return 0; #endif #endif } /* Disable paging */ void platform_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 /* ifdef HAVE_MLOCKALL */ msg(M_WARN, "WARNING: mlockall call failed (function not implemented)"); #endif } /* * Wrapper for chdir library function */ int platform_chdir(const char *dir) { #ifdef HAVE_CHDIR #ifdef _WIN32 int res; struct gc_arena gc = gc_new(); res = _wchdir(wide_string(dir, &gc)); gc_free(&gc); return res; #else /* ifdef _WIN32 */ return chdir(dir); #endif #else /* ifdef HAVE_CHDIR */ return -1; #endif } /* * convert execve() return into a success/failure value */ bool platform_system_ok(int stat) { #ifdef _WIN32 return stat == 0; #else return stat != -1 && WIFEXITED(stat) && WEXITSTATUS(stat) == 0; #endif } int platform_access(const char *path, int mode) { #ifdef _WIN32 struct gc_arena gc = gc_new(); int ret = _waccess(wide_string(path, &gc), mode & ~X_OK); gc_free(&gc); return ret; #else return access(path, mode); #endif } /* * Go to sleep for n milliseconds. */ void platform_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 platform_sleep_until_signal(void) { #ifdef _WIN32 ASSERT(0); #else select(0, NULL, NULL, NULL, NULL); #endif } /* delete a file, return true if succeeded */ bool platform_unlink(const char *filename) { #if defined(_WIN32) struct gc_arena gc = gc_new(); BOOL ret = DeleteFileW(wide_string(filename, &gc)); gc_free(&gc); return (ret != 0); #elif defined(HAVE_UNLINK) return (unlink(filename) == 0); #else /* if defined(_WIN32) */ return false; #endif } FILE * platform_fopen(const char *path, const char *mode) { #ifdef _WIN32 struct gc_arena gc = gc_new(); FILE *f = _wfopen(wide_string(path, &gc), wide_string(mode, &gc)); gc_free(&gc); return f; #else return fopen(path, mode); #endif } int platform_open(const char *path, int flags, int mode) { #ifdef _WIN32 struct gc_arena gc = gc_new(); int fd = _wopen(wide_string(path, &gc), flags, mode); gc_free(&gc); return fd; #else return open(path, flags, mode); #endif } int platform_stat(const char *path, platform_stat_t *buf) { #ifdef _WIN32 struct gc_arena gc = gc_new(); int res = _wstat(wide_string(path, &gc), buf); gc_free(&gc); return res; #else return stat(path, buf); #endif } /* create a temporary filename in directory */ const char * platform_create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc) { int fd; const char *retfname = NULL; unsigned int attempts = 0; char fname[256] = { 0 }; const char *fname_fmt = PACKAGE "_%.*s_%08lx%08lx.tmp"; const int max_prefix_len = sizeof(fname) - (sizeof(PACKAGE) + 7 + (2 * 8)); while (attempts < 6) { ++attempts; if (!openvpn_snprintf(fname, sizeof(fname), fname_fmt, max_prefix_len, prefix, (unsigned long) get_random(), (unsigned long) get_random())) { msg(M_WARN, "ERROR: temporary filename too long"); return NULL; } retfname = platform_gen_path(directory, fname, gc); if (!retfname) { msg(M_WARN, "Failed to create temporary filename and path"); return NULL; } /* Atomically create the file. Errors out if the file already * exists. */ fd = platform_open(retfname, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR); if (fd != -1) { close(fd); return retfname; } else if (fd == -1 && errno != EEXIST) { /* Something else went wrong, no need to retry. */ msg(M_WARN | M_ERRNO, "Could not create temporary file '%s'", retfname); return NULL; } } msg(M_WARN, "Failed to create temporary file after %i attempts", attempts); return NULL; } /* * Put a directory and filename together. */ const char * platform_gen_path(const char *directory, const char *filename, struct gc_arena *gc) { #ifdef _WIN32 const int CC_PATH_RESERVED = CC_LESS_THAN|CC_GREATER_THAN|CC_COLON |CC_DOUBLE_QUOTE|CC_SLASH|CC_BACKSLASH|CC_PIPE|CC_QUESTION_MARK|CC_ASTERISK; #else const int CC_PATH_RESERVED = CC_SLASH; #endif if (!gc) { return NULL; /* Would leak memory otherwise */ } const char *safe_filename = string_mod_const(filename, CC_PRINT, CC_PATH_RESERVED, '_', 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; } } bool platform_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; } } /* return true if filename can be opened for read */ bool platform_test_file(const char *filename) { bool ret = false; if (filename) { FILE *fp = platform_fopen(filename, "r"); if (fp) { fclose(fp); ret = true; } else { if (openvpn_errno() == EACCES) { msg( M_WARN | M_ERRNO, "Could not access file '%s'", filename); } } } dmsg(D_TEST_FILE, "TEST FILE '%s' [%d]", filename ? filename : "UNDEF", ret); return ret; }