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