14a131ac |
/*
* 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.
* |
49979459 |
* Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net> |
14a131ac |
*
* 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.
* |
caa54ac3 |
* 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. |
14a131ac |
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#elif defined(_MSC_VER)
#include "config-msvc.h"
#endif
#include "syshead.h"
#include "buffer.h" |
674b1664 |
#include "crypto.h" |
14a131ac |
#include "error.h" |
b7bea782 |
#include "misc.h" |
14a131ac |
#include "win32.h"
#include "memdbg.h"
#include "platform.h"
/* Redefine the top level directory of the filesystem |
81d882d5 |
* to restrict access to files for security */ |
14a131ac |
void |
81d882d5 |
platform_chroot(const char *path) |
14a131ac |
{ |
81d882d5 |
if (path) |
14a131ac |
{
#ifdef HAVE_CHROOT |
81d882d5 |
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); |
14a131ac |
#endif
}
}
/* Get/Set UID of process */
bool |
81d882d5 |
platform_user_get(const char *username, struct platform_state_user *state) |
14a131ac |
{ |
81d882d5 |
bool ret = false;
CLEAR(*state);
if (username) |
14a131ac |
{
#if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID) |
81d882d5 |
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); |
14a131ac |
#endif
} |
81d882d5 |
return ret; |
14a131ac |
}
void |
81d882d5 |
platform_user_set(const struct platform_state_user *state) |
14a131ac |
{
#if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID) |
81d882d5 |
if (state->username && state->pw) |
14a131ac |
{ |
81d882d5 |
if (setuid(state->pw->pw_uid))
{
msg(M_ERR, "setuid('%s') failed", state->username);
}
msg(M_INFO, "UID set to %s", state->username); |
14a131ac |
}
#endif
}
/* Get/Set GID of process */
bool |
81d882d5 |
platform_group_get(const char *groupname, struct platform_state_group *state) |
14a131ac |
{ |
81d882d5 |
bool ret = false;
CLEAR(*state);
if (groupname) |
14a131ac |
{
#if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID) |
81d882d5 |
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); |
14a131ac |
#endif
} |
81d882d5 |
return ret; |
14a131ac |
}
void |
81d882d5 |
platform_group_set(const struct platform_state_group *state) |
14a131ac |
{
#if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID) |
81d882d5 |
if (state->groupname && state->gr) |
14a131ac |
{ |
81d882d5 |
if (setgid(state->gr->gr_gid))
{
msg(M_ERR, "setgid('%s') failed", state->groupname);
}
msg(M_INFO, "GID set to %s", state->groupname); |
14a131ac |
#ifdef HAVE_SETGROUPS |
81d882d5 |
{
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);
}
} |
14a131ac |
#endif
}
#endif
}
/* Change process priority */
void |
81d882d5 |
platform_nice(int niceval) |
14a131ac |
{ |
81d882d5 |
if (niceval) |
14a131ac |
{
#ifdef HAVE_NICE |
81d882d5 |
errno = 0;
if (nice(niceval) < 0 && errno != 0)
{ |
e441d861 |
msg(M_WARN | M_ERRNO, "WARNING: nice %d failed", niceval); |
81d882d5 |
}
else
{
msg(M_INFO, "nice %d succeeded", niceval);
}
#else /* ifdef HAVE_NICE */
msg(M_WARN, "WARNING: nice %d failed (function not implemented)", niceval); |
14a131ac |
#endif
}
}
/* Get current PID */
unsigned int |
e2a0cad4 |
platform_getpid(void) |
14a131ac |
{ |
445b192a |
#ifdef _WIN32 |
81d882d5 |
return (unsigned int) GetCurrentProcessId(); |
14a131ac |
#else
#ifdef HAVE_GETPID |
81d882d5 |
return (unsigned int) getpid(); |
14a131ac |
#else |
81d882d5 |
return 0; |
14a131ac |
#endif
#endif
}
/* Disable paging */
void
platform_mlockall(bool print_msg)
{
#ifdef HAVE_MLOCKALL |
81d882d5 |
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)"); |
14a131ac |
#endif
}
/*
* Wrapper for chdir library function
*/
int |
81d882d5 |
platform_chdir(const char *dir) |
14a131ac |
{
#ifdef HAVE_CHDIR |
445b192a |
#ifdef _WIN32 |
81d882d5 |
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); |
14a131ac |
#endif |
81d882d5 |
#else /* ifdef HAVE_CHDIR */
return -1; |
14a131ac |
#endif
}
/* |
05634736 |
* convert execve() return into a success/failure value |
14a131ac |
*/
bool |
81d882d5 |
platform_system_ok(int stat) |
14a131ac |
{ |
445b192a |
#ifdef _WIN32 |
81d882d5 |
return stat == 0; |
14a131ac |
#else |
81d882d5 |
return stat != -1 && WIFEXITED(stat) && WEXITSTATUS(stat) == 0; |
14a131ac |
#endif
}
int |
81d882d5 |
platform_access(const char *path, int mode) |
14a131ac |
{ |
445b192a |
#ifdef _WIN32 |
81d882d5 |
struct gc_arena gc = gc_new();
int ret = _waccess(wide_string(path, &gc), mode & ~X_OK);
gc_free(&gc);
return ret; |
14a131ac |
#else |
81d882d5 |
return access(path, mode); |
14a131ac |
#endif
}
/*
* Go to sleep for n milliseconds.
*/
void |
81d882d5 |
platform_sleep_milliseconds(unsigned int n) |
14a131ac |
{ |
445b192a |
#ifdef _WIN32 |
81d882d5 |
Sleep(n); |
14a131ac |
#else |
81d882d5 |
struct timeval tv;
tv.tv_sec = n / 1000;
tv.tv_usec = (n % 1000) * 1000;
select(0, NULL, NULL, NULL, &tv); |
14a131ac |
#endif
}
/*
* Go to sleep indefinitely.
*/
void |
81d882d5 |
platform_sleep_until_signal(void) |
14a131ac |
{ |
445b192a |
#ifdef _WIN32 |
81d882d5 |
ASSERT(0); |
14a131ac |
#else |
81d882d5 |
select(0, NULL, NULL, NULL, NULL); |
14a131ac |
#endif
}
/* delete a file, return true if succeeded */
bool |
81d882d5 |
platform_unlink(const char *filename) |
14a131ac |
{ |
445b192a |
#if defined(_WIN32) |
81d882d5 |
struct gc_arena gc = gc_new();
BOOL ret = DeleteFileW(wide_string(filename, &gc));
gc_free(&gc);
return (ret != 0); |
14a131ac |
#elif defined(HAVE_UNLINK) |
81d882d5 |
return (unlink(filename) == 0);
#else /* if defined(_WIN32) */
return false; |
14a131ac |
#endif
}
FILE * |
81d882d5 |
platform_fopen(const char *path, const char *mode) |
14a131ac |
{ |
445b192a |
#ifdef _WIN32 |
81d882d5 |
struct gc_arena gc = gc_new();
FILE *f = _wfopen(wide_string(path, &gc), wide_string(mode, &gc));
gc_free(&gc);
return f; |
14a131ac |
#else |
81d882d5 |
return fopen(path, mode); |
14a131ac |
#endif
}
int |
81d882d5 |
platform_open(const char *path, int flags, int mode) |
14a131ac |
{ |
445b192a |
#ifdef _WIN32 |
81d882d5 |
struct gc_arena gc = gc_new();
int fd = _wopen(wide_string(path, &gc), flags, mode);
gc_free(&gc);
return fd; |
14a131ac |
#else |
81d882d5 |
return open(path, flags, mode); |
14a131ac |
#endif
}
int |
81d882d5 |
platform_stat(const char *path, platform_stat_t *buf) |
14a131ac |
{ |
445b192a |
#ifdef _WIN32 |
81d882d5 |
struct gc_arena gc = gc_new();
int res = _wstat(wide_string(path, &gc), buf);
gc_free(&gc);
return res; |
14a131ac |
#else |
81d882d5 |
return stat(path, buf); |
14a131ac |
#endif
}
|
b7bea782 |
/* 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;
} |