| 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 | |
| 52 | static int validate_exec(const char *path); |
| 53 | static int resolve_symlinks(char *path); |
| 54 | static char *pipe_read_line(char *cmd, char *line, int maxsize); |
| 55 | |
| 56 | #ifdef WIN32 |
| 57 | static 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 | */ |
| 67 | static int |
| 68 | validate_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 | */ |
| 128 | int |
| 129 | find_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 | */ |
| 231 | static int |
| 232 | resolve_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 | */ |
| 323 | int |
| 324 | find_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 | */ |
| 364 | static char * |
| 365 | pipe_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 | */ |
| 526 | int |
| 527 | pclose_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 | */ |
| 564 | void |
| 565 | set_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 | */ |
| 645 | BOOL |
| 646 | AddUserToTokenDacl(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 | |
| 766 | cleanup: |
| 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 | */ |
| 787 | static BOOL |
| 788 | GetTokenUser(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 | |