OpenVPN
platform.c
Go to the documentation of this file.
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-2026 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, see <https://www.gnu.org/licenses/>.
21 */
22
23#ifdef HAVE_CONFIG_H
24#include "config.h"
25#endif
26
27#include "syshead.h"
28
29#include "openvpn.h"
30#include "options.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#ifdef _WIN32
43#include <direct.h>
44#endif
45
46#ifdef HAVE_LIBCAPNG
47#include <cap-ng.h>
48#include <sys/prctl.h>
49#endif
50
51/* Redefine the top level directory of the filesystem
52 * to restrict access to files for security */
53void
54platform_chroot(const char *path)
55{
56 if (path)
57 {
58#ifdef HAVE_CHROOT
59 const char *top = "/";
60 if (chroot(path))
61 {
62 msg(M_ERR, "chroot to '%s' failed", path);
63 }
64 if (platform_chdir(top))
65 {
66 msg(M_ERR, "cd to '%s' failed", top);
67 }
68 msg(M_INFO, "chroot to '%s' and cd to '%s' succeeded", path, top);
69#else /* ifdef HAVE_CHROOT */
71 "Sorry but I can't chroot to '%s' because this operating system doesn't appear to support the chroot() system call",
72 path);
73#endif
74 }
75}
76
77/* Get/Set UID of process */
78
79bool
80platform_user_get(const char *username, struct platform_state_user *state)
81{
82 CLEAR(*state);
83 if (username)
84 {
85#if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID)
86 const struct passwd *pw = getpwnam(username);
87 if (!pw)
88 {
89 msg(M_ERR, "failed to find UID for user %s", username);
90 }
91 else
92 {
93 state->uid = pw->pw_uid;
94 state->user_valid = true;
95 }
96 state->username = username;
97#else /* if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID) */
99 "cannot get UID for user %s -- platform lacks getpwname() or setuid() system calls",
100 username);
101#endif
102 }
103 return state->user_valid;
104}
105
106static void
108{
109#if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID)
110 if (state->username && state->user_valid)
111 {
112 if (setuid(state->uid))
113 {
114 msg(M_ERR, "setuid('%s') failed", state->username);
115 }
116 msg(M_INFO, "UID set to %s", state->username);
117 }
118#endif
119}
120
121/* Get/Set GID of process */
122
123bool
124platform_group_get(const char *groupname, struct platform_state_group *state)
125{
126 CLEAR(*state);
127 if (groupname)
128 {
129#if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID)
130 const struct group *gr = getgrnam(groupname);
131 if (!gr)
132 {
133 msg(M_ERR, "failed to find GID for group %s", groupname);
134 }
135 else
136 {
137 state->gid = gr->gr_gid;
138 state->group_valid = true;
139 }
140 state->groupname = groupname;
141#else /* if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID) */
142 msg(M_FATAL,
143 "cannot get GID for group %s -- platform lacks getgrnam() or setgid() system calls",
144 groupname);
145#endif
146 }
147 return state->group_valid;
148}
149
150static void
152{
153#if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID)
154 if (state->groupname && state->group_valid)
155 {
156 if (setgid(state->gid))
157 {
158 msg(M_ERR, "setgid('%s') failed", state->groupname);
159 }
160 msg(M_INFO, "GID set to %s", state->groupname);
161#ifdef HAVE_SETGROUPS
162 {
163 gid_t gr_list[1];
164 gr_list[0] = state->gid;
165 if (setgroups(1, gr_list))
166 {
167 msg(M_ERR, "setgroups('%s') failed", state->groupname);
168 }
169 }
170#endif
171 }
172#endif
173}
174
175/*
176 * Determine if we need to retain process capabilities. DCO and SITNL need it.
177 * Enforce it for DCO, but only try and soft-fail for SITNL to keep backwards compat.
178 *
179 * Returns the tri-state expected by platform_user_group_set.
180 * -1: try to keep caps, but continue if impossible
181 * 0: don't keep caps
182 * 1: keep caps, fail hard if impossible
183 */
184static int
186{
187 if (!c)
188 {
189 return -1;
190 }
191
192 if (dco_enabled(&c->options))
193 {
194#ifdef TARGET_LINUX
195 /* DCO on Linux does not work at all without CAP_NET_ADMIN */
196 return 1;
197#else
198 /* Windows/BSD/... has no equivalent capability mechanism */
199 return -1;
200#endif
201 }
202
203#ifdef ENABLE_SITNL
204 return -1;
205#else
206 return 0;
207#endif
208}
209
210/* Set user and group, retaining neccesary capabilities required by the platform.
211 *
212 * The keep_caps argument has 3 possible states:
213 * >0: Retain capabilities, and fail hard on failure to do so.
214 * ==0: Don't attempt to retain any capabilities, just sitch user/group.
215 * <0: Try to retain capabilities, but continue on failure.
216 */
217void
219 const struct platform_state_group *group_state, struct context *c)
220{
221 int keep_caps = need_keep_caps(c);
222 unsigned int err_flags = (keep_caps > 0) ? M_FATAL : M_NONFATAL;
223#ifdef HAVE_LIBCAPNG
224 int new_gid = -1, new_uid = -1;
225 int res;
226
227 if (keep_caps == 0)
228 {
229 goto fallback;
230 }
231
232 /*
233 * new_uid/new_gid defaults to -1, which will not make
234 * libcap-ng change the UID/GID unless configured
235 */
236 if (group_state->groupname && group_state->group_valid)
237 {
238 new_gid = (int)group_state->gid;
239 }
240 if (user_state->username && user_state->user_valid)
241 {
242 new_uid = (int)user_state->uid;
243 }
244
245 /* Prepare capabilities before dropping UID/GID */
246 capng_clear(CAPNG_SELECT_BOTH);
247 res = capng_update(CAPNG_ADD, CAPNG_EFFECTIVE | CAPNG_PERMITTED, CAP_NET_ADMIN);
248 if (res < 0)
249 {
250 msg(err_flags, "capng_update(CAP_NET_ADMIN) failed: %d", res);
251 goto fallback;
252 }
253
254 /* Change to new UID/GID.
255 * capng_change_id() internally calls capng_apply() to apply prepared capabilities.
256 */
257 res = capng_change_id(new_uid, new_gid, CAPNG_DROP_SUPP_GRP);
258 if (res == -4 || res == -6)
259 {
260 /* -4 and -6 mean failure of setuid/gid respectively.
261 * There is no point for us to continue if those failed. */
262 msg(M_ERR, "capng_change_id('%s','%s') failed: %d", user_state->username,
263 group_state->groupname, res);
264 }
265 else if (res == -3)
266 {
267 msg(M_NONFATAL | M_ERRNO, "capng_change_id() failed applying capabilities");
268 msg(err_flags, "NOTE: previous error likely due to missing capability CAP_SETPCAP.");
269 goto fallback;
270 }
271 else if (res < 0)
272 {
273 msg(err_flags | M_ERRNO, "capng_change_id('%s','%s') failed retaining capabilities: %d",
274 user_state->username, group_state->groupname, res);
275 goto fallback;
276 }
277
278 if (new_uid >= 0)
279 {
280 msg(M_INFO, "UID set to %s", user_state->username);
281 }
282 if (new_gid >= 0)
283 {
284 msg(M_INFO, "GID set to %s", group_state->groupname);
285 }
286
287 msg(M_INFO, "Capabilities retained: CAP_NET_ADMIN");
288 return;
289
290fallback:
291 /* capng_change_id() can leave this flag clobbered on failure
292 * This is working around a bug in libcap-ng, which can leave the flag set
293 * on failure: https://github.com/stevegrubb/libcap-ng/issues/33 */
294 if (prctl(PR_GET_KEEPCAPS) && prctl(PR_SET_KEEPCAPS, 0) < 0)
295 {
296 msg(M_ERR, "Clearing KEEPCAPS flag failed");
297 }
298#endif /* HAVE_LIBCAPNG */
299
300 if (keep_caps)
301 {
302 msg(err_flags, "Unable to retain capabilities");
303 }
304
305 platform_group_set(group_state);
306 platform_user_set(user_state);
307}
308
309/* Change process priority */
310void
311platform_nice(int niceval)
312{
313 if (niceval)
314 {
315#ifdef HAVE_NICE
316 errno = 0;
317 if (nice(niceval) < 0 && errno != 0)
318 {
319 msg(M_WARN | M_ERRNO, "WARNING: nice %d failed", niceval);
320 }
321 else
322 {
323 msg(M_INFO, "nice %d succeeded", niceval);
324 }
325#else /* ifdef HAVE_NICE */
326 msg(M_WARN, "WARNING: nice %d failed (function not implemented)", niceval);
327#endif
328 }
329}
330
331/* Get current PID */
332unsigned int
334{
335#ifdef _WIN32
336 return (unsigned int)GetCurrentProcessId();
337#else
338 return (unsigned int)getpid();
339#endif
340}
341
342/* Disable paging */
343void
344platform_mlockall(bool print_msg)
345{
346#ifdef HAVE_MLOCKALL
347
348#if defined(HAVE_GETRLIMIT) && defined(RLIMIT_MEMLOCK)
349#define MIN_LOCKED_MEM_MB 100
350 struct rlimit rl;
351 if (getrlimit(RLIMIT_MEMLOCK, &rl) < 0)
352 {
353 msg(M_WARN | M_ERRNO, "WARNING: getrlimit(RLIMIT_MEMLOCK) failed");
354 }
355 else
356 {
357 msg(M_INFO, "mlock: MEMLOCK limit: soft=%ld KB, hard=%ld KB",
358 ((long int)rl.rlim_cur) / 1024, ((long int)rl.rlim_max) / 1024);
359 if (rl.rlim_cur < MIN_LOCKED_MEM_MB * 1024 * 1024)
360 {
361 msg(M_INFO, "mlock: RLIMIT_MEMLOCK < %d MB, increase limit", MIN_LOCKED_MEM_MB);
362 rl.rlim_cur = MIN_LOCKED_MEM_MB * 1024 * 1024;
363 if (rl.rlim_max < rl.rlim_cur)
364 {
365 rl.rlim_max = rl.rlim_cur;
366 }
367 if (setrlimit(RLIMIT_MEMLOCK, &rl) < 0)
368 {
369 msg(M_FATAL | M_ERRNO, "ERROR: setrlimit() failed");
370 }
371 }
372 }
373#endif /* if defined(HAVE_GETRLIMIT) && defined(RLIMIT_MEMLOCK) */
374
375 if (mlockall(MCL_CURRENT | MCL_FUTURE))
376 {
377 msg(M_WARN | M_ERRNO, "WARNING: mlockall call failed");
378 }
379 else if (print_msg)
380 {
381 msg(M_INFO, "mlockall call succeeded");
382 }
383#else /* ifdef HAVE_MLOCKALL */
384 msg(M_WARN, "WARNING: mlockall call failed (function not implemented)");
385#endif /* ifdef HAVE_MLOCKALL */
386}
387
388/*
389 * Wrapper for chdir library function
390 */
391int
392platform_chdir(const char *dir)
393{
394#ifdef _WIN32
395 int res;
396 struct gc_arena gc = gc_new();
397 res = _wchdir(wide_string(dir, &gc));
398 gc_free(&gc);
399 return res;
400#else /* ifdef _WIN32 */
401#ifdef HAVE_CHDIR
402 return chdir(dir);
403#else /* ifdef HAVE_CHDIR */
404 return -1;
405#endif
406#endif
407}
408
409/*
410 * convert execve() return into a success/failure value
411 */
412bool
414{
415#ifdef _WIN32
416 return stat == 0;
417#else
418 return stat != -1 && WIFEXITED(stat) && WEXITSTATUS(stat) == 0;
419#endif
420}
421
422#ifdef _WIN32
423int
425{
426 if (stat >= 0 && stat < 255)
427 {
428 return stat;
429 }
430 else
431 {
432 return -1;
433 }
434}
435#else /* ifdef _WIN32 */
436int
437platform_ret_code(int stat)
438{
439 if (!WIFEXITED(stat) || stat == -1)
440 {
441 return -1;
442 }
443
444 int status = WEXITSTATUS(stat);
445 if (status >= 0 && status < 255)
446 {
447 return status;
448 }
449 else
450 {
451 return -1;
452 }
453}
454#endif /* ifdef _WIN32 */
455
456int
457platform_access(const char *path, int mode)
458{
459#ifdef _WIN32
460 struct gc_arena gc = gc_new();
461 int ret = _waccess(wide_string(path, &gc), mode & ~X_OK);
462 gc_free(&gc);
463 return ret;
464#else
465 return access(path, mode);
466#endif
467}
468
469/*
470 * Go to sleep for n milliseconds.
471 */
472void
474{
475#ifdef _WIN32
476 Sleep(n);
477#else
478 struct timeval tv;
479 tv.tv_sec = n / 1000;
480 tv.tv_usec = (n % 1000) * 1000;
481 select(0, NULL, NULL, NULL, &tv);
482#endif
483}
484
485/* delete a file, return true if succeeded */
486bool
487platform_unlink(const char *filename)
488{
489#if defined(_WIN32)
490 struct gc_arena gc = gc_new();
491 BOOL ret = DeleteFileW(wide_string(filename, &gc));
492 gc_free(&gc);
493 return (ret != 0);
494#else
495 return (unlink(filename) == 0);
496#endif
497}
498
499FILE *
500platform_fopen(const char *path, const char *mode)
501{
502#ifdef _WIN32
503 struct gc_arena gc = gc_new();
504 FILE *f = _wfopen(wide_string(path, &gc), wide_string(mode, &gc));
505 gc_free(&gc);
506 return f;
507#else
508 return fopen(path, mode);
509#endif
510}
511
512int
513platform_open(const char *path, int flags, int mode)
514{
515#ifdef _WIN32
516 struct gc_arena gc = gc_new();
517 int fd = _wopen(wide_string(path, &gc), flags, mode);
518 gc_free(&gc);
519 return fd;
520#else
521 return open(path, flags, mode);
522#endif
523}
524
525int
526platform_stat(const char *path, platform_stat_t *buf)
527{
528#ifdef _WIN32
529 struct gc_arena gc = gc_new();
530 int res = _wstat(wide_string(path, &gc), buf);
531 gc_free(&gc);
532 return res;
533#else
534 return stat(path, buf);
535#endif
536}
537
538/* create a temporary filename in directory */
539const char *
540platform_create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc)
541{
542 unsigned int attempts = 0;
543 char fname[256] = { 0 };
544 const char *fname_fmt = PACKAGE "_%.*s_%08" PRIx64 "%08" PRIx64 ".tmp";
545 const int max_prefix_len = sizeof(fname) - (sizeof(PACKAGE) + 7 + (2 * 8));
546
547 while (attempts < 6)
548 {
549 ++attempts;
550
551 if (!checked_snprintf(fname, sizeof(fname), fname_fmt, max_prefix_len, prefix,
552 (uint64_t)get_random(), (uint64_t)get_random()))
553 {
554 msg(M_WARN, "ERROR: temporary filename too long");
555 return NULL;
556 }
557
558 const char *retfname = platform_gen_path(directory, fname, gc);
559 if (!retfname)
560 {
561 msg(M_WARN, "Failed to create temporary filename and path");
562 return NULL;
563 }
564
565 /* Atomically create the file. Errors out if the file already
566 * exists. */
567 const int fd = platform_open(retfname, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR);
568 if (fd != -1)
569 {
570 close(fd);
571 return retfname;
572 }
573 else if (errno != EEXIST)
574 {
575 /* Something else went wrong, no need to retry. */
576 msg(M_WARN | M_ERRNO, "Could not create temporary file '%s'", retfname);
577 return NULL;
578 }
579 }
580
581 msg(M_WARN, "Failed to create temporary file after %u attempts", attempts);
582 return NULL;
583}
584
585const char *
587{
588 const char *ret;
589#ifdef _WIN32
590 /* On Windows, find temp dir via environment variables */
591 ret = win_get_tempdir();
592
593 if (!ret)
594 {
595 /* Error out if we can't find a valid temporary directory, which should
596 * be very unlikely. */
597 msg(M_USAGE, "Could not find a suitable temporary directory."
598 " (GetTempPath() failed). Consider using --tmp-dir");
599 }
600#else
601 /* Non-windows platforms use $TMPDIR, and if not set, default to '/tmp' */
602 ret = getenv("TMPDIR");
603 if (!ret)
604 {
605 ret = "/tmp";
606 }
607#endif
608
609 return ret;
610}
611
612
613/*
614 * Put a directory and filename together.
615 */
616const char *
617platform_gen_path(const char *directory, const char *filename, struct gc_arena *gc)
618{
619#ifdef _WIN32
620 const int CC_PATH_RESERVED = CC_LESS_THAN | CC_GREATER_THAN | CC_COLON | CC_DOUBLE_QUOTE
622 | CC_ASTERISK;
623#else
624 const int CC_PATH_RESERVED = CC_SLASH;
625#endif
626
627 if (!gc)
628 {
629 return NULL; /* Would leak memory otherwise */
630 }
631
632 const char *safe_filename = string_mod_const(filename, CC_PRINT, CC_PATH_RESERVED, '_', gc);
633
634 if (safe_filename && strcmp(safe_filename, ".") && strcmp(safe_filename, "..")
635#ifdef _WIN32
636 && win_safe_filename(safe_filename)
637#endif
638 )
639 {
640 const size_t outsize = strlen(safe_filename) + (directory ? strlen(directory) : 0) + 16;
641 struct buffer out = alloc_buf_gc(outsize, gc);
642 char dirsep[2];
643
644 dirsep[0] = PATH_SEPARATOR;
645 dirsep[1] = '\0';
646
647 if (directory)
648 {
649 buf_printf(&out, "%s%s", directory, dirsep);
650 }
651 buf_printf(&out, "%s", safe_filename);
652
653 return BSTR(&out);
654 }
655 else
656 {
657 return NULL;
658 }
659}
660
661bool
662platform_absolute_pathname(const char *pathname)
663{
664 if (pathname)
665 {
666 const int c = pathname[0];
667#ifdef _WIN32
668 return c == '\\' || (isalpha(c) && pathname[1] == ':' && pathname[2] == '\\');
669#else
670 return c == '/';
671#endif
672 }
673 else
674 {
675 return false;
676 }
677}
678
679/* return true if filename can be opened for read */
680bool
681platform_test_file(const char *filename)
682{
683 bool ret = false;
684 if (filename)
685 {
686 FILE *fp = platform_fopen(filename, "r");
687 if (fp)
688 {
689 fclose(fp);
690 ret = true;
691 }
692 else
693 {
694 if (errno == EACCES)
695 {
696 msg(M_WARN | M_ERRNO, "Could not access file '%s'", filename);
697 }
698 }
699 }
700
701 dmsg(D_TEST_FILE, "TEST FILE '%s' [%d]", filename ? filename : "UNDEF", ret);
702
703 return ret;
704}
bool buf_printf(struct buffer *buf, const char *format,...)
printf-style append to a buffer with overflow check.
Definition buffer.c:226
struct buffer alloc_buf_gc(size_t size, struct gc_arena *gc)
Allocate a buffer of the given size under garbage collection.
Definition buffer.c:77
const char * string_mod_const(const char *str, const unsigned int inclusive, const unsigned int exclusive, const char replace, struct gc_arena *gc)
Returns a copy of a string with certain classes of characters of it replaced with a specified charact...
Definition buffer.c:1045
bool checked_snprintf(char *str, size_t size, const char *format,...)
Like snprintf() but returns an boolean.
Definition buffer.c:1111
Buffer management functions and garbage collection.
#define CC_DOUBLE_QUOTE
double quote
Definition buffer.h:1725
#define CC_PIPE
pipe
Definition buffer.h:1731
#define BSTR(buf)
Return the buffer content pointer cast to char *.
Definition buffer.h:157
#define CC_COLON
colon
Definition buffer.h:1722
#define CC_ASTERISK
asterisk
Definition buffer.h:1733
#define CC_BACKSLASH
backslash
Definition buffer.h:1717
#define CC_LESS_THAN
less than sign
Definition buffer.h:1729
#define CC_SLASH
slash
Definition buffer.h:1723
#define CC_GREATER_THAN
greater than sign
Definition buffer.h:1730
static void gc_free(struct gc_arena *a)
Free all allocations in a garbage collection arena.
Definition buffer.h:1976
#define CC_PRINT
printable (>= 32, != 127)
Definition buffer.h:1708
#define CC_QUESTION_MARK
question mark
Definition buffer.h:1732
static struct gc_arena gc_new(void)
Allocate and return a new, empty garbage collection arena.
Definition buffer.h:1960
int64_t get_random(void)
an analogue to the random() function, but use prng_bytes and also int64_t instead of long to avoid LL...
Definition crypto.c:1735
Data Channel Cryptography Module.
#define D_TEST_FILE
Definition errlevel.h:136
#define M_INFO
Definition errlevel.h:54
static SERVICE_STATUS status
Definition interactive.c:52
#define CLEAR(x)
Definition basic.h:32
#define M_USAGE
Definition error.h:107
#define M_FATAL
Definition error.h:90
#define M_NONFATAL
Definition error.h:91
#define dmsg(flags,...)
Definition error.h:164
#define M_ERR
Definition error.h:106
#define msg(flags,...)
Definition error.h:152
#define M_WARN
Definition error.h:92
#define M_ERRNO
Definition error.h:95
static bool dco_enabled(const struct options *o)
Returns whether the current configuration has dco enabled.
Definition options.h:972
bool platform_test_file(const char *filename)
Return true if filename can be opened for read.
Definition platform.c:681
void platform_sleep_milliseconds(unsigned int n)
Definition platform.c:473
unsigned int platform_getpid(void)
Definition platform.c:333
const char * platform_create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc)
Create a temporary file in directory, returns the filename of the created file.
Definition platform.c:540
int platform_ret_code(int stat)
Return an exit code if valid and between 0 and 255, -1 otherwise.
Definition platform.c:424
void platform_user_group_set(const struct platform_state_user *user_state, const struct platform_state_group *group_state, struct context *c)
Definition platform.c:218
void platform_nice(int niceval)
Definition platform.c:311
const char * platform_gen_path(const char *directory, const char *filename, struct gc_arena *gc)
Put a directory and filename together.
Definition platform.c:617
int platform_access(const char *path, int mode)
Definition platform.c:457
bool platform_user_get(const char *username, struct platform_state_user *state)
Definition platform.c:80
static void platform_user_set(const struct platform_state_user *state)
Definition platform.c:107
bool platform_unlink(const char *filename)
Definition platform.c:487
static void platform_group_set(const struct platform_state_group *state)
Definition platform.c:151
FILE * platform_fopen(const char *path, const char *mode)
Definition platform.c:500
bool platform_absolute_pathname(const char *pathname)
Return true if pathname is absolute.
Definition platform.c:662
int platform_chdir(const char *dir)
Definition platform.c:392
void platform_mlockall(bool print_msg)
Definition platform.c:344
void platform_chroot(const char *path)
Definition platform.c:54
const char * platform_get_tmp_dir(void)
Get a directory for temporary files.
Definition platform.c:586
static int need_keep_caps(struct context *c)
Definition platform.c:185
bool platform_group_get(const char *groupname, struct platform_state_group *state)
Definition platform.c:124
int platform_open(const char *path, int flags, int mode)
Definition platform.c:513
bool platform_system_ok(int stat)
interpret the status code returned by execve()
Definition platform.c:413
int platform_stat(const char *path, platform_stat_t *buf)
Definition platform.c:526
struct _stat platform_stat_t
Definition platform.h:118
Wrapper structure for dynamically allocated memory.
Definition buffer.h:71
Contains all state information for one tunnel.
Definition openvpn.h:471
struct options options
Options loaded from command line or configuration file.
Definition openvpn.h:472
Garbage collection arena used to keep track of dynamically allocated memory.
Definition buffer.h:127
#define PATH_SEPARATOR
Definition syshead.h:432
struct gc_arena gc
Definition test_ssl.c:122
bool win_safe_filename(const char *fn)
Definition win32-util.c:186
const char * win_get_tempdir(void)
Definition win32-util.c:220
WCHAR * wide_string(const char *utf8, struct gc_arena *gc)
Definition win32-util.c:40