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 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 27 /* All Rights Reserved */ 28 29 30 31 #pragma ident "%Z%%M% %I% %E% SMI" 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 default: 374 call_pass = 1; 375 break; 376 } 377 if (warning) 378 warningmsg(warning, logname); 379 } 380 381 if (uidstr) { 382 /* convert uidstr to integer */ 383 errno = 0; 384 uid = (uid_t)strtol(uidstr, &ptr, (int)10); 385 if (*ptr || errno == ERANGE) { 386 errmsg(M_INVALID, uidstr, "user id"); 387 exit(EX_BADARG); 388 } 389 390 if (uid != pstruct->pw_uid) { 391 switch (valid_uid(uid, NULL)) { 392 case NOTUNIQUE: 393 if (!oflag) { 394 /* override not specified */ 395 errmsg(M_UID_USED, uid); 396 exit(EX_ID_EXISTS); 397 } 398 break; 399 case RESERVED: 400 errmsg(M_RESERVED, uid); 401 break; 402 case TOOBIG: 403 errmsg(M_TOOBIG, "uid", uid); 404 exit(EX_BADARG); 405 break; 406 } 407 408 call_pass = 1; 409 410 } else { 411 /* uid's the same, so don't change anything */ 412 uidstr = NULL; 413 oflag = 0; 414 } 415 416 } else uid = pstruct->pw_uid; 417 418 if (group) { 419 switch (valid_group(group, &g_ptr, &warning)) { 420 case INVALID: 421 errmsg(M_INVALID, group, "group id"); 422 exit(EX_BADARG); 423 /*NOTREACHED*/ 424 case TOOBIG: 425 errmsg(M_TOOBIG, "gid", group); 426 exit(EX_BADARG); 427 /*NOTREACHED*/ 428 case UNIQUE: 429 errmsg(M_GRP_NOTUSED, group); 430 exit(EX_NAME_NOT_EXIST); 431 /*NOTREACHED*/ 432 case RESERVED: 433 gid = (gid_t)strtol(group, &ptr, (int)10); 434 errmsg(M_RESERVED_GID, gid); 435 break; 436 } 437 if (warning) 438 warningmsg(warning, group); 439 440 if (g_ptr != NULL) 441 gid = g_ptr->gr_gid; 442 else 443 gid = pstruct->pw_gid; 444 445 /* call passmgmt if gid is different, else ignore group */ 446 if (gid != pstruct->pw_gid) 447 call_pass = 1; 448 else group = NULL; 449 450 } else gid = pstruct->pw_gid; 451 452 if (grps && *grps) { 453 if (!(gidlist = valid_lgroup(grps, gid))) 454 exit(EX_BADARG); 455 } else 456 gidlist = (int **)0; 457 458 if (projects && *projects) { 459 if (! (projlist = valid_lproject(projects))) 460 exit(EX_BADARG); 461 } else 462 projlist = (projid_t **)0; 463 464 if (dir) { 465 if (REL_PATH(dir)) { 466 errmsg(M_RELPATH, dir); 467 exit(EX_BADARG); 468 } 469 if (strcmp(pstruct->pw_dir, dir) == 0) { 470 /* home directory is the same so ignore dflag & mflag */ 471 dir = NULL; 472 mflag = 0; 473 } else call_pass = 1; 474 } 475 476 if (mflag) { 477 if (stat(dir, &statbuf) == 0) { 478 /* Home directory exists */ 479 if (check_perm(statbuf, pstruct->pw_uid, 480 pstruct->pw_gid, S_IWOTH|S_IXOTH) != 0) { 481 errmsg(M_NO_PERM, logname, dir); 482 exit(EX_NO_PERM); 483 } 484 485 } else ret = create_home(dir, NULL, uid, gid); 486 487 if (ret == EX_SUCCESS) 488 ret = move_dir(pstruct->pw_dir, dir, logname); 489 490 if (ret != EX_SUCCESS) 491 exit(ret); 492 } 493 494 if (shell) { 495 if (REL_PATH(shell)) { 496 errmsg(M_RELPATH, shell); 497 exit(EX_BADARG); 498 } 499 if (strcmp(pstruct->pw_shell, shell) == 0) { 500 /* ignore s option if shell is not different */ 501 shell = NULL; 502 } else { 503 if (stat(shell, &statbuf) < 0 || 504 (statbuf.st_mode & S_IFMT) != S_IFREG || 505 (statbuf.st_mode & 0555) != 0555) { 506 507 errmsg(M_INVALID, shell, "shell"); 508 exit(EX_BADARG); 509 } 510 511 call_pass = 1; 512 } 513 } 514 515 if (comment) 516 /* ignore comment if comment is not changed */ 517 if (strcmp(pstruct->pw_comment, comment)) 518 call_pass = 1; 519 else 520 comment = NULL; 521 522 /* inactive string is a positive integer */ 523 if (inactstr) { 524 /* convert inactstr to integer */ 525 inact = (int)strtol(inactstr, &ptr, 10); 526 if (*ptr || inact < 0) { 527 errmsg(M_INVALID, inactstr, "inactivity period"); 528 exit(EX_BADARG); 529 } 530 call_pass = 1; 531 } 532 533 /* expiration string is a date, newer than today */ 534 if (expire) { 535 if (*expire && 536 valid_expire(expire, (time_t *)0) == INVALID) { 537 errmsg(M_INVALID, expire, "expiration date"); 538 exit(EX_BADARG); 539 } 540 call_pass = 1; 541 } 542 543 if (nkeys > 0) 544 call_pass = 1; 545 546 /* that's it for validations - now do the work */ 547 548 if (grps) { 549 /* redefine login's supplentary group memberships */ 550 ret = edit_group(logname, new_logname, gidlist, 1); 551 if (ret != EX_SUCCESS) { 552 errmsg(M_UPDATE, "modified"); 553 exit(ret); 554 } 555 } 556 if (projects) { 557 ret = edit_project(logname, (char *)NULL, projlist, 0); 558 if (ret != EX_SUCCESS) { 559 errmsg(M_UPDATE, "modified"); 560 exit(ret); 561 } 562 } 563 564 565 if (!call_pass) exit(ret); 566 567 /* only get to here if need to call passmgmt */ 568 /* set up arguments to passmgmt in nargv array */ 569 nargv = malloc((30 + nkeys * 2) * sizeof (char *)); 570 571 argindex = 0; 572 nargv[argindex++] = "passmgmt"; 573 nargv[argindex++] = "-m"; /* modify */ 574 575 if (comment) { /* comment */ 576 nargv[argindex++] = "-c"; 577 nargv[argindex++] = comment; 578 } 579 580 if (dir) { 581 /* flags for home directory */ 582 nargv[argindex++] = "-h"; 583 nargv[argindex++] = dir; 584 } 585 586 if (group) { 587 /* set gid flag */ 588 nargv[argindex++] = "-g"; 589 (void) sprintf(gidstring, "%u", gid); 590 nargv[argindex++] = gidstring; 591 } 592 593 if (shell) { /* shell */ 594 nargv[argindex++] = "-s"; 595 nargv[argindex++] = shell; 596 } 597 598 if (inactstr) { 599 nargv[argindex++] = "-f"; 600 nargv[argindex++] = inactstr; 601 } 602 603 if (expire) { 604 nargv[argindex++] = "-e"; 605 nargv[argindex++] = expire; 606 } 607 608 if (uidstr) { /* set uid flag */ 609 nargv[argindex++] = "-u"; 610 (void) sprintf(uidstring, "%u", uid); 611 nargv[argindex++] = uidstring; 612 } 613 614 if (oflag) nargv[argindex++] = "-o"; 615 616 if (new_logname) { /* redefine login name */ 617 nargv[argindex++] = "-l"; 618 nargv[argindex++] = new_logname; 619 } 620 621 if (nkeys > 0) 622 addkey_args(nargv, &argindex); 623 624 /* finally - login name */ 625 nargv[argindex++] = logname; 626 627 /* set the last to null */ 628 nargv[argindex++] = NULL; 629 630 /* now call passmgmt */ 631 ret = PEX_FAILED; 632 for (tries = 3; ret != PEX_SUCCESS && tries--; ) { 633 switch (ret = call_passmgmt(nargv)) { 634 case PEX_SUCCESS: 635 case PEX_BUSY: 636 break; 637 638 case PEX_HOSED_FILES: 639 errmsg(M_HOSED_FILES); 640 exit(EX_INCONSISTENT); 641 break; 642 643 case PEX_SYNTAX: 644 case PEX_BADARG: 645 /* should NEVER occur that passmgmt usage is wrong */ 646 if (is_role(usertype)) 647 errmsg(M_MRUSAGE); 648 else 649 errmsg(M_MUSAGE); 650 exit(EX_SYNTAX); 651 break; 652 653 case PEX_BADUID: 654 /* uid in use - shouldn't happen print message anyway */ 655 errmsg(M_UID_USED, uid); 656 exit(EX_ID_EXISTS); 657 break; 658 659 case PEX_BADNAME: 660 /* invalid loname */ 661 errmsg(M_USED, logname); 662 exit(EX_NAME_EXISTS); 663 break; 664 665 default: 666 errmsg(M_UPDATE, "modified"); 667 exit(ret); 668 break; 669 } 670 } 671 if (tries == 0) { 672 errmsg(M_UPDATE, "modified"); 673 } 674 675 exit(ret); 676 /*NOTREACHED*/ 677 } 678