1 /*
2 * OpenVPN -- An application to securely tunnel IP networks
3 * over a single TCP/UDP port, with support for SSL/TLS-based
4 * session authentication and key exchange,
5 * packet encryption, packet authentication, and
6 * packet compression.
7 *
8 * Copyright (C) 2002-2022 OpenVPN Inc <sales@openvpn.net>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 */
23
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #elif defined(_MSC_VER)
27 #include "config-msvc.h"
28 #endif
29
30 #include "syshead.h"
31
32 #include "buffer.h"
33 #include "crypto.h"
34 #include "error.h"
35 #include "misc.h"
36 #include "win32.h"
37
38 #include "memdbg.h"
39
40 #include "platform.h"
41
42 /* Redefine the top level directory of the filesystem
43 * to restrict access to files for security */
44 void
platform_chroot(const char * path)45 platform_chroot(const char *path)
46 {
47 if (path)
48 {
49 #ifdef HAVE_CHROOT
50 const char *top = "/";
51 if (chroot(path))
52 {
53 msg(M_ERR, "chroot to '%s' failed", path);
54 }
55 if (platform_chdir(top))
56 {
57 msg(M_ERR, "cd to '%s' failed", top);
58 }
59 msg(M_INFO, "chroot to '%s' and cd to '%s' succeeded", path, top);
60 #else /* ifdef HAVE_CHROOT */
61 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);
62 #endif
63 }
64 }
65
66 /* Get/Set UID of process */
67
68 bool
platform_user_get(const char * username,struct platform_state_user * state)69 platform_user_get(const char *username, struct platform_state_user *state)
70 {
71 bool ret = false;
72 CLEAR(*state);
73 if (username)
74 {
75 #if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID)
76 state->pw = getpwnam(username);
77 if (!state->pw)
78 {
79 msg(M_ERR, "failed to find UID for user %s", username);
80 }
81 state->username = username;
82 ret = true;
83 #else /* if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID) */
84 msg(M_FATAL, "cannot get UID for user %s -- platform lacks getpwname() or setuid() system calls", username);
85 #endif
86 }
87 return ret;
88 }
89
90 void
platform_user_set(const struct platform_state_user * state)91 platform_user_set(const struct platform_state_user *state)
92 {
93 #if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID)
94 if (state->username && state->pw)
95 {
96 if (setuid(state->pw->pw_uid))
97 {
98 msg(M_ERR, "setuid('%s') failed", state->username);
99 }
100 msg(M_INFO, "UID set to %s", state->username);
101 }
102 #endif
103 }
104
105 /* Get/Set GID of process */
106
107 bool
platform_group_get(const char * groupname,struct platform_state_group * state)108 platform_group_get(const char *groupname, struct platform_state_group *state)
109 {
110 bool ret = false;
111 CLEAR(*state);
112 if (groupname)
113 {
114 #if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID)
115 state->gr = getgrnam(groupname);
116 if (!state->gr)
117 {
118 msg(M_ERR, "failed to find GID for group %s", groupname);
119 }
120 state->groupname = groupname;
121 ret = true;
122 #else /* if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID) */
123 msg(M_FATAL, "cannot get GID for group %s -- platform lacks getgrnam() or setgid() system calls", groupname);
124 #endif
125 }
126 return ret;
127 }
128
129 void
platform_group_set(const struct platform_state_group * state)130 platform_group_set(const struct platform_state_group *state)
131 {
132 #if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID)
133 if (state->groupname && state->gr)
134 {
135 if (setgid(state->gr->gr_gid))
136 {
137 msg(M_ERR, "setgid('%s') failed", state->groupname);
138 }
139 msg(M_INFO, "GID set to %s", state->groupname);
140 #ifdef HAVE_SETGROUPS
141 {
142 gid_t gr_list[1];
143 gr_list[0] = state->gr->gr_gid;
144 if (setgroups(1, gr_list))
145 {
146 msg(M_ERR, "setgroups('%s') failed", state->groupname);
147 }
148 }
149 #endif
150 }
151 #endif
152 }
153
154 /* Change process priority */
155 void
platform_nice(int niceval)156 platform_nice(int niceval)
157 {
158 if (niceval)
159 {
160 #ifdef HAVE_NICE
161 errno = 0;
162 if (nice(niceval) < 0 && errno != 0)
163 {
164 msg(M_WARN | M_ERRNO, "WARNING: nice %d failed", niceval);
165 }
166 else
167 {
168 msg(M_INFO, "nice %d succeeded", niceval);
169 }
170 #else /* ifdef HAVE_NICE */
171 msg(M_WARN, "WARNING: nice %d failed (function not implemented)", niceval);
172 #endif
173 }
174 }
175
176 /* Get current PID */
177 unsigned int
platform_getpid(void)178 platform_getpid(void)
179 {
180 #ifdef _WIN32
181 return (unsigned int) GetCurrentProcessId();
182 #else
183 #ifdef HAVE_GETPID
184 return (unsigned int) getpid();
185 #else
186 return 0;
187 #endif
188 #endif
189 }
190
191 /* Disable paging */
192 void
platform_mlockall(bool print_msg)193 platform_mlockall(bool print_msg)
194 {
195 #ifdef HAVE_MLOCKALL
196 if (mlockall(MCL_CURRENT | MCL_FUTURE))
197 {
198 msg(M_WARN | M_ERRNO, "WARNING: mlockall call failed");
199 }
200 else if (print_msg)
201 {
202 msg(M_INFO, "mlockall call succeeded");
203 }
204 #else /* ifdef HAVE_MLOCKALL */
205 msg(M_WARN, "WARNING: mlockall call failed (function not implemented)");
206 #endif
207 }
208
209 /*
210 * Wrapper for chdir library function
211 */
212 int
platform_chdir(const char * dir)213 platform_chdir(const char *dir)
214 {
215 #ifdef HAVE_CHDIR
216 #ifdef _WIN32
217 int res;
218 struct gc_arena gc = gc_new();
219 res = _wchdir(wide_string(dir, &gc));
220 gc_free(&gc);
221 return res;
222 #else /* ifdef _WIN32 */
223 return chdir(dir);
224 #endif
225 #else /* ifdef HAVE_CHDIR */
226 return -1;
227 #endif
228 }
229
230 /*
231 * convert execve() return into a success/failure value
232 */
233 bool
platform_system_ok(int stat)234 platform_system_ok(int stat)
235 {
236 #ifdef _WIN32
237 return stat == 0;
238 #else
239 return stat != -1 && WIFEXITED(stat) && WEXITSTATUS(stat) == 0;
240 #endif
241 }
242
243 int
platform_access(const char * path,int mode)244 platform_access(const char *path, int mode)
245 {
246 #ifdef _WIN32
247 struct gc_arena gc = gc_new();
248 int ret = _waccess(wide_string(path, &gc), mode & ~X_OK);
249 gc_free(&gc);
250 return ret;
251 #else
252 return access(path, mode);
253 #endif
254 }
255
256 /*
257 * Go to sleep for n milliseconds.
258 */
259 void
platform_sleep_milliseconds(unsigned int n)260 platform_sleep_milliseconds(unsigned int n)
261 {
262 #ifdef _WIN32
263 Sleep(n);
264 #else
265 struct timeval tv;
266 tv.tv_sec = n / 1000;
267 tv.tv_usec = (n % 1000) * 1000;
268 select(0, NULL, NULL, NULL, &tv);
269 #endif
270 }
271
272 /*
273 * Go to sleep indefinitely.
274 */
275 void
platform_sleep_until_signal(void)276 platform_sleep_until_signal(void)
277 {
278 #ifdef _WIN32
279 ASSERT(0);
280 #else
281 select(0, NULL, NULL, NULL, NULL);
282 #endif
283 }
284
285 /* delete a file, return true if succeeded */
286 bool
platform_unlink(const char * filename)287 platform_unlink(const char *filename)
288 {
289 #if defined(_WIN32)
290 struct gc_arena gc = gc_new();
291 BOOL ret = DeleteFileW(wide_string(filename, &gc));
292 gc_free(&gc);
293 return (ret != 0);
294 #elif defined(HAVE_UNLINK)
295 return (unlink(filename) == 0);
296 #else /* if defined(_WIN32) */
297 return false;
298 #endif
299 }
300
301 FILE *
platform_fopen(const char * path,const char * mode)302 platform_fopen(const char *path, const char *mode)
303 {
304 #ifdef _WIN32
305 struct gc_arena gc = gc_new();
306 FILE *f = _wfopen(wide_string(path, &gc), wide_string(mode, &gc));
307 gc_free(&gc);
308 return f;
309 #else
310 return fopen(path, mode);
311 #endif
312 }
313
314 int
platform_open(const char * path,int flags,int mode)315 platform_open(const char *path, int flags, int mode)
316 {
317 #ifdef _WIN32
318 struct gc_arena gc = gc_new();
319 int fd = _wopen(wide_string(path, &gc), flags, mode);
320 gc_free(&gc);
321 return fd;
322 #else
323 return open(path, flags, mode);
324 #endif
325 }
326
327 int
platform_stat(const char * path,platform_stat_t * buf)328 platform_stat(const char *path, platform_stat_t *buf)
329 {
330 #ifdef _WIN32
331 struct gc_arena gc = gc_new();
332 int res = _wstat(wide_string(path, &gc), buf);
333 gc_free(&gc);
334 return res;
335 #else
336 return stat(path, buf);
337 #endif
338 }
339
340 /* create a temporary filename in directory */
341 const char *
platform_create_temp_file(const char * directory,const char * prefix,struct gc_arena * gc)342 platform_create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc)
343 {
344 int fd;
345 const char *retfname = NULL;
346 unsigned int attempts = 0;
347 char fname[256] = { 0 };
348 const char *fname_fmt = PACKAGE "_%.*s_%08lx%08lx.tmp";
349 const int max_prefix_len = sizeof(fname) - (sizeof(PACKAGE) + 7 + (2 * 8));
350
351 while (attempts < 6)
352 {
353 ++attempts;
354
355 if (!openvpn_snprintf(fname, sizeof(fname), fname_fmt, max_prefix_len,
356 prefix, (unsigned long) get_random(),
357 (unsigned long) get_random()))
358 {
359 msg(M_WARN, "ERROR: temporary filename too long");
360 return NULL;
361 }
362
363 retfname = platform_gen_path(directory, fname, gc);
364 if (!retfname)
365 {
366 msg(M_WARN, "Failed to create temporary filename and path");
367 return NULL;
368 }
369
370 /* Atomically create the file. Errors out if the file already
371 * exists. */
372 fd = platform_open(retfname, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR);
373 if (fd != -1)
374 {
375 close(fd);
376 return retfname;
377 }
378 else if (fd == -1 && errno != EEXIST)
379 {
380 /* Something else went wrong, no need to retry. */
381 msg(M_WARN | M_ERRNO, "Could not create temporary file '%s'",
382 retfname);
383 return NULL;
384 }
385 }
386
387 msg(M_WARN, "Failed to create temporary file after %i attempts", attempts);
388 return NULL;
389 }
390
391 /*
392 * Put a directory and filename together.
393 */
394 const char *
platform_gen_path(const char * directory,const char * filename,struct gc_arena * gc)395 platform_gen_path(const char *directory, const char *filename,
396 struct gc_arena *gc)
397 {
398 #ifdef _WIN32
399 const int CC_PATH_RESERVED = CC_LESS_THAN|CC_GREATER_THAN|CC_COLON
400 |CC_DOUBLE_QUOTE|CC_SLASH|CC_BACKSLASH|CC_PIPE|CC_QUESTION_MARK|CC_ASTERISK;
401 #else
402 const int CC_PATH_RESERVED = CC_SLASH;
403 #endif
404
405 if (!gc)
406 {
407 return NULL; /* Would leak memory otherwise */
408 }
409
410 const char *safe_filename = string_mod_const(filename, CC_PRINT, CC_PATH_RESERVED, '_', gc);
411
412 if (safe_filename
413 && strcmp(safe_filename, ".")
414 && strcmp(safe_filename, "..")
415 #ifdef _WIN32
416 && win_safe_filename(safe_filename)
417 #endif
418 )
419 {
420 const size_t outsize = strlen(safe_filename) + (directory ? strlen(directory) : 0) + 16;
421 struct buffer out = alloc_buf_gc(outsize, gc);
422 char dirsep[2];
423
424 dirsep[0] = OS_SPECIFIC_DIRSEP;
425 dirsep[1] = '\0';
426
427 if (directory)
428 {
429 buf_printf(&out, "%s%s", directory, dirsep);
430 }
431 buf_printf(&out, "%s", safe_filename);
432
433 return BSTR(&out);
434 }
435 else
436 {
437 return NULL;
438 }
439 }
440
441 bool
platform_absolute_pathname(const char * pathname)442 platform_absolute_pathname(const char *pathname)
443 {
444 if (pathname)
445 {
446 const int c = pathname[0];
447 #ifdef _WIN32
448 return c == '\\' || (isalpha(c) && pathname[1] == ':' && pathname[2] == '\\');
449 #else
450 return c == '/';
451 #endif
452 }
453 else
454 {
455 return false;
456 }
457 }
458
459 /* return true if filename can be opened for read */
460 bool
platform_test_file(const char * filename)461 platform_test_file(const char *filename)
462 {
463 bool ret = false;
464 if (filename)
465 {
466 FILE *fp = platform_fopen(filename, "r");
467 if (fp)
468 {
469 fclose(fp);
470 ret = true;
471 }
472 else
473 {
474 if (openvpn_errno() == EACCES)
475 {
476 msg( M_WARN | M_ERRNO, "Could not access file '%s'", filename);
477 }
478 }
479 }
480
481 dmsg(D_TEST_FILE, "TEST FILE '%s' [%d]",
482 filename ? filename : "UNDEF",
483 ret);
484
485 return ret;
486 }
487