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