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