1/*-------------------------------------------------------------------------
2 *
3 * exec.c
4 * Functions for finding and validating executable files
5 *
6 *
7 * Portions Copyright (c) 1996-2019, 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#ifndef FRONTEND
18#include "postgres.h"
19#else
20#include "postgres_fe.h"
21#endif
22
23#include <signal.h>
24#include <sys/stat.h>
25#include <sys/wait.h>
26#include <unistd.h>
27
28/*
29 * Hacky solution to allow expressing both frontend and backend error reports
30 * in one macro call. First argument of log_error is an errcode() call of
31 * some sort (ignored if FRONTEND); the rest are errmsg_internal() arguments,
32 * i.e. message string and any parameters for it.
33 *
34 * Caller must provide the gettext wrapper around the message string, if
35 * appropriate, so that it gets translated in the FRONTEND case; this
36 * motivates using errmsg_internal() not errmsg(). We handle appending a
37 * newline, if needed, inside the macro, so that there's only one translatable
38 * string per call not two.
39 */
40#ifndef FRONTEND
41#define log_error(errcodefn, ...) \
42 ereport(LOG, (errcodefn, errmsg_internal(__VA_ARGS__)))
43#else
44#define log_error(errcodefn, ...) \
45 (fprintf(stderr, __VA_ARGS__), fputc('\n', stderr))
46#endif
47
48#ifdef _MSC_VER
49#define getcwd(cwd,len) GetCurrentDirectory(len, cwd)
50#endif
51
52static int validate_exec(const char *path);
53static int resolve_symlinks(char *path);
54static char *pipe_read_line(char *cmd, char *line, int maxsize);
55
56#ifdef WIN32
57static BOOL GetTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser);
58#endif
59
60/*
61 * validate_exec -- validate "path" as an executable file
62 *
63 * returns 0 if the file is found and no error is encountered.
64 * -1 if the regular file "path" does not exist or cannot be executed.
65 * -2 if the file is otherwise valid but cannot be read.
66 */
67static int
68validate_exec(const char *path)
69{
70 struct stat buf;
71 int is_r;
72 int is_x;
73
74#ifdef WIN32
75 char path_exe[MAXPGPATH + sizeof(".exe") - 1];
76
77 /* Win32 requires a .exe suffix for stat() */
78 if (strlen(path) >= strlen(".exe") &&
79 pg_strcasecmp(path + strlen(path) - strlen(".exe"), ".exe") != 0)
80 {
81 strlcpy(path_exe, path, sizeof(path_exe) - 4);
82 strcat(path_exe, ".exe");
83 path = path_exe;
84 }
85#endif
86
87 /*
88 * Ensure that the file exists and is a regular file.
89 *
90 * XXX if you have a broken system where stat() looks at the symlink
91 * instead of the underlying file, you lose.
92 */
93 if (stat(path, &buf) < 0)
94 return -1;
95
96 if (!S_ISREG(buf.st_mode))
97 return -1;
98
99 /*
100 * Ensure that the file is both executable and readable (required for
101 * dynamic loading).
102 */
103#ifndef WIN32
104 is_r = (access(path, R_OK) == 0);
105 is_x = (access(path, X_OK) == 0);
106#else
107 is_r = buf.st_mode & S_IRUSR;
108 is_x = buf.st_mode & S_IXUSR;
109#endif
110 return is_x ? (is_r ? 0 : -2) : -1;
111}
112
113
114/*
115 * find_my_exec -- find an absolute path to a valid executable
116 *
117 * argv0 is the name passed on the command line
118 * retpath is the output area (must be of size MAXPGPATH)
119 * Returns 0 if OK, -1 if error.
120 *
121 * The reason we have to work so hard to find an absolute path is that
122 * on some platforms we can't do dynamic loading unless we know the
123 * executable's location. Also, we need a full path not a relative
124 * path because we will later change working directory. Finally, we want
125 * a true path not a symlink location, so that we can locate other files
126 * that are part of our installation relative to the executable.
127 */
128int
129find_my_exec(const char *argv0, char *retpath)
130{
131 char cwd[MAXPGPATH],
132 test_path[MAXPGPATH];
133 char *path;
134
135 if (!getcwd(cwd, MAXPGPATH))
136 {
137 log_error(errcode_for_file_access(),
138 _("could not identify current directory: %m"));
139 return -1;
140 }
141
142 /*
143 * If argv0 contains a separator, then PATH wasn't used.
144 */
145 if (first_dir_separator(argv0) != NULL)
146 {
147 if (is_absolute_path(argv0))
148 StrNCpy(retpath, argv0, MAXPGPATH);
149 else
150 join_path_components(retpath, cwd, argv0);
151 canonicalize_path(retpath);
152
153 if (validate_exec(retpath) == 0)
154 return resolve_symlinks(retpath);
155
156 log_error(errcode(ERRCODE_WRONG_OBJECT_TYPE),
157 _("invalid binary \"%s\""), retpath);
158 return -1;
159 }
160
161#ifdef WIN32
162 /* Win32 checks the current directory first for names without slashes */
163 join_path_components(retpath, cwd, argv0);
164 if (validate_exec(retpath) == 0)
165 return resolve_symlinks(retpath);
166#endif
167
168 /*
169 * Since no explicit path was supplied, the user must have been relying on
170 * PATH. We'll search the same PATH.
171 */
172 if ((path = getenv("PATH")) && *path)
173 {
174 char *startp = NULL,
175 *endp = NULL;
176
177 do
178 {
179 if (!startp)
180 startp = path;
181 else
182 startp = endp + 1;
183
184 endp = first_path_var_separator(startp);
185 if (!endp)
186 endp = startp + strlen(startp); /* point to end */
187
188 StrNCpy(test_path, startp, Min(endp - startp + 1, MAXPGPATH));
189
190 if (is_absolute_path(test_path))
191 join_path_components(retpath, test_path, argv0);
192 else
193 {
194 join_path_components(retpath, cwd, test_path);
195 join_path_components(retpath, retpath, argv0);
196 }
197 canonicalize_path(retpath);
198
199 switch (validate_exec(retpath))
200 {
201 case 0: /* found ok */
202 return resolve_symlinks(retpath);
203 case -1: /* wasn't even a candidate, keep looking */
204 break;
205 case -2: /* found but disqualified */
206 log_error(errcode(ERRCODE_WRONG_OBJECT_TYPE),
207 _("could not read binary \"%s\""),
208 retpath);
209 break;
210 }
211 } while (*endp);
212 }
213
214 log_error(errcode(ERRCODE_UNDEFINED_FILE),
215 _("could not find a \"%s\" to execute"), argv0);
216 return -1;
217}
218
219
220/*
221 * resolve_symlinks - resolve symlinks to the underlying file
222 *
223 * Replace "path" by the absolute path to the referenced file.
224 *
225 * Returns 0 if OK, -1 if error.
226 *
227 * Note: we are not particularly tense about producing nice error messages
228 * because we are not really expecting error here; we just determined that
229 * the symlink does point to a valid executable.
230 */
231static int
232resolve_symlinks(char *path)
233{
234#ifdef HAVE_READLINK
235 struct stat buf;
236 char orig_wd[MAXPGPATH],
237 link_buf[MAXPGPATH];
238 char *fname;
239
240 /*
241 * To resolve a symlink properly, we have to chdir into its directory and
242 * then chdir to where the symlink points; otherwise we may fail to
243 * resolve relative links correctly (consider cases involving mount
244 * points, for example). After following the final symlink, we use
245 * getcwd() to figure out where the heck we're at.
246 *
247 * One might think we could skip all this if path doesn't point to a
248 * symlink to start with, but that's wrong. We also want to get rid of
249 * any directory symlinks that are present in the given path. We expect
250 * getcwd() to give us an accurate, symlink-free path.
251 */
252 if (!getcwd(orig_wd, MAXPGPATH))
253 {
254 log_error(errcode_for_file_access(),
255 _("could not identify current directory: %m"));
256 return -1;
257 }
258
259 for (;;)
260 {
261 char *lsep;
262 int rllen;
263
264 lsep = last_dir_separator(path);
265 if (lsep)
266 {
267 *lsep = '\0';
268 if (chdir(path) == -1)
269 {
270 log_error(errcode_for_file_access(),
271 _("could not change directory to \"%s\": %m"), path);
272 return -1;
273 }
274 fname = lsep + 1;
275 }
276 else
277 fname = path;
278
279 if (lstat(fname, &buf) < 0 ||
280 !S_ISLNK(buf.st_mode))
281 break;
282
283 errno = 0;
284 rllen = readlink(fname, link_buf, sizeof(link_buf));
285 if (rllen < 0 || rllen >= sizeof(link_buf))
286 {
287 log_error(errcode_for_file_access(),
288 _("could not read symbolic link \"%s\": %m"), fname);
289 return -1;
290 }
291 link_buf[rllen] = '\0';
292 strcpy(path, link_buf);
293 }
294
295 /* must copy final component out of 'path' temporarily */
296 strlcpy(link_buf, fname, sizeof(link_buf));
297
298 if (!getcwd(path, MAXPGPATH))
299 {
300 log_error(errcode_for_file_access(),
301 _("could not identify current directory: %m"));
302 return -1;
303 }
304 join_path_components(path, path, link_buf);
305 canonicalize_path(path);
306
307 if (chdir(orig_wd) == -1)
308 {
309 log_error(errcode_for_file_access(),
310 _("could not change directory to \"%s\": %m"), orig_wd);
311 return -1;
312 }
313#endif /* HAVE_READLINK */
314
315 return 0;
316}
317
318
319/*
320 * Find another program in our binary's directory,
321 * then make sure it is the proper version.
322 */
323int
324find_other_exec(const char *argv0, const char *target,
325 const char *versionstr, char *retpath)
326{
327 char cmd[MAXPGPATH];
328 char line[MAXPGPATH];
329
330 if (find_my_exec(argv0, retpath) < 0)
331 return -1;
332
333 /* Trim off program name and keep just directory */
334 *last_dir_separator(retpath) = '\0';
335 canonicalize_path(retpath);
336
337 /* Now append the other program's name */
338 snprintf(retpath + strlen(retpath), MAXPGPATH - strlen(retpath),
339 "/%s%s", target, EXE);
340
341 if (validate_exec(retpath) != 0)
342 return -1;
343
344 snprintf(cmd, sizeof(cmd), "\"%s\" -V", retpath);
345
346 if (!pipe_read_line(cmd, line, sizeof(line)))
347 return -1;
348
349 if (strcmp(line, versionstr) != 0)
350 return -2;
351
352 return 0;
353}
354
355
356/*
357 * The runtime library's popen() on win32 does not work when being
358 * called from a service when running on windows <= 2000, because
359 * there is no stdin/stdout/stderr.
360 *
361 * Executing a command in a pipe and reading the first line from it
362 * is all we need.
363 */
364static char *
365pipe_read_line(char *cmd, char *line, int maxsize)
366{
367#ifndef WIN32
368 FILE *pgver;
369
370 /* flush output buffers in case popen does not... */
371 fflush(stdout);
372 fflush(stderr);
373
374 errno = 0;
375 if ((pgver = popen(cmd, "r")) == NULL)
376 {
377 perror("popen failure");
378 return NULL;
379 }
380
381 errno = 0;
382 if (fgets(line, maxsize, pgver) == NULL)
383 {
384 if (feof(pgver))
385 fprintf(stderr, "no data was returned by command \"%s\"\n", cmd);
386 else
387 perror("fgets failure");
388 pclose(pgver); /* no error checking */
389 return NULL;
390 }
391
392 if (pclose_check(pgver))
393 return NULL;
394
395 return line;
396#else /* WIN32 */
397
398 SECURITY_ATTRIBUTES sattr;
399 HANDLE childstdoutrd,
400 childstdoutwr,
401 childstdoutrddup;
402 PROCESS_INFORMATION pi;
403 STARTUPINFO si;
404 char *retval = NULL;
405
406 sattr.nLength = sizeof(SECURITY_ATTRIBUTES);
407 sattr.bInheritHandle = TRUE;
408 sattr.lpSecurityDescriptor = NULL;
409
410 if (!CreatePipe(&childstdoutrd, &childstdoutwr, &sattr, 0))
411 return NULL;
412
413 if (!DuplicateHandle(GetCurrentProcess(),
414 childstdoutrd,
415 GetCurrentProcess(),
416 &childstdoutrddup,
417 0,
418 FALSE,
419 DUPLICATE_SAME_ACCESS))
420 {
421 CloseHandle(childstdoutrd);
422 CloseHandle(childstdoutwr);
423 return NULL;
424 }
425
426 CloseHandle(childstdoutrd);
427
428 ZeroMemory(&pi, sizeof(pi));
429 ZeroMemory(&si, sizeof(si));
430 si.cb = sizeof(si);
431 si.dwFlags = STARTF_USESTDHANDLES;
432 si.hStdError = childstdoutwr;
433 si.hStdOutput = childstdoutwr;
434 si.hStdInput = INVALID_HANDLE_VALUE;
435
436 if (CreateProcess(NULL,
437 cmd,
438 NULL,
439 NULL,
440 TRUE,
441 0,
442 NULL,
443 NULL,
444 &si,
445 &pi))
446 {
447 /* Successfully started the process */
448 char *lineptr;
449
450 ZeroMemory(line, maxsize);
451
452 /* Try to read at least one line from the pipe */
453 /* This may require more than one wait/read attempt */
454 for (lineptr = line; lineptr < line + maxsize - 1;)
455 {
456 DWORD bytesread = 0;
457
458 /* Let's see if we can read */
459 if (WaitForSingleObject(childstdoutrddup, 10000) != WAIT_OBJECT_0)
460 break; /* Timeout, but perhaps we got a line already */
461
462 if (!ReadFile(childstdoutrddup, lineptr, maxsize - (lineptr - line),
463 &bytesread, NULL))
464 break; /* Error, but perhaps we got a line already */
465
466 lineptr += strlen(lineptr);
467
468 if (!bytesread)
469 break; /* EOF */
470
471 if (strchr(line, '\n'))
472 break; /* One or more lines read */
473 }
474
475 if (lineptr != line)
476 {
477 /* OK, we read some data */
478 int len;
479
480 /* If we got more than one line, cut off after the first \n */
481 lineptr = strchr(line, '\n');
482 if (lineptr)
483 *(lineptr + 1) = '\0';
484
485 len = strlen(line);
486
487 /*
488 * If EOL is \r\n, convert to just \n. Because stdout is a
489 * text-mode stream, the \n output by the child process is
490 * received as \r\n, so we convert it to \n. The server main.c
491 * sets setvbuf(stdout, NULL, _IONBF, 0) which has the effect of
492 * disabling \n to \r\n expansion for stdout.
493 */
494 if (len >= 2 && line[len - 2] == '\r' && line[len - 1] == '\n')
495 {
496 line[len - 2] = '\n';
497 line[len - 1] = '\0';
498 len--;
499 }
500
501 /*
502 * We emulate fgets() behaviour. So if there is no newline at the
503 * end, we add one...
504 */
505 if (len == 0 || line[len - 1] != '\n')
506 strcat(line, "\n");
507
508 retval = line;
509 }
510
511 CloseHandle(pi.hProcess);
512 CloseHandle(pi.hThread);
513 }
514
515 CloseHandle(childstdoutwr);
516 CloseHandle(childstdoutrddup);
517
518 return retval;
519#endif /* WIN32 */
520}
521
522
523/*
524 * pclose() plus useful error reporting
525 */
526int
527pclose_check(FILE *stream)
528{
529 int exitstatus;
530 char *reason;
531
532 exitstatus = pclose(stream);
533
534 if (exitstatus == 0)
535 return 0; /* all is well */
536
537 if (exitstatus == -1)
538 {
539 /* pclose() itself failed, and hopefully set errno */
540 log_error(errcode(ERRCODE_SYSTEM_ERROR),
541 _("pclose failed: %m"));
542 }
543 else
544 {
545 reason = wait_result_to_str(exitstatus);
546 log_error(errcode(ERRCODE_SYSTEM_ERROR),
547 "%s", reason);
548 pfree(reason);
549 }
550 return exitstatus;
551}
552
553/*
554 * set_pglocale_pgservice
555 *
556 * Set application-specific locale and service directory
557 *
558 * This function takes the value of argv[0] rather than a full path.
559 *
560 * (You may be wondering why this is in exec.c. It requires this module's
561 * services and doesn't introduce any new dependencies, so this seems as
562 * good as anyplace.)
563 */
564void
565set_pglocale_pgservice(const char *argv0, const char *app)
566{
567 char path[MAXPGPATH];
568 char my_exec_path[MAXPGPATH];
569 char env_path[MAXPGPATH + sizeof("PGSYSCONFDIR=")]; /* longer than
570 * PGLOCALEDIR */
571 char *dup_path;
572
573 /* don't set LC_ALL in the backend */
574 if (strcmp(app, PG_TEXTDOMAIN("postgres")) != 0)
575 {
576 setlocale(LC_ALL, "");
577
578 /*
579 * One could make a case for reproducing here PostmasterMain()'s test
580 * for whether the process is multithreaded. Unlike the postmaster,
581 * no frontend program calls sigprocmask() or otherwise provides for
582 * mutual exclusion between signal handlers. While frontends using
583 * fork(), if multithreaded, are formally exposed to undefined
584 * behavior, we have not witnessed a concrete bug. Therefore,
585 * complaining about multithreading here may be mere pedantry.
586 */
587 }
588
589 if (find_my_exec(argv0, my_exec_path) < 0)
590 return;
591
592#ifdef ENABLE_NLS
593 get_locale_path(my_exec_path, path);
594 bindtextdomain(app, path);
595 textdomain(app);
596
597 if (getenv("PGLOCALEDIR") == NULL)
598 {
599 /* set for libpq to use */
600 snprintf(env_path, sizeof(env_path), "PGLOCALEDIR=%s", path);
601 canonicalize_path(env_path + 12);
602 dup_path = strdup(env_path);
603 if (dup_path)
604 putenv(dup_path);
605 }
606#endif
607
608 if (getenv("PGSYSCONFDIR") == NULL)
609 {
610 get_etc_path(my_exec_path, path);
611
612 /* set for libpq to use */
613 snprintf(env_path, sizeof(env_path), "PGSYSCONFDIR=%s", path);
614 canonicalize_path(env_path + 13);
615 dup_path = strdup(env_path);
616 if (dup_path)
617 putenv(dup_path);
618 }
619}
620
621#ifdef WIN32
622
623/*
624 * AddUserToTokenDacl(HANDLE hToken)
625 *
626 * This function adds the current user account to the restricted
627 * token used when we create a restricted process.
628 *
629 * This is required because of some security changes in Windows
630 * that appeared in patches to XP/2K3 and in Vista/2008.
631 *
632 * On these machines, the Administrator account is not included in
633 * the default DACL - you just get Administrators + System. For
634 * regular users you get User + System. Because we strip Administrators
635 * when we create the restricted token, we are left with only System
636 * in the DACL which leads to access denied errors for later CreatePipe()
637 * and CreateProcess() calls when running as Administrator.
638 *
639 * This function fixes this problem by modifying the DACL of the
640 * token the process will use, and explicitly re-adding the current
641 * user account. This is still secure because the Administrator account
642 * inherits its privileges from the Administrators group - it doesn't
643 * have any of its own.
644 */
645BOOL
646AddUserToTokenDacl(HANDLE hToken)
647{
648 int i;
649 ACL_SIZE_INFORMATION asi;
650 ACCESS_ALLOWED_ACE *pace;
651 DWORD dwNewAclSize;
652 DWORD dwSize = 0;
653 DWORD dwTokenInfoLength = 0;
654 PACL pacl = NULL;
655 PTOKEN_USER pTokenUser = NULL;
656 TOKEN_DEFAULT_DACL tddNew;
657 TOKEN_DEFAULT_DACL *ptdd = NULL;
658 TOKEN_INFORMATION_CLASS tic = TokenDefaultDacl;
659 BOOL ret = FALSE;
660
661 /* Figure out the buffer size for the DACL info */
662 if (!GetTokenInformation(hToken, tic, (LPVOID) NULL, dwTokenInfoLength, &dwSize))
663 {
664 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
665 {
666 ptdd = (TOKEN_DEFAULT_DACL *) LocalAlloc(LPTR, dwSize);
667 if (ptdd == NULL)
668 {
669 log_error(errcode(ERRCODE_OUT_OF_MEMORY),
670 _("out of memory"));
671 goto cleanup;
672 }
673
674 if (!GetTokenInformation(hToken, tic, (LPVOID) ptdd, dwSize, &dwSize))
675 {
676 log_error(errcode(ERRCODE_SYSTEM_ERROR),
677 "could not get token information: error code %lu",
678 GetLastError());
679 goto cleanup;
680 }
681 }
682 else
683 {
684 log_error(errcode(ERRCODE_SYSTEM_ERROR),
685 "could not get token information buffer size: error code %lu",
686 GetLastError());
687 goto cleanup;
688 }
689 }
690
691 /* Get the ACL info */
692 if (!GetAclInformation(ptdd->DefaultDacl, (LPVOID) &asi,
693 (DWORD) sizeof(ACL_SIZE_INFORMATION),
694 AclSizeInformation))
695 {
696 log_error(errcode(ERRCODE_SYSTEM_ERROR),
697 "could not get ACL information: error code %lu",
698 GetLastError());
699 goto cleanup;
700 }
701
702 /* Get the current user SID */
703 if (!GetTokenUser(hToken, &pTokenUser))
704 goto cleanup; /* callee printed a message */
705
706 /* Figure out the size of the new ACL */
707 dwNewAclSize = asi.AclBytesInUse + sizeof(ACCESS_ALLOWED_ACE) +
708 GetLengthSid(pTokenUser->User.Sid) - sizeof(DWORD);
709
710 /* Allocate the ACL buffer & initialize it */
711 pacl = (PACL) LocalAlloc(LPTR, dwNewAclSize);
712 if (pacl == NULL)
713 {
714 log_error(errcode(ERRCODE_OUT_OF_MEMORY),
715 _("out of memory"));
716 goto cleanup;
717 }
718
719 if (!InitializeAcl(pacl, dwNewAclSize, ACL_REVISION))
720 {
721 log_error(errcode(ERRCODE_SYSTEM_ERROR),
722 "could not initialize ACL: error code %lu", GetLastError());
723 goto cleanup;
724 }
725
726 /* Loop through the existing ACEs, and build the new ACL */
727 for (i = 0; i < (int) asi.AceCount; i++)
728 {
729 if (!GetAce(ptdd->DefaultDacl, i, (LPVOID *) &pace))
730 {
731 log_error(errcode(ERRCODE_SYSTEM_ERROR),
732 "could not get ACE: error code %lu", GetLastError());
733 goto cleanup;
734 }
735
736 if (!AddAce(pacl, ACL_REVISION, MAXDWORD, pace, ((PACE_HEADER) pace)->AceSize))
737 {
738 log_error(errcode(ERRCODE_SYSTEM_ERROR),
739 "could not add ACE: error code %lu", GetLastError());
740 goto cleanup;
741 }
742 }
743
744 /* Add the new ACE for the current user */
745 if (!AddAccessAllowedAceEx(pacl, ACL_REVISION, OBJECT_INHERIT_ACE, GENERIC_ALL, pTokenUser->User.Sid))
746 {
747 log_error(errcode(ERRCODE_SYSTEM_ERROR),
748 "could not add access allowed ACE: error code %lu",
749 GetLastError());
750 goto cleanup;
751 }
752
753 /* Set the new DACL in the token */
754 tddNew.DefaultDacl = pacl;
755
756 if (!SetTokenInformation(hToken, tic, (LPVOID) &tddNew, dwNewAclSize))
757 {
758 log_error(errcode(ERRCODE_SYSTEM_ERROR),
759 "could not set token information: error code %lu",
760 GetLastError());
761 goto cleanup;
762 }
763
764 ret = TRUE;
765
766cleanup:
767 if (pTokenUser)
768 LocalFree((HLOCAL) pTokenUser);
769
770 if (pacl)
771 LocalFree((HLOCAL) pacl);
772
773 if (ptdd)
774 LocalFree((HLOCAL) ptdd);
775
776 return ret;
777}
778
779/*
780 * GetTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser)
781 *
782 * Get the users token information from a process token.
783 *
784 * The caller of this function is responsible for calling LocalFree() on the
785 * returned TOKEN_USER memory.
786 */
787static BOOL
788GetTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser)
789{
790 DWORD dwLength;
791
792 *ppTokenUser = NULL;
793
794 if (!GetTokenInformation(hToken,
795 TokenUser,
796 NULL,
797 0,
798 &dwLength))
799 {
800 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
801 {
802 *ppTokenUser = (PTOKEN_USER) LocalAlloc(LPTR, dwLength);
803
804 if (*ppTokenUser == NULL)
805 {
806 log_error(errcode(ERRCODE_OUT_OF_MEMORY),
807 _("out of memory"));
808 return FALSE;
809 }
810 }
811 else
812 {
813 log_error(errcode(ERRCODE_SYSTEM_ERROR),
814 "could not get token information buffer size: error code %lu",
815 GetLastError());
816 return FALSE;
817 }
818 }
819
820 if (!GetTokenInformation(hToken,
821 TokenUser,
822 *ppTokenUser,
823 dwLength,
824 &dwLength))
825 {
826 LocalFree(*ppTokenUser);
827 *ppTokenUser = NULL;
828
829 log_error(errcode(ERRCODE_SYSTEM_ERROR),
830 "could not get token information: error code %lu",
831 GetLastError());
832 return FALSE;
833 }
834
835 /* Memory in *ppTokenUser is LocalFree():d by the caller */
836 return TRUE;
837}
838
839#endif
840