xref: /original-bsd/usr.bin/chpass/chpass.c (revision c74f8e57)
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