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 #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) strncpy(username, pw->pw_name, sizeof (username) - 1);
 700         username[sizeof (username) - 1] = '\0';
 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 }