exec.c 20.6 KB
Newer Older
1 2
/*-------------------------------------------------------------------------
 *
3
 * exec.c
4 5
 *		Functions for finding and validating executable files
 *
6
 *
7
 * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
B
Add:  
Bruce Momjian 已提交
8
 * Portions Copyright (c) 1994, Regents of the University of California
9 10 11
 *
 *
 * IDENTIFICATION
12
 *	  $PostgreSQL: pgsql/src/port/exec.c,v 1.61 2008/12/11 07:34:09 petere Exp $
13 14 15
 *
 *-------------------------------------------------------------------------
 */
16 17

#ifndef FRONTEND
18
#include "postgres.h"
19 20 21
#else
#include "postgres_fe.h"
#endif
22

23 24
#include <grp.h>
#include <pwd.h>
25
#include <signal.h>
B
Bruce Momjian 已提交
26
#include <sys/stat.h>
27
#include <sys/wait.h>
28
#include <unistd.h>
29

30 31 32 33 34 35 36 37 38 39
#ifndef S_IRUSR					/* XXX [TRH] should be in a header */
#define S_IRUSR		 S_IREAD
#define S_IWUSR		 S_IWRITE
#define S_IXUSR		 S_IEXEC
#define S_IRGRP		 ((S_IRUSR)>>3)
#define S_IWGRP		 ((S_IWUSR)>>3)
#define S_IXGRP		 ((S_IXUSR)>>3)
#define S_IROTH		 ((S_IRUSR)>>6)
#define S_IWOTH		 ((S_IWUSR)>>6)
#define S_IXOTH		 ((S_IXUSR)>>6)
40 41
#endif

42 43
#ifndef FRONTEND
/* We use only 3-parameter elog calls in this file, for simplicity */
44
/* NOTE: caller must provide gettext call around str! */
45
#define log_error(str, param)	elog(LOG, str, param)
46
#else
47
#define log_error(str, param)	(fprintf(stderr, str, param), fputc('\n', stderr))
48 49
#endif

50
#ifdef WIN32_ONLY_COMPILER
51 52 53 54 55 56 57
#define getcwd(cwd,len)  GetCurrentDirectory(len, cwd)
#endif

static int	validate_exec(const char *path);
static int	resolve_symlinks(char *path);
static char *pipe_read_line(char *cmd, char *line, int maxsize);

58 59 60
#ifdef WIN32
static BOOL GetUserSid(PSID * ppSidUser, HANDLE hToken);
#endif
61

62
/*
63
 * validate_exec -- validate "path" as an executable file
64 65
 *
 * returns 0 if the file is found and no error is encountered.
66 67
 *		  -1 if the regular file "path" does not exist or cannot be executed.
 *		  -2 if the file is otherwise valid but cannot be read.
68
 */
