/*
 * mkpwdbm - make hashed data base for password files
 *
 * usage: mkpwdbm [-f] [-v]
 *
 * This will make a data base for the password file (and shadow if
 * it exists) allowing quick access by name and user id.
 *
 * [-f]  -- Force creation of the files.  By default new files will
 *          be created only if the old files existed.  Also enables
 *	    error messages.  None will be generated if executed with
 *	    no flags.
 *
 * [-v]  -- Verbose option.  Gives information on number entries found
 *          and enables error messages.  By default no terminal output
 *          will be done.
 *
 * [-p]  -- Rebuild only password file.
 *
 * [-s]  -- Rebuild only shadow file.
 *
 * This program is intended to be exec'd by any proram which alters
 * the byte count, or line position of the password files.
 *
 * Exit status:  0 no files found, or new files built without error,
 *		 1 error occured.
 *
 */

#ident	"$Header: mkpwdbm.c 1.7 89/02/14 $"

/*
 * $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <shadow.h>
#include <ndbm.h>

#ifdef DEBUG
#define	LOCKFILE	".pwdbm.lock"
#undef  PASSWD
#define PASSWD		"passwd"
#undef  SHADOW
#define SHADOW		"shadow"
#undef  PASSWD_DBM
#define PASSWD_DBM	"passwd_dbm"
#undef  SHADOW_DBM
#define SHADOW_DBM	"shadow_dbm"
#else
#define LOCKFILE	"/etc/.pwdbm.lock"
#endif

int force;		/* force generation of files */
int verbose;		/* verbose mode */
int p_only;		/* build only password file */
int s_only;		/* build only shadow file */
int debug;		/* debug info */
int msgs;		/* allow error messages */
int rval;		/* exit status */

char *prog;		/* program name */

DBM *db;		/* data base struct pointer */

char dbmf[80];		/* dbm file, base name */
char dbmf_pag[80];	/* dbm page file */
char dbmf_dir[80];	/* dbm data file */
char dbmt[80];		/* temp dbm file, base name */
char dbmt_pag[80];	/* temp dbm page file */
char dbmt_dir[80];	/* temp dbm data file */
char line[BUFSIZ+1];	/* for reading file entries */

extern int getopt();
extern int optind;
extern int opterr;
extern char *optarg;
extern long ftell();
extern errno;

main(argc, argv)
	int argc;
	char *argv[];
{
	int c, i;
	struct flock flk;
	int fd;

	opterr = 0;	/* supress warnings from getopt */
	prog = argv[0];

	while ((c = getopt(argc, argv, "fvpsd")) != EOF) {
		switch (c) {
		case 'f':
			force = 1;
			msgs = 1;
			break;
		case 'v':
			verbose = 1;
			msgs = 1;
			break;
		case 'p':
			p_only = 1;
			break;
		case 's':
			s_only = 1;
			break;
		case 'd':
#ifdef DEBUG
			debug = 1;
			msgs = 1;
			break;
#else
			printf("Not compiled with debug.\n");
			exit(1);
#endif
		default:
#ifdef DEBUG
			printf("usage: %s [-fvd]\n", prog);
#else
			printf("usage: %s [-fv]\n", prog);
#endif
			exit(0);
		}
	}

#ifndef DEBUG
	/* Insure run as root. */

	if (geteuid() != 0) {
		printf("%s: Must be run as root.\n", prog);
		exit(1);
	}
	
	if (force)
		if (getuid() != 0) {
			printf("%s: Must be root to force file creation\n",
			    prog);
			exit(1);
		}

	/* Disable signals. */

	for (i=1; i < NSIG; i++)
		(void) sigset(i, SIG_IGN);
	errno = 0;	/* Sigkill will set it. */

#endif 

#ifndef NOLOCK

	/* Try for lock file, give up if not found. */

	if ((fd = creat(LOCKFILE, 0644)) == -1) {
	    if (msgs) {
		printf("Cannot create file %s\n", LOCKFILE);
		printf("Assume another rebuild is in progress.\n");
	    }
	    exit(1);
	}

	flk.l_type = F_WRLCK;
	flk.l_whence = 0;
	flk.l_start = 0;
	flk.l_len = 0;
	flk.l_pid = 0;

	if (fcntl(fd, F_SETLK, &flk) == -1) {
	    if (msgs) {
		printf("Cannot acquire lock on file %s\n", LOCKFILE);
		printf("Assume another rebuild is in progress.\n");
	    }
	    exit(1);
	}

#endif

	/*
	 * If we didn't have "-p" or "-s" flags both files are build.
	 * If either of these flags then typically only one of the hash
	 * files will be constructed.  Although not expected, both flags
	 * could be set, so still check each individually.
	 */

	if (p_only || s_only) {
		if (p_only)
			rval = do_passwd();
		if ((rval == 0) && (s_only))
			rval = do_shadow();
	} else {
		rval = do_passwd();
		if (rval == 0)
			rval = do_shadow();
	}

#ifndef NOLOCK
	/* Remove lock and delete lock file. */

	flk.l_type = F_UNLCK;
	(void) fcntl(fd, F_SETLK, &flk);
	(void) close(fd);
	(void) unlink(LOCKFILE);
#endif
	exit(rval);
}

