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