69
static int
70
validate_exec(const char *path)
71
{
72
	struct stat buf;
B
Bruce Momjian 已提交
73

74
#ifndef WIN32
75 76 77
	uid_t		euid;
	struct group *gp;
	struct passwd *pwp;
78 79
	int			i;
	int			in_grp = 0;
B
Bruce Momjian 已提交
80
#else
81
	char		path_exe[MAXPGPATH + sizeof(".exe") - 1];
82
#endif
83 84
	int			is_r;
	int			is_x;
85

86 87
#ifdef WIN32
	/* Win32 requires a .exe suffix for stat() */
88 89
	if (strlen(path) >= strlen(".exe") &&
		pg_strcasecmp(path + strlen(path) - strlen(".exe"), ".exe") != 0)
90 91 92 93 94 95 96
	{
		strcpy(path_exe, path);
		strcat(path_exe, ".exe");
		path = path_exe;
	}
#endif

97 98 99
	/*
	 * Ensure that the file exists and is a regular file.
	 *
100 101
	 * XXX if you have a broken system where stat() looks at the symlink
	 * instead of the underlying file, you lose.
102 103
	 */
	if (stat(path, &buf) < 0)
104
		return -1;
105

106
	if (!S_ISREG(buf.st_mode))
107
		return -1;
108 109

	/*
110
	 * Ensure that we are using an authorized executable.
111 112 113 114 115 116
	 */

	/*
	 * Ensure that the file is both executable and readable (required for
	 * dynamic loading).
	 */
117
#ifdef WIN32
B
Bruce Momjian 已提交
118 119 120
	is_r = buf.st_mode & S_IRUSR;
	is_x = buf.st_mode & S_IXUSR;
	return is_x ? (is_r ? 0 : -2) : -1;
121
#else
122
	euid = geteuid();
123 124

	/* If owned by us, just check owner bits */
125 126 127 128
	if (euid == buf.st_uid)
	{
		is_r = buf.st_mode & S_IRUSR;
		is_x = buf.st_mode & S_IXUSR;
129
		return is_x ? (is_r ? 0 : -2) : -1;
130
	}
131 132

	/* OK, check group bits */
B
Bruce Momjian 已提交
133 134

	pwp = getpwuid(euid);		/* not thread-safe */
135 136
	if (pwp)
	{
137
		if (pwp->pw_gid == buf.st_gid)	/* my primary group? */
138 139
			++in_grp;
		else if (pwp->pw_name &&
140
				 (gp = getgrgid(buf.st_gid)) != NULL && /* not thread-safe */
141
				 gp->gr_mem != NULL)
B
Bruce Momjian 已提交
142
		{						/* try list of member groups */
143 144 145 146 147 148 149 150 151 152 153 154 155
			for (i = 0; gp->gr_mem[i]; ++i)
			{
				if (!strcmp(gp->gr_mem[i], pwp->pw_name))
				{
					++in_grp;
					break;
				}
			}
		}
		if (in_grp)
		{
			is_r = buf.st_mode & S_IRGRP;
			is_x = buf.st_mode & S_IXGRP;
156
			return is_x ? (is_r ? 0 : -2) : -1;
157 158
		}
	}
159 160

	/* Check "other" bits */
161 162
	is_r = buf.st_mode & S_IROTH;
	is_x = buf.st_mode & S_IXOTH;
163
	return is_x ? (is_r ? 0 : -2) : -1;
164
#endif
165 166
}

167

168
/*
169
 * find_my_exec -- find an absolute path to a valid executable
170
 *
171 172 173 174
 *	argv0 is the name passed on the command line
 *	retpath is the output area (must be of size MAXPGPATH)
 *	Returns 0 if OK, -1 if error.
 *
175
 * The reason we have to work so hard to find an absolute path is that
176 177
 * on some platforms we can't do dynamic loading unless we know the
 * executable's location.  Also, we need a full path not a relative
178 179 180
 * path because we will later change working directory.  Finally, we want
 * a true path not a symlink location, so that we can locate other files
 * that are part of our installation relative to the executable.
181
 *
182
 * This function is not thread-safe because it calls validate_exec(),
B
Bruce Momjian 已提交
183
 * which calls getgrgid().	This function should be used only in
184
 * non-threaded binaries, not in library routines.
185 186
 */