/*
 * do_passwd
 *
 * Create data base for password file.
 */

int
do_passwd()
{
	register int i;
	register char *p;
	int have_dir, have_pag;
	struct stat sb_dir, sb_pag, sb_pwd;
	long val;
	datum d, k;
	FILE *fp;
	int count, dup_n, dup_uid;
	char buf[BUFSIZ], *q;

	count = dup_n = dup_uid = 0;

	/* Create names for data base files. */

	strcpy(dbmf, PASSWD_DBM);
	strcpy(dbmf_dir, PASSWD_DBM);
	strcpy(dbmf_pag, PASSWD_DBM);
	strcat(dbmf_dir, ".dir");
	strcat(dbmf_pag, ".pag");

	if (debug) {
		printf("dbmf_dir= '%s'\n", dbmf_dir);
		printf("dbmf_pag= '%s'\n", dbmf_pag);
	}

	/*
	 * Check status on files.  If not "force" mode, then data base files
	 * must exist.  Password file must always exist.
	 */

	have_dir = stat(dbmf_dir, &sb_dir);
	if ( (! force) && (have_dir != 0) ) {
	    if (verbose)
		printf("No data base file '%s', no action for passwd file\n",
		    dbmf_dir);
	    return(0);
	}

	have_pag = stat(dbmf_pag, &sb_pag);
	if ( (! force) && (have_pag != 0) ) {
	    if (verbose)
		printf("No data base file '%s', no action for passwd file\n",
		    dbmf_pag);
	    return(0);
	}

	if (stat(PASSWD, &sb_pwd) != 0) {
	    if (msgs)
		printf("No password file '%s', no action for passwd file\n",
			    PASSWD);
	    return(0);
	}

	/* Mode of the password data base should be 444 or less. */

	(void) umask(~(sb_pwd.st_mode & 0444));

	/* Open temporary dbm file. */

	strcpy(dbmt, PASSWD);
	strcat(dbmt, "_dbm_tmp");
	strcpy(dbmt_dir, dbmt);
	strcpy(dbmt_pag, dbmt);
	strcat(dbmt_dir, ".dir");
	strcat(dbmt_pag, ".pag");

	if (debug) {
		printf("dbmt_dir= '%s'\n", dbmt_dir);
		printf("dbmt_pag= '%s'\n", dbmt_pag);
	}

	(void) unlink(dbmt_dir);	/* just in case left around */
	(void) unlink(dbmt_pag);

	db = dbm_open(dbmt, O_RDWR|O_CREAT, 0444);
	if (db == (DBM *) 0) {
	    if (msgs)
		printf("Create of temp data base '%s' failed.\n", dbmt);
	    return(1);
	}

	/* Insert stat struct for password file. */

	k.dptr = "pw_stat_struct";
	k.dsize = strlen("pw_stat_struct");	/* must be > 13 */
	d.dptr = (char *) &sb_pwd;
	d.dsize = sizeof(sb_pwd);
	if (dbm_store(db, k, d, DBM_INSERT)) {
	    (void) dbm_close(db);
	    if (msgs)
		printf("Data base store failed\n");
	    return(1);
	}

	/*
	 * Now enter all names and user ids.  Names must be unique.
	 * User ids may repeat.  First one allways wins, since that is
	 * what library look up would find.
	 */

	if ((fp = fopen(PASSWD, "r")) == NULL) {
	    (void) dbm_close(db);
	    if (msgs)
		printf("Open of password file failed.\n");
	    return(1);
	}

	for (;;) {
	    val = ftell(fp);
	    if (fgets(line, BUFSIZ, fp) == NULL)
		break;
	    p = line;
	    i = 0;
	    while (*p != ':') {
		p++;
		i++;
	    }

	    *p = 0;			/* terminate user name */
	    strcpy(buf, "n_");
	    strcat(buf, line);
	    k.dptr = buf;
	    k.dsize = i+2;
	    d.dptr = (char *) &val;
	    d.dsize = sizeof(long);
	    if (dbm_store(db, k, d, DBM_INSERT)) {
		if (msgs) {
		    printf("Warning: duplicate name '%s' found in '%s' at offset %ld.\n",
			line, PASSWD, val);
		}
		dup_n++;
	    }
	    p++;		/* move past : at end of name */
	    while (*p != ':')
		p++;		/* skip over passwd */
	    p++;		/* move to first char of uid */
	    q = p;
	    i = 0;
	    while (*p != ':') {
		i++;
		p++;
	    }
	    *p = '\0';		/* terminate uid */
	    strcpy(buf, "u_");
	    strcat(buf, q);
	    k.dptr = buf;
	    k.dsize = i+2;
	    if (dbm_store(db, k, d, DBM_INSERT))
		dup_uid++;
	    count++;
	}

	(void) fclose(fp);
	(void) dbm_close(db);

	/* Remove old data base files. */

	if (unlink(dbmf_dir) && access(dbmf_dir, 0) == 0) {
	    (void) unlink(dbmt_dir);
	    (void) unlink(dbmt_pag);
	    if (msgs)
		printf("Cannot unlink data base file '%s'\n", dbmf_dir);
	    return(1);
	}

	if (unlink(dbmf_pag) && access(dbmf_pag, 0) == 0) {
	    (void) unlink(dbmt_dir);
	    (void) unlink(dbmt_pag);
	    if (msgs)
		printf("Cannot unlink data base file '%s'\n", dbmf_pag);
	    return(1);
	}

	/* Link data base to temporary data base. */

	if (link(dbmt_dir, dbmf_dir)) {
	    (void) unlink(dbmf_pag);
	    (void) unlink(dbmt_pag);
	    (void) unlink(dbmt_dir);
	    if (msgs)
		printf("Cannot link data base file '%s' to '%s'\n",
		    dbmt_dir, dbmf_dir);
	    return(1);
	}

	if (link(dbmt_pag, dbmf_pag)) {
	    (void) unlink(dbmf_dir);
	    (void) unlink(dbmt_dir);
	    (void) unlink(dbmt_pag);
	    if (msgs)
		printf("Cannot link data base file '%s' to '%s'\n",
		    dbmt_pag, dbmf_pag);
	    return(1);
	}

	/* Unlink temporary data base files. */

	(void) unlink(dbmt_dir);
	(void) unlink(dbmt_pag);

	/* Give information about actions performed. */

	if (verbose) {
	    printf("Found %d entries in file '%s'\n", count, PASSWD);
	    if (dup_n != 0)
		printf("Duplicate name count: %d\n", dup_n);
	    if (dup_uid != 0)
		printf("Duplicate uid count: %d\n", dup_uid);
	}

	return(0);
}

