Coverage Report

Created: 2025-06-15 06:31

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