int
B
Bruce Momjian 已提交
187
find_my_exec(const char *argv0, char *retpath)
188
{
B
Bruce Momjian 已提交
189 190 191
	char		cwd[MAXPGPATH],
				test_path[MAXPGPATH];
	char	   *path;
192

193
	if (!getcwd(cwd, MAXPGPATH))
194
	{
195
		log_error(_("could not identify current directory: %s"),
196 197 198
				  strerror(errno));
		return -1;
	}
199

200
	/*
201
	 * If argv0 contains a separator, then PATH wasn't used.
202
	 */
203
	if (first_dir_separator(argv0) != NULL)
204
	{
205
		if (is_absolute_path(argv0))
B
Bruce Momjian 已提交
206
			StrNCpy(retpath, argv0, MAXPGPATH);
207
		else
208
			join_path_components(retpath, cwd, argv0);
B
Bruce Momjian 已提交
209
		canonicalize_path(retpath);
210

B
Bruce Momjian 已提交
211
		if (validate_exec(retpath) == 0)
212
			return resolve_symlinks(retpath);
213

214
		log_error(_("invalid binary \"%s\""), retpath);
215
		return -1;
216
	}
217

218 219
#ifdef WIN32
	/* Win32 checks the current directory first for names without slashes */
220 221
	join_path_components(retpath, cwd, argv0);
	if (validate_exec(retpath) == 0)
222
		return resolve_symlinks(retpath);
223 224
#endif

225
	/*
B
Bruce Momjian 已提交
226 227
	 * Since no explicit path was supplied, the user must have been relying on
	 * PATH.  We'll search the same PATH.
228
	 */
229
	if ((path = getenv("PATH")) && *path)
230
	{
B
Bruce Momjian 已提交
231 232
		char	   *startp = NULL,
				   *endp = NULL;
233 234

		do
235
		{
236 237 238 239
			if (!startp)
				startp = path;
			else
				startp = endp + 1;
240

241
			endp = first_path_separator(startp);
242
			if (!endp)
B
Bruce Momjian 已提交
243
				endp = startp + strlen(startp); /* point to end */
244 245 246 247

			StrNCpy(test_path, startp, Min(endp - startp + 1, MAXPGPATH));

			if (is_absolute_path(test_path))
248
				join_path_components(retpath, test_path, argv0);
249
			else
250 251 252 253
			{
				join_path_components(retpath, cwd, test_path);
				join_path_components(retpath, retpath, argv0);
			}
B
Bruce Momjian 已提交
254
			canonicalize_path(retpath);
255

B
Bruce Momjian 已提交
256
			switch (validate_exec(retpath))
257
			{
B
Bruce Momjian 已提交
258
				case 0: /* found ok */
259
					return resolve_symlinks(retpath);
260
				case -1:		/* wasn't even a candidate, keep looking */
261
					break;
262
				case -2:		/* found but disqualified */
263
					log_error(_("could not read binary \"%s\""),
264
							  retpath);
265
					break;
266
			}
267
		} while (*endp);
268 269
	}

270
	log_error(_("could not find a \"%s\" to execute"), argv0);
271
	return -1;
272
}
273

274 275 276 277

/*
 * resolve_symlinks - resolve symlinks to the underlying file
 *
278
 * Replace "path" by the absolute path to the referenced file.
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
 *
 * Returns 0 if OK, -1 if error.
 *
 * Note: we are not particularly tense about producing nice error messages
 * because we are not really expecting error here; we just determined that
 * the symlink does point to a valid executable.
 */
