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