Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * exec.c
4 : * Functions for finding and validating executable files
5 : *
6 : *
7 : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
8 : * Portions Copyright (c) 1994, Regents of the University of California
9 : *
10 : *
11 : * IDENTIFICATION
12 : * src/common/exec.c
13 : *
14 : *-------------------------------------------------------------------------
15 : */
16 :
17 : /*
18 : * On macOS, "man realpath" avers:
19 : * Defining _DARWIN_C_SOURCE or _DARWIN_BETTER_REALPATH before including
20 : * stdlib.h will cause the provided implementation of realpath() to use
21 : * F_GETPATH from fcntl(2) to discover the path.
22 : * This should be harmless everywhere else.
23 : */
24 : #define _DARWIN_BETTER_REALPATH
25 :
26 : #ifndef FRONTEND
27 : #include "postgres.h"
28 : #else
29 : #include "postgres_fe.h"
30 : #endif
31 :
32 : #include <signal.h>
33 : #include <sys/stat.h>
34 : #include <sys/wait.h>
35 : #include <unistd.h>
36 :
37 : #ifdef EXEC_BACKEND
38 : #if defined(HAVE_SYS_PERSONALITY_H)
39 : #include <sys/personality.h>
40 : #elif defined(HAVE_SYS_PROCCTL_H)
41 : #include <sys/procctl.h>
42 : #endif
43 : #endif
44 :
45 : #include "common/string.h"
46 :
47 : /* Inhibit mingw CRT's auto-globbing of command line arguments */
48 : #if defined(WIN32) && !defined(_MSC_VER)
49 : extern int _CRT_glob;
50 : int _CRT_glob = 0; /* 0 turns off globbing; 1 turns it on */
51 : #endif
52 :
53 : /*
54 : * Hacky solution to allow expressing both frontend and backend error reports
55 : * in one macro call. First argument of log_error is an errcode() call of
56 : * some sort (ignored if FRONTEND); the rest are errmsg_internal() arguments,
57 : * i.e. message string and any parameters for it.
58 : *
59 : * Caller must provide the gettext wrapper around the message string, if
60 : * appropriate, so that it gets translated in the FRONTEND case; this
61 : * motivates using errmsg_internal() not errmsg(). We handle appending a
62 : * newline, if needed, inside the macro, so that there's only one translatable
63 : * string per call not two.
64 : */
65 : #ifndef FRONTEND
66 : #define log_error(errcodefn, ...) \
67 : ereport(LOG, (errcodefn, errmsg_internal(__VA_ARGS__)))
68 : #else
69 : #define log_error(errcodefn, ...) \
70 : (fprintf(stderr, __VA_ARGS__), fputc('\n', stderr))
71 : #endif
72 :
73 : static int normalize_exec_path(char *path);
74 : static char *pg_realpath(const char *fname);
75 :
76 : #ifdef WIN32
77 : static BOOL GetTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser);
78 : #endif
79 :
80 : /*
81 : * validate_exec -- validate "path" as an executable file
82 : *
83 : * returns 0 if the file is found and no error is encountered.
84 : * -1 if the regular file "path" does not exist or cannot be executed.
85 : * -2 if the file is otherwise valid but cannot be read.
86 : * in the failure cases, errno is set appropriately
87 : */
88 : int
89 38874 : validate_exec(const char *path)
90 : {
91 : struct stat buf;
92 : int is_r;
93 : int is_x;
94 :
95 : #ifdef WIN32
96 : char path_exe[MAXPGPATH + sizeof(".exe") - 1];
97 :
98 : /* Win32 requires a .exe suffix for stat() */
99 : if (strlen(path) < strlen(".exe") ||
100 : pg_strcasecmp(path + strlen(path) - strlen(".exe"), ".exe") != 0)
101 : {
102 : strlcpy(path_exe, path, sizeof(path_exe) - 4);
103 : strcat(path_exe, ".exe");
104 : path = path_exe;
105 : }
106 : #endif
107 :
108 : /*
109 : * Ensure that the file exists and is a regular file.
110 : *
111 : * XXX if you have a broken system where stat() looks at the symlink
112 : * instead of the underlying file, you lose.
113 : */
114 38874 : if (stat(path, &buf) < 0)
115 0 : return -1;
116 :
117 38874 : if (!S_ISREG(buf.st_mode))
118 : {
119 : /*
120 : * POSIX offers no errno code that's simply "not a regular file". If
121 : * it's a directory we can use EISDIR. Otherwise, it's most likely a
122 : * device special file, and EPERM (Operation not permitted) isn't too
123 : * horribly off base.
124 : */
125 0 : errno = S_ISDIR(buf.st_mode) ? EISDIR : EPERM;
126 0 : return -1;
127 : }
128 :
129 : /*
130 : * Ensure that the file is both executable and readable (required for
131 : * dynamic loading).
132 : */
133 : #ifndef WIN32
134 38874 : is_r = (access(path, R_OK) == 0);
135 38874 : is_x = (access(path, X_OK) == 0);
136 : /* access() will set errno if it returns -1 */
137 : #else
138 : is_r = buf.st_mode & S_IRUSR;
139 : is_x = buf.st_mode & S_IXUSR;
140 : errno = EACCES; /* appropriate thing if we return nonzero */
141 : #endif
142 38874 : return is_x ? (is_r ? 0 : -2) : -1;
143 : }
144 :
145 :
146 : /*
147 : * find_my_exec -- find an absolute path to this program's executable
148 : *
149 : * argv0 is the name passed on the command line
150 : * retpath is the output area (must be of size MAXPGPATH)
151 : * Returns 0 if OK, -1 if error.
152 : *
153 : * The reason we have to work so hard to find an absolute path is that
154 : * on some platforms we can't do dynamic loading unless we know the
155 : * executable's location. Also, we need an absolute path not a relative
156 : * path because we may later change working directory. Finally, we want
157 : * a true path not a symlink location, so that we can locate other files
158 : * that are part of our installation relative to the executable.
159 : */
160 : int
161 36598 : find_my_exec(const char *argv0, char *retpath)
162 : {
163 : char *path;
164 :
165 : /*
166 : * If argv0 contains a separator, then PATH wasn't used.
167 : */
168 36598 : strlcpy(retpath, argv0, MAXPGPATH);
169 36598 : if (first_dir_separator(retpath) != NULL)
170 : {
171 27486 : if (validate_exec(retpath) == 0)
172 27486 : return normalize_exec_path(retpath);
173 :
174 0 : log_error(errcode(ERRCODE_WRONG_OBJECT_TYPE),
175 : _("invalid binary \"%s\": %m"), retpath);
176 0 : return -1;
177 : }
178 :
179 : #ifdef WIN32
180 : /* Win32 checks the current directory first for names without slashes */
181 : if (validate_exec(retpath) == 0)
182 : return normalize_exec_path(retpath);
183 : #endif
184 :
185 : /*
186 : * Since no explicit path was supplied, the user must have been relying on
187 : * PATH. We'll search the same PATH.
188 : */
189 9112 : if ((path = getenv("PATH")) && *path)
190 : {
191 9112 : char *startp = NULL,
192 9112 : *endp = NULL;
193 :
194 : do
195 : {
196 9112 : if (!startp)
197 9112 : startp = path;
198 : else
199 0 : startp = endp + 1;
200 :
201 9112 : endp = first_path_var_separator(startp);
202 9112 : if (!endp)
203 0 : endp = startp + strlen(startp); /* point to end */
204 :
205 9112 : strlcpy(retpath, startp, Min(endp - startp + 1, MAXPGPATH));
206 :
207 9112 : join_path_components(retpath, retpath, argv0);
208 9112 : canonicalize_path(retpath);
209 :
210 9112 : switch (validate_exec(retpath))
211 : {
212 9112 : case 0: /* found ok */
213 9112 : return normalize_exec_path(retpath);
214 0 : case -1: /* wasn't even a candidate, keep looking */
215 0 : break;
216 0 : case -2: /* found but disqualified */
217 0 : log_error(errcode(ERRCODE_WRONG_OBJECT_TYPE),
218 : _("could not read binary \"%s\": %m"),
219 : retpath);
220 0 : break;
221 : }
222 0 : } while (*endp);
223 : }
224 :
225 0 : log_error(errcode(ERRCODE_UNDEFINED_FILE),
226 : _("could not find a \"%s\" to execute"), argv0);
227 0 : return -1;
228 : }
229 :
230 :
231 : /*
232 : * normalize_exec_path - resolve symlinks and convert to absolute path
233 : *
234 : * Given a path that refers to an executable, chase through any symlinks
235 : * to find the real file location; then convert that to an absolute path.
236 : *
237 : * On success, replaces the contents of "path" with the absolute path.
238 : * ("path" is assumed to be of size MAXPGPATH.)
239 : * Returns 0 if OK, -1 if error.
240 : */
241 : static int
242 36598 : normalize_exec_path(char *path)
243 : {
244 : /*
245 : * We used to do a lot of work ourselves here, but now we just let
246 : * realpath(3) do all the heavy lifting.
247 : */
248 36598 : char *abspath = pg_realpath(path);
249 :
250 36598 : if (abspath == NULL)
251 : {
252 0 : log_error(errcode_for_file_access(),
253 : _("could not resolve path \"%s\" to absolute form: %m"),
254 : path);
255 0 : return -1;
256 : }
257 36598 : strlcpy(path, abspath, MAXPGPATH);
258 36598 : free(abspath);
259 :
260 : #ifdef WIN32
261 : /* On Windows, be sure to convert '\' to '/' */
262 : canonicalize_path(path);
263 : #endif
264 :
265 36598 : return 0;
266 : }
267 :
268 :
269 : /*
270 : * pg_realpath() - realpath(3) with POSIX.1-2008 semantics
271 : *
272 : * This is equivalent to realpath(fname, NULL), in that it returns a
273 : * malloc'd buffer containing the absolute path equivalent to fname.
274 : * On error, returns NULL with errno set.
275 : *
276 : * On Windows, what you get is spelled per platform conventions,
277 : * so you probably want to apply canonicalize_path() to the result.
278 : *
279 : * For now, this is needed only here so mark it static. If you choose to
280 : * move it into its own file, move the _DARWIN_BETTER_REALPATH #define too!
281 : */
282 : static char *
283 36598 : pg_realpath(const char *fname)
284 : {
285 : char *path;
286 :
287 : #ifndef WIN32
288 36598 : path = realpath(fname, NULL);
289 : #else /* WIN32 */
290 :
291 : /*
292 : * Microsoft is resolutely non-POSIX, but _fullpath() does the same thing.
293 : * The documentation claims it reports errors by setting errno, which is a
294 : * bit surprising for Microsoft, but we'll believe that until it's proven
295 : * wrong. Clear errno first, though, so we can at least tell if a failure
296 : * occurs and doesn't set it.
297 : */
298 : errno = 0;
299 : path = _fullpath(NULL, fname, 0);
300 : #endif
301 :
302 36598 : return path;
303 : }
304 :
305 :
306 : /*
307 : * Find another program in our binary's directory,
308 : * then make sure it is the proper version.
309 : */
310 : int
311 1800 : find_other_exec(const char *argv0, const char *target,
312 : const char *versionstr, char *retpath)
313 : {
314 : char cmd[MAXPGPATH];
315 : char *line;
316 :
317 1800 : if (find_my_exec(argv0, retpath) < 0)
318 0 : return -1;
319 :
320 : /* Trim off program name and keep just directory */
321 1800 : *last_dir_separator(retpath) = '\0';
322 1800 : canonicalize_path(retpath);
323 :
324 : /* Now append the other program's name */
325 1800 : snprintf(retpath + strlen(retpath), MAXPGPATH - strlen(retpath),
326 : "/%s%s", target, EXE);
327 :
328 1800 : if (validate_exec(retpath) != 0)
329 0 : return -1;
330 :
331 1800 : snprintf(cmd, sizeof(cmd), "\"%s\" -V", retpath);
332 :
333 1800 : if ((line = pipe_read_line(cmd)) == NULL)
334 0 : return -1;
335 :
336 1800 : if (strcmp(line, versionstr) != 0)
337 : {
338 0 : pfree(line);
339 0 : return -2;
340 : }
341 :
342 1800 : pfree(line);
343 1800 : return 0;
344 : }
345 :
346 :
347 : /*
348 : * Execute a command in a pipe and read the first line from it. The returned
349 : * string is palloc'd (malloc'd in frontend code), the caller is responsible
350 : * for freeing.
351 : */
352 : char *
353 2278 : pipe_read_line(char *cmd)
354 : {
355 : FILE *pipe_cmd;
356 : char *line;
357 :
358 2278 : fflush(NULL);
359 :
360 2278 : errno = 0;
361 2278 : if ((pipe_cmd = popen(cmd, "r")) == NULL)
362 : {
363 0 : log_error(errcode(ERRCODE_SYSTEM_ERROR),
364 : _("could not execute command \"%s\": %m"), cmd);
365 0 : return NULL;
366 : }
367 :
368 : /* Make sure popen() didn't change errno */
369 2278 : errno = 0;
370 2278 : line = pg_get_line(pipe_cmd, NULL);
371 :
372 2278 : if (line == NULL)
373 : {
374 0 : if (ferror(pipe_cmd))
375 0 : log_error(errcode_for_file_access(),
376 : _("could not read from command \"%s\": %m"), cmd);
377 : else
378 0 : log_error(errcode(ERRCODE_NO_DATA),
379 : _("no data was returned by command \"%s\""), cmd);
380 : }
381 :
382 2278 : (void) pclose_check(pipe_cmd);
383 :
384 2278 : return line;
385 : }
386 :
387 :
388 : /*
389 : * pclose() plus useful error reporting
390 : */
391 : int
392 2480 : pclose_check(FILE *stream)
393 : {
394 : int exitstatus;
395 : char *reason;
396 :
397 2480 : exitstatus = pclose(stream);
398 :
399 2480 : if (exitstatus == 0)
400 2474 : return 0; /* all is well */
401 :
402 6 : if (exitstatus == -1)
403 : {
404 : /* pclose() itself failed, and hopefully set errno */
405 0 : log_error(errcode(ERRCODE_SYSTEM_ERROR),
406 : _("%s() failed: %m"), "pclose");
407 : }
408 : else
409 : {
410 6 : reason = wait_result_to_str(exitstatus);
411 6 : log_error(errcode(ERRCODE_SYSTEM_ERROR),
412 : "%s", reason);
413 6 : pfree(reason);
414 : }
415 6 : return exitstatus;
416 : }
417 :
418 : /*
419 : * set_pglocale_pgservice
420 : *
421 : * Set application-specific locale and service directory
422 : *
423 : * This function takes the value of argv[0] rather than a full path.
424 : *
425 : * (You may be wondering why this is in exec.c. It requires this module's
426 : * services and doesn't introduce any new dependencies, so this seems as
427 : * good as anyplace.)
428 : */
429 : void
430 31456 : set_pglocale_pgservice(const char *argv0, const char *app)
431 : {
432 : char path[MAXPGPATH];
433 : char my_exec_path[MAXPGPATH];
434 :
435 : /* don't set LC_ALL in the backend */
436 31456 : if (strcmp(app, PG_TEXTDOMAIN("postgres")) != 0)
437 : {
438 27648 : setlocale(LC_ALL, "");
439 :
440 : /*
441 : * One could make a case for reproducing here PostmasterMain()'s test
442 : * for whether the process is multithreaded. Unlike the postmaster,
443 : * no frontend program calls sigprocmask() or otherwise provides for
444 : * mutual exclusion between signal handlers. While frontends using
445 : * fork(), if multithreaded, are formally exposed to undefined
446 : * behavior, we have not witnessed a concrete bug. Therefore,
447 : * complaining about multithreading here may be mere pedantry.
448 : */
449 : }
450 :
451 31456 : if (find_my_exec(argv0, my_exec_path) < 0)
452 0 : return;
453 :
454 : #ifdef ENABLE_NLS
455 31456 : get_locale_path(my_exec_path, path);
456 31456 : bindtextdomain(app, path);
457 31456 : textdomain(app);
458 : /* set for libpq to use, but don't override existing setting */
459 31456 : setenv("PGLOCALEDIR", path, 0);
460 : #endif
461 :
462 31456 : if (getenv("PGSYSCONFDIR") == NULL)
463 : {
464 23538 : get_etc_path(my_exec_path, path);
465 : /* set for libpq to use */
466 23538 : setenv("PGSYSCONFDIR", path, 0);
467 : }
468 : }
469 :
470 : #ifdef EXEC_BACKEND
471 : /*
472 : * For the benefit of PostgreSQL developers testing EXEC_BACKEND on Unix
473 : * systems (code paths normally exercised only on Windows), provide a way to
474 : * disable address space layout randomization, if we know how on this platform.
475 : * Otherwise, backends may fail to attach to shared memory at the fixed address
476 : * chosen by the postmaster. (See also the macOS-specific hack in
477 : * sysv_shmem.c.)
478 : */
479 : int
480 : pg_disable_aslr(void)
481 : {
482 : #if defined(HAVE_SYS_PERSONALITY_H)
483 : return personality(ADDR_NO_RANDOMIZE);
484 : #elif defined(HAVE_SYS_PROCCTL_H) && defined(PROC_ASLR_FORCE_DISABLE)
485 : int data = PROC_ASLR_FORCE_DISABLE;
486 :
487 : return procctl(P_PID, 0, PROC_ASLR_CTL, &data);
488 : #else
489 : errno = ENOSYS;
490 : return -1;
491 : #endif
492 : }
493 : #endif
494 :
495 : #ifdef WIN32
496 :
497 : /*
498 : * AddUserToTokenDacl(HANDLE hToken)
499 : *
500 : * This function adds the current user account to the restricted
501 : * token used when we create a restricted process.
502 : *
503 : * This is required because of some security changes in Windows
504 : * that appeared in patches to XP/2K3 and in Vista/2008.
505 : *
506 : * On these machines, the Administrator account is not included in
507 : * the default DACL - you just get Administrators + System. For
508 : * regular users you get User + System. Because we strip Administrators
509 : * when we create the restricted token, we are left with only System
510 : * in the DACL which leads to access denied errors for later CreatePipe()
511 : * and CreateProcess() calls when running as Administrator.
512 : *
513 : * This function fixes this problem by modifying the DACL of the
514 : * token the process will use, and explicitly re-adding the current
515 : * user account. This is still secure because the Administrator account
516 : * inherits its privileges from the Administrators group - it doesn't
517 : * have any of its own.
518 : */
519 : BOOL
520 : AddUserToTokenDacl(HANDLE hToken)
521 : {
522 : int i;
523 : ACL_SIZE_INFORMATION asi;
524 : ACCESS_ALLOWED_ACE *pace;
525 : DWORD dwNewAclSize;
526 : DWORD dwSize = 0;
527 : DWORD dwTokenInfoLength = 0;
528 : PACL pacl = NULL;
529 : PTOKEN_USER pTokenUser = NULL;
530 : TOKEN_DEFAULT_DACL tddNew;
531 : TOKEN_DEFAULT_DACL *ptdd = NULL;
532 : TOKEN_INFORMATION_CLASS tic = TokenDefaultDacl;
533 : BOOL ret = FALSE;
534 :
535 : /* Figure out the buffer size for the DACL info */
536 : if (!GetTokenInformation(hToken, tic, (LPVOID) NULL, dwTokenInfoLength, &dwSize))
537 : {
538 : if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
539 : {
540 : ptdd = (TOKEN_DEFAULT_DACL *) LocalAlloc(LPTR, dwSize);
541 : if (ptdd == NULL)
542 : {
543 : log_error(errcode(ERRCODE_OUT_OF_MEMORY),
544 : _("out of memory"));
545 : goto cleanup;
546 : }
547 :
548 : if (!GetTokenInformation(hToken, tic, (LPVOID) ptdd, dwSize, &dwSize))
549 : {
550 : log_error(errcode(ERRCODE_SYSTEM_ERROR),
551 : "could not get token information: error code %lu",
552 : GetLastError());
553 : goto cleanup;
554 : }
555 : }
556 : else
557 : {
558 : log_error(errcode(ERRCODE_SYSTEM_ERROR),
559 : "could not get token information buffer size: error code %lu",
560 : GetLastError());
561 : goto cleanup;
562 : }
563 : }
564 :
565 : /* Get the ACL info */
566 : if (!GetAclInformation(ptdd->DefaultDacl, (LPVOID) &asi,
567 : (DWORD) sizeof(ACL_SIZE_INFORMATION),
568 : AclSizeInformation))
569 : {
570 : log_error(errcode(ERRCODE_SYSTEM_ERROR),
571 : "could not get ACL information: error code %lu",
572 : GetLastError());
573 : goto cleanup;
574 : }
575 :
576 : /* Get the current user SID */
577 : if (!GetTokenUser(hToken, &pTokenUser))
578 : goto cleanup; /* callee printed a message */
579 :
580 : /* Figure out the size of the new ACL */
581 : dwNewAclSize = asi.AclBytesInUse + sizeof(ACCESS_ALLOWED_ACE) +
582 : GetLengthSid(pTokenUser->User.Sid) - sizeof(DWORD);
583 :
584 : /* Allocate the ACL buffer & initialize it */
585 : pacl = (PACL) LocalAlloc(LPTR, dwNewAclSize);
586 : if (pacl == NULL)
587 : {
588 : log_error(errcode(ERRCODE_OUT_OF_MEMORY),
589 : _("out of memory"));
590 : goto cleanup;
591 : }
592 :
593 : if (!InitializeAcl(pacl, dwNewAclSize, ACL_REVISION))
594 : {
595 : log_error(errcode(ERRCODE_SYSTEM_ERROR),
596 : "could not initialize ACL: error code %lu", GetLastError());
597 : goto cleanup;
598 : }
599 :
600 : /* Loop through the existing ACEs, and build the new ACL */
601 : for (i = 0; i < (int) asi.AceCount; i++)
602 : {
603 : if (!GetAce(ptdd->DefaultDacl, i, (LPVOID *) &pace))
604 : {
605 : log_error(errcode(ERRCODE_SYSTEM_ERROR),
606 : "could not get ACE: error code %lu", GetLastError());
607 : goto cleanup;
608 : }
609 :
610 : if (!AddAce(pacl, ACL_REVISION, MAXDWORD, pace, ((PACE_HEADER) pace)->AceSize))
611 : {
612 : log_error(errcode(ERRCODE_SYSTEM_ERROR),
613 : "could not add ACE: error code %lu", GetLastError());
614 : goto cleanup;
615 : }
616 : }
617 :
618 : /* Add the new ACE for the current user */
619 : if (!AddAccessAllowedAceEx(pacl, ACL_REVISION, OBJECT_INHERIT_ACE, GENERIC_ALL, pTokenUser->User.Sid))
620 : {
621 : log_error(errcode(ERRCODE_SYSTEM_ERROR),
622 : "could not add access allowed ACE: error code %lu",
623 : GetLastError());
624 : goto cleanup;
625 : }
626 :
627 : /* Set the new DACL in the token */
628 : tddNew.DefaultDacl = pacl;
629 :
630 : if (!SetTokenInformation(hToken, tic, (LPVOID) &tddNew, dwNewAclSize))
631 : {
632 : log_error(errcode(ERRCODE_SYSTEM_ERROR),
633 : "could not set token information: error code %lu",
634 : GetLastError());
635 : goto cleanup;
636 : }
637 :
638 : ret = TRUE;
639 :
640 : cleanup:
641 : if (pTokenUser)
642 : LocalFree((HLOCAL) pTokenUser);
643 :
644 : if (pacl)
645 : LocalFree((HLOCAL) pacl);
646 :
647 : if (ptdd)
648 : LocalFree((HLOCAL) ptdd);
649 :
650 : return ret;
651 : }
652 :
653 : /*
654 : * GetTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser)
655 : *
656 : * Get the users token information from a process token.
657 : *
658 : * The caller of this function is responsible for calling LocalFree() on the
659 : * returned TOKEN_USER memory.
660 : */
661 : static BOOL
662 : GetTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser)
663 : {
664 : DWORD dwLength;
665 :
666 : *ppTokenUser = NULL;
667 :
668 : if (!GetTokenInformation(hToken,
669 : TokenUser,
670 : NULL,
671 : 0,
672 : &dwLength))
673 : {
674 : if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
675 : {
676 : *ppTokenUser = (PTOKEN_USER) LocalAlloc(LPTR, dwLength);
677 :
678 : if (*ppTokenUser == NULL)
679 : {
680 : log_error(errcode(ERRCODE_OUT_OF_MEMORY),
681 : _("out of memory"));
682 : return FALSE;
683 : }
684 : }
685 : else
686 : {
687 : log_error(errcode(ERRCODE_SYSTEM_ERROR),
688 : "could not get token information buffer size: error code %lu",
689 : GetLastError());
690 : return FALSE;
691 : }
692 : }
693 :
694 : if (!GetTokenInformation(hToken,
695 : TokenUser,
696 : *ppTokenUser,
697 : dwLength,
698 : &dwLength))
699 : {
700 : LocalFree(*ppTokenUser);
701 : *ppTokenUser = NULL;
702 :
703 : log_error(errcode(ERRCODE_SYSTEM_ERROR),
704 : "could not get token information: error code %lu",
705 : GetLastError());
706 : return FALSE;
707 : }
708 :
709 : /* Memory in *ppTokenUser is LocalFree():d by the caller */
710 : return TRUE;
711 : }
712 :
713 : #endif
|