static int
resolve_symlinks(char *path)
{
#ifdef HAVE_READLINK
	struct stat buf;
	char		orig_wd[MAXPGPATH],
				link_buf[MAXPGPATH];
	char	   *fname;

	/*
B
Bruce Momjian 已提交
296 297
	 * To resolve a symlink properly, we have to chdir into its directory and
	 * then chdir to where the symlink points; otherwise we may fail to
298 299 300
	 * resolve relative links correctly (consider cases involving mount
	 * points, for example).  After following the final symlink, we use
	 * getcwd() to figure out where the heck we're at.
301
	 *
302 303 304
	 * One might think we could skip all this if path doesn't point to a
	 * symlink to start with, but that's wrong.  We also want to get rid of
	 * any directory symlinks that are present in the given path. We expect
B
Bruce Momjian 已提交
305
	 * getcwd() to give us an accurate, symlink-free path.
306 307 308
	 */
	if (!getcwd(orig_wd, MAXPGPATH))
	{
309
		log_error(_("could not identify current directory: %s"),
310 311 312 313 314 315
				  strerror(errno));
		return -1;
	}

	for (;;)
	{
B
Bruce Momjian 已提交
316 317
		char	   *lsep;
		int			rllen;
318 319 320 321 322 323 324

		lsep = last_dir_separator(path);
		if (lsep)
		{
			*lsep = '\0';
			if (chdir(path) == -1)
			{
325
				log_error(_("could not change directory to \"%s\""), path);
326 327 328 329 330 331 332 333
				return -1;
			}
			fname = lsep + 1;
		}
		else
			fname = path;

		if (lstat(fname, &buf) < 0 ||
334
			!S_ISLNK(buf.st_mode))
335 336 337 338 339
			break;

		rllen = readlink(fname, link_buf, sizeof(link_buf));
		if (rllen < 0 || rllen >= sizeof(link_buf))
		{
340
			log_error(_("could not read symbolic link \"%s\""), fname);
341 342 343 344 345 346 347 348 349 350 351
			return -1;
		}
		link_buf[rllen] = '\0';
		strcpy(path, link_buf);
	}

	/* must copy final component out of 'path' temporarily */
	strcpy(link_buf, fname);

	if (!getcwd(path, MAXPGPATH))
	{
352
		log_error(_("could not identify current directory: %s"),
353 354 355 356 357 358 359 360
				  strerror(errno));
		return -1;
	}
	join_path_components(path, path, link_buf);
	canonicalize_path(path);

	if (chdir(orig_wd) == -1)
	{
361
		log_error(_("could not change directory to \"%s\""), orig_wd);
362 363
		return -1;
	}
B
Bruce Momjian 已提交
364
#endif   /* HAVE_READLINK */
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406

	return 0;
}


/*
 * Find another program in our binary's directory,
 * then make sure it is the proper version.
 */
int
find_other_exec(const char *argv0, const char *target,
				const char *versionstr, char *retpath)
{
	char		cmd[MAXPGPATH];
	char		line[100];

	if (find_my_exec(argv0, retpath) < 0)
		return -1;

	/* Trim off program name and keep just directory */
	*last_dir_separator(retpath) = '\0';
	canonicalize_path(retpath);

	/* Now append the other program's name */
	snprintf(retpath + strlen(retpath), MAXPGPATH - strlen(retpath),
			 "/%s%s", target, EXE);

	if (validate_exec(retpath) != 0)
		return -1;

	snprintf(cmd, sizeof(cmd), "\"%s\" -V 2>%s", retpath, DEVNULL);

	if (!pipe_read_line(cmd, line, sizeof(line)))
		return -1;

	if (strcmp(line, versionstr) != 0)
		return -2;

	return 0;
}


407
/*
408
 * The runtime library's popen() on win32 does not work when being
409 410 411 412 413 414
 * called from a service when running on windows <= 2000, because
 * there is no stdin/stdout/stderr.
 *
 * Executing a command in a pipe and reading the first line from it
 * is all we need.
 */
