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