1 /* 2 * Copyright (c) 1983 Regents of the University of California. 3 * All rights reserved. The Berkeley software License Agreement 4 * specifies the terms and conditions for redistribution. 5 */ 6 7 #ifndef lint 8 char copyright[] = 9 "@(#) Copyright (c) 1983 Regents of the University of California.\n\ 10 All rights reserved.\n"; 11 #endif not lint 12 13 #ifndef lint 14 static char sccsid[] = "@(#)passwd.c 4.26 (Berkeley) 11/17/86"; 15 #endif not lint 16 17 /* 18 * Modify a field in the password file (either 19 * password, login shell, or gecos field). 20 * This program should be suid with an owner 21 * with write permission on /etc/passwd. 22 */ 23 #include <sys/types.h> 24 #include <sys/file.h> 25 #include <sys/time.h> 26 #include <sys/resource.h> 27 28 #include <stdio.h> 29 #include <signal.h> 30 #include <pwd.h> 31 #include <ndbm.h> 32 #include <errno.h> 33 #include <strings.h> 34 #include <ctype.h> 35 36 /* 37 * This should be the first thing returned from a getloginshells() 38 * but too many programs know that it is /bin/sh. 39 */ 40 #define DEFSHELL "/bin/sh" 41 42 char temp[] = "/etc/ptmp"; 43 char passwd[] = "/etc/passwd"; 44 char *getpass(); 45 char *getlogin(); 46 char *getfingerinfo(); 47 char *getloginshell(); 48 char *getnewpasswd(); 49 char *malloc(); 50 char *getusershell(); 51 extern int errno; 52 53 main(argc, argv) 54 char *argv[]; 55 { 56 struct passwd *pwd; 57 char *cp, *uname, *progname; 58 int fd, u, dochfn, dochsh, err; 59 FILE *tf; 60 DBM *dp; 61 62 if ((progname = rindex(argv[0], '/')) == NULL) 63 progname = argv[0]; 64 else 65 progname++; 66 dochfn = 0, dochsh = 0; 67 argc--, argv++; 68 while (argc > 0 && argv[0][0] == '-') { 69 for (cp = &argv[0][1]; *cp; cp++) switch (*cp) { 70 71 case 'f': 72 if (dochsh) 73 goto bad; 74 dochfn = 1; 75 break; 76 77 case 's': 78 if (dochfn) { 79 bad: 80 fprintf(stderr, 81 "passwd: Only one of -f and -s allowed.\n"); 82 exit(1); 83 } 84 dochsh = 1; 85 break; 86 87 default: 88 fprintf(stderr, "passwd: -%c: unknown option.\n", *cp); 89 exit(1); 90 } 91 argc--, argv++; 92 } 93 if (!dochfn && !dochsh) { 94 if (strcmp(progname, "chfn") == 0) 95 dochfn = 1; 96 else if (strcmp(progname, "chsh") == 0) 97 dochsh = 1; 98 } 99 if (argc < 1) { 100 if ((uname = getlogin()) == NULL) { 101 fprintf(stderr, "Usage: %s [-f] [-s] [user]\n", progname); 102 exit(1); 103 } 104 printf("Changing %s for %s.\n", 105 dochfn ? "finger information" : 106 dochsh ? "login shell" : "password", 107 uname); 108 } else 109 uname = *argv++; 110 pwd = getpwnam(uname); 111 if (pwd == NULL) { 112 fprintf(stderr, "passwd: %s: unknown user.\n", uname); 113 exit(1); 114 } 115 u = getuid(); 116 if (u != 0 && u != pwd->pw_uid) { 117 printf("Permission denied.\n"); 118 exit(1); 119 } 120 if (dochfn) 121 cp = getfingerinfo(pwd); 122 else if (dochsh) 123 cp = getloginshell(pwd, u, *argv); 124 else 125 cp = getnewpasswd(pwd, u); 126 (void) signal(SIGHUP, SIG_IGN); 127 (void) signal(SIGINT, SIG_IGN); 128 (void) signal(SIGQUIT, SIG_IGN); 129 (void) signal(SIGTSTP, SIG_IGN); 130 (void) umask(0); 131 fd = open(temp, O_WRONLY|O_CREAT|O_EXCL, 0644); 132 if (fd < 0) { 133 err = errno; 134 135 fprintf(stderr, "passwd: "); 136 if (err == EEXIST) 137 fprintf(stderr, "password file busy - try again.\n"); 138 else { 139 errno = err; 140 perror(temp); 141 } 142 exit(1); 143 } 144 if ((tf = fdopen(fd, "w")) == NULL) { 145 fprintf(stderr, "passwd: fdopen failed?\n"); 146 exit(1); 147 } 148 if ((dp = dbm_open(passwd, O_RDWR, 0644)) == NULL) { 149 err = errno; 150 fprintf(stderr, "Warning: dbm_open failed: "); 151 errno = err; 152 perror(passwd); 153 } else if (flock(dp->dbm_dirf, LOCK_EX) < 0) { 154 perror("Warning: lock failed"); 155 dbm_close(dp); 156 dp = NULL; 157 } 158 unlimit(RLIMIT_CPU); 159 unlimit(RLIMIT_FSIZE); 160 /* 161 * Copy passwd to temp, replacing matching lines 162 * with new password. 163 */ 164 while ((pwd = getpwent()) != NULL) { 165 if (strcmp(pwd->pw_name, uname) == 0) { 166 if (u && u != pwd->pw_uid) { 167 fprintf(stderr, "passwd: permission denied.\n"); 168 goto out; 169 } 170 if (dochfn) 171 pwd->pw_gecos = cp; 172 else if (dochsh) 173 pwd->pw_shell = cp; 174 else 175 pwd->pw_passwd = cp; 176 if (pwd->pw_gecos[0] == '*') /* ??? */ 177 pwd->pw_gecos++; 178 replace(dp, pwd); 179 } 180 fprintf(tf,"%s:%s:%d:%d:%s:%s:%s\n", 181 pwd->pw_name, 182 pwd->pw_passwd, 183 pwd->pw_uid, 184 pwd->pw_gid, 185 pwd->pw_gecos, 186 pwd->pw_dir, 187 pwd->pw_shell); 188 } 189 endpwent(); 190 if (dp != NULL && dbm_error(dp)) 191 fprintf(stderr, "Warning: dbm_store failed\n"); 192 (void) fflush(tf); 193 if (ferror(tf)) { 194 fprintf(stderr, "Warning: %s write error, %s not updated\n", 195 temp, passwd); 196 goto out; 197 } 198 (void) fclose(tf); 199 if (dp != NULL) 200 dbm_close(dp); 201 if (rename(temp, passwd) < 0) { 202 perror("passwd: rename"); 203 out: 204 (void) unlink(temp); 205 exit(1); 206 } 207 exit(0); 208 } 209 210 unlimit(lim) 211 { 212 struct rlimit rlim; 213 214 rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY; 215 (void) setrlimit(lim, &rlim); 216 } 217 218 /* 219 * Replace the password entry in the dbm data base with pwd. 220 */ 221 replace(dp, pwd) 222 DBM *dp; 223 struct passwd *pwd; 224 { 225 datum key, content; 226 register char *cp, *tp; 227 char buf[BUFSIZ]; 228 229 if (dp == NULL) 230 return; 231 232 cp = buf; 233 #define COMPACT(e) tp = pwd->pw_/**/e; while (*cp++ = *tp++); 234 COMPACT(name); 235 COMPACT(passwd); 236 bcopy((char *)&pwd->pw_uid, cp, sizeof (int)); 237 cp += sizeof (int); 238 bcopy((char *)&pwd->pw_gid, cp, sizeof (int)); 239 cp += sizeof (int); 240 bcopy((char *)&pwd->pw_quota, cp, sizeof (int)); 241 cp += sizeof (int); 242 COMPACT(comment); 243 COMPACT(gecos); 244 COMPACT(dir); 245 COMPACT(shell); 246 content.dptr = buf; 247 content.dsize = cp - buf; 248 key.dptr = pwd->pw_name; 249 key.dsize = strlen(pwd->pw_name); 250 dbm_store(dp, key, content, DBM_REPLACE); 251 key.dptr = (char *)&pwd->pw_uid; 252 key.dsize = sizeof (int); 253 dbm_store(dp, key, content, DBM_REPLACE); 254 } 255 256 char * 257 getnewpasswd(pwd, u) 258 register struct passwd *pwd; 259 int u; 260 { 261 char saltc[2]; 262 long salt; 263 int i, insist = 0, ok, flags; 264 int c, pwlen; 265 static char pwbuf[10]; 266 long time(); 267 char *crypt(), *pw, *p; 268 269 if (pwd->pw_passwd[0] && u != 0) { 270 (void) strcpy(pwbuf, getpass("Old password:")); 271 pw = crypt(pwbuf, pwd->pw_passwd); 272 if (strcmp(pw, pwd->pw_passwd) != 0) { 273 printf("Sorry.\n"); 274 exit(1); 275 } 276 } 277 tryagain: 278 (void) strcpy(pwbuf, getpass("New password:")); 279 pwlen = strlen(pwbuf); 280 if (pwlen == 0) { 281 printf("Password unchanged.\n"); 282 exit(1); 283 } 284 /* 285 * Insure password is of reasonable length and 286 * composition. If we really wanted to make things 287 * sticky, we could check the dictionary for common 288 * words, but then things would really be slow. 289 */ 290 ok = 0; 291 flags = 0; 292 p = pwbuf; 293 while (c = *p++) { 294 if (c >= 'a' && c <= 'z') 295 flags |= 2; 296 else if (c >= 'A' && c <= 'Z') 297 flags |= 4; 298 else if (c >= '0' && c <= '9') 299 flags |= 1; 300 else 301 flags |= 8; 302 } 303 if (flags >= 7 && pwlen >= 4) 304 ok = 1; 305 if ((flags == 2 || flags == 4) && pwlen >= 6) 306 ok = 1; 307 if ((flags == 3 || flags == 5 || flags == 6) && pwlen >= 5) 308 ok = 1; 309 if (!ok && insist < 2) { 310 printf("Please use %s.\n", flags == 1 ? 311 "at least one non-numeric character" : 312 "a longer password"); 313 insist++; 314 goto tryagain; 315 } 316 if (strcmp(pwbuf, getpass("Retype new password:")) != 0) { 317 printf("Mismatch - password unchanged.\n"); 318 exit(1); 319 } 320 (void) time(&salt); 321 salt = 9 * getpid(); 322 saltc[0] = salt & 077; 323 saltc[1] = (salt>>6) & 077; 324 for (i = 0; i < 2; i++) { 325 c = saltc[i] + '.'; 326 if (c > '9') 327 c += 7; 328 if (c > 'Z') 329 c += 6; 330 saltc[i] = c; 331 } 332 return (crypt(pwbuf, saltc)); 333 } 334 335 char * 336 getloginshell(pwd, u, arg) 337 struct passwd *pwd; 338 int u; 339 char *arg; 340 { 341 static char newshell[BUFSIZ]; 342 char *cp, *valid, *getusershell(); 343 344 if (pwd->pw_shell == 0 || *pwd->pw_shell == '\0') 345 pwd->pw_shell = DEFSHELL; 346 if (u != 0) { 347 do { 348 valid = getusershell(); 349 if (valid == NULL) { 350 printf("Cannot change from restricted shell %s\n", 351 pwd->pw_shell); 352 exit(1); 353 } 354 } while (strcmp(pwd->pw_shell, valid) != 0); 355 } 356 if (arg != 0) { 357 (void) strncpy(newshell, arg, sizeof newshell - 1); 358 newshell[sizeof newshell - 1] = '\0'; 359 } else { 360 printf("Old shell: %s\nNew shell: ", pwd->pw_shell); 361 (void)fgets(newshell, sizeof (newshell) - 1, stdin); 362 cp = index(newshell, '\n'); 363 if (cp) 364 *cp = '\0'; 365 if (newshell[0] == 0) { 366 puts("Login shell unchanged."); 367 exit(1); 368 } 369 } 370 /* 371 * Allow user to give shell name w/o preceding pathname. 372 */ 373 if (u != 0 || newshell[0] != '/') { 374 endusershell(); 375 do { 376 valid = getusershell(); 377 if (valid == 0) { 378 if (u == 0) { 379 valid = newshell; 380 break; 381 } 382 printf("%s is unacceptable as a new shell.\n", 383 newshell); 384 exit(1); 385 } 386 if (newshell[0] == '/') { 387 cp = valid; 388 } else { 389 cp = rindex(valid, '/'); 390 if (cp == 0) 391 cp = valid; 392 else 393 cp++; 394 } 395 } while (strcmp(newshell, cp) != 0); 396 } else { 397 valid = newshell; 398 } 399 if (strcmp(valid, pwd->pw_shell) == 0) { 400 puts("Login shell unchanged."); 401 exit(1); 402 } 403 if (access(valid, X_OK) < 0) { 404 printf("%s is unavailable.\n", valid); 405 exit(1); 406 } 407 if (strcmp(valid, DEFSHELL) == 0) 408 valid[0] = '\0'; 409 return (valid); 410 } 411 412 struct default_values { 413 char *name; 414 char *office_num; 415 char *office_phone; 416 char *home_phone; 417 }; 418 419 /* 420 * Get name, room number, school phone, and home phone. 421 */ 422 char * 423 getfingerinfo(pwd) 424 struct passwd *pwd; 425 { 426 char in_str[BUFSIZ]; 427 struct default_values *defaults, *get_defaults(); 428 static char answer[4*BUFSIZ]; 429 430 answer[0] = '\0'; 431 defaults = get_defaults(pwd->pw_gecos); 432 printf("Default values are printed inside of '[]'.\n"); 433 printf("To accept the default, type <return>.\n"); 434 printf("To have a blank entry, type the word 'none'.\n"); 435 /* 436 * Get name. 437 */ 438 do { 439 printf("\nName [%s]: ", defaults->name); 440 (void) fgets(in_str, BUFSIZ, stdin); 441 if (special_case(in_str, defaults->name)) 442 break; 443 } while (illegal_input(in_str)); 444 (void) strcpy(answer, in_str); 445 /* 446 * Get room number. 447 */ 448 do { 449 printf("Room number (Exs: 597E or 197C) [%s]: ", 450 defaults->office_num); 451 (void) fgets(in_str, BUFSIZ, stdin); 452 if (special_case(in_str, defaults->office_num)) 453 break; 454 } while (illegal_input(in_str) || illegal_building(in_str)); 455 (void) strcat(strcat(answer, ","), in_str); 456 /* 457 * Get office phone number. 458 * Remove hyphens. 459 */ 460 do { 461 printf("Office Phone (Ex: 6426000) [%s]: ", 462 defaults->office_phone); 463 (void) fgets(in_str, BUFSIZ, stdin); 464 if (special_case(in_str, defaults->office_phone)) 465 break; 466 remove_hyphens(in_str); 467 } while (illegal_input(in_str) || not_all_digits(in_str)); 468 (void) strcat(strcat(answer, ","), in_str); 469 /* 470 * Get home phone number. 471 * Remove hyphens if present. 472 */ 473 do { 474 printf("Home Phone (Ex: 9875432) [%s]: ", defaults->home_phone); 475 (void) fgets(in_str, BUFSIZ, stdin); 476 if (special_case(in_str, defaults->home_phone)) 477 break; 478 remove_hyphens(in_str); 479 } while (illegal_input(in_str) || not_all_digits(in_str)); 480 (void) strcat(strcat(answer, ","), in_str); 481 if (strcmp(answer, pwd->pw_gecos) == 0) { 482 printf("Finger information unchanged.\n"); 483 exit(1); 484 } 485 return (answer); 486 } 487 488 /* 489 * Prints an error message if a ':' or a newline is found in the string. 490 * A message is also printed if the input string is too long. 491 * The password file uses :'s as seperators, and are not allowed in the "gcos" 492 * field. Newlines serve as delimiters between users in the password file, 493 * and so, those too, are checked for. (I don't think that it is possible to 494 * type them in, but better safe than sorry) 495 * 496 * Returns '1' if a colon or newline is found or the input line is too long. 497 */ 498 illegal_input(input_str) 499 char *input_str; 500 { 501 char *ptr; 502 int error_flag = 0; 503 int length = strlen(input_str); 504 505 if (index(input_str, ':')) { 506 printf("':' is not allowed.\n"); 507 error_flag = 1; 508 } 509 if (input_str[length-1] != '\n') { 510 /* the newline and the '\0' eat up two characters */ 511 printf("Maximum number of characters allowed is %d\n", 512 BUFSIZ-2); 513 /* flush the rest of the input line */ 514 while (getchar() != '\n') 515 /* void */; 516 error_flag = 1; 517 } 518 /* 519 * Delete newline by shortening string by 1. 520 */ 521 input_str[length-1] = '\0'; 522 /* 523 * Don't allow control characters, etc in input string. 524 */ 525 for (ptr=input_str; *ptr != '\0'; ptr++) { 526 if ((int) *ptr < 040) { 527 printf("Control characters are not allowed.\n"); 528 error_flag = 1; 529 break; 530 } 531 } 532 return (error_flag); 533 } 534 535 /* 536 * Removes '-'s from the input string. 537 */ 538 remove_hyphens(str) 539 char *str; 540 { 541 char *hyphen; 542 543 while ((hyphen = index(str, '-')) != NULL) 544 (void) strcpy(hyphen, hyphen+1); 545 } 546 547 /* 548 * Checks to see if 'str' contains only digits (0-9). If not, then 549 * an error message is printed and '1' is returned. 550 */ 551 not_all_digits(str) 552 char *str; 553 { 554 char *ptr; 555 556 for (ptr = str; *ptr != '\0'; ++ptr) 557 if (!isdigit(*ptr)) { 558 printf("Phone numbers can only contain digits.\n"); 559 return (1); 560 } 561 return (0); 562 } 563 564 /* 565 * Deal with Berkeley buildings. Abbreviating Cory to C and Evans to E. 566 * Correction changes "str". 567 * 568 * Returns 1 if incorrect room format. 569 * 570 * Note: this function assumes that the newline has been removed from str. 571 */ 572 illegal_building(str) 573 register char *str; 574 { 575 int length = strlen(str); 576 register char *ptr; 577 578 /* 579 * If the string is [Ee]vans or [Cc]ory or ends in 580 * [ \t0-9][Ee]vans or [ \t0-9M][Cc]ory, then contract the name 581 * into 'E' or 'C', as the case may be, and delete leading blanks. 582 */ 583 if (length >= 5 && strcmp(ptr = str + length - 4, "vans") == 0 && 584 (*--ptr == 'e' || *ptr == 'E') && 585 (--ptr < str || isspace(*ptr) || isdigit(*ptr))) { 586 for (; ptr > str && isspace(*ptr); ptr--) 587 ; 588 ptr++; 589 *ptr++ = 'E'; 590 *ptr = '\0'; 591 } else 592 if (length >= 4 && strcmp(ptr = str + length - 3, "ory") == 0 && 593 (*--ptr == 'c' || *ptr == 'C') && 594 (--ptr < str || *ptr == 'M' || isspace(*ptr) || isdigit(*ptr))) { 595 for (; ptr > str && isspace(*ptr); ptr--) 596 ; 597 ptr++; 598 *ptr++ = 'C'; 599 *ptr = '\0'; 600 } 601 return (0); 602 } 603 604 /* 605 * get_defaults picks apart "str" and returns a structure points. 606 * "str" contains up to 4 fields separated by commas. 607 * Any field that is missing is set to blank. 608 */ 609 struct default_values * 610 get_defaults(str) 611 char *str; 612 { 613 struct default_values *answer; 614 615 answer = (struct default_values *) 616 malloc((unsigned)sizeof(struct default_values)); 617 if (answer == (struct default_values *) NULL) { 618 fprintf(stderr, 619 "\nUnable to allocate storage in get_defaults!\n"); 620 exit(1); 621 } 622 /* 623 * Values if no corresponding string in "str". 624 */ 625 answer->name = str; 626 answer->office_num = ""; 627 answer->office_phone = ""; 628 answer->home_phone = ""; 629 str = index(answer->name, ','); 630 if (str == 0) 631 return (answer); 632 *str = '\0'; 633 answer->office_num = str + 1; 634 str = index(answer->office_num, ','); 635 if (str == 0) 636 return (answer); 637 *str = '\0'; 638 answer->office_phone = str + 1; 639 str = index(answer->office_phone, ','); 640 if (str == 0) 641 return (answer); 642 *str = '\0'; 643 answer->home_phone = str + 1; 644 return (answer); 645 } 646 647 /* 648 * special_case returns true when either the default is accepted 649 * (str = '\n'), or when 'none' is typed. 'none' is accepted in 650 * either upper or lower case (or any combination). 'str' is modified 651 * in these two cases. 652 */ 653 special_case(str,default_str) 654 char *str, *default_str; 655 { 656 static char word[] = "none\n"; 657 char *ptr, *wordptr; 658 659 /* 660 * If the default is accepted, then change the old string do the 661 * default string. 662 */ 663 if (*str == '\n') { 664 (void) strcpy(str, default_str); 665 return (1); 666 } 667 /* 668 * Check to see if str is 'none'. (It is questionable if case 669 * insensitivity is worth the hair). 670 */ 671 wordptr = word-1; 672 for (ptr = str; *ptr != '\0'; ++ptr) { 673 ++wordptr; 674 if (*wordptr == '\0') /* then words are different sizes */ 675 return (0); 676 if (*ptr == *wordptr) 677 continue; 678 if (isupper(*ptr) && (tolower(*ptr) == *wordptr)) 679 continue; 680 /* 681 * At this point we have a mismatch, so we return 682 */ 683 return (0); 684 } 685 /* 686 * Make sure that words are the same length. 687 */ 688 if (*(wordptr+1) != '\0') 689 return (0); 690 /* 691 * Change 'str' to be the null string 692 */ 693 *str = '\0'; 694 return (1); 695 } 696