B
Bruce Momjian 已提交
415 416
static char *
pipe_read_line(char *cmd, char *line, int maxsize)
417 418
{
#ifndef WIN32
B
Bruce Momjian 已提交
419
	FILE	   *pgver;
420 421 422 423 424 425 426

	/* flush output buffers in case popen does not... */
	fflush(stdout);
	fflush(stderr);

	if ((pgver = popen(cmd, "r")) == NULL)
		return NULL;
B
Bruce Momjian 已提交
427

428 429 430 431 432 433 434 435
	if (fgets(line, maxsize, pgver) == NULL)
	{
		perror("fgets failure");
		return NULL;
	}

	if (pclose_check(pgver))
		return NULL;
B
Bruce Momjian 已提交
436

437
	return line;
B
Bruce Momjian 已提交
438
#else							/* WIN32 */
439

440
	SECURITY_ATTRIBUTES sattr;
B
Bruce Momjian 已提交
441 442 443
	HANDLE		childstdoutrd,
				childstdoutwr,
				childstdoutrddup;
444 445
	PROCESS_INFORMATION pi;
	STARTUPINFO si;
B
Bruce Momjian 已提交
446
	char	   *retval = NULL;
447 448 449 450 451 452 453

	sattr.nLength = sizeof(SECURITY_ATTRIBUTES);
	sattr.bInheritHandle = TRUE;
	sattr.lpSecurityDescriptor = NULL;

	if (!CreatePipe(&childstdoutrd, &childstdoutwr, &sattr, 0))
		return NULL;
B
Bruce Momjian 已提交
454

455 456 457 458 459 460 461 462 463 464 465 466 467 468
	if (!DuplicateHandle(GetCurrentProcess(),
						 childstdoutrd,
						 GetCurrentProcess(),
						 &childstdoutrddup,
						 0,
						 FALSE,
						 DUPLICATE_SAME_ACCESS))
	{
		CloseHandle(childstdoutrd);
		CloseHandle(childstdoutwr);
		return NULL;
	}

	CloseHandle(childstdoutrd);
B
Bruce Momjian 已提交
469 470 471

	ZeroMemory(&pi, sizeof(pi));
	ZeroMemory(&si, sizeof(si));
472 473 474 475 476
	si.cb = sizeof(si);
	si.dwFlags = STARTF_USESTDHANDLES;
	si.hStdError = childstdoutwr;
	si.hStdOutput = childstdoutwr;
	si.hStdInput = INVALID_HANDLE_VALUE;
B
Bruce Momjian 已提交
477

478 479 480 481 482 483 484 485 486 487 488 489
	if (CreateProcess(NULL,
					  cmd,
					  NULL,
					  NULL,
					  TRUE,
					  0,
					  NULL,
					  NULL,
					  &si,
					  &pi))
	{
		/* Successfully started the process */
B
Bruce Momjian 已提交
490
		char	   *lineptr;
491

B
Bruce Momjian 已提交
492 493
		ZeroMemory(line, maxsize);

494 495
		/* Try to read at least one line from the pipe */
		/* This may require more than one wait/read attempt */
B
Bruce Momjian 已提交
496
		for (lineptr = line; lineptr < line + maxsize - 1;)
497
		{
498 499 500 501
			DWORD		bytesread = 0;

			/* Let's see if we can read */
			if (WaitForSingleObject(childstdoutrddup, 10000) != WAIT_OBJECT_0)
B
Bruce Momjian 已提交
502
				break;			/* Timeout, but perhaps we got a line already */
503

B
Bruce Momjian 已提交
504
			if (!ReadFile(childstdoutrddup, lineptr, maxsize - (lineptr - line),
505
						  &bytesread, NULL))
B
Bruce Momjian 已提交
506
				break;			/* Error, but perhaps we got a line already */
507 508 509 510

			lineptr += strlen(lineptr);

			if (!bytesread)
B
Bruce Momjian 已提交
511
				break;			/* EOF */
512 513

			if (strchr(line, '\n'))
B
Bruce Momjian 已提交
514
				break;			/* One or more lines read */
515
		}
516

517
		if (lineptr != line)
518
		{
519 520 521 522
			/* OK, we read some data */
			int			len;

			/* If we got more than one line, cut off after the first \n */
B
Bruce Momjian 已提交
523
			lineptr = strchr(line, '\n');
524
			if (lineptr)
B
Bruce Momjian 已提交
525
				*(lineptr + 1) = '\0';
526 527

			len = strlen(line);
528

529
			/*
B
Bruce Momjian 已提交
530 531
			 * If EOL is \r\n, convert to just \n. Because stdout is a
			 * text-mode stream, the \n output by the child process is
B
Bruce Momjian 已提交
532 533 534
			 * received as \r\n, so we convert it to \n.  The server main.c
			 * sets setvbuf(stdout, NULL, _IONBF, 0) which has the effect of
			 * disabling \n to \r\n expansion for stdout.
535
			 */
B
Bruce Momjian 已提交
536
			if (len >= 2 && line[len - 2] == '\r' && line[len - 1] == '\n')
537
			{
B
Bruce Momjian 已提交
538 539
				line[len - 2] = '\n';
				line[len - 1] = '\0';
540
				len--;
541 542
			}

B
Bruce Momjian 已提交
543
			/*
B
Bruce Momjian 已提交
544 545
			 * We emulate fgets() behaviour. So if there is no newline at the
			 * end, we add one...
B
Bruce Momjian 已提交
546
			 */
B
Bruce Momjian 已提交
547 548
			if (len == 0 || line[len - 1] != '\n')
				strcat(line, "\n");
549 550

			retval = line;
551 552 553 554 555
		}

		CloseHandle(pi.hProcess);
		CloseHandle(pi.hThread);
	}
B
Bruce Momjian 已提交
556

557 558 559 560
	CloseHandle(childstdoutwr);
	CloseHandle(childstdoutrddup);

	return retval;
B
Bruce Momjian 已提交
561
#endif   /* WIN32 */
562 563 564
}