/*
 * do_shadow()
 *
 * Create data base for shadow file.
 *
 */

do_shadow()
{
	register int i;
	register char *p;
	int have_dir, have_pag;
	struct stat sb_dir, sb_pag, sb_pwd;
	long val;
	datum d, k;
	FILE *fp;
	int count, dup_n;
	char buf[BUFSIZ];

	count = dup_n = 0;

	/* Create names for data base files. */

	strcpy(dbmf, SHADOW_DBM);
	strcpy(dbmf_dir, SHADOW_DBM);
	strcpy(dbmf_pag, SHADOW_DBM);
	strcat(dbmf_dir, ".dir");
	strcat(dbmf_pag, ".pag");

	if (debug) {
		printf("dbmf_dir= '%s'\n", dbmf_dir);
		printf("dbmf_pag= '%s'\n", dbmf_pag);
	}

	/*
	 * Check status on files.  If not "force" mode, then data base files
	 * must exist.  Password file must always exist.
	 */

	have_dir = stat(dbmf_dir, &sb_dir);
	if ( (! force) && (have_dir != 0) ) {
	    if (verbose)
		printf("No data base file '%s', no action for shadow file\n",
		    dbmf_dir);
	    return(0);
	}

	have_pag = stat(dbmf_pag, &sb_pag);
	if ( (! force) && (have_pag != 0) ) {
	    if (verbose)
		printf("No data base file '%s', no action for shadow file\n",
		    dbmf_pag);
	    return(0);
	}

	if (stat(SHADOW, &sb_pwd) != 0) {
	    if (msgs)
		printf("No shadow file '%s', no action for shadow file\n",
			    SHADOW);
	    return(0);
	}

	/* Mode of the shadow data base should be 400 or 000. */

	(void) umask(~(sb_pwd.st_mode & 0400));

	/* Open temporary dbm file. */

	strcpy(dbmt, SHADOW);
	strcat(dbmt, "_dbm_tmp");
	strcpy(dbmt_dir, dbmt);
	strcpy(dbmt_pag, dbmt);
	strcat(dbmt_dir, ".dir");
	strcat(dbmt_pag, ".pag");

	if (debug) {
		printf("dbmt_dir= '%s'\n", dbmt_dir);
		printf("dbmt_pag= '%s'\n", dbmt_pag);
	}

	(void) unlink(dbmt_dir);	/* just in case left around */
	(void) unlink(dbmt_pag);

	db = dbm_open(dbmt, O_RDWR|O_CREAT, 0400);
	if (db == (DBM *) 0) {
	    if (msgs)
		printf("Create of temp data base '%s' failed.\n", dbmt);
	    return(1);
	}

	/* Insert stat struct for shadow file. */

	k.dptr = "pw_stat_struct";
	k.dsize = strlen("pw_stat_struct");	/* must be > 13 */
	d.dptr = (char *) &sb_pwd;
	d.dsize = sizeof(sb_pwd);
	if (dbm_store(db, k, d, DBM_INSERT)) {
	    (void) dbm_close(db);
	    if (msgs)
		printf("Data base store failed\n");
	    return(1);
	}

	/* Now enter all names.  Names must be unique. */

	if ((fp = fopen(SHADOW, "r")) == NULL) {
	    (void) dbm_close(db);
	    if (msgs)
		printf("Open of shadow file failed.\n");
	    return(1);
	}

	for (;;) {
	    val = ftell(fp);
	    if (fgets(line, BUFSIZ, fp) == NULL)
		break;
	    p = line;
	    i = 0;
	    while (*p != ':') {
		p++;
		i++;
	    }

	    *p = 0;			/* terminate user name */
	    strcpy(buf, "n_");
	    strcat(buf, line);
	    k.dptr = buf;
	    k.dsize = i+2;
	    d.dptr = (char *) &val;
	    d.dsize = sizeof(long);
	    if (dbm_store(db, k, d, DBM_INSERT)) {
		if (msgs) {
		    printf("Warning: duplicate name '%s' found in '%s' at offset %ld.\n",
			line, SHADOW, val);
		}
		dup_n++;
	    }
	    count++;
	}

	(void) fclose(fp);
	(void) dbm_close(db);

	/* Remove old data base files. */

	if (unlink(dbmf_dir) && access(dbmf_dir, 0) == 0) {
	    (void) unlink(dbmt_dir);
	    (void) unlink(dbmt_pag);
	    if (msgs)
		printf("Cannot unlink data base file '%s'\n", dbmf_dir);
	    return(1);
	}

	if (unlink(dbmf_pag) && access(dbmf_pag, 0) == 0) {
	    (void) unlink(dbmt_dir);
	    (void) unlink(dbmt_pag);
	    if (msgs)
		printf("Cannot unlink data base file '%s'\n", dbmf_pag);
	    return(1);
	}

	/* Link data base to temporary data base. */

	if (link(dbmt_dir, dbmf_dir)) {
	    (void) unlink(dbmf_pag);
	    (void) unlink(dbmt_pag);
	    (void) unlink(dbmt_dir);
	    if (msgs)
		printf("Cannot link data base file '%s' to '%s'\n",
		    dbmt_dir, dbmf_dir);
	    return(1);
	}

	if (link(dbmt_pag, dbmf_pag)) {
	    (void) unlink(dbmf_dir);
	    (void) unlink(dbmt_dir);
	    (void) unlink(dbmt_pag);
	    if (msgs)
		printf("Cannot link data base file '%s' to '%s'\n",
		    dbmt_pag, dbmf_pag);
	    return(1);
	}

	/* Unlink temporary data base files. */

	(void) unlink(dbmt_dir);
	(void) unlink(dbmt_pag);

	/* Give information about actions performed. */

	if (verbose) {
	    printf("Found %d entries in file '%s'\n", count, SHADOW);
	    if (dup_n != 0)
		printf("Duplicate name count: %d\n", dup_n);
	}

	return(0);
}
