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 | |