Merge commit '00f1a4f432b3d8aad1aa270e91c44c57f03ef407'
[unleashed.git] / usr / src / cmd / newtask / newtask.c
blob92ca08bae7c5553bbd676fe566e7995001c4ec94
1 /*
2 * CDDL HEADER START
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.
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.
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]
20 * CDDL HEADER END
23 * Copyright (c) 2013 Gary Mills
24 * Copyright 2015, Joyent, Inc.
26 * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
27 * Use is subject to license terms.
30 #include <sys/types.h>
31 #include <sys/task.h>
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>
50 #include "utils.h"
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 "/bin/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
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;
70 static priv_set_t *nset;
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);
77 static void usage(void);
79 static void preserve_error(const char *format, ...);
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 *);
87 static void
88 usage(void)
90 (void) fprintf(stderr, gettext("usage: \n\t%s [-v] [-p project] "
91 "[-c pid | [-Fl] [command [args ...]]]\n"), pname);
92 exit(2);
95 int
96 main(int argc, char *argv[])
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;
112 nset = setproject_initpriv();
113 if (nset == NULL)
114 die(gettext("privilege initialization failed\n"));
116 pname = getpname(argv[0]);
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*/
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();
148 if (procname != NULL) {
149 /* Change project/task of an existing process */
150 return (update_running_proc(newproj_flag, procname, projname));
154 * Get user data, so that we can confirm project membership as
155 * well as construct an appropriate login environment.
157 uid = getuid();
158 if ((pw = match_user(uid, projname, 1)) == NULL) {
159 die("%s\n", global_error);
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.
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);
186 __priv_relinquish();
188 taskid = gettaskid();
190 if (verbose)
191 (void) fprintf(stderr, "%d\n", (int)taskid);
194 * Validate user's shell from passwd database.
196 if (strcmp(pw->pw_shell, "") == 0) {
197 pw->pw_shell = SHELL;
200 if (login_flag) {
202 * Since we've been invoked as a "simulated login", set up the
203 * environment.
205 char *cur_tz = getenv("TZ");
206 char *cur_term = getenv("TERM");
208 char **envnext;
210 size_t len_home = strlen(pw->pw_dir) + strlen("HOME=") + 1;
211 size_t len_logname = strlen(pw->pw_name) + strlen("LOGNAME=") +
213 size_t len_shell = strlen(pw->pw_shell) + strlen("SHELL=") + 1;
214 size_t len_mail = strlen(pw->pw_name) +
215 strlen("MAIL=/var/mail/") + 1;
216 size_t len_tz;
217 size_t len_term;
219 char *env_home = safe_malloc(len_home);
220 char *env_logname = safe_malloc(len_logname);
221 char *env_shell = safe_malloc(len_shell);
222 char *env_mail = safe_malloc(len_mail);
223 char *env_tz;
224 char *env_term;
226 (void) snprintf(env_home, len_home, "HOME=%s", pw->pw_dir);
227 (void) snprintf(env_logname, len_logname, "LOGNAME=%s",
228 pw->pw_name);
229 (void) snprintf(env_shell, len_shell, "SHELL=%s", pw->pw_shell);
230 (void) snprintf(env_mail, len_mail, "MAIL=/var/mail/%s",
231 pw->pw_name);
233 env[0] = env_home;
234 env[1] = env_logname;
235 env[2] = (pw->pw_uid == 0 ? supath : path);
236 env[3] = env_shell;
237 env[4] = env_mail;
238 env[5] = NULL;
239 env[6] = NULL;
240 env[7] = NULL;
242 envnext = (char **)&env[5];
245 * It's possible that TERM wasn't defined in the outer
246 * environment.
248 if (cur_term != NULL) {
249 len_term = strlen(cur_term) + strlen("TERM=") + 1;
250 env_term = safe_malloc(len_term);
252 (void) snprintf(env_term, len_term, "TERM=%s",
253 cur_term);
254 *envnext = env_term;
255 envnext++;
259 * It is also possible that TZ wasn't defined in the outer
260 * environment. In that case, we must attempt to open the file
261 * defining the default timezone and select the appropriate
262 * entry. If there is no default timezone there, try
263 * TIMEZONE in /etc/default/login, duplicating the algorithm
264 * that login uses.
266 if (cur_tz != NULL) {
267 len_tz = strlen(cur_tz) + strlen("TZ=") + 1;
268 env_tz = safe_malloc(len_tz);
270 (void) snprintf(env_tz, len_tz, "TZ=%s", cur_tz);
271 *envnext = env_tz;
272 } else {
273 if ((env_tz = getdefault(TIMEZONEFILE, "TZ=",
274 "TZ=")) != NULL)
275 *envnext = env_tz;
276 else {
277 env_tz = getdefault(LOGINFILE, "TIMEZONE=",
278 "TZ=");
279 *envnext = env_tz;
283 environ = (char **)&env[0];
286 * Prefix the shell string with a hyphen, indicating a login
287 * shell.
289 shell = safe_malloc(PATH_MAX);
290 (void) snprintf(shell, PATH_MAX, "-%s", basename(pw->pw_shell));
291 } else {
292 shell = basename(pw->pw_shell);
296 * If there are no arguments, we launch the user's shell; otherwise, the
297 * remaining commands are assumed to form a valid command invocation
298 * that we can exec.
300 if (optind >= argc) {
301 targs = alloca(2 * sizeof (char *));
302 filename = pw->pw_shell;
303 targs[0] = shell;
304 targs[1] = NULL;
305 } else {
306 targs = &argv[optind];
307 filename = targs[0];
310 if (execvp(filename, targs) == -1)
311 die(gettext("exec of %s failed"), targs[0]);
314 * We should never get here.
316 return (1);
319 static int
320 update_running_proc(int newproj_flag, char *procname, char *projname)
322 struct ps_prochandle *p;
323 prcred_t original_prcred, current_prcred;
324 projid_t prprojid;
325 taskid_t taskid;
326 int error = 0, gret;
327 struct project project;
328 char prbuf[PROJECT_BUFSZ];
329 struct passwd *passwd_entry;
330 int grab_retry_count = 0;
333 * Catch signals from terminal. There isn't much sense in
334 * doing anything but ignoring them since we don't do anything
335 * after the point we'd be capable of handling them again.
337 (void) sigignore(SIGHUP);
338 (void) sigignore(SIGINT);
339 (void) sigignore(SIGQUIT);
340 (void) sigignore(SIGTERM);
342 /* flush stdout before grabbing the proc to avoid deadlock */
343 (void) fflush(stdout);
346 * We need to grab the process, which will force it to stop execution
347 * until the grab is released, in order to aquire some information about
348 * it, such as its current project (which is achieved via an injected
349 * system call and therefore needs an agent) and its credentials. We
350 * will then need to release it again because it may be a process that
351 * we rely on for later calls, for example nscd.
353 if ((p = proc_arg_grab(procname, PR_ARG_PIDS, 0, &gret)) == NULL) {
354 warn(gettext("failed to grab for process %s: %s\n"),
355 procname, Pgrab_error(gret));
356 return (1);
358 if (Pcreate_agent(p) != 0) {
359 Prelease(p, 0);
360 warn(gettext("cannot control process %s\n"), procname);
361 return (1);
365 * The victim process is now held. Do not call any functions
366 * which generate stdout/stderr until the process has been
367 * released.
371 * The target process will soon be restarted (in case it is in newtask's
372 * execution path) and then stopped again. We need to ensure that our cached
373 * data doesn't change while the process runs so return here if the target
374 * process changes its user id in between our stop operations, so that we can
375 * try again.
377 pgrab_retry:
379 /* Cache required information about the process. */
380 if (Pcred(p, &original_prcred, 0) != 0) {
381 preserve_error(gettext("cannot get process credentials %s\n"),
382 procname);
383 error = 1;
385 if ((prprojid = pr_getprojid(p)) == -1) {
386 preserve_error(gettext("cannot get process project id %s\n"),
387 procname);
388 error = 1;
392 * We now have all the required information, so release the target
393 * process and perform our sanity checks. The process needs to be
394 * running at this point because it may be in the execution path of the
395 * calls made below.
397 Pdestroy_agent(p);
398 Prelease(p, 0);
400 /* if our data acquisition failed, then we can't continue. */
401 if (error) {
402 warn("%s\n", global_error);
403 return (1);
406 if (newproj_flag == 0) {
408 * Just changing the task, so set projname to the current
409 * project of the running process.
411 if (getprojbyid(prprojid, &project, &prbuf,
412 PROJECT_BUFSZ) == NULL) {
413 warn(gettext("unable to get project name "
414 "for projid %d"), prprojid);
415 return (1);
417 projname = project.pj_name;
418 } else {
420 * cache info for the project which user passed in via the
421 * command line
423 if (getprojbyname(projname, &project, &prbuf,
424 PROJECT_BUFSZ) == NULL) {
425 warn(gettext("unknown project \"%s\"\n"), projname);
426 return (1);
431 * Use our cached information to verify that the owner of the running
432 * process is a member of proj
434 if ((passwd_entry = match_user(original_prcred.pr_ruid,
435 projname, 0)) == NULL) {
436 warn("%s\n", global_error);
437 return (1);
441 * We can now safely stop the process again in order to change the
442 * project and taskid as required.
444 if ((p = proc_arg_grab(procname, PR_ARG_PIDS, 0, &gret)) == NULL) {
445 warn(gettext("failed to grab for process %s: %s\n"),
446 procname, Pgrab_error(gret));
447 return (1);
449 if (Pcreate_agent(p) != 0) {
450 Prelease(p, 0);
451 warn(gettext("cannot control process %s\n"), procname);
452 return (1);
456 * Now that the target process is stopped, check the validity of our
457 * cached info. If we aren't superuser then match_user() will have
458 * checked to make sure that the owner of the process is in the relevant
459 * project. If our ruid has changed, then match_user()'s conclusion may
460 * be invalid.
462 if (getuid() != 0) {
463 if (Pcred(p, &current_prcred, 0) != 0) {
464 Pdestroy_agent(p);
465 Prelease(p, 0);
466 warn(gettext("can't get process credentials %s\n"),
467 procname);
468 return (1);
471 if (original_prcred.pr_ruid != current_prcred.pr_ruid) {
472 if (grab_retry_count++ < GRAB_RETRY_MAX)
473 goto pgrab_retry;
475 warn(gettext("process consistently changed its "
476 "user id %s\n"), procname);
477 return (1);
481 error = set_ids(p, &project, passwd_entry);
483 if (verbose)
484 taskid = pr_gettaskid(p);
486 Pdestroy_agent(p);
487 Prelease(p, 0);
489 if (error) {
491 * error is serious enough to stop, only if negative.
492 * Otherwise, it simply indicates one of the resource
493 * control assignments failed, which is worth warning
494 * about.
496 warn("%s\n", global_error);
497 if (error < 0)
498 return (1);
501 if (verbose)
502 (void) fprintf(stderr, "%d\n", (int)taskid);
504 return (0);
507 static int
508 set_ids(struct ps_prochandle *p, struct project *project,
509 struct passwd *passwd_entry)
511 int be_su = 0;
512 prcred_t old_prcred;
513 int error;
514 prpriv_t *old_prpriv, *new_prpriv;
515 size_t prsz = sizeof (prpriv_t);
516 priv_set_t *eset, *pset;
517 int ind;
519 if (Pcred(p, &old_prcred, 0) != 0) {
520 preserve_error(gettext("can't get process credentials"));
521 return (1);
524 old_prpriv = proc_get_priv(Pstatus(p)->pr_pid);
525 if (old_prpriv == NULL) {
526 preserve_error(gettext("can't get process privileges"));
527 return (1);
530 prsz = PRIV_PRPRIV_SIZE(old_prpriv);
532 new_prpriv = malloc(prsz);
533 if (new_prpriv == NULL) {
534 preserve_error(gettext("can't allocate memory"));
535 proc_free_priv(old_prpriv);
536 return (1);
539 (void) memcpy(new_prpriv, old_prpriv, prsz);
542 * If the process already has the proc_taskid privilege,
543 * we don't need to elevate its privileges; if it doesn't,
544 * we try to do it here.
545 * As we do not wish to leave a window in which the process runs
546 * with elevated privileges, we make sure that the process dies
547 * when we go away unexpectedly.
550 ind = priv_getsetbyname(PRIV_EFFECTIVE);
551 eset = (priv_set_t *)&new_prpriv->pr_sets[new_prpriv->pr_setsize * ind];
552 ind = priv_getsetbyname(PRIV_PERMITTED);
553 pset = (priv_set_t *)&new_prpriv->pr_sets[new_prpriv->pr_setsize * ind];
555 if (!priv_issubset(nset, eset)) {
556 be_su = 1;
557 priv_union(nset, eset);
558 priv_union(nset, pset);
559 if (Psetflags(p, PR_KLC) != 0) {
560 preserve_error(gettext("cannot set process "
561 "privileges"));
562 (void) Punsetflags(p, PR_KLC);
563 free(new_prpriv);
564 proc_free_priv(old_prpriv);
565 return (1);
567 (void) __priv_bracket(PRIV_ON);
568 if (Psetpriv(p, new_prpriv) != 0) {
569 (void) __priv_bracket(PRIV_OFF);
570 preserve_error(gettext("cannot set process "
571 "privileges"));
572 (void) Punsetflags(p, PR_KLC);
573 free(new_prpriv);
574 proc_free_priv(old_prpriv);
575 return (1);
577 (void) __priv_bracket(PRIV_OFF);
580 (void) __priv_bracket(PRIV_ON);
581 if ((error = setproject_proc(project->pj_name,
582 passwd_entry->pw_name, 0, Pstatus(p)->pr_pid, p, project)) != 0) {
583 /* global_error is set by setproject_err */
584 setproject_err(passwd_entry->pw_name, project->pj_name,
585 error, project);
587 (void) __priv_bracket(PRIV_OFF);
589 /* relinquish added privileges */
590 if (be_su) {
591 (void) __priv_bracket(PRIV_ON);
592 if (Psetpriv(p, old_prpriv) != 0) {
594 * We shouldn't ever be in a state where we can't
595 * set the process back to its old creds, but we
596 * don't want to take the chance of leaving a
597 * non-privileged process with enhanced creds. So,
598 * release the process from libproc control, knowing
599 * that it will be killed.
601 (void) __priv_bracket(PRIV_OFF);
602 Pdestroy_agent(p);
603 die(gettext("cannot relinquish superuser credentials "
604 "for pid %d. The process was killed."),
605 Pstatus(p)->pr_pid);
607 (void) __priv_bracket(PRIV_OFF);
608 if (Punsetflags(p, PR_KLC) != 0)
609 preserve_error(gettext("error relinquishing "
610 "credentials. Process %d will be killed."),
611 Pstatus(p)->pr_pid);
613 free(new_prpriv);
614 proc_free_priv(old_prpriv);
616 return (error);
620 * preserve_error() should be called rather than warn() by any
621 * function that is called while the victim process is being
622 * held by Pgrab.
624 * It saves a single error message to be printed until after
625 * the process has been released. Since multiple errors are not
626 * stored, any error should be considered critical.
628 void
629 preserve_error(const char *format, ...)
631 va_list alist;
633 va_start(alist, format);
636 * GLOBAL_ERR_SZ is pretty big. If the error is longer
637 * than that, just truncate it, rather than chance missing
638 * the error altogether.
640 (void) vsnprintf(global_error, GLOBAL_ERR_SZ-1, format, alist);
642 va_end(alist);
647 * Given the input arguments, return the passwd structure that matches best.
648 * Also, since we use getpwnam() and friends, subsequent calls to this
649 * function will re-use the memory previously returned.
651 static struct passwd *
652 match_user(uid_t uid, char *projname, int is_my_uid)
654 char prbuf[PROJECT_BUFSZ], username[LOGNAME_MAX+1];
655 struct project prj;
656 char *tmp_name;
657 struct passwd *pw = NULL;
660 * In order to allow users with the same UID but distinguishable
661 * user names to be in different projects we play a guessing
662 * game of which username is most appropriate. If we're checking
663 * for the uid of the calling process, the login name is a
664 * good starting point.
666 if (is_my_uid) {
667 if ((tmp_name = getlogin()) == NULL ||
668 (pw = getpwnam(tmp_name)) == NULL || (pw->pw_uid != uid) ||
669 (pw->pw_name == NULL))
670 pw = NULL;
674 * If the login name doesn't work, we try the first match for
675 * the current uid in the password file.
677 if (pw == NULL) {
678 if (((pw = getpwuid(uid)) == NULL) || pw->pw_name == NULL) {
679 preserve_error(gettext("cannot find username "
680 "for uid %d"), uid);
681 return (NULL);
686 * If projname wasn't supplied, we've done our best, so just return
687 * what we've got now. Alternatively, if newtask's invoker has
688 * superuser privileges, return the pw structure we've got now, with
689 * no further checking from inproj(). Superuser should be able to
690 * join any project, and the subsequent call to setproject() will
691 * allow this.
693 if (projname == NULL || getuid() == (uid_t)0)
694 return (pw);
696 (void) strlcpy(username, pw->pw_name, sizeof (username));
698 if (inproj(username, projname, prbuf, PROJECT_BUFSZ) == 0) {
699 char **u;
700 tmp_name = NULL;
703 * If the previous guesses didn't work, walk through all
704 * project members and test for UID-equivalence.
707 if (getprojbyname(projname, &prj, prbuf,
708 PROJECT_BUFSZ) == NULL) {
709 preserve_error(gettext("unknown project \"%s\""),
710 projname);
711 return (NULL);
714 for (u = prj.pj_users; *u; u++) {
715 if ((pw = getpwnam(*u)) == NULL)
716 continue;
718 if (pw->pw_uid == uid) {
719 tmp_name = pw->pw_name;
720 break;
724 if (tmp_name == NULL) {
725 preserve_error(gettext("user \"%s\" is not a member of "
726 "project \"%s\""), username, projname);
727 return (NULL);
731 return (pw);
734 void
735 setproject_err(char *username, char *projname, int error, struct project *proj)
737 kva_t *kv_array = NULL;
738 char prbuf[PROJECT_BUFSZ];
739 struct project local_proj;
741 switch (error) {
742 case SETPROJ_ERR_TASK:
743 if (errno == EAGAIN)
744 preserve_error(gettext("resource control limit has "
745 "been reached"));
746 else if (errno == ESRCH)
747 preserve_error(gettext("user \"%s\" is not a member of "
748 "project \"%s\""), username, projname);
749 else if (errno == EACCES)
750 preserve_error(gettext("the invoking task is final"));
751 else
752 preserve_error(
753 gettext("could not join project \"%s\""),
754 projname);
755 break;
756 case SETPROJ_ERR_POOL:
757 if (errno == EACCES)
758 preserve_error(gettext("no resource pool accepting "
759 "default bindings exists for project \"%s\""),
760 projname);
761 else if (errno == ESRCH)
762 preserve_error(gettext("specified resource pool does "
763 "not exist for project \"%s\""), projname);
764 else
765 preserve_error(gettext("could not bind to default "
766 "resource pool for project \"%s\""), projname);
767 break;
768 default:
769 if (error <= 0) {
770 preserve_error(gettext("setproject failed for "
771 "project \"%s\""), projname);
772 return;
775 * If we have a stopped target process it may be in
776 * getprojbyname()'s execution path which would make it unsafe
777 * to access the project table, so only do that if the caller
778 * hasn't provided a cached version of the project structure.
780 if (proj == NULL)
781 proj = getprojbyname(projname, &local_proj, prbuf,
782 PROJECT_BUFSZ);
784 if (proj == NULL || (kv_array = _str2kva(proj->pj_attr,
785 KV_ASSIGN, KV_DELIMITER)) == NULL ||
786 kv_array->length < error) {
787 preserve_error(gettext("warning, resource control "
788 "assignment failed for project \"%s\" "
789 "attribute %d"),
790 projname, error);
791 if (kv_array)
792 _kva_free(kv_array);
793 return;
795 preserve_error(gettext("warning, %s resource control "
796 "assignment failed for project \"%s\""),
797 kv_array->data[error - 1].key, projname);
798 _kva_free(kv_array);