1: /* 2: * Copyright (c) 1988 The Regents of the University of California. 3: * All rights reserved. 4: * 5: * Redistribution and use in source and binary forms are permitted 6: * provided that the above copyright notice and this paragraph are 7: * duplicated in all such forms and that any documentation, 8: * advertising materials, and other materials related to such 9: * distribution and use acknowledge that the software was developed 10: * by the University of California, Berkeley. The name of the 11: * University may not be used to endorse or promote products derived 12: * from this software without specific prior written permission. 13: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 14: * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 15: * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 16: */ 17: 18: #if !defined(lint) && defined(DOSCCS) 19: char copyright[] = 20: "@(#) Copyright (c) 1988 The Regents of the University of California.\n\ 21: All rights reserved.\n"; 22: 23: static char sccsid[] = "@(#)chpass.c 5.10.1 (2.11BSD) 1996/1/12"; 24: #endif /* not lint */ 25: 26: #include <sys/param.h> 27: #include <sys/file.h> 28: #include <sys/stat.h> 29: #include <sys/signal.h> 30: #include <sys/time.h> 31: #include <sys/resource.h> 32: #include <pwd.h> 33: #include <errno.h> 34: #include <stdio.h> 35: #include <ctype.h> 36: #include <chpass.h> 37: #include <strings.h> 38: #include <stdlib.h> 39: 40: char e1[] = ": "; 41: char e2[] = ":,"; 42: 43: int p_change(), p_class(), p_expire(), p_gecos(), p_gid(), p_hdir(); 44: int p_login(), p_passwd(), p_shell(), p_uid(); 45: 46: struct entry list[] = { 47: { "Login", p_login, 1, 5, e1, }, 48: { "Password", p_passwd, 1, 8, e1, }, 49: { "Uid", p_uid, 1, 3, e1, }, 50: { "Gid", p_gid, 1, 3, e1, }, 51: { "Class", p_class, 1, 5, e1, }, 52: { "Change", p_change, 1, 6, NULL, }, 53: { "Expire", p_expire, 1, 6, NULL, }, 54: #define E_NAME 7 55: { "Full Name", p_gecos, 0, 9, e2, }, 56: #define E_BPHONE 8 57: { "Office Phone", p_gecos, 0, 12, e2, }, 58: #define E_HPHONE 9 59: { "Home Phone", p_gecos, 0, 10, e2, }, 60: #define E_LOCATE 10 61: { "Location", p_gecos, 0, 8, e2, }, 62: { "Home directory", p_hdir, 1, 14, e1, }, 63: { "Shell", p_shell, 0, 5, e1, }, 64: { NULL, 0, }, 65: }; 66: 67: uid_t uid; 68: 69: main(argc, argv) 70: int argc; 71: char **argv; 72: { 73: extern int errno, optind; 74: extern char *optarg; 75: register char *p; 76: struct passwd lpw, *pw; 77: struct rlimit rlim; 78: FILE *temp_fp; 79: int aflag, ch, fd; 80: char *fend, *passwd, *temp, *tend; 81: char from[MAXPATHLEN], to[MAXPATHLEN]; 82: char *getusershell(); 83: 84: uid = getuid(); 85: aflag = 0; 86: while ((ch = getopt(argc, argv, "a:")) != EOF) 87: switch(ch) { 88: case 'a': 89: if (uid) { 90: (void)fprintf(stderr, 91: "chpass: %s\n", strerror(EACCES)); 92: exit(1); 93: } 94: loadpw(optarg, pw = &lpw); 95: aflag = 1; 96: break; 97: case '?': 98: default: 99: usage(); 100: } 101: argc -= optind; 102: argv += optind; 103: 104: if (!aflag) 105: switch(argc) { 106: case 0: 107: if (!(pw = getpwuid(uid))) { 108: (void)fprintf(stderr, 109: "chpass: unknown user: uid %u\n", uid); 110: exit(1); 111: } 112: break; 113: case 1: 114: if (!(pw = getpwnam(*argv))) { 115: (void)fprintf(stderr, 116: "chpass: unknown user %s.\n", *argv); 117: exit(1); 118: } 119: if (uid && uid != pw->pw_uid) { 120: (void)fprintf(stderr, 121: "chpass: %s\n", strerror(EACCES)); 122: exit(1); 123: } 124: break; 125: default: 126: usage(); 127: } 128: 129: (void)signal(SIGHUP, SIG_IGN); 130: (void)signal(SIGINT, SIG_IGN); 131: (void)signal(SIGQUIT, SIG_IGN); 132: (void)signal(SIGTSTP, SIG_IGN); 133: 134: rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY; 135: (void)setrlimit(RLIMIT_CPU, &rlim); 136: (void)setrlimit(RLIMIT_FSIZE, &rlim); 137: 138: (void)umask(0); 139: 140: temp = _PATH_PTMP; 141: if ((fd = open(temp, O_WRONLY|O_CREAT|O_EXCL, 0600)) < 0) { 142: if (errno == EEXIST) { 143: (void)fprintf(stderr, 144: "chpass: password file busy -- try again later.\n"); 145: exit(1); 146: } 147: (void)fprintf(stderr, "chpass: %s: %s; ", 148: temp, strerror(errno)); 149: goto bad; 150: } 151: if (!(temp_fp = fdopen(fd, "w"))) { 152: (void)fprintf(stderr, "chpass: can't write %s; ", temp); 153: goto bad; 154: } 155: 156: if (!aflag && !info(pw)) 157: goto bad; 158: 159: /* root should have a 0 uid and a reasonable shell */ 160: if (!strcmp(pw->pw_name, "root")) { 161: if (pw->pw_uid) { 162: (void)fprintf(stderr, "chpass: root uid should be 0."); 163: exit(1); 164: } 165: setusershell(); 166: for (;;) 167: if (!(p = getusershell())) { 168: (void)fprintf(stderr, 169: "chpass: warning, unknown root shell."); 170: break; 171: } 172: else if (!strcmp(pw->pw_shell, p)) 173: break; 174: } 175: 176: passwd = _PATH_MASTERPASSWD; 177: if (!freopen(passwd, "r", stdin)) { 178: (void)fprintf(stderr, "chpass: can't read %s; ", passwd); 179: goto bad; 180: } 181: if (!copy(pw, temp_fp)) 182: goto bad; 183: 184: (void)fclose(temp_fp); 185: (void)fclose(stdin); 186: 187: switch(fork()) { 188: case 0: 189: break; 190: case -1: 191: (void)fprintf(stderr, "chpass: can't fork; "); 192: goto bad; 193: /* NOTREACHED */ 194: default: 195: exit(0); 196: /* NOTREACHED */ 197: } 198: 199: if (makedb(temp)) { 200: (void)fprintf(stderr, "chpass: mkpasswd failed; "); 201: bad: (void)fprintf(stderr, "%s unchanged.\n", _PATH_MASTERPASSWD); 202: (void)unlink(temp); 203: exit(1); 204: } 205: 206: /* 207: * possible race; have to rename four files, and someone could slip 208: * in between them. LOCK_EX and rename the ``passwd.dir'' file first 209: * so that getpwent(3) can't slip in; the lock should never fail and 210: * it's unclear what to do if it does. Rename ``ptmp'' last so that 211: * passwd/vipw/chpass can't slip in. 212: */ 213: (void)setpriority(PRIO_PROCESS, 0, -20); 214: fend = strcpy(from, temp) + strlen(temp); 215: tend = strcpy(to, _PATH_PASSWD) + strlen(_PATH_PASSWD); 216: bcopy(".dir", fend, 5); 217: bcopy(".dir", tend, 5); 218: if ((fd = open(from, O_RDONLY, 0)) >= 0) 219: (void)flock(fd, LOCK_EX); 220: /* here we go... */ 221: (void)rename(from, to); 222: bcopy(".pag", fend, 5); 223: bcopy(".pag", tend, 5); 224: (void)rename(from, to); 225: bcopy(".orig", fend, 6); 226: (void)rename(from, _PATH_PASSWD); 227: (void)rename(temp, passwd); 228: /* done! */ 229: exit(0); 230: } 231: 232: info(pw) 233: struct passwd *pw; 234: { 235: struct stat begin, end; 236: FILE *fp; 237: int fd, rval; 238: char *tfile; 239: 240: tfile = "/tmp/passwd.XXXXXX"; 241: if ((fd = mkstemp(tfile)) == -1 || !(fp = fdopen(fd, "w+"))) { 242: (void)fprintf(stderr, "chpass: no temporary file"); 243: return(0); 244: } 245: 246: print(fp, pw); 247: (void)fflush(fp); 248: 249: /* 250: * give the file to the real user; setuid permissions 251: * are discarded in edit() 252: */ 253: (void)fchown(fd, getuid(), getgid()); 254: 255: for (rval = 0;;) { 256: (void)fstat(fd, &begin); 257: if (edit(tfile)) { 258: (void)fprintf(stderr, "chpass: edit failed; "); 259: break; 260: } 261: (void)fstat(fd, &end); 262: if (begin.st_mtime == end.st_mtime) { 263: (void)fprintf(stderr, "chpass: no changes made; "); 264: break; 265: } 266: (void)rewind(fp); 267: if (check(fp, pw)) { 268: rval = 1; 269: break; 270: } 271: (void)fflush(stderr); 272: if (prompt()) 273: break; 274: } 275: (void)fclose(fp); 276: (void)unlink(tfile); 277: return(rval); 278: } 279: 280: check(fp, pw) 281: FILE *fp; 282: struct passwd *pw; 283: { 284: register struct entry *ep; 285: register char *p; 286: static char buf[256]; 287: 288: while (fgets(buf, sizeof(buf), fp)) { 289: if (!buf[0] || buf[0] == '#') 290: continue; 291: if (!(p = index(buf, '\n'))) { 292: (void)fprintf(stderr, "chpass: line too long.\n"); 293: return(0); 294: } 295: *p = '\0'; 296: for (ep = list;; ++ep) { 297: if (!ep->prompt) { 298: (void)fprintf(stderr, 299: "chpass: unrecognized field.\n"); 300: return(0); 301: } 302: if (!strncasecmp(buf, ep->prompt, ep->len)) { 303: if (ep->restricted && uid) 304: break; 305: if (!(p = index(buf, ':'))) { 306: (void)fprintf(stderr, 307: "chpass: line corrupted.\n"); 308: return(0); 309: } 310: while (isspace(*++p)); 311: if (ep->except && strpbrk(p, ep->except)) { 312: (void)fprintf(stderr, 313: "chpass: illegal character in the \"%s\" field.\n", 314: ep->prompt); 315: return(0); 316: } 317: if ((ep->func)(p, pw, ep)) 318: return(0); 319: break; 320: } 321: } 322: } 323: /* 324: * special checks... 325: * 326: * there has to be a limit on the size of the gecos fields, 327: * otherwise getpwent(3) can choke. 328: * ``if I swallow anything evil, put your fingers down my throat...'' 329: * -- The Who 330: */ 331: if (strlen(list[E_NAME].save) + strlen(list[E_BPHONE].save) + 332: strlen(list[E_HPHONE].save) + strlen(list[E_LOCATE].save) 333: > 128) { 334: (void)fprintf(stderr, "chpass: gecos field too large.\n"); 335: exit(1); 336: } 337: (void)sprintf(pw->pw_gecos = buf, "%s,%s,%s,%s", 338: list[E_NAME].save, list[E_LOCATE].save, list[E_BPHONE].save, 339: list[E_HPHONE].save); 340: return(1); 341: } 342: 343: copy(pw, fp) 344: struct passwd *pw; 345: FILE *fp; 346: { 347: register int done; 348: register char *p; 349: char buf[256]; 350: 351: for (done = 0; fgets(buf, sizeof(buf), stdin);) { 352: /* skip lines that are too big */ 353: if (!index(buf, '\n')) { 354: (void)fprintf(stderr, "chpass: line too long; "); 355: return(0); 356: } 357: if (done) { 358: (void)fprintf(fp, "%s", buf); 359: continue; 360: } 361: if (!(p = index(buf, ':'))) { 362: (void)fprintf(stderr, "chpass: corrupted entry; "); 363: return(0); 364: } 365: *p = '\0'; 366: if (strcmp(buf, pw->pw_name)) { 367: *p = ':'; 368: (void)fprintf(fp, "%s", buf); 369: continue; 370: } 371: (void)fprintf(fp, "%s:%s:%d:%d:%s:%ld:%ld:%s:%s:%s\n", 372: pw->pw_name, pw->pw_passwd, pw->pw_uid, pw->pw_gid, 373: pw->pw_class, pw->pw_change, pw->pw_expire, pw->pw_gecos, 374: pw->pw_dir, pw->pw_shell); 375: done = 1; 376: } 377: if (!done) 378: (void)fprintf(fp, "%s:%s:%d:%d:%s:%ld:%ld:%s:%s:%s\n", 379: pw->pw_name, pw->pw_passwd, pw->pw_uid, pw->pw_gid, 380: pw->pw_class, pw->pw_change, pw->pw_expire, pw->pw_gecos, 381: pw->pw_dir, pw->pw_shell); 382: return(1); 383: } 384: 385: makedb(file) 386: char *file; 387: { 388: int status, pid, w; 389: 390: if (!(pid = vfork())) { 391: execl(_PATH_MKPASSWD, "mkpasswd", "-p", file, NULL); 392: _exit(127); 393: } 394: while ((w = wait(&status)) != pid && w != -1); 395: return(w == -1 || status); 396: } 397: 398: edit(file) 399: char *file; 400: { 401: int status, pid, w; 402: char *p, *editor, *getenv(); 403: 404: if (editor = getenv("EDITOR")) { 405: if (p = rindex(editor, '/')) 406: ++p; 407: else 408: p = editor; 409: } 410: else 411: p = editor = "vi"; 412: if (!(pid = vfork())) { 413: (void)setgid(getgid()); 414: (void)setuid(getuid()); 415: execlp(editor, p, file, NULL); 416: _exit(127); 417: } 418: while ((w = wait(&status)) != pid && w != -1); 419: return(w == -1 || status); 420: } 421: 422: loadpw(arg, pw) 423: char *arg; 424: register struct passwd *pw; 425: { 426: register char *cp; 427: char *bp = arg; 428: 429: pw->pw_name = strsep(&bp, ":"); 430: pw->pw_passwd = strsep(&bp, ":"); 431: if (!(cp = strsep(&bp, ":"))) 432: goto bad; 433: pw->pw_uid = atoi(cp); 434: if (!(cp = strsep(&bp, ":"))) 435: goto bad; 436: pw->pw_gid = atoi(cp); 437: pw->pw_class = strsep(&bp, ":"); 438: if (!(cp = strsep(&bp, ":"))) 439: goto bad; 440: pw->pw_change = atol(cp); 441: if (!(cp = strsep(&bp, ":"))) 442: goto bad; 443: pw->pw_expire = atol(cp); 444: pw->pw_gecos = strsep(&bp, ":"); 445: pw->pw_dir = strsep(&bp, ":"); 446: pw->pw_shell = strsep(&bp, ":"); 447: if (!pw->pw_shell || strsep(&bp, ":")) { 448: bad: (void)fprintf(stderr, "chpass: bad password list.\n"); 449: exit(1); 450: } 451: } 452: 453: prompt() 454: { 455: register int c; 456: 457: for (;;) { 458: (void)printf("re-edit the password file? [y]: "); 459: (void)fflush(stdout); 460: c = getchar(); 461: if (c != EOF && c != (int)'\n') 462: while (getchar() != (int)'\n'); 463: return(c == (int)'n'); 464: } 465: /* NOTREACHED */ 466: } 467: 468: usage() 469: { 470: (void)fprintf(stderr, "usage: chpass [-a list] [user]\n"); 471: exit(1); 472: }