565 566 567 568 569 570 571 572
/*
 * pclose() plus useful error reporting
 * Is this necessary?  bjm 2004-05-11
 * It is better here because pipe.c has win32 backend linkage.
 */
int
pclose_check(FILE *stream)
{
B
Bruce Momjian 已提交
573
	int			exitstatus;
574 575 576 577

	exitstatus = pclose(stream);

	if (exitstatus == 0)
B
Bruce Momjian 已提交
578
		return 0;				/* all is well */
579 580 581 582 583 584 585

	if (exitstatus == -1)
	{
		/* pclose() itself failed, and hopefully set errno */
		perror("pclose failed");
	}
	else if (WIFEXITED(exitstatus))
586
		log_error(_("child process exited with exit code %d"),
B
Bruce Momjian 已提交
587
				  WEXITSTATUS(exitstatus));
588
	else if (WIFSIGNALED(exitstatus))
589
#if defined(WIN32)
590 591 592
		log_error(_("child process was terminated by exception 0x%X"),
				  WTERMSIG(exitstatus));
#elif defined(HAVE_DECL_SYS_SIGLIST) && HAVE_DECL_SYS_SIGLIST
593
	{
B
Bruce Momjian 已提交
594
		char		str[256];
595

596
		snprintf(str, sizeof(str), "%d: %s", WTERMSIG(exitstatus),
B
Bruce Momjian 已提交
597 598
				 WTERMSIG(exitstatus) < NSIG ?
				 sys_siglist[WTERMSIG(exitstatus)] : "(unknown)");
599 600
		log_error(_("child process was terminated by signal %s"), str);
	}
601
#else
602
		log_error(_("child process was terminated by signal %d"),
603
				  WTERMSIG(exitstatus));
604
#endif
605
	else
606
		log_error(_("child process exited with unrecognized status %d"),
B
Bruce Momjian 已提交
607
				  exitstatus);
608 609 610

	return -1;
}
611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632


/*
 *	set_pglocale_pgservice
 *
 *	Set application-specific locale and service directory
 *
 *	This function takes the value of argv[0] rather than a full path.
 *
 * (You may be wondering why this is in exec.c.  It requires this module's
 * services and doesn't introduce any new dependencies, so this seems as
 * good as anyplace.)
 */
