xref: /illumos-gate/usr/src/cmd/newtask/newtask.c (revision 7c478bd9)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <sys/types.h>
30 #include <sys/task.h>
31 
32 #include <alloca.h>
33 #include <libproc.h>
34 #include <libintl.h>
35 #include <libgen.h>
36 #include <limits.h>
37 #include <project.h>
38 #include <pwd.h>
39 #include <secdb.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <sys/varargs.h>
44 #include <unistd.h>
45 #include <errno.h>
46 #include <signal.h>
47 #include <priv_utils.h>
48 
49 #include "utils.h"
50 
51 #define	OPTIONS_STRING	"Fc:lp:v"
52 #define	NENV		8
53 #define	ENVSIZE		255
54 #define	PATH		"PATH=/usr/bin"
55 #define	SUPATH		"PATH=/usr/sbin:/usr/bin"
56 #define	SHELL		"/usr/bin/sh"
57 #define	SHELL2		"/sbin/sh"
58 #define	TIMEZONEFILE	"/etc/default/init"
59 #define	LOGINFILE	"/etc/default/login"
60 #define	GLOBAL_ERR_SZ	1024
61 #define	GRAB_RETRY_MAX	100
62 
63 static const char *pname;
64 extern char **environ;
65 static char *supath = SUPATH;
66 static char *path = PATH;
67 static char global_error[GLOBAL_ERR_SZ];
68 static int verbose = 0;
69 
70 static priv_set_t *nset;
71 
72 /* Private definitions for libproject */
73 extern projid_t setproject_proc(const char *, const char *, int, pid_t,
74     struct ps_prochandle *, struct project *);
75 extern priv_set_t *setproject_initpriv(void);
76 
77 static void usage(void);
78 
79 static void preserve_error(const char *format, ...);
80 
81 static int update_running_proc(int, char *, char *);
82 static int set_ids(struct ps_prochandle *, struct project *,
83     struct passwd *);
84 static struct passwd *match_user(uid_t, char *, int);
85 static void setproject_err(char *, char *, int, struct project *);
86 
87 static void
88 usage(void)
89 {
90 	(void) fprintf(stderr, gettext("usage: \n\t%s [-v] [-p project] "
91 	    "[-c pid | [-Fl] [command [args ...]]]\n"), pname);
92 	exit(2);
93 }
94 
95 int
96 main(int argc, char *argv[])
97 {
98 	int c;
99 	struct passwd *pw;
100 	char *projname = NULL;
101 	uid_t uid;
102 	int login_flag = 0;
103 	int finalize_flag = TASK_NORMAL;
104 	int newproj_flag = 0;
105 	taskid_t taskid;
106 	char *shell;
107 	char *env[NENV];
108 	char **targs;
109 	char *filename, *procname = NULL;
110 	int error;
111 
112 	nset = setproject_initpriv();
113 	if (nset == NULL)
114 		die(gettext("privilege initialization failed\n"));
115 
116 	pname = getpname(argv[0]);
117 
118 	while ((c = getopt(argc, argv, OPTIONS_STRING)) != EOF) {
119 		switch (c) {
120 		case 'v':
121 			verbose = 1;
122 			break;
123 		case 'p':
124 			newproj_flag = 1;
125 			projname = optarg;
126 			break;
127 		case 'F':
128 			finalize_flag = TASK_FINAL;
129 			break;
130 		case 'l':
131 			login_flag++;
132 			break;
133 		case 'c':
134 			procname = optarg;
135 			break;
136 		case '?':
137 		default:
138 			usage();
139 			/*NOTREACHED*/
140 		}
141 	}
142 
143 	/* -c option is invalid with -F, -l, or a specified command */
144 	if ((procname != NULL) &&
145 	    (finalize_flag == TASK_FINAL || login_flag || optind < argc))
146 		usage();
147 
148 	if (procname != NULL) {
149 		/* Change project/task of an existing process */
150 		return (update_running_proc(newproj_flag, procname, projname));
151 	}
152 
153 	/*
154 	 * Get user data, so that we can confirm project membership as
155 	 * well as construct an appropriate login environment.
156 	 */
157 	uid = getuid();
158 	if ((pw = match_user(uid, projname, 1)) == NULL) {
159 		die("%s\n", global_error);
160 	}
161 
162 	/*
163 	 * If no projname was specified, we're just creating a new task
164 	 * under the current project, so we can just set the new taskid.
165 	 * If our project is changing, we need to update any attendant
166 	 * pool/rctl bindings, so let setproject() do the dirty work.
167 	 */
168 	(void) __priv_bracket(PRIV_ON);
169 	if (projname == NULL) {
170 		if (settaskid(getprojid(), finalize_flag) == -1)
171 			if (errno == EAGAIN)
172 				die(gettext("resource control limit has been "
173 				    "reached"));
174 			else
175 				die(gettext("settaskid failed"));
176 	} else {
177 		if ((error = setproject(projname,
178 		    pw->pw_name, finalize_flag)) != 0) {
179 			setproject_err(pw->pw_name, projname, error, NULL);
180 			if (error < 0)
181 				die("%s\n", global_error);
182 			else
183 				warn("%s\n", global_error);
184 		}
185 	}
186 	__priv_relinquish();
187 
188 	taskid = gettaskid();
189 
190 	if (verbose)
191 		(void) fprintf(stderr, "%d\n", (int)taskid);
192 
193 	/*
194 	 * Validate user's shell from passwd database.
195 	 */
196 	if (strcmp(pw->pw_shell, "") == 0) {
197 		if (access(SHELL, X_OK) == 0)
198 			pw->pw_shell = SHELL;
199 		else
200 			pw->pw_shell = SHELL2;
201 	}
202 
203 	if (login_flag) {
204 		/*
205 		 * Since we've been invoked as a "simulated login", set up the
206 		 * environment.
207 		 */
208 		char *cur_tz = getenv("TZ");
209 		char *cur_term = getenv("TERM");
210 
211 		char **envnext;
212 
213 		size_t len_home = strlen(pw->pw_dir) + strlen("HOME=") + 1;
214 		size_t len_logname = strlen(pw->pw_name) + strlen("LOGNAME=") +
215 		    1;
216 		size_t len_shell = strlen(pw->pw_shell) + strlen("SHELL=") + 1;
217 		size_t len_mail = strlen(pw->pw_name) +
218 		    strlen("MAIL=/var/mail/") + 1;
219 		size_t len_tz;
220 		size_t len_term;
221 
222 		char *env_home = safe_malloc(len_home);
223 		char *env_logname = safe_malloc(len_logname);
224 		char *env_shell = safe_malloc(len_shell);
225 		char *env_mail = safe_malloc(len_mail);
226 		char *env_tz;
227 		char *env_term;
228 
229 		(void) snprintf(env_home, len_home, "HOME=%s", pw->pw_dir);
230 		(void) snprintf(env_logname, len_logname, "LOGNAME=%s",
231 		    pw->pw_name);
232 		(void) snprintf(env_shell, len_shell, "SHELL=%s", pw->pw_shell);
233 		(void) snprintf(env_mail, len_mail, "MAIL=/var/mail/%s",
234 		    pw->pw_name);
235 
236 		env[0] = env_home;
237 		env[1] = env_logname;
238 		env[2] = (pw->pw_uid == 0 ? supath : path);
239 		env[3] = env_shell;
240 		env[4] = env_mail;
241 		env[5] = NULL;
242 		env[6] = NULL;
243 		env[7] = NULL;
244 
245 		envnext = (char **)&env[5];
246 
247 		/*
248 		 * It's possible that TERM wasn't defined in the outer
249 		 * environment.
250 		 */
251 		if (cur_term != NULL) {
252 			len_term = strlen(cur_term) + strlen("TERM=") + 1;
253 			env_term = safe_malloc(len_term);
254 
255 			(void) snprintf(env_term, len_term, "TERM=%s",
256 			    cur_term);
257 			*envnext = env_term;
258 			envnext++;
259 		}
260 
261 		/*
262 		 * It is also possible that TZ wasn't defined in the outer
263 		 * environment.  In that case, we must attempt to open the file
264 		 * defining the default timezone and select the appropriate
265 		 * entry. If there is no default timezone there, try
266 		 * TIMEZONE in /etc/default/login, duplicating the algorithm
267 		 * that login uses.
268 		 */
269 		if (cur_tz != NULL) {
270 			len_tz = strlen(cur_tz) + strlen("TZ=") + 1;
271 			env_tz = safe_malloc(len_tz);
272 
273 			(void) snprintf(env_tz, len_tz, "TZ=%s", cur_tz);
274 			*envnext = env_tz;
275 		} else {
276 			if ((env_tz = getdefault(TIMEZONEFILE, "TZ=",
277 			    "TZ=")) != NULL)
278 				*envnext = env_tz;
279 			else {
280 				env_tz = getdefault(LOGINFILE, "TIMEZONE=",
281 				    "TZ=");
282 				*envnext = env_tz;
283 			}
284 		}
285 
286 		environ = (char **)&env[0];
287 
288 		/*
289 		 * Prefix the shell string with a hyphen, indicating a login
290 		 * shell.
291 		 */
292 		shell = safe_malloc(PATH_MAX);
293 		(void) snprintf(shell, PATH_MAX, "-%s", basename(pw->pw_shell));
294 	} else {
295 		shell = basename(pw->pw_shell);
296 	}
297 
298 	/*
299 	 * If there are no arguments, we launch the user's shell; otherwise, the
300 	 * remaining commands are assumed to form a valid command invocation
301 	 * that we can exec.
302 	 */
303 	if (optind >= argc) {
304 		targs = alloca(2 * sizeof (char *));
305 		filename = pw->pw_shell;
306 		targs[0] = shell;
307 		targs[1] = NULL;
308 	} else {
309 		targs = &argv[optind];
310 		filename = targs[0];
311 	}
312 
313 	if (execvp(filename, targs) == -1)
314 		die(gettext("exec of %s failed"), targs[0]);
315 
316 	/*
317 	 * We should never get here.
318 	 */
319 	return (1);
320 }
321 
322 static int
323 update_running_proc(int newproj_flag, char *procname, char *projname)
324 {
325 	struct ps_prochandle *p;
326 	prcred_t original_prcred, current_prcred;
327 	projid_t prprojid;
328 	taskid_t taskid;
329 	int error = 0, gret;
330 	struct project project;
331 	char prbuf[PROJECT_BUFSZ];
332 	struct passwd *passwd_entry;
333 	int grab_retry_count = 0;
334 
335 	/*
336 	 * Catch signals from terminal. There isn't much sense in
337 	 * doing anything but ignoring them since we don't do anything
338 	 * after the point we'd be capable of handling them again.
339 	 */
340 	(void) sigignore(SIGHUP);
341 	(void) sigignore(SIGINT);
342 	(void) sigignore(SIGQUIT);
343 	(void) sigignore(SIGTERM);
344 
345 	/* flush stdout before grabbing the proc to avoid deadlock */
346 	(void) fflush(stdout);
347 
348 	/*
349 	 * We need to grab the process, which will force it to stop execution
350 	 * until the grab is released, in order to aquire some information about
351 	 * it, such as its current project (which is achieved via an injected
352 	 * system call and therefore needs an agent) and its credentials. We
353 	 * will then need to release it again because it may be a process that
354 	 * we rely on for later calls, for example nscd.
355 	 */
356 	if ((p = proc_arg_grab(procname, PR_ARG_PIDS, 0, &gret)) == NULL) {
357 		warn(gettext("failed to grab for process %s: %s\n"),
358 		    procname, Pgrab_error(gret));
359 		return (1);
360 	}
361 	if (Pcreate_agent(p) != 0) {
362 		Prelease(p, 0);
363 		warn(gettext("cannot control process %s\n"), procname);
364 		return (1);
365 	}
366 
367 	/*
368 	 * The victim process is now held. Do not call any functions
369 	 * which generate stdout/stderr until the process has been
370 	 * released.
371 	 */
372 
373 /*
374  * The target process will soon be restarted (in case it is in newtask's
375  * execution path) and then stopped again. We need to ensure that our cached
376  * data doesn't change while the process runs so return here if the target
377  * process changes its user id in between our stop operations, so that we can
378  * try again.
379  */
380 pgrab_retry:
381 
382 	/* Cache required information about the process. */
383 	if (Pcred(p, &original_prcred, 0) != 0) {
384 		preserve_error(gettext("cannot get process credentials %s\n"),
385 		    procname);
386 		error = 1;
387 	}
388 	if ((prprojid = pr_getprojid(p)) == -1) {
389 		preserve_error(gettext("cannot get process project id %s\n"),
390 		    procname);
391 		error = 1;
392 	}
393 
394 	/*
395 	 * We now have all the required information, so release the target
396 	 * process and perform our sanity checks. The process needs to be
397 	 * running at this point because it may be in the execution path of the
398 	 * calls made below.
399 	 */
400 	Pdestroy_agent(p);
401 	Prelease(p, 0);
402 
403 	/* if our data acquisition failed, then we can't continue. */
404 	if (error) {
405 		warn("%s\n", global_error);
406 		return (1);
407 	}
408 
409 	if (newproj_flag == 0) {
410 		/*
411 		 * Just changing the task, so set projname to the current
412 		 * project of the running process.
413 		 */
414 		if (getprojbyid(prprojid, &project, &prbuf,
415 		    PROJECT_BUFSZ) == NULL) {
416 			warn(gettext("unable to get project name "
417 			    "for projid %d"), prprojid);
418 			return (1);
419 		}
420 		projname = project.pj_name;
421 	} else {
422 		/*
423 		 * cache info for the project which user passed in via the
424 		 * command line
425 		 */
426 		if (getprojbyname(projname, &project, &prbuf,
427 		    PROJECT_BUFSZ) == NULL) {
428 			warn(gettext("unknown project \"%s\"\n"), projname);
429 			return (1);
430 		}
431 	}
432 
433 	/*
434 	 * Use our cached information to verify that the owner of the running
435 	 * process is a member of proj
436 	 */
437 	if ((passwd_entry = match_user(original_prcred.pr_ruid,
438 	    projname, 0)) == NULL) {
439 		warn("%s\n", global_error);
440 		return (1);
441 	}
442 
443 	/*
444 	 * We can now safely stop the process again in order to change the
445 	 * project and taskid as required.
446 	 */
447 	if ((p = proc_arg_grab(procname, PR_ARG_PIDS, 0, &gret)) == NULL) {
448 		warn(gettext("failed to grab for process %s: %s\n"),
449 		    procname, Pgrab_error(gret));
450 		return (1);
451 	}
452 	if (Pcreate_agent(p) != 0) {
453 		Prelease(p, 0);
454 		warn(gettext("cannot control process %s\n"), procname);
455 		return (1);
456 	}
457 
458 	/*
459 	 * Now that the target process is stopped, check the validity of our
460 	 * cached info. If we aren't superuser then match_user() will have
461 	 * checked to make sure that the owner of the process is in the relevant
462 	 * project. If our ruid has changed, then match_user()'s conclusion may
463 	 * be invalid.
464 	 */
465 	if (getuid() != 0) {
466 		if (Pcred(p, &current_prcred, 0) != 0) {
467 			Pdestroy_agent(p);
468 			Prelease(p, 0);
469 			warn(gettext("can't get process credentials %s\n"),
470 			    procname);
471 			return (1);
472 		}
473 
474 		if (original_prcred.pr_ruid != current_prcred.pr_ruid) {
475 			if (grab_retry_count++ < GRAB_RETRY_MAX)
476 				goto pgrab_retry;
477 
478 			warn(gettext("process consistently changed its "
479 			    "user id %s\n"), procname);
480 			return (1);
481 		}
482 	}
483 
484 	error = set_ids(p, &project, passwd_entry);
485 
486 	if (verbose)
487 		taskid = pr_gettaskid(p);
488 
489 	Pdestroy_agent(p);
490 	Prelease(p, 0);
491 
492 	if (error) {
493 		/*
494 		 * error is serious enough to stop, only if negative.
495 		 * Otherwise, it simply indicates one of the resource
496 		 * control assignments failed, which is worth warning
497 		 * about.
498 		 */
499 		warn("%s\n", global_error);
500 		if (error < 0)
501 			return (1);
502 	}
503 
504 	if (verbose)
505 		(void) fprintf(stderr, "%d\n", (int)taskid);
506 
507 	return (0);
508 }
509 
510 static int
511 set_ids(struct ps_prochandle *p, struct project *project,
512     struct passwd *passwd_entry)
513 {
514 	int be_su = 0;
515 	prcred_t old_prcred;
516 	int error;
517 	prpriv_t *old_prpriv, *new_prpriv;
518 	size_t prsz = sizeof (prpriv_t);
519 	priv_set_t *eset, *pset;
520 	int ind;
521 
522 	if (Pcred(p, &old_prcred, 0) != 0) {
523 		preserve_error(gettext("can't get process credentials"));
524 		return (1);
525 	}
526 
527 	old_prpriv = proc_get_priv(Pstatus(p)->pr_pid);
528 	if (old_prpriv == NULL) {
529 		preserve_error(gettext("can't get process privileges"));
530 		return (1);
531 	}
532 
533 	prsz = PRIV_PRPRIV_SIZE(old_prpriv);
534 
535 	new_prpriv = malloc(prsz);
536 	if (new_prpriv == NULL) {
537 		preserve_error(gettext("can't allocate memory"));
538 		free(old_prpriv);
539 		return (1);
540 	}
541 
542 	(void) memcpy(new_prpriv, old_prpriv, prsz);
543 
544 	/*
545 	 * If the process already has the proc_taskid privilege,
546 	 * we don't need to elevate its privileges; if it doesn't,
547 	 * we try to do it here.
548 	 * As we do not wish to leave a window in which the process runs
549 	 * with elevated privileges, we make sure that the process dies
550 	 * when we go away unexpectedly.
551 	 */
552 
553 	ind = priv_getsetbyname(PRIV_EFFECTIVE);
554 	eset = (priv_set_t *)&new_prpriv->pr_sets[new_prpriv->pr_setsize * ind];
555 	ind = priv_getsetbyname(PRIV_PERMITTED);
556 	pset = (priv_set_t *)&new_prpriv->pr_sets[new_prpriv->pr_setsize * ind];
557 
558 	if (!priv_issubset(nset, eset)) {
559 		be_su = 1;
560 		priv_union(nset, eset);
561 		priv_union(nset, pset);
562 		if (Psetflags(p, PR_KLC) != 0) {
563 			preserve_error(gettext("cannot set process "
564 			    "privileges"));
565 			(void) Punsetflags(p, PR_KLC);
566 			free(new_prpriv);
567 			free(old_prpriv);
568 			return (1);
569 		}
570 		(void) __priv_bracket(PRIV_ON);
571 		if (Psetpriv(p, new_prpriv) != 0) {
572 			(void) __priv_bracket(PRIV_OFF);
573 			preserve_error(gettext("cannot set process "
574 			    "privileges"));
575 			(void) Punsetflags(p, PR_KLC);
576 			free(new_prpriv);
577 			free(old_prpriv);
578 			return (1);
579 		}
580 		(void) __priv_bracket(PRIV_OFF);
581 	}
582 
583 	(void) __priv_bracket(PRIV_ON);
584 	if ((error = setproject_proc(project->pj_name,
585 	    passwd_entry->pw_name, 0, Pstatus(p)->pr_pid, p, project)) != 0) {
586 		/* global_error is set by setproject_err */
587 		setproject_err(passwd_entry->pw_name, project->pj_name,
588 		    error, project);
589 	}
590 	(void) __priv_bracket(PRIV_OFF);
591 
592 	/* relinquish added privileges */
593 	if (be_su) {
594 		(void) __priv_bracket(PRIV_ON);
595 		if (Psetpriv(p, old_prpriv) != 0) {
596 			/*
597 			 * We shouldn't ever be in a state where we can't
598 			 * set the process back to its old creds, but we
599 			 * don't want to take the chance of leaving a
600 			 * non-privileged process with enhanced creds. So,
601 			 * release the process from libproc control, knowing
602 			 * that it will be killed.
603 			 */
604 			(void) __priv_bracket(PRIV_OFF);
605 			Pdestroy_agent(p);
606 			die(gettext("cannot relinquish superuser credentials "
607 			    "for pid %d. The process was killed."),
608 			    Pstatus(p)->pr_pid);
609 		}
610 		(void) __priv_bracket(PRIV_OFF);
611 		if (Punsetflags(p, PR_KLC) != 0)
612 			preserve_error(gettext("error relinquishing "
613 			    "credentials. Process %d will be killed."),
614 			    Pstatus(p)->pr_pid);
615 	}
616 	free(new_prpriv);
617 	free(old_prpriv);
618 
619 	return (error);
620 }
621 
622 /*
623  * preserve_error() should be called rather than warn() by any
624  * function that is called while the victim process is being
625  * held by Pgrab.
626  *
627  * It saves a single error message to be printed until after
628  * the process has been released. Since multiple errors are not
629  * stored, any error should be considered critical.
630  */
631 void
632 preserve_error(const char *format, ...)
633 {
634 	va_list alist;
635 
636 	va_start(alist, format);
637 
638 	/*
639 	 * GLOBAL_ERR_SZ is pretty big. If the error is longer
640 	 * than that, just truncate it, rather than chance missing
641 	 * the error altogether.
642 	 */
643 	(void) vsnprintf(global_error, GLOBAL_ERR_SZ-1, format, alist);
644 
645 	va_end(alist);
646 
647 }
648 
649 /*
650  * Given the input arguments, return the passwd structure that matches best.
651  * Also, since we use getpwnam() and friends, subsequent calls to this
652  * function will re-use the memory previously returned.
653  */
654 static struct passwd *
655 match_user(uid_t uid, char *projname, int is_my_uid)
656 {
657 	char prbuf[PROJECT_BUFSZ], username[LOGNAME_MAX+1];
658 	struct project prj;
659 	char *tmp_name;
660 	struct passwd *pw = NULL;
661 
662 	/*
663 	 * In order to allow users with the same UID but distinguishable
664 	 * user names to be in different projects we play a guessing
665 	 * game of which username is most appropriate. If we're checking
666 	 * for the uid of the calling process, the login name is a
667 	 * good starting point.
668 	 */
669 	if (is_my_uid) {
670 		if ((tmp_name = getlogin()) == NULL ||
671 		    (pw = getpwnam(tmp_name)) == NULL || (pw->pw_uid != uid) ||
672 		    (pw->pw_name == NULL))
673 			pw = NULL;
674 	}
675 
676 	/*
677 	 * If the login name doesn't work,  we try the first match for
678 	 * the current uid in the password file.
679 	 */
680 	if (pw == NULL) {
681 		if (((pw = getpwuid(uid)) == NULL) || pw->pw_name == NULL) {
682 			preserve_error(gettext("cannot find username "
683 			    "for uid %d"), uid);
684 			return (NULL);
685 		}
686 	}
687 
688 	/*
689 	 * If projname wasn't supplied, we've done our best, so just return
690 	 * what we've got now. Alternatively, if newtask's invoker has
691 	 * superuser privileges, return the pw structure we've got now, with
692 	 * no further checking from inproj(). Superuser should be able to
693 	 * join any project, and the subsequent call to setproject() will
694 	 * allow this.
695 	 */
696 	if (projname == NULL || getuid() == (uid_t)0)
697 		return (pw);
698 
699 	(void) strcpy(username, pw->pw_name);
700 
701 	if (inproj(username, projname, prbuf, PROJECT_BUFSZ) == 0) {
702 		char **u;
703 		tmp_name = NULL;
704 
705 		/*
706 		 * If the previous guesses didn't work, walk through all
707 		 * project members and test for UID-equivalence.
708 		 */
709 
710 		if (getprojbyname(projname, &prj, prbuf,
711 		    PROJECT_BUFSZ) == NULL) {
712 			preserve_error(gettext("unknown project \"%s\""),
713 			    projname);
714 			return (NULL);
715 		}
716 
717 		for (u = prj.pj_users; *u; u++) {
718 			if ((pw = getpwnam(*u)) == NULL)
719 				continue;
720 
721 			if (pw->pw_uid == uid) {
722 				tmp_name = pw->pw_name;
723 				break;
724 			}
725 		}
726 
727 		if (tmp_name == NULL) {
728 			preserve_error(gettext("user \"%s\" is not a member of "
729 			    "project \"%s\""), username, projname);
730 			return (NULL);
731 		}
732 	}
733 
734 	return (pw);
735 }
736 
737 void
738 setproject_err(char *username, char *projname, int error, struct project *proj)
739 {
740 	kva_t *kv_array = NULL;
741 	char prbuf[PROJECT_BUFSZ];
742 	struct project local_proj;
743 
744 	switch (error) {
745 	case SETPROJ_ERR_TASK:
746 		if (errno == EAGAIN)
747 			preserve_error(gettext("resource control limit has "
748 			    "been reached"));
749 		else if (errno == ESRCH)
750 			preserve_error(gettext("user \"%s\" is not a member of "
751 			    "project \"%s\""), username, projname);
752 		else if (errno == EACCES)
753 			preserve_error(gettext("the invoking task is final"));
754 		else
755 			preserve_error(
756 			    gettext("could not join project \"%s\""),
757 			    projname);
758 		break;
759 	case SETPROJ_ERR_POOL:
760 		if (errno == EACCES)
761 			preserve_error(gettext("no resource pool accepting "
762 			    "default bindings exists for project \"%s\""),
763 			    projname);
764 		else if (errno == ESRCH)
765 			preserve_error(gettext("specified resource pool does "
766 			    "not exist for project \"%s\""), projname);
767 		else
768 			preserve_error(gettext("could not bind to default "
769 			    "resource pool for project \"%s\""), projname);
770 		break;
771 	default:
772 		if (error <= 0) {
773 			preserve_error(gettext("setproject failed for "
774 			    "project \"%s\""), projname);
775 			return;
776 		}
777 		/*
778 		 * If we have a stopped target process it may be in
779 		 * getprojbyname()'s execution path which would make it unsafe
780 		 * to access the project table, so only do that if the caller
781 		 * hasn't provided a cached version of the project structure.
782 		 */
783 		if (proj == NULL)
784 			proj = getprojbyname(projname, &local_proj, prbuf,
785 			    PROJECT_BUFSZ);
786 
787 		if (proj == NULL || (kv_array = _str2kva(proj->pj_attr,
788 		    KV_ASSIGN, KV_DELIMITER)) == NULL ||
789 		    kv_array->length < error) {
790 			preserve_error(gettext("warning, resource control "
791 			    "assignment failed for project \"%s\" "
792 			    "attribute %d"),
793 			    projname, error);
794 			if (kv_array)
795 				_kva_free(kv_array);
796 			return;
797 		}
798 		preserve_error(gettext("warning, %s resource control "
799 		    "assignment failed for project \"%s\""),
800 		    kv_array->data[error - 1].key, projname);
801 		_kva_free(kv_array);
802 	}
803 }
804