src/openvpn/platform.c
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;
 }