pwd_mkdb.c revision 19085
1/*-
2 * Copyright (c) 1991, 1993, 1994
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by the University of
16 *	California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#ifndef lint
35static char copyright[] =
36"@(#) Copyright (c) 1991, 1993, 1994\n\
37	The Regents of the University of California.  All rights reserved.\n";
38#endif /* not lint */
39
40#ifndef lint
41static char sccsid[] = "@(#)pwd_mkdb.c	8.5 (Berkeley) 4/20/94";
42#endif /* not lint */
43
44#include <sys/param.h>
45#include <sys/stat.h>
46
47#include <db.h>
48#include <err.h>
49#include <errno.h>
50#include <fcntl.h>
51#include <limits.h>
52#include <pwd.h>
53#include <signal.h>
54#include <stdio.h>
55#include <stdlib.h>
56#include <string.h>
57#include <unistd.h>
58
59#include "pw_scan.h"
60
61#define	INSECURE	1
62#define	SECURE		2
63#define	PERM_INSECURE	(S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)
64#define	PERM_SECURE	(S_IRUSR|S_IWUSR)
65
66HASHINFO openinfo = {
67	4096,		/* bsize */
68	32,		/* ffactor */
69	256,		/* nelem */
70	2048 * 1024,	/* cachesize */
71	NULL,		/* hash() */
72	0		/* lorder */
73};
74
75static enum state { FILE_INSECURE, FILE_SECURE, FILE_ORIG } clean;
76static struct passwd pwd;			/* password structure */
77static char *pname;				/* password file name */
78static char prefix[MAXPATHLEN];
79
80void	cleanup __P((void));
81void	error __P((char *));
82void	cp __P((char *, char *, mode_t mode));
83void	mv __P((char *, char *));
84int	scan __P((FILE *, struct passwd *));
85void	usage __P((void));
86
87int
88main(argc, argv)
89	int argc;
90	char *argv[];
91{
92	DB *dp, *sdp, *pw_db;
93	DBT data, sdata, key;
94	FILE *fp, *oldfp;
95	sigset_t set;
96	int ch, cnt, ypcnt, len, makeold, tfd, yp_enabled = 0;
97	char *p, *t;
98	char buf[MAX(MAXPATHLEN, LINE_MAX * 2)], tbuf[1024];
99	char sbuf[MAX(MAXPATHLEN, LINE_MAX * 2)];
100	char buf2[MAXPATHLEN];
101	char sbuf2[MAXPATHLEN];
102	char *username;
103	u_int method, methoduid;
104	int cflag;
105
106	cflag = 0;
107	strcpy(prefix, _PATH_PWD);
108	makeold = 0;
109	username = NULL;
110	while ((ch = getopt(argc, argv, "cd:pu:v")) != EOF)
111		switch(ch) {
112		case 'c':                       /* verify only */
113			cflag = 1;
114			break;
115		case 'd':
116			strcpy(prefix, optarg);
117			break;
118		case 'p':			/* create V7 "file.orig" */
119			makeold = 1;
120			break;
121		case 'u':			/* only update this record */
122			username = optarg;
123			break;
124		case 'v':                       /* backward compatible */
125			break;
126		default:
127			usage();
128		}
129	argc -= optind;
130	argv += optind;
131
132	if (argc != 1 || (username && (*username == '+' || *username == '-')))
133		usage();
134
135	/*
136	 * This could be changed to allow the user to interrupt.
137	 * Probably not worth the effort.
138	 */
139	sigemptyset(&set);
140	sigaddset(&set, SIGTSTP);
141	sigaddset(&set, SIGHUP);
142	sigaddset(&set, SIGINT);
143	sigaddset(&set, SIGQUIT);
144	sigaddset(&set, SIGTERM);
145	(void)sigprocmask(SIG_BLOCK, &set, (sigset_t *)NULL);
146
147	/* We don't care what the user wants. */
148	(void)umask(0);
149
150	pname = *argv;
151	/* Open the original password file */
152	if (!(fp = fopen(pname, "r")))
153		error(pname);
154
155	/* check only if password database is valid */
156	if (cflag) {
157		for (cnt = 1; scan(fp, &pwd); ++cnt);
158		exit(0);
159	}
160
161	/* Open the temporary insecure password database. */
162	(void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _MP_DB);
163	(void)snprintf(sbuf, sizeof(sbuf), "%s/%s.tmp", prefix, _SMP_DB);
164	if (username) {
165		(void)snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _MP_DB);
166		(void)snprintf(sbuf2, sizeof(sbuf2), "%s/%s", prefix, _SMP_DB);
167
168		clean = FILE_INSECURE;
169		cp(buf2, buf, PERM_INSECURE);
170		dp = dbopen(buf,
171		    O_RDWR|O_EXCL, PERM_INSECURE, DB_HASH, &openinfo);
172		if (dp == NULL)
173			error(buf);
174
175		clean = FILE_SECURE;
176		cp(sbuf2, sbuf, PERM_SECURE);
177		sdp = dbopen(sbuf,
178		    O_RDWR|O_EXCL, PERM_SECURE, DB_HASH, &openinfo);
179		if (sdp == NULL)
180			error(sbuf);
181
182		/*
183		 * Do some trouble to check if we should store this users
184		 * uid. Don't use getpwnam/getpwuid as that interferes
185		 * with NIS.
186		 */
187		pw_db = dbopen(_PATH_MP_DB, O_RDONLY, 0, DB_HASH, NULL);
188		if (!pw_db)
189			error(_MP_DB);
190		buf[0] = _PW_KEYBYNAME;
191		len = strlen(username);
192
193		/* Only check that username fits in buffer */
194		memmove(buf + 1, username, MIN(len, sizeof(buf) - 1));
195		key.data = (u_char *)buf;
196		key.size = len + 1;
197		if ((pw_db->get)(pw_db, &key, &data, 0) == 0) {
198			p = (char *)data.data;
199
200			/* jump over pw_name and pw_passwd, to get to pw_uid */
201			while (*p++)
202				;
203			while (*p++)
204				;
205
206			buf[0] = _PW_KEYBYUID;
207			memmove(buf + 1, p, sizeof(int));
208			key.data = (u_char *)buf;
209			key.size = sizeof(int) + 1;
210
211			if ((pw_db->get)(pw_db, &key, &data, 0) == 0) {
212				/* First field of data.data holds pw_pwname */
213				if (!strcmp(data.data, username))
214					methoduid = 0;
215				else
216					methoduid = R_NOOVERWRITE;
217			} else {
218				methoduid = R_NOOVERWRITE;
219			}
220		} else {
221			methoduid = R_NOOVERWRITE;
222		}
223		(void)(pw_db->close)(pw_db);
224		method = 0;
225	} else {
226		dp = dbopen(buf,
227		    O_RDWR|O_CREAT|O_EXCL, PERM_INSECURE, DB_HASH, &openinfo);
228		if (dp == NULL)
229			error(buf);
230		clean = FILE_INSECURE;
231
232		sdp = dbopen(sbuf,
233		    O_RDWR|O_CREAT|O_EXCL, PERM_SECURE, DB_HASH, &openinfo);
234		if (sdp == NULL)
235			error(sbuf);
236		clean = FILE_SECURE;
237
238		method = R_NOOVERWRITE;
239		methoduid = R_NOOVERWRITE;
240	}
241
242	/*
243	 * Open file for old password file.  Minor trickiness -- don't want to
244	 * chance the file already existing, since someone (stupidly) might
245	 * still be using this for permission checking.  So, open it first and
246	 * fdopen the resulting fd.  The resulting file should be readable by
247	 * everyone.
248	 */
249	if (makeold) {
250		(void)snprintf(buf, sizeof(buf), "%s.orig", pname);
251		if ((tfd = open(buf,
252		    O_WRONLY|O_CREAT|O_EXCL, PERM_INSECURE)) < 0)
253			error(buf);
254		if ((oldfp = fdopen(tfd, "w")) == NULL)
255			error(buf);
256		clean = FILE_ORIG;
257	}
258
259	/*
260	 * The databases actually contain three copies of the original data.
261	 * Each password file entry is converted into a rough approximation
262	 * of a ``struct passwd'', with the strings placed inline.  This
263	 * object is then stored as the data for three separate keys.  The
264	 * first key * is the pw_name field prepended by the _PW_KEYBYNAME
265	 * character.  The second key is the pw_uid field prepended by the
266	 * _PW_KEYBYUID character.  The third key is the line number in the
267	 * original file prepended by the _PW_KEYBYNUM character.  (The special
268	 * characters are prepended to ensure that the keys do not collide.)
269	 */
270	ypcnt = 1;
271	data.data = (u_char *)buf;
272	sdata.data = (u_char *)sbuf;
273	key.data = (u_char *)tbuf;
274	for (cnt = 1; scan(fp, &pwd); ++cnt) {
275		if (pwd.pw_name[0] == '+' || pwd.pw_name[0] == '-')
276			yp_enabled = 1;
277#define	COMPACT(e)	t = e; while (*p++ = *t++);
278		if (!username || (strcmp(username, pwd.pw_name) == 0)) {
279			/* Create insecure data. */
280			p = buf;
281			COMPACT(pwd.pw_name);
282			COMPACT("*");
283			memmove(p, &pwd.pw_uid, sizeof(int));
284			p += sizeof(int);
285			memmove(p, &pwd.pw_gid, sizeof(int));
286			p += sizeof(int);
287			memmove(p, &pwd.pw_change, sizeof(time_t));
288			p += sizeof(time_t);
289			COMPACT(pwd.pw_class);
290			COMPACT(pwd.pw_gecos);
291			COMPACT(pwd.pw_dir);
292			COMPACT(pwd.pw_shell);
293			memmove(p, &pwd.pw_expire, sizeof(time_t));
294			p += sizeof(time_t);
295			memmove(p, &pwd.pw_fields, sizeof pwd.pw_fields);
296			p += sizeof pwd.pw_fields;
297			data.size = p - buf;
298
299			/* Create secure data. */
300			p = sbuf;
301			COMPACT(pwd.pw_name);
302			COMPACT(pwd.pw_passwd);
303			memmove(p, &pwd.pw_uid, sizeof(int));
304			p += sizeof(int);
305			memmove(p, &pwd.pw_gid, sizeof(int));
306			p += sizeof(int);
307			memmove(p, &pwd.pw_change, sizeof(time_t));
308			p += sizeof(time_t);
309			COMPACT(pwd.pw_class);
310			COMPACT(pwd.pw_gecos);
311			COMPACT(pwd.pw_dir);
312			COMPACT(pwd.pw_shell);
313			memmove(p, &pwd.pw_expire, sizeof(time_t));
314			p += sizeof(time_t);
315			memmove(p, &pwd.pw_fields, sizeof pwd.pw_fields);
316			p += sizeof pwd.pw_fields;
317			sdata.size = p - sbuf;
318
319			/* Store insecure by name. */
320			tbuf[0] = _PW_KEYBYNAME;
321			len = strlen(pwd.pw_name);
322			memmove(tbuf + 1, pwd.pw_name, len);
323			key.size = len + 1;
324			if ((dp->put)(dp, &key, &data, method) == -1)
325				error("put");
326
327			/* Store insecure by number. */
328			tbuf[0] = _PW_KEYBYNUM;
329			memmove(tbuf + 1, &cnt, sizeof(cnt));
330			key.size = sizeof(cnt) + 1;
331			if ((dp->put)(dp, &key, &data, method) == -1)
332				error("put");
333
334			/* Store insecure by uid. */
335			tbuf[0] = _PW_KEYBYUID;
336			memmove(tbuf + 1, &pwd.pw_uid, sizeof(pwd.pw_uid));
337			key.size = sizeof(pwd.pw_uid) + 1;
338			if ((dp->put)(dp, &key, &data, methoduid) == -1)
339				error("put");
340
341			/* Store secure by name. */
342			tbuf[0] = _PW_KEYBYNAME;
343			len = strlen(pwd.pw_name);
344			memmove(tbuf + 1, pwd.pw_name, len);
345			key.size = len + 1;
346			if ((sdp->put)(sdp, &key, &sdata, method) == -1)
347				error("put");
348
349			/* Store secure by number. */
350			tbuf[0] = _PW_KEYBYNUM;
351			memmove(tbuf + 1, &cnt, sizeof(cnt));
352			key.size = sizeof(cnt) + 1;
353			if ((sdp->put)(sdp, &key, &sdata, method) == -1)
354				error("put");
355
356			/* Store secure by uid. */
357			tbuf[0] = _PW_KEYBYUID;
358			memmove(tbuf + 1, &pwd.pw_uid, sizeof(pwd.pw_uid));
359			key.size = sizeof(pwd.pw_uid) + 1;
360			if ((sdp->put)(sdp, &key, &sdata, methoduid) == -1)
361				error("put");
362
363			/* Store insecure and secure special plus and special minus */
364			if (pwd.pw_name[0] == '+' || pwd.pw_name[0] == '-') {
365				tbuf[0] = _PW_KEYYPBYNUM;
366				memmove(tbuf + 1, &ypcnt, sizeof(cnt));
367				ypcnt++;
368				key.size = sizeof(cnt) + 1;
369				if ((dp->put)(dp, &key, &data, method) == -1)
370					error("put");
371				if ((sdp->put)(sdp, &key, &sdata, method) == -1)
372					error("put");
373			}
374		}
375		/* Create original format password file entry */
376		if (makeold) {
377			char uidstr[20];
378			char gidstr[20];
379
380			snprintf(uidstr, sizeof(uidstr), "%d", pwd.pw_uid);
381			snprintf(gidstr, sizeof(gidstr), "%d", pwd.pw_gid);
382
383			(void)fprintf(oldfp, "%s:*:%s:%s:%s:%s:%s\n",
384			    pwd.pw_name, pwd.pw_fields & _PWF_UID ? uidstr : "",
385			    pwd.pw_fields & _PWF_GID ? gidstr : "",
386			    pwd.pw_gecos, pwd.pw_dir, pwd.pw_shell);
387		}
388	}
389	/* If YP enabled, set flag. */
390	if (yp_enabled) {
391		buf[0] = yp_enabled + 2;
392		data.size = 1;
393		tbuf[0] = _PW_KEYYPENABLED;
394		key.size = 1;
395		if ((dp->put)(dp, &key, &data, method) == -1)
396			error("put");
397		if ((sdp->put)(sdp, &key, &data, method) == -1)
398			error("put");
399	}
400
401	(void)(dp->close)(dp);
402	(void)(sdp->close)(sdp);
403	if (makeold) {
404		(void)fflush(oldfp);
405		(void)fclose(oldfp);
406	}
407
408	/* Set master.passwd permissions, in case caller forgot. */
409	(void)fchmod(fileno(fp), S_IRUSR|S_IWUSR);
410	(void)fclose(fp);
411
412	/* Install as the real password files. */
413	(void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _MP_DB);
414	(void)snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _MP_DB);
415	mv(buf, buf2);
416	(void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _SMP_DB);
417	(void)snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _SMP_DB);
418	mv(buf, buf2);
419	if (makeold) {
420		(void)snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _PASSWD);
421		(void)snprintf(buf, sizeof(buf), "%s.orig", pname);
422		mv(buf, buf2);
423	}
424	/*
425	 * Move the master password LAST -- chpass(1), passwd(1) and vipw(8)
426	 * all use flock(2) on it to block other incarnations of themselves.
427	 * The rename means that everything is unlocked, as the original file
428	 * can no longer be accessed.
429	 */
430	(void)snprintf(buf, sizeof(buf), "%s/%s", prefix, _MASTERPASSWD);
431	mv(pname, buf);
432	exit(0);
433}
434
435int
436scan(fp, pw)
437	FILE *fp;
438	struct passwd *pw;
439{
440	static int lcnt;
441	static char line[LINE_MAX];
442	char *p;
443
444	if (!fgets(line, sizeof(line), fp))
445		return (0);
446	++lcnt;
447	/*
448	 * ``... if I swallow anything evil, put your fingers down my
449	 * throat...''
450	 *	-- The Who
451	 */
452	if (!(p = strchr(line, '\n'))) {
453		warnx("line too long");
454		goto fmt;
455
456	}
457	*p = '\0';
458	if (!pw_scan(line, pw)) {
459		warnx("at line #%d", lcnt);
460fmt:		errno = EFTYPE;	/* XXX */
461		error(pname);
462	}
463
464	return (1);
465}
466
467void
468cp(from, to, mode)
469	char *from, *to;
470	mode_t mode;
471{
472	static char buf[MAXBSIZE];
473	int from_fd, rcount, to_fd, wcount;
474
475	if ((from_fd = open(from, O_RDONLY, 0)) < 0)
476		error(from);
477	if ((to_fd = open(to, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0)
478		error(to);
479	while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) {
480		wcount = write(to_fd, buf, rcount);
481		if (rcount != wcount || wcount == -1) {
482			int sverrno = errno;
483
484			(void)snprintf(buf, sizeof(buf), "%s to %s", from, to);
485			errno = sverrno;
486			error(buf);
487		}
488	}
489	if (rcount < 0) {
490		int sverrno = errno;
491
492		(void)snprintf(buf, sizeof(buf), "%s to %s", from, to);
493		errno = sverrno;
494		error(buf);
495	}
496}
497
498
499void
500mv(from, to)
501	char *from, *to;
502{
503	char buf[MAXPATHLEN];
504
505	if (rename(from, to)) {
506		int sverrno = errno;
507		(void)snprintf(buf, sizeof(buf), "%s to %s", from, to);
508		errno = sverrno;
509		error(buf);
510	}
511}
512
513void
514error(name)
515	char *name;
516{
517
518	warn(name);
519	cleanup();
520	exit(1);
521}
522
523void
524cleanup()
525{
526	char buf[MAXPATHLEN];
527
528	switch(clean) {
529	case FILE_ORIG:
530		(void)snprintf(buf, sizeof(buf), "%s.orig", pname);
531		(void)unlink(buf);
532		/* FALLTHROUGH */
533	case FILE_SECURE:
534		(void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _SMP_DB);
535		(void)unlink(buf);
536		/* FALLTHROUGH */
537	case FILE_INSECURE:
538		(void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _MP_DB);
539		(void)unlink(buf);
540	}
541}
542
543void
544usage()
545{
546
547	(void)fprintf(stderr, "usage: pwd_mkdb [-c] [-p] [-d <dest dir>] [-u <local username>] file\n");
548	exit(1);
549}
550