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.24 (Berkeley) 5/28/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: for (valid = getusershell(); valid; valid = getusershell()) 348: if (strcmp(pwd->pw_shell, valid) == 0) 349: break; 350: if (valid == NULL) { 351: printf("Cannot change from restricted shell %s\n", 352: pwd->pw_shell); 353: exit(1); 354: } 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: } 366: if (newshell[0] == '\0' || strcmp(newshell, pwd->pw_shell) == 0) { 367: printf("Login shell unchanged.\n"); 368: exit(1); 369: } 370: /* 371: * Allow user to give shell name w/o preceding pathname. 372: */ 373: if (u == 0) { 374: valid = newshell; 375: } else { 376: for (valid = getusershell(); valid; valid = getusershell()) { 377: if (newshell[0] == '/') { 378: cp = valid; 379: } else { 380: cp = rindex(valid, '/'); 381: if (cp == 0) 382: cp = valid; 383: else 384: cp++; 385: } 386: if (strcmp(newshell, cp) == 0) 387: break; 388: } 389: } 390: if (valid == 0) { 391: printf("%s is unacceptable as a new shell.\n", 392: newshell); 393: exit(1); 394: } 395: if (access(valid, X_OK) < 0) { 396: printf("%s is unavailable.\n", valid); 397: exit(1); 398: } 399: if (strcmp(valid, DEFSHELL) == 0) 400: valid[0] = '\0'; 401: return (valid); 402: } 403: 404: struct default_values { 405: char *name; 406: char *office_num; 407: char *office_phone; 408: char *home_phone; 409: }; 410: 411: /* 412: * Get name, room number, school phone, and home phone. 413: */ 414: char * 415: getfingerinfo(pwd) 416: struct passwd *pwd; 417: { 418: char in_str[BUFSIZ]; 419: struct default_values *defaults, *get_defaults(); 420: static char answer[4*BUFSIZ]; 421: 422: answer[0] = '\0'; 423: defaults = get_defaults(pwd->pw_gecos); 424: printf("Default values are printed inside of '[]'.\n"); 425: printf("To accept the default, type <return>.\n"); 426: printf("To have a blank entry, type the word 'none'.\n"); 427: /* 428: * Get name. 429: */ 430: do { 431: printf("\nName [%s]: ", defaults->name); 432: (void) fgets(in_str, BUFSIZ, stdin); 433: if (special_case(in_str, defaults->name)) 434: break; 435: } while (illegal_input(in_str)); 436: (void) strcpy(answer, in_str); 437: /* 438: * Get room number. 439: */ 440: do { 441: printf("Room number (Exs: 597E or 197C) [%s]: ", 442: defaults->office_num); 443: (void) fgets(in_str, BUFSIZ, stdin); 444: if (special_case(in_str, defaults->office_num)) 445: break; 446: } while (illegal_input(in_str) || illegal_building(in_str)); 447: (void) strcat(strcat(answer, ","), in_str); 448: /* 449: * Get office phone number. 450: * Remove hyphens. 451: */ 452: do { 453: printf("Office Phone (Ex: 6426000) [%s]: ", 454: defaults->office_phone); 455: (void) fgets(in_str, BUFSIZ, stdin); 456: if (special_case(in_str, defaults->office_phone)) 457: break; 458: remove_hyphens(in_str); 459: } while (illegal_input(in_str) || not_all_digits(in_str)); 460: (void) strcat(strcat(answer, ","), in_str); 461: /* 462: * Get home phone number. 463: * Remove hyphens if present. 464: */ 465: do { 466: printf("Home Phone (Ex: 9875432) [%s]: ", defaults->home_phone); 467: (void) fgets(in_str, BUFSIZ, stdin); 468: if (special_case(in_str, defaults->home_phone)) 469: break; 470: remove_hyphens(in_str); 471: } while (illegal_input(in_str) || not_all_digits(in_str)); 472: (void) strcat(strcat(answer, ","), in_str); 473: if (strcmp(answer, pwd->pw_gecos) == 0) { 474: printf("Finger information unchanged.\n"); 475: exit(1); 476: } 477: return (answer); 478: } 479: 480: /* 481: * Prints an error message if a ':' or a newline is found in the string. 482: * A message is also printed if the input string is too long. 483: * The password file uses :'s as seperators, and are not allowed in the "gcos" 484: * field. Newlines serve as delimiters between users in the password file, 485: * and so, those too, are checked for. (I don't think that it is possible to 486: * type them in, but better safe than sorry) 487: * 488: * Returns '1' if a colon or newline is found or the input line is too long. 489: */ 490: illegal_input(input_str) 491: char *input_str; 492: { 493: char *ptr; 494: int error_flag = 0; 495: int length = strlen(input_str); 496: 497: if (index(input_str, ':')) { 498: printf("':' is not allowed.\n"); 499: error_flag = 1; 500: } 501: if (input_str[length-1] != '\n') { 502: /* the newline and the '\0' eat up two characters */ 503: printf("Maximum number of characters allowed is %d\n", 504: BUFSIZ-2); 505: /* flush the rest of the input line */ 506: while (getchar() != '\n') 507: /* void */; 508: error_flag = 1; 509: } 510: /* 511: * Delete newline by shortening string by 1. 512: */ 513: input_str[length-1] = '\0'; 514: /* 515: * Don't allow control characters, etc in input string. 516: */ 517: for (ptr=input_str; *ptr != '\0'; ptr++) { 518: if ((int) *ptr < 040) { 519: printf("Control characters are not allowed.\n"); 520: error_flag = 1; 521: break; 522: } 523: } 524: return (error_flag); 525: } 526: 527: /* 528: * Removes '-'s from the input string. 529: */ 530: remove_hyphens(str) 531: char *str; 532: { 533: char *hyphen; 534: 535: while ((hyphen = index(str, '-')) != NULL) 536: (void) strcpy(hyphen, hyphen+1); 537: } 538: 539: /* 540: * Checks to see if 'str' contains only digits (0-9). If not, then 541: * an error message is printed and '1' is returned. 542: */ 543: not_all_digits(str) 544: char *str; 545: { 546: char *ptr; 547: 548: for (ptr = str; *ptr != '\0'; ++ptr) 549: if (!isdigit(*ptr)) { 550: printf("Phone numbers can only contain digits.\n"); 551: return (1); 552: } 553: return (0); 554: } 555: 556: /* 557: * Deal with Berkeley buildings. Abbreviating Cory to C and Evans to E. 558: * Correction changes "str". 559: * 560: * Returns 1 if incorrect room format. 561: * 562: * Note: this function assumes that the newline has been removed from str. 563: */ 564: illegal_building(str) 565: register char *str; 566: { 567: int length = strlen(str); 568: register char *ptr; 569: 570: /* 571: * If the string is [Ee]vans or [Cc]ory or ends in 572: * [ \t0-9][Ee]vans or [ \t0-9M][Cc]ory, then contract the name 573: * into 'E' or 'C', as the case may be, and delete leading blanks. 574: */ 575: if (length >= 5 && strcmp(ptr = str + length - 4, "vans") == 0 && 576: (*--ptr == 'e' || *ptr == 'E') && 577: (--ptr < str || isspace(*ptr) || isdigit(*ptr))) { 578: for (; ptr > str && isspace(*ptr); ptr--) 579: ; 580: ptr++; 581: *ptr++ = 'E'; 582: *ptr = '\0'; 583: } else 584: if (length >= 4 && strcmp(ptr = str + length - 3, "ory") == 0 && 585: (*--ptr == 'c' || *ptr == 'C') && 586: (--ptr < str || *ptr == 'M' || isspace(*ptr) || isdigit(*ptr))) { 587: for (; ptr > str && isspace(*ptr); ptr--) 588: ; 589: ptr++; 590: *ptr++ = 'C'; 591: *ptr = '\0'; 592: } 593: return (0); 594: } 595: 596: /* 597: * get_defaults picks apart "str" and returns a structure points. 598: * "str" contains up to 4 fields separated by commas. 599: * Any field that is missing is set to blank. 600: */ 601: struct default_values * 602: get_defaults(str) 603: char *str; 604: { 605: struct default_values *answer; 606: 607: answer = (struct default_values *) 608: malloc((unsigned)sizeof(struct default_values)); 609: if (answer == (struct default_values *) NULL) { 610: fprintf(stderr, 611: "\nUnable to allocate storage in get_defaults!\n"); 612: exit(1); 613: } 614: /* 615: * Values if no corresponding string in "str". 616: */ 617: answer->name = str; 618: answer->office_num = ""; 619: answer->office_phone = ""; 620: answer->home_phone = ""; 621: str = index(answer->name, ','); 622: if (str == 0) 623: return (answer); 624: *str = '\0'; 625: answer->office_num = str + 1; 626: str = index(answer->office_num, ','); 627: if (str == 0) 628: return (answer); 629: *str = '\0'; 630: answer->office_phone = str + 1; 631: str = index(answer->office_phone, ','); 632: if (str == 0) 633: return (answer); 634: *str = '\0'; 635: answer->home_phone = str + 1; 636: return (answer); 637: } 638: 639: /* 640: * special_case returns true when either the default is accepted 641: * (str = '\n'), or when 'none' is typed. 'none' is accepted in 642: * either upper or lower case (or any combination). 'str' is modified 643: * in these two cases. 644: */ 645: special_case(str,default_str) 646: char *str, *default_str; 647: { 648: static char word[] = "none\n"; 649: char *ptr, *wordptr; 650: 651: /* 652: * If the default is accepted, then change the old string do the 653: * default string. 654: */ 655: if (*str == '\n') { 656: (void) strcpy(str, default_str); 657: return (1); 658: } 659: /* 660: * Check to see if str is 'none'. (It is questionable if case 661: * insensitivity is worth the hair). 662: */ 663: wordptr = word-1; 664: for (ptr = str; *ptr != '\0'; ++ptr) { 665: ++wordptr; 666: if (*wordptr == '\0') /* then words are different sizes */ 667: return (0); 668: if (*ptr == *wordptr) 669: continue; 670: if (isupper(*ptr) && (tolower(*ptr) == *wordptr)) 671: continue; 672: /* 673: * At this point we have a mismatch, so we return 674: */ 675: return (0); 676: } 677: /* 678: * Make sure that words are the same length. 679: */ 680: if (*(wordptr+1) != '\0') 681: return (0); 682: /* 683: * Change 'str' to be the null string 684: */ 685: *str = '\0'; 686: return (1); 687: }