void
set_pglocale_pgservice(const char *argv0, const char *app)
{
	char		path[MAXPGPATH];
	char		my_exec_path[MAXPGPATH];
	char		env_path[MAXPGPATH + sizeof("PGSYSCONFDIR=")];	/* longer than
																 * PGLOCALEDIR */

	/* don't set LC_ALL in the backend */
633
	if (strcmp(app, PG_TEXTDOMAIN("postgres")) != 0)
634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662
		setlocale(LC_ALL, "");

	if (find_my_exec(argv0, my_exec_path) < 0)
		return;

#ifdef ENABLE_NLS
	get_locale_path(my_exec_path, path);
	bindtextdomain(app, path);
	textdomain(app);

	if (getenv("PGLOCALEDIR") == NULL)
	{
		/* set for libpq to use */
		snprintf(env_path, sizeof(env_path), "PGLOCALEDIR=%s", path);
		canonicalize_path(env_path + 12);
		putenv(strdup(env_path));
	}
#endif

	if (getenv("PGSYSCONFDIR") == NULL)
	{
		get_etc_path(my_exec_path, path);

		/* set for libpq to use */
		snprintf(env_path, sizeof(env_path), "PGSYSCONFDIR=%s", path);
		canonicalize_path(env_path + 13);
		putenv(strdup(env_path));
	}
}
663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708

#ifdef WIN32

/*
 * AddUserToDacl(HANDLE hProcess)
 *
 * This function adds the current user account to the default DACL
 * which gets attached to the restricted token used when we create
 * a restricted process.
 *
 * This is required because of some security changes in Windows
 * that appeared in patches to XP/2K3 and in Vista/2008.
 *
 * On these machines, the Administrator account is not included in
 * the default DACL - you just get Administrators + System. For
 * regular users you get User + System. Because we strip Administrators
 * when we create the restricted token, we are left with only System
 * in the DACL which leads to access denied errors for later CreatePipe()
 * and CreateProcess() calls when running as Administrator.
 *
 * This function fixes this problem by modifying the DACL of the
 * specified process and explicitly re-adding the current user account.
 * This is still secure because the Administrator account inherits it's
 * privileges from the Administrators group - it doesn't have any of
 * it's own.
 */
