pwd_mkdb.c revision 50479
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 const 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
41#if 0
42static char sccsid[] = "@(#)pwd_mkdb.c	8.5 (Berkeley) 4/20/94";
43#endif
44static const char rcsid[] =
45  "$FreeBSD: head/usr.sbin/pwd_mkdb/pwd_mkdb.c 50479 1999-08-28 01:35:59Z peter $";
46#endif /* not lint */
47
48#include <sys/param.h>
49#include <sys/stat.h>
50
51#include <db.h>
52#include <err.h>
53#include <errno.h>
54#include <fcntl.h>
55#include <limits.h>
56#include <pwd.h>
57#include <signal.h>
58#include <stdio.h>
59#include <stdlib.h>
60#include <string.h>
61#include <unistd.h>
62
63#include "pw_scan.h"
64
65#define	INSECURE	1
66#define	SECURE		2
67#define	PERM_INSECURE	(S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)
68#define	PERM_SECURE	(S_IRUSR|S_IWUSR)
69
70HASHINFO openinfo = {
71	4096,		/* bsize */
72	32,		/* ffactor */
73	256,		/* nelem */
74	2048 * 1024,	/* cachesize */
75	NULL,		/* hash() */
76	0		/* lorder */
77};
78
79static enum state { FILE_INSECURE, FILE_SECURE, FILE_ORIG } clean;
80static struct passwd pwd;			/* password structure */
81static char *pname;				/* password file name */
82static char prefix[MAXPATHLEN];
83
84static int is_comment;	/* flag for comments */
85static char line[LINE_MAX];
86
87void	cleanup __P((void));
88void	error __P((char *));
89void	cp __P((char *, char *, mode_t mode));
90void	mv __P((char *, char *));
91int	scan __P((FILE *, struct passwd *));
92static void	usage __P((void));
93
94int
95main(argc, argv)
96	int argc;
97	char *argv[];
98{
99	DB *dp, *sdp, *pw_db;
100	DBT data, sdata, key;
101	FILE *fp, *oldfp;
102	sigset_t set;
103	int ch, cnt, ypcnt, len, makeold, tfd, yp_enabled = 0;
104	char *p, *t;
105	char buf[MAX(MAXPATHLEN, LINE_MAX * 2)], tbuf[1024];
106	char sbuf[MAX(MAXPATHLEN, LINE_MAX * 2)];
107	char buf2[MAXPATHLEN];
108	char sbuf2[MAXPATHLEN];
109	char *username;
110	u_int method, methoduid;
111	int Cflag;
112	int nblock = 0;
113
114	Cflag = 0;
115	strcpy(prefix, _PATH_PWD);
116	makeold = 0;
117	username = NULL;
118	while ((ch = getopt(argc, argv, "Cd:ps:u:vN")) != -1)
119		switch(ch) {
120		case 'C':                       /* verify only */
121			Cflag = 1;
122			break;
123		case 'd':
124			strncpy(prefix, optarg, sizeof prefix - 1);
125			break;
126		case 'p':			/* create V7 "file.orig" */
127			makeold = 1;
128			break;
129		case 's':			/* change default cachesize */
130			openinfo.cachesize = atoi(optarg) * 1024 * 1024;
131			break;
132		case 'u':			/* only update this record */
133			username = optarg;
134			break;
135		case 'v':                       /* backward compatible */
136			break;
137		case 'N':			/* do not wait for lock	*/
138			nblock = LOCK_NB;
139			break;
140		default:
141			usage();
142		}
143	argc -= optind;
144	argv += optind;
145
146	if (argc != 1 || (username && (*username == '+' || *username == '-')))
147		usage();
148
149	/*
150	 * This could be changed to allow the user to interrupt.
151	 * Probably not worth the effort.
152	 */
153	sigemptyset(&set);
154	sigaddset(&set, SIGTSTP);
155	sigaddset(&set, SIGHUP);
156	sigaddset(&set, SIGINT);
157	sigaddset(&set, SIGQUIT);
158	sigaddset(&set, SIGTERM);
159	(void)sigprocmask(SIG_BLOCK, &set, (sigset_t *)NULL);
160
161	/* We don't care what the user wants. */
162	(void)umask(0);
163
164	pname = *argv;
165
166	/*
167	 * Open and lock the original password file.  We have to check
168	 * the hardlink count after we get the lock to handle any potential
169	 * unlink/rename race.
170	 *
171	 * This lock is necessary when someone runs pwd_mkdb manually, directly
172	 * on master.passwd, to handle the case where a user might try to
173	 * change his password while pwd_mkdb is running.
174	 */
175	for (;;) {
176		struct stat st;
177
178		if (!(fp = fopen(pname, "r")))
179			error(pname);
180		if (flock(fileno(fp), LOCK_EX|nblock) < 0)
181			error("flock");
182		if (fstat(fileno(fp), &st) < 0)
183			error(pname);
184		if (st.st_nlink != 0)
185			break;
186		fclose(fp);
187		fp = NULL;
188	}
189
190	/* check only if password database is valid */
191	if (Cflag) {
192		for (cnt = 1; scan(fp, &pwd); ++cnt);
193		exit(0);
194	}
195
196	/* Open the temporary insecure password database. */
197	(void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _MP_DB);
198	(void)snprintf(sbuf, sizeof(sbuf), "%s/%s.tmp", prefix, _SMP_DB);
199	if (username) {
200		(void)snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _MP_DB);
201		(void)snprintf(sbuf2, sizeof(sbuf2), "%s/%s", prefix, _SMP_DB);
202
203		clean = FILE_INSECURE;
204		cp(buf2, buf, PERM_INSECURE);
205		dp = dbopen(buf,
206		    O_RDWR|O_EXCL, PERM_INSECURE, DB_HASH, &openinfo);
207		if (dp == NULL)
208			error(buf);
209
210		clean = FILE_SECURE;
211		cp(sbuf2, sbuf, PERM_SECURE);
212		sdp = dbopen(sbuf,
213		    O_RDWR|O_EXCL, PERM_SECURE, DB_HASH, &openinfo);
214		if (sdp == NULL)
215			error(sbuf);
216
217		/*
218		 * Do some trouble to check if we should store this users
219		 * uid. Don't use getpwnam/getpwuid as that interferes
220		 * with NIS.
221		 */
222		pw_db = dbopen(_PATH_MP_DB, O_RDONLY, 0, DB_HASH, NULL);
223		if (!pw_db)
224			error(_MP_DB);
225		buf[0] = _PW_KEYBYNAME;
226		len = strlen(username);
227
228		/* Only check that username fits in buffer */
229		memmove(buf + 1, username, MIN(len, sizeof(buf) - 1));
230		key.data = (u_char *)buf;
231		key.size = len + 1;
232		if ((pw_db->get)(pw_db, &key, &data, 0) == 0) {
233			p = (char *)data.data;
234
235			/* jump over pw_name and pw_passwd, to get to pw_uid */
236			while (*p++)
237				;
238			while (*p++)
239				;
240
241			buf[0] = _PW_KEYBYUID;
242			memmove(buf + 1, p, sizeof(int));
243			key.data = (u_char *)buf;
244			key.size = sizeof(int) + 1;
245
246			if ((pw_db->get)(pw_db, &key, &data, 0) == 0) {
247				/* First field of data.data holds pw_pwname */
248				if (!strcmp(data.data, username))
249					methoduid = 0;
250				else
251					methoduid = R_NOOVERWRITE;
252			} else {
253				methoduid = R_NOOVERWRITE;
254			}
255		} else {
256			methoduid = R_NOOVERWRITE;
257		}
258		if ((pw_db->close)(pw_db))
259			error("close pw_db");
260		method = 0;
261	} else {
262		dp = dbopen(buf,
263		    O_RDWR|O_CREAT|O_EXCL, PERM_INSECURE, DB_HASH, &openinfo);
264		if (dp == NULL)
265			error(buf);
266		clean = FILE_INSECURE;
267
268		sdp = dbopen(sbuf,
269		    O_RDWR|O_CREAT|O_EXCL, PERM_SECURE, DB_HASH, &openinfo);
270		if (sdp == NULL)
271			error(sbuf);
272		clean = FILE_SECURE;
273
274		method = R_NOOVERWRITE;
275		methoduid = R_NOOVERWRITE;
276	}
277
278	/*
279	 * Open file for old password file.  Minor trickiness -- don't want to
280	 * chance the file already existing, since someone (stupidly) might
281	 * still be using this for permission checking.  So, open it first and
282	 * fdopen the resulting fd.  The resulting file should be readable by
283	 * everyone.
284	 */
285	if (makeold) {
286		(void)snprintf(buf, sizeof(buf), "%s.orig", pname);
287		if ((tfd = open(buf,
288		    O_WRONLY|O_CREAT|O_EXCL, PERM_INSECURE)) < 0)
289			error(buf);
290		if ((oldfp = fdopen(tfd, "w")) == NULL)
291			error(buf);
292		clean = FILE_ORIG;
293	}
294
295	/*
296	 * The databases actually contain three copies of the original data.
297	 * Each password file entry is converted into a rough approximation
298	 * of a ``struct passwd'', with the strings placed inline.  This
299	 * object is then stored as the data for three separate keys.  The
300	 * first key * is the pw_name field prepended by the _PW_KEYBYNAME
301	 * character.  The second key is the pw_uid field prepended by the
302	 * _PW_KEYBYUID character.  The third key is the line number in the
303	 * original file prepended by the _PW_KEYBYNUM character.  (The special
304	 * characters are prepended to ensure that the keys do not collide.)
305	 */
306	ypcnt = 1;
307	data.data = (u_char *)buf;
308	sdata.data = (u_char *)sbuf;
309	key.data = (u_char *)tbuf;
310	for (cnt = 1; scan(fp, &pwd); ++cnt) {
311		if (!is_comment &&
312		    (pwd.pw_name[0] == '+' || pwd.pw_name[0] == '-'))
313			yp_enabled = 1;
314#define	COMPACT(e)	t = e; while ((*p++ = *t++));
315#ifdef PASSWD_IGNORE_COMMENTS
316           if(is_comment)
317                --cnt;
318#endif
319		if (!is_comment &&
320		    (!username || (strcmp(username, pwd.pw_name) == 0))) {
321			/* Create insecure data. */
322			p = buf;
323			COMPACT(pwd.pw_name);
324			COMPACT("*");
325			memmove(p, &pwd.pw_uid, sizeof(int));
326			p += sizeof(int);
327			memmove(p, &pwd.pw_gid, sizeof(int));
328			p += sizeof(int);
329			memmove(p, &pwd.pw_change, sizeof(time_t));
330			p += sizeof(time_t);
331			COMPACT(pwd.pw_class);
332			COMPACT(pwd.pw_gecos);
333			COMPACT(pwd.pw_dir);
334			COMPACT(pwd.pw_shell);
335			memmove(p, &pwd.pw_expire, sizeof(time_t));
336			p += sizeof(time_t);
337			memmove(p, &pwd.pw_fields, sizeof pwd.pw_fields);
338			p += sizeof pwd.pw_fields;
339			data.size = p - buf;
340
341			/* Create secure data. */
342			p = sbuf;
343			COMPACT(pwd.pw_name);
344			COMPACT(pwd.pw_passwd);
345			memmove(p, &pwd.pw_uid, sizeof(int));
346			p += sizeof(int);
347			memmove(p, &pwd.pw_gid, sizeof(int));
348			p += sizeof(int);
349			memmove(p, &pwd.pw_change, sizeof(time_t));
350			p += sizeof(time_t);
351			COMPACT(pwd.pw_class);
352			COMPACT(pwd.pw_gecos);
353			COMPACT(pwd.pw_dir);
354			COMPACT(pwd.pw_shell);
355			memmove(p, &pwd.pw_expire, sizeof(time_t));
356			p += sizeof(time_t);
357			memmove(p, &pwd.pw_fields, sizeof pwd.pw_fields);
358			p += sizeof pwd.pw_fields;
359			sdata.size = p - sbuf;
360
361			/* Store insecure by name. */
362			tbuf[0] = _PW_KEYBYNAME;
363			len = strlen(pwd.pw_name);
364			memmove(tbuf + 1, pwd.pw_name, len);
365			key.size = len + 1;
366			if ((dp->put)(dp, &key, &data, method) == -1)
367				error("put");
368
369			/* Store insecure by number. */
370			tbuf[0] = _PW_KEYBYNUM;
371			memmove(tbuf + 1, &cnt, sizeof(cnt));
372			key.size = sizeof(cnt) + 1;
373			if ((dp->put)(dp, &key, &data, method) == -1)
374				error("put");
375
376			/* Store insecure by uid. */
377			tbuf[0] = _PW_KEYBYUID;
378			memmove(tbuf + 1, &pwd.pw_uid, sizeof(pwd.pw_uid));
379			key.size = sizeof(pwd.pw_uid) + 1;
380			if ((dp->put)(dp, &key, &data, methoduid) == -1)
381				error("put");
382
383			/* Store secure by name. */
384			tbuf[0] = _PW_KEYBYNAME;
385			len = strlen(pwd.pw_name);
386			memmove(tbuf + 1, pwd.pw_name, len);
387			key.size = len + 1;
388			if ((sdp->put)(sdp, &key, &sdata, method) == -1)
389				error("put");
390
391			/* Store secure by number. */
392			tbuf[0] = _PW_KEYBYNUM;
393			memmove(tbuf + 1, &cnt, sizeof(cnt));
394			key.size = sizeof(cnt) + 1;
395			if ((sdp->put)(sdp, &key, &sdata, method) == -1)
396				error("put");
397
398			/* Store secure by uid. */
399			tbuf[0] = _PW_KEYBYUID;
400			memmove(tbuf + 1, &pwd.pw_uid, sizeof(pwd.pw_uid));
401			key.size = sizeof(pwd.pw_uid) + 1;
402			if ((sdp->put)(sdp, &key, &sdata, methoduid) == -1)
403				error("put");
404
405			/* Store insecure and secure special plus and special minus */
406			if (pwd.pw_name[0] == '+' || pwd.pw_name[0] == '-') {
407				tbuf[0] = _PW_KEYYPBYNUM;
408				memmove(tbuf + 1, &ypcnt, sizeof(cnt));
409				ypcnt++;
410				key.size = sizeof(cnt) + 1;
411				if ((dp->put)(dp, &key, &data, method) == -1)
412					error("put");
413				if ((sdp->put)(sdp, &key, &sdata, method) == -1)
414					error("put");
415			}
416		}
417		/* Create original format password file entry */
418		if (is_comment && makeold){	/* copy comments */
419			if (fprintf(oldfp, "%s\n", line) < 0)
420				error("write old");
421		} else if (makeold) {
422			char uidstr[20];
423			char gidstr[20];
424
425			snprintf(uidstr, sizeof(uidstr), "%d", pwd.pw_uid);
426			snprintf(gidstr, sizeof(gidstr), "%d", pwd.pw_gid);
427
428			if (fprintf(oldfp, "%s:*:%s:%s:%s:%s:%s\n",
429			    pwd.pw_name, pwd.pw_fields & _PWF_UID ? uidstr : "",
430			    pwd.pw_fields & _PWF_GID ? gidstr : "",
431			    pwd.pw_gecos, pwd.pw_dir, pwd.pw_shell) < 0)
432				error("write old");
433		}
434	}
435	/* If YP enabled, set flag. */
436	if (yp_enabled) {
437		buf[0] = yp_enabled + 2;
438		data.size = 1;
439		tbuf[0] = _PW_KEYYPENABLED;
440		key.size = 1;
441		if ((dp->put)(dp, &key, &data, method) == -1)
442			error("put");
443		if ((sdp->put)(sdp, &key, &data, method) == -1)
444			error("put");
445	}
446
447	if ((dp->close)(dp) == -1)
448		error("close");
449	if ((sdp->close)(sdp) == -1)
450		error("close");
451	if (makeold) {
452		(void)fflush(oldfp);
453		if (fclose(oldfp) == EOF)
454			error("close old");
455	}
456
457	/* Set master.passwd permissions, in case caller forgot. */
458	(void)fchmod(fileno(fp), S_IRUSR|S_IWUSR);
459
460	/* Install as the real password files. */
461	(void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _MP_DB);
462	(void)snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _MP_DB);
463	mv(buf, buf2);
464	(void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _SMP_DB);
465	(void)snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _SMP_DB);
466	mv(buf, buf2);
467	if (makeold) {
468		(void)snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _PASSWD);
469		(void)snprintf(buf, sizeof(buf), "%s.orig", pname);
470		mv(buf, buf2);
471	}
472	/*
473	 * Move the master password LAST -- chpass(1), passwd(1) and vipw(8)
474	 * all use flock(2) on it to block other incarnations of themselves.
475	 * The rename means that everything is unlocked, as the original file
476	 * can no longer be accessed.
477	 */
478	(void)snprintf(buf, sizeof(buf), "%s/%s", prefix, _MASTERPASSWD);
479	mv(pname, buf);
480
481	/*
482	 * Close locked password file after rename()
483	 */
484	if (fclose(fp) == EOF)
485		error("close fp");
486
487	exit(0);
488}
489
490int
491scan(fp, pw)
492	FILE *fp;
493	struct passwd *pw;
494{
495	static int lcnt;
496	char *p;
497
498	if (!fgets(line, sizeof(line), fp))
499		return (0);
500	++lcnt;
501	/*
502	 * ``... if I swallow anything evil, put your fingers down my
503	 * throat...''
504	 *	-- The Who
505	 */
506	if (!(p = strchr(line, '\n'))) {
507		warnx("line too long");
508		goto fmt;
509
510	}
511	*p = '\0';
512
513#ifdef PASSWD_IGNORE_COMMENTS
514	/*
515	 * Ignore comments: ^[ \t]*#
516	 */
517	for (p = line; *p != '\0'; p++)
518		if (*p != ' ' && *p != '\t')
519			break;
520	if (*p == '#' || *p == '\0') {
521		is_comment = 1;
522		return(1);
523	} else
524		is_comment = 0;
525#endif
526
527	if (!pw_scan(line, pw)) {
528		warnx("at line #%d", lcnt);
529fmt:		errno = EFTYPE;	/* XXX */
530		error(pname);
531	}
532
533	return (1);
534}
535
536void
537cp(from, to, mode)
538	char *from, *to;
539	mode_t mode;
540{
541	static char buf[MAXBSIZE];
542	int from_fd, rcount, to_fd, wcount;
543
544	if ((from_fd = open(from, O_RDONLY, 0)) < 0)
545		error(from);
546	if ((to_fd = open(to, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0)
547		error(to);
548	while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) {
549		wcount = write(to_fd, buf, rcount);
550		if (rcount != wcount || wcount == -1) {
551			int sverrno = errno;
552
553			(void)snprintf(buf, sizeof(buf), "%s to %s", from, to);
554			errno = sverrno;
555			error(buf);
556		}
557	}
558	if (rcount < 0) {
559		int sverrno = errno;
560
561		(void)snprintf(buf, sizeof(buf), "%s to %s", from, to);
562		errno = sverrno;
563		error(buf);
564	}
565}
566
567
568void
569mv(from, to)
570	char *from, *to;
571{
572	char buf[MAXPATHLEN];
573
574	if (rename(from, to)) {
575		int sverrno = errno;
576		(void)snprintf(buf, sizeof(buf), "%s to %s", from, to);
577		errno = sverrno;
578		error(buf);
579	}
580}
581
582void
583error(name)
584	char *name;
585{
586
587	warn("%s", name);
588	cleanup();
589	exit(1);
590}
591
592void
593cleanup()
594{
595	char buf[MAXPATHLEN];
596
597	switch(clean) {
598	case FILE_ORIG:
599		(void)snprintf(buf, sizeof(buf), "%s.orig", pname);
600		(void)unlink(buf);
601		/* FALLTHROUGH */
602	case FILE_SECURE:
603		(void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _SMP_DB);
604		(void)unlink(buf);
605		/* FALLTHROUGH */
606	case FILE_INSECURE:
607		(void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _MP_DB);
608		(void)unlink(buf);
609	}
610}
611
612static void
613usage()
614{
615
616	(void)fprintf(stderr,
617"usage: pwd_mkdb [-C] [-N] [-p] [-d <dest dir>] [-s <cachesize>] [-u <local username>] file\n");
618	exit(1);
619}
620