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