BOOL
AddUserToDacl(HANDLE hProcess)
{
	int			i;
	ACL_SIZE_INFORMATION asi;
	ACCESS_ALLOWED_ACE *pace;
	DWORD		dwNewAclSize;
	DWORD		dwSize = 0;
	DWORD		dwTokenInfoLength = 0;
	HANDLE		hToken = NULL;
	PACL		pacl = NULL;
	PSID		psidUser = NULL;
	TOKEN_DEFAULT_DACL tddNew;
	TOKEN_DEFAULT_DACL *ptdd = NULL;
	TOKEN_INFORMATION_CLASS tic = TokenDefaultDacl;
	BOOL		ret = FALSE;

	/* Get the token for the process */
	if (!OpenProcessToken(hProcess, TOKEN_QUERY | TOKEN_ADJUST_DEFAULT, &hToken))
	{
709
		log_error("could not open process token: %lu", GetLastError());
710 711 712 713 714 715 716 717 718 719 720
		goto cleanup;
	}

	/* Figure out the buffer size for the DACL info */
	if (!GetTokenInformation(hToken, tic, (LPVOID) NULL, dwTokenInfoLength, &dwSize))
	{
		if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
		{
			ptdd = (TOKEN_DEFAULT_DACL *) LocalAlloc(LPTR, dwSize);
			if (ptdd == NULL)
			{
721
				log_error("could not allocate %lu bytes of memory", dwSize);
722 723 724 725 726
				goto cleanup;
			}

			if (!GetTokenInformation(hToken, tic, (LPVOID) ptdd, dwSize, &dwSize))
			{
727
				log_error("could not get token information: %lu", GetLastError());
728 729 730 731 732
				goto cleanup;
			}
		}
		else
		{
733
			log_error("could not get token information buffer size: %lu", GetLastError());
734 735 736 737 738 739 740 741 742
			goto cleanup;
		}
	}

	/* Get the ACL info */
	if (!GetAclInformation(ptdd->DefaultDacl, (LPVOID) & asi,
						   (DWORD) sizeof(ACL_SIZE_INFORMATION),
						   AclSizeInformation))
	{
743
		log_error("could not get ACL information: %lu", GetLastError());
744 745 746 747 748 749
		goto cleanup;
	}

	/* Get the SID for the current user. We need to add this to the ACL. */
	if (!GetUserSid(&psidUser, hToken))
	{
750
		log_error("could not get user SID: %lu", GetLastError());
751 752 753 754 755 756 757 758 759 760
		goto cleanup;
	}

	/* Figure out the size of the new ACL */
	dwNewAclSize = asi.AclBytesInUse + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(psidUser) - sizeof(DWORD);

	/* Allocate the ACL buffer & initialize it */
	pacl = (PACL) LocalAlloc(LPTR, dwNewAclSize);
	if (pacl == NULL)
	{
761
		log_error("could not allocate %lu bytes of memory", dwNewAclSize);
762 763 764 765 766
		goto cleanup;
	}

	if (!InitializeAcl(pacl, dwNewAclSize, ACL_REVISION))
	{
767
		log_error("could not initialize ACL: %lu", GetLastError());
768 769 770 771 772 773 774 775
		goto cleanup;
	}

	/* Loop through the existing ACEs, and build the new ACL */
	for (i = 0; i < (int) asi.AceCount; i++)
	{
		if (!GetAce(ptdd->DefaultDacl, i, (LPVOID *) & pace))
		{
776
			log_error("could not get ACE: %lu", GetLastError());
777 778 779 780 781
			goto cleanup;
		}

		if (!AddAce(pacl, ACL_REVISION, MAXDWORD, pace, ((PACE_HEADER) pace)->AceSize))
		{
782
			log_error("could not add ACE: %lu", GetLastError());
783 784 785 786 787 788 789
			goto cleanup;
		}
	}

	/* Add the new ACE for the current user */
	if (!AddAccessAllowedAce(pacl, ACL_REVISION, GENERIC_ALL, psidUser))
	{
790
		log_error("could not add access allowed ACE: %lu", GetLastError());
791 792 793 794 795 796 797 798
		goto cleanup;
	}

	/* Set the new DACL in the token */
	tddNew.DefaultDacl = pacl;

	if (!SetTokenInformation(hToken, tic, (LPVOID) & tddNew, dwNewAclSize))
	{
799
		log_error("could not set token information: %lu", GetLastError());
800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844
		goto cleanup;
	}

	ret = TRUE;

cleanup:
	if (psidUser)
		FreeSid(psidUser);

	if (pacl)
		LocalFree((HLOCAL) pacl);

	if (ptdd)
		LocalFree((HLOCAL) ptdd);

	if (hToken)
		CloseHandle(hToken);

	return ret;
}

/*
 * GetUserSid*PSID *ppSidUser, HANDLE hToken)
 *
 * Get the SID for the current user
 */
static BOOL
GetUserSid(PSID * ppSidUser, HANDLE hToken)
{
	DWORD		dwLength;
	PTOKEN_USER pTokenUser = NULL;


	if (!GetTokenInformation(hToken,
							 TokenUser,
							 pTokenUser,
							 0,
							 &dwLength))
	{
		if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
		{
			pTokenUser = (PTOKEN_USER) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLength);

			if (pTokenUser == NULL)
			{
845
				log_error("could not allocate %lu bytes of memory", dwLength);
846 847 848 849 850
				return FALSE;
			}
		}
		else
		{
851
			log_error("could not get token information buffer size: %lu", GetLastError());
852 853 854 855 856 857 858 859 860 861 862 863 864
			return FALSE;
		}
	}

	if (!GetTokenInformation(hToken,
							 TokenUser,
							 pTokenUser,
							 dwLength,
							 &dwLength))
	{
		HeapFree(GetProcessHeap(), 0, pTokenUser);
		pTokenUser = NULL;

865
		log_error("could not get token information: %lu", GetLastError());
866 867 868 869 870 871 872 873
		return FALSE;
	}

	*ppSidUser = pTokenUser->User.Sid;
	return TRUE;
}

#endif