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 (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright (c) 2013 Gary Mills
  23  *
  24  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
  25  * Use is subject to license terms.
  26  */
  27 
  28 /*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T     */
  29 /*        All Rights Reserved   */
  30 
  31 
  32 
  33 #include <sys/types.h>
  34 #include <sys/stat.h>
  35 #include <sys/param.h>
  36 #include <stdio.h>
  37 #include <stdlib.h>
  38 #include <ctype.h>
  39 #include <limits.h>
  40 #include <string.h>
  41 #include <userdefs.h>
  42 #include <user_attr.h>
  43 #include <nss_dbdefs.h>
  44 #include <errno.h>
  45 #include <project.h>
  46 #include "users.h"
  47 #include "messages.h"
  48 #include "funcs.h"
  49 
  50 /*
  51  *  usermod [-u uid [-o] | -g group | -G group [[,group]...] | -d dir [-m]
  52  *              | -s shell | -c comment | -l new_logname]
  53  *              | -f inactive | -e expire ]
  54  *              [ -A authorization [, authorization ...]]
  55  *              [ -P profile [, profile ...]]
  56  *              [ -R role [, role ...]]
  57  *              [ -K key=value ]
  58  *              [ -p project [, project]] login
  59  *
  60  *      This command adds new user logins to the system.  Arguments are:
  61  *
  62  *      uid - an integer less than MAXUID
  63  *      group - an existing group's integer ID or char string name
  64  *      dir - a directory
  65  *      shell - a program to be used as a shell
  66  *      comment - any text string
  67  *      skel_dir - a directory
  68  *      base_dir - a directory
  69  *      rid - an integer less than 2**16 (USHORT)
  70  *      login - a string of printable chars except colon (:)
  71  *      inactive - number of days a login maybe inactive before it is locked
  72  *      expire - date when a login is no longer valid
  73  *      authorization - One or more comma separated authorizations defined
  74  *                      in auth_attr(4).
  75  *      profile - One or more comma separated execution profiles defined
  76  *                in prof_attr(4)
  77  *      role - One or more comma-separated role names defined in user_attr(4)
  78  *      key=value - One or more -K options each specifying a valid user_attr(4)
  79  *              attribute.
  80  *
  81  */
  82 
  83 extern int **valid_lgroup(), isbusy();
  84 extern int valid_uid(), check_perm(), create_home(), move_dir();
  85 extern int valid_expire(), edit_group(), call_passmgmt();
  86 extern projid_t **valid_lproject();
  87 
  88 static uid_t uid;               /* new uid */
  89 static gid_t gid;                       /* gid of new login */
  90 static char *new_logname = NULL;        /* new login name with -l option */
  91 static char *uidstr = NULL;             /* uid from command line */
  92 static char *group = NULL;              /* group from command line */
  93 static char *grps = NULL;               /* multi groups from command line */
  94 static char *dir = NULL;                /* home dir from command line */
  95 static char *shell = NULL;              /* shell from command line */
  96 static char *comment = NULL;            /* comment from command line */
  97 static char *logname = NULL;            /* login name to add */
  98 static char *inactstr = NULL;           /* inactive from command line */
  99 static char *expire = NULL;             /* expiration date from command line */
 100 static char *projects = NULL;           /* project ids from command line */
 101 static char *usertype;
 102 
 103 char *cmdname;
 104 static char gidstring[32], uidstring[32];
 105 char inactstring[10];
 106 
 107 char *
 108 strcpmalloc(str)
 109 char *str;
 110 {
 111         if (str == NULL)
 112                 return (NULL);
 113 
 114         return (strdup(str));
 115 }
 116 struct passwd *
 117 passwd_cpmalloc(opw)
 118 struct passwd *opw;
 119 {
 120         struct passwd *npw;
 121 
 122         if (opw == NULL)
 123                 return (NULL);
 124 
 125 
 126         npw = malloc(sizeof (struct passwd));
 127 
 128         npw->pw_name = strcpmalloc(opw->pw_name);
 129         npw->pw_passwd = strcpmalloc(opw->pw_passwd);
 130         npw->pw_uid = opw->pw_uid;
 131         npw->pw_gid = opw->pw_gid;
 132         npw->pw_age = strcpmalloc(opw->pw_age);
 133         npw->pw_comment = strcpmalloc(opw->pw_comment);
 134         npw->pw_gecos  = strcpmalloc(opw->pw_gecos);
 135         npw->pw_dir = strcpmalloc(opw->pw_dir);
 136         npw->pw_shell = strcpmalloc(opw->pw_shell);
 137 
 138         return (npw);
 139 }
 140 
 141 int
 142 main(argc, argv)
 143 int argc;
 144 char **argv;
 145 {
 146         int ch, ret = EX_SUCCESS, call_pass = 0, oflag = 0;
 147         int tries, mflag = 0, inact, **gidlist, flag = 0;
 148         boolean_t fail_if_busy = B_FALSE;
 149         char *ptr;
 150         struct passwd *pstruct;         /* password struct for login */
 151         struct passwd *pw;
 152         struct group *g_ptr;    /* validated group from -g */
 153         struct stat statbuf;            /* status buffer for stat */
 154 #ifndef att
 155         FILE *pwf;              /* fille ptr for opened passwd file */
 156 #endif
 157         int warning;
 158         projid_t **projlist;
 159         char **nargv;                   /* arguments for execvp of passmgmt */
 160         int argindex;                   /* argument index into nargv */
 161         userattr_t *ua;
 162         char *val;
 163         int isrole;                     /* current account is role */
 164 
 165         cmdname = argv[0];
 166 
 167         if (geteuid() != 0) {
 168                 errmsg(M_PERM_DENIED);
 169                 exit(EX_NO_PERM);
 170         }
 171 
 172         opterr = 0;                     /* no print errors from getopt */
 173         /* get user type based on the program name */
 174         usertype = getusertype(argv[0]);
 175 
 176         while ((ch = getopt(argc, argv,
 177                                 "c:d:e:f:G:g:l:mop:s:u:A:P:R:K:")) != EOF)
 178                 switch (ch) {
 179                 case 'c':
 180                         comment = optarg;
 181                         flag++;
 182                         break;
 183                 case 'd':
 184                         dir = optarg;
 185                         fail_if_busy = B_TRUE;
 186                         flag++;
 187                         break;
 188                 case 'e':
 189                         expire = optarg;
 190                         flag++;
 191                         break;
 192                 case 'f':
 193                         inactstr = optarg;
 194                         flag++;
 195                         break;
 196                 case 'G':
 197                         grps = optarg;
 198                         flag++;
 199                         break;
 200                 case 'g':
 201                         group = optarg;
 202                         fail_if_busy = B_TRUE;
 203                         flag++;
 204                         break;
 205                 case 'l':
 206                         new_logname = optarg;
 207                         fail_if_busy = B_TRUE;
 208                         flag++;
 209                         break;
 210                 case 'm':
 211                         mflag++;
 212                         flag++;
 213                         fail_if_busy = B_TRUE;
 214                         break;
 215                 case 'o':
 216                         oflag++;
 217                         flag++;
 218                         fail_if_busy = B_TRUE;
 219                         break;
 220                 case 'p':
 221                         projects = optarg;
 222                         flag++;
 223                         break;
 224                 case 's':
 225                         shell = optarg;
 226                         flag++;
 227                         break;
 228                 case 'u':
 229                         uidstr = optarg;
 230                         flag++;
 231                         fail_if_busy = B_TRUE;
 232                         break;
 233                 case 'A':
 234                         change_key(USERATTR_AUTHS_KW, optarg);
 235                         flag++;
 236                         break;
 237                 case 'P':
 238                         change_key(USERATTR_PROFILES_KW, optarg);
 239                         flag++;
 240                         break;
 241                 case 'R':
 242                         change_key(USERATTR_ROLES_KW, optarg);
 243                         flag++;
 244                         break;
 245                 case 'K':
 246                         change_key(NULL, optarg);
 247                         flag++;
 248                         break;
 249                 default:
 250                 case '?':
 251                         if (is_role(usertype))
 252                                 errmsg(M_MRUSAGE);
 253                         else
 254                                 errmsg(M_MUSAGE);
 255                         exit(EX_SYNTAX);
 256                 }
 257 
 258         if (optind != argc - 1 || flag == 0) {
 259                 if (is_role(usertype))
 260                         errmsg(M_MRUSAGE);
 261                 else
 262                         errmsg(M_MUSAGE);
 263                 exit(EX_SYNTAX);
 264         }
 265 
 266         if ((!uidstr && oflag) || (mflag && !dir)) {
 267                 if (is_role(usertype))
 268                         errmsg(M_MRUSAGE);
 269                 else
 270                         errmsg(M_MUSAGE);
 271                 exit(EX_SYNTAX);
 272         }
 273 
 274         logname = argv[optind];
 275 
 276         /* Determine whether the account is a role or not */
 277         if ((ua = getusernam(logname)) == NULL ||
 278             (val = kva_match(ua->attr, USERATTR_TYPE_KW)) == NULL ||
 279             strcmp(val, USERATTR_TYPE_NONADMIN_KW) != 0)
 280                 isrole = 0;
 281         else
 282                 isrole = 1;
 283 
 284         /* Verify that rolemod is used for roles and usermod for users */
 285         if (isrole != is_role(usertype)) {
 286                 if (isrole)
 287                         errmsg(M_ISROLE);
 288                 else
 289                         errmsg(M_ISUSER);
 290                 exit(EX_SYNTAX);
 291         }
 292 
 293         /* Set the usertype key; defaults to the commandline  */
 294         usertype = getsetdefval(USERATTR_TYPE_KW, usertype);
 295 
 296         if (is_role(usertype)) {
 297                 /* Roles can't have roles */
 298                 if (getsetdefval(USERATTR_ROLES_KW, NULL) != NULL) {
 299                         errmsg(M_MRUSAGE);
 300                         exit(EX_SYNTAX);
 301                 }
 302                 /* If it was an ordinary user, delete its roles */
 303                 if (!isrole)
 304                         change_key(USERATTR_ROLES_KW, "");
 305         }
 306 
 307 #ifdef att
 308         pw = getpwnam(logname);
 309 #else
 310         /*
 311          * Do this with fgetpwent to make sure we are only looking on local
 312          * system (since passmgmt only works on local system).
 313          */
 314         if ((pwf = fopen("/etc/passwd", "r")) == NULL) {
 315                 errmsg(M_OOPS, "open", "/etc/passwd");
 316                 exit(EX_FAILURE);
 317         }
 318         while ((pw = fgetpwent(pwf)) != NULL)
 319                 if (strcmp(pw->pw_name, logname) == 0)
 320                         break;
 321 
 322         fclose(pwf);
 323 #endif
 324 
 325         if (pw == NULL) {
 326                 char            pwdb[NSS_BUFLEN_PASSWD];
 327                 struct passwd   pwd;
 328 
 329                 if (getpwnam_r(logname, &pwd, pwdb, sizeof (pwdb)) == NULL) {
 330                         /* This user does not exist. */
 331                         errmsg(M_EXIST, logname);
 332                         exit(EX_NAME_NOT_EXIST);
 333                 } else {
 334                         /* This user exists in non-local name service. */
 335                         errmsg(M_NONLOCAL, logname);
 336                         exit(EX_NOT_LOCAL);
 337                 }
 338         }
 339 
 340         pstruct = passwd_cpmalloc(pw);
 341 
 342         /*
 343          * We can't modify a logged in user if any of the following
 344          * are being changed:
 345          * uid (-u & -o), group (-g), home dir (-m), loginname (-l).
 346          * If none of those are specified it is okay to go ahead
 347          * some types of changes only take effect on next login, some
 348          * like authorisations and profiles take effect instantly.
 349          * One might think that -K type=role should require that the
 350          * user not be logged in, however this would make it very
 351          * difficult to make the root account a role using this command.
 352          */
 353         if (isbusy(logname)) {
 354                 if (fail_if_busy) {
 355                         errmsg(M_BUSY, logname, "change");
 356                         exit(EX_BUSY);
 357                 }
 358                 warningmsg(WARN_LOGGED_IN, logname);
 359         }
 360 
 361         if (new_logname && strcmp(new_logname, logname)) {
 362                 switch (valid_login(new_logname, (struct passwd **)NULL,
 363                         &warning)) {
 364                 case INVALID:
 365                         errmsg(M_INVALID, new_logname, "login name");
 366                         exit(EX_BADARG);
 367                         /*NOTREACHED*/
 368 
 369                 case NOTUNIQUE:
 370                         errmsg(M_USED, new_logname);
 371                         exit(EX_NAME_EXISTS);
 372                         /*NOTREACHED*/
 373 
 374                 case LONGNAME:
 375                         errmsg(M_TOO_LONG, new_logname);
 376                         exit(EX_BADARG);
 377                         /*NOTREACHED*/
 378 
 379                 default:
 380                         call_pass = 1;
 381                         break;
 382                 }
 383                 if (warning)
 384                         warningmsg(warning, logname);
 385         }
 386 
 387         if (uidstr) {
 388                 /* convert uidstr to integer */
 389                 errno = 0;
 390                 uid = (uid_t)strtol(uidstr, &ptr, (int)10);
 391                 if (*ptr || errno == ERANGE) {
 392                         errmsg(M_INVALID, uidstr, "user id");
 393                         exit(EX_BADARG);
 394                 }
 395 
 396                 if (uid != pstruct->pw_uid) {
 397                         switch (valid_uid(uid, NULL)) {
 398                         case NOTUNIQUE:
 399                                 if (!oflag) {
 400                                         /* override not specified */
 401                                         errmsg(M_UID_USED, uid);
 402                                         exit(EX_ID_EXISTS);
 403                                 }
 404                                 break;
 405                         case RESERVED:
 406                                 errmsg(M_RESERVED, uid);
 407                                 break;
 408                         case TOOBIG:
 409                                 errmsg(M_TOOBIG, "uid", uid);
 410                                 exit(EX_BADARG);
 411                                 break;
 412                         }
 413 
 414                         call_pass = 1;
 415 
 416                 } else {
 417                         /* uid's the same, so don't change anything */
 418                         uidstr = NULL;
 419                         oflag = 0;
 420                 }
 421 
 422         } else uid = pstruct->pw_uid;
 423 
 424         if (group) {
 425                 switch (valid_group(group, &g_ptr, &warning)) {
 426                 case INVALID:
 427                         errmsg(M_INVALID, group, "group id");
 428                         exit(EX_BADARG);
 429                         /*NOTREACHED*/
 430                 case TOOBIG:
 431                         errmsg(M_TOOBIG, "gid", group);
 432                         exit(EX_BADARG);
 433                         /*NOTREACHED*/
 434                 case UNIQUE:
 435                         errmsg(M_GRP_NOTUSED, group);
 436                         exit(EX_NAME_NOT_EXIST);
 437                         /*NOTREACHED*/
 438                 case RESERVED:
 439                         gid = (gid_t)strtol(group, &ptr, (int)10);
 440                         errmsg(M_RESERVED_GID, gid);
 441                         break;
 442                 }
 443                 if (warning)
 444                         warningmsg(warning, group);
 445 
 446                 if (g_ptr != NULL)
 447                         gid = g_ptr->gr_gid;
 448                 else
 449                         gid = pstruct->pw_gid;
 450 
 451                 /* call passmgmt if gid is different, else ignore group */
 452                 if (gid != pstruct->pw_gid)
 453                         call_pass = 1;
 454                 else group = NULL;
 455 
 456         } else gid = pstruct->pw_gid;
 457 
 458         if (grps && *grps) {
 459                 if (!(gidlist = valid_lgroup(grps, gid)))
 460                         exit(EX_BADARG);
 461         } else
 462                 gidlist = (int **)0;
 463 
 464         if (projects && *projects) {
 465                 if (! (projlist = valid_lproject(projects)))
 466                         exit(EX_BADARG);
 467         } else
 468                 projlist = (projid_t **)0;
 469 
 470         if (dir) {
 471                 if (REL_PATH(dir)) {
 472                         errmsg(M_RELPATH, dir);
 473                         exit(EX_BADARG);
 474                 }
 475                 if (strcmp(pstruct->pw_dir, dir) == 0) {
 476                         /* home directory is the same so ignore dflag & mflag */
 477                         dir = NULL;
 478                         mflag = 0;
 479                 } else call_pass = 1;
 480         }
 481 
 482         if (mflag) {
 483                 if (stat(dir, &statbuf) == 0) {
 484                         /* Home directory exists */
 485                         if (check_perm(statbuf, pstruct->pw_uid,
 486                             pstruct->pw_gid, S_IWOTH|S_IXOTH) != 0) {
 487                                 errmsg(M_NO_PERM, logname, dir);
 488                                 exit(EX_NO_PERM);
 489                         }
 490 
 491                 } else ret = create_home(dir, NULL, uid, gid);
 492 
 493                 if (ret == EX_SUCCESS)
 494                         ret = move_dir(pstruct->pw_dir, dir, logname);
 495 
 496                 if (ret != EX_SUCCESS)
 497                         exit(ret);
 498         }
 499 
 500         if (shell) {
 501                 if (REL_PATH(shell)) {
 502                         errmsg(M_RELPATH, shell);
 503                         exit(EX_BADARG);
 504                 }
 505                 if (strcmp(pstruct->pw_shell, shell) == 0) {
 506                         /* ignore s option if shell is not different */
 507                         shell = NULL;
 508                 } else {
 509                         if (stat(shell, &statbuf) < 0 ||
 510                             (statbuf.st_mode & S_IFMT) != S_IFREG ||
 511                             (statbuf.st_mode & 0555) != 0555) {
 512 
 513                                 errmsg(M_INVALID, shell, "shell");
 514                                 exit(EX_BADARG);
 515                         }
 516 
 517                         call_pass = 1;
 518                 }
 519         }
 520 
 521         if (comment)
 522                 /* ignore comment if comment is not changed */
 523                 if (strcmp(pstruct->pw_comment, comment))
 524                         call_pass = 1;
 525                 else
 526                         comment = NULL;
 527 
 528         /* inactive string is a positive integer */
 529         if (inactstr) {
 530                 /* convert inactstr to integer */
 531                 inact = (int)strtol(inactstr, &ptr, 10);
 532                 if (*ptr || inact < 0) {
 533                         errmsg(M_INVALID, inactstr, "inactivity period");
 534                         exit(EX_BADARG);
 535                 }
 536                 call_pass = 1;
 537         }
 538 
 539         /* expiration string is a date, newer than today */
 540         if (expire) {
 541                 if (*expire &&
 542                     valid_expire(expire, (time_t *)0) == INVALID) {
 543                         errmsg(M_INVALID, expire, "expiration date");
 544                         exit(EX_BADARG);
 545                 }
 546                 call_pass = 1;
 547         }
 548 
 549         if (nkeys > 0)
 550                 call_pass = 1;
 551 
 552         /* that's it for validations - now do the work */
 553 
 554         if (grps) {
 555                 /* redefine login's supplentary group memberships */
 556                 ret = edit_group(logname, new_logname, gidlist, 1);
 557                 if (ret != EX_SUCCESS) {
 558                         errmsg(M_UPDATE, "modified");
 559                         exit(ret);
 560                 }
 561         }
 562         if (projects) {
 563                 ret = edit_project(logname, (char *)NULL, projlist, 0);
 564                 if (ret != EX_SUCCESS) {
 565                         errmsg(M_UPDATE, "modified");
 566                         exit(ret);
 567                 }
 568         }
 569 
 570 
 571         if (!call_pass) exit(ret);
 572 
 573         /* only get to here if need to call passmgmt */
 574         /* set up arguments to  passmgmt in nargv array */
 575         nargv = malloc((30 + nkeys * 2) * sizeof (char *));
 576 
 577         argindex = 0;
 578         nargv[argindex++] = PASSMGMT;
 579         nargv[argindex++] = "-m";       /* modify */
 580 
 581         if (comment) {  /* comment */
 582                 nargv[argindex++] = "-c";
 583                 nargv[argindex++] = comment;
 584         }
 585 
 586         if (dir) {
 587                 /* flags for home directory */
 588                 nargv[argindex++] = "-h";
 589                 nargv[argindex++] = dir;
 590         }
 591 
 592         if (group) {
 593                 /* set gid flag */
 594                 nargv[argindex++] = "-g";
 595                 (void) sprintf(gidstring, "%u", gid);
 596                 nargv[argindex++] = gidstring;
 597         }
 598 
 599         if (shell) {    /* shell */
 600                 nargv[argindex++] = "-s";
 601                 nargv[argindex++] = shell;
 602         }
 603 
 604         if (inactstr) {
 605                 nargv[argindex++] = "-f";
 606                 nargv[argindex++] = inactstr;
 607         }
 608 
 609         if (expire) {
 610                 nargv[argindex++] = "-e";
 611                 nargv[argindex++] = expire;
 612         }
 613 
 614         if (uidstr) {   /* set uid flag */
 615                 nargv[argindex++] = "-u";
 616                 (void) sprintf(uidstring, "%u", uid);
 617                 nargv[argindex++] = uidstring;
 618         }
 619 
 620         if (oflag) nargv[argindex++] = "-o";
 621 
 622         if (new_logname) {      /* redefine login name */
 623                 nargv[argindex++] = "-l";
 624                 nargv[argindex++] = new_logname;
 625         }
 626 
 627         if (nkeys > 0)
 628                 addkey_args(nargv, &argindex);
 629 
 630         /* finally - login name */
 631         nargv[argindex++] = logname;
 632 
 633         /* set the last to null */
 634         nargv[argindex++] = NULL;
 635 
 636         /* now call passmgmt */
 637         ret = PEX_FAILED;
 638         for (tries = 3; ret != PEX_SUCCESS && tries--; ) {
 639                 switch (ret = call_passmgmt(nargv)) {
 640                 case PEX_SUCCESS:
 641                 case PEX_BUSY:
 642                         break;
 643 
 644                 case PEX_HOSED_FILES:
 645                         errmsg(M_HOSED_FILES);
 646                         exit(EX_INCONSISTENT);
 647                         break;
 648 
 649                 case PEX_SYNTAX:
 650                 case PEX_BADARG:
 651                         /* should NEVER occur that passmgmt usage is wrong */
 652                         if (is_role(usertype))
 653                                 errmsg(M_MRUSAGE);
 654                         else
 655                                 errmsg(M_MUSAGE);
 656                         exit(EX_SYNTAX);
 657                         break;
 658 
 659                 case PEX_BADUID:
 660                         /* uid in use - shouldn't happen print message anyway */
 661                         errmsg(M_UID_USED, uid);
 662                         exit(EX_ID_EXISTS);
 663                         break;
 664 
 665                 case PEX_BADNAME:
 666                         /* invalid loname */
 667                         errmsg(M_USED, logname);
 668                         exit(EX_NAME_EXISTS);
 669                         break;
 670 
 671                 default:
 672                         errmsg(M_UPDATE, "modified");
 673                         exit(ret);
 674                         break;
 675                 }
 676         }
 677         if (tries == 0) {
 678                 errmsg(M_UPDATE, "modified");
 679         }
 680 
 681         exit(ret);
 682         /*NOTREACHED*/
 683 }