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