/*
 * pwd_unmkdb: A program to generate a master.passwd from a spwd.db
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <db.h>
#include <err.h>
#include <fcntl.h>
#include <limits.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>


int
main(int argc, char *argv[])
{
	char *dbfile=NULL;
	DB *srcdb;
	char *ofname=NULL;
	FILE *outfile;
	struct stat stret;
	struct passwd pstore;
	int goch;
	int version;
	int32_t pw_change, pw_expire;

	while( (goch = getopt(argc, argv, "i:o:")) != -1 )
		switch((char)goch)
		{
			case 'i':
				/* Infile */
				dbfile = optarg;
				break;
			case 'o':
				/* Outfile */
				ofname = optarg;
				break;
			default:
				fprintf(stderr, "Bad argument '%c'\n", goch);
				exit(1);
				/* NOTREACHED */
		}

	argc-=optind;
	argv+=optind;


	/* Our defaults */
	if(dbfile==NULL)
	{
		dbfile = strdup("spwd.db");
		if(dbfile==NULL)
			err(1, "strdup()");
	}

	if(ofname==NULL)
	{
		ofname = strdup("master.passwd");
		if(ofname==NULL)
			err(1, "strdup()");
	}


	/* Protection == good! */
	umask(077);


	/* Verify our range to target */
	if(stat(dbfile, &stret)<-1)
		err(1, "stat()'ing %s", dbfile);

	if((stret.st_mode&S_IFREG)==0)
	{
		fprintf(stderr, "%s isn't a regular file", dbfile);
		exit(1);
	}


	/* Open, sesame! */
	fprintf(stderr, "dbopen()'ing %s...  ", dbfile);
	srcdb = dbopen(dbfile, O_RDONLY, 0, DB_HASH, NULL);
	if(srcdb==NULL)
		errx(1, "dbopen()'ing %s", dbfile);
	fprintf(stderr, "Done\n");


	/* Look up version */
	{
		DBT lkey, ldata;
		int rval;

		lkey.data = _PWD_VERSION_KEY;
		lkey.size = strlen(_PWD_VERSION_KEY);
		rval = (srcdb->get)(srcdb, &lkey, &ldata, 0);
		if(rval == -1)
			errx(1, "srcdb->get'ing");
		else if(rval == 0)
			version = *(unsigned char *)ldata.data;
		else
			version = 3;
		if(version != 3 && version != 4)
			warnx("Unexpected passwd version %d; may not work right", version);
	}


	/* Open, other-sesame! */
	if(strcmp(ofname, "-") == 0)
	{
		int sodup = dup(STDOUT_FILENO);
		if(sodup == -1)
			errx(1, "dup(stdout)");
		outfile = fdopen(sodup, "w");
		if(outfile==NULL)
			errx(1, "fdopen()'ing stdout");
	}
	else
	{
		fprintf(stderr, "fopen()'ing %s...  ", ofname);
		outfile = fopen(ofname, "w");
		if(outfile==NULL)
			errx(1, "fopen()'ing %s", ofname);
		fprintf(stderr, "Done\n");
	}


	/* So do it already */
	fprintf(stderr, "Looping through the records in %s (v%d)...  ",
			dbfile, version);

	DBT ldata;
	char *buf=NULL;
	size_t bufsz=0;
	uint32_t keycount=0;
	while(1)
	{
		DBT lkey;
		char keystr[(sizeof(keycount)+1)];
		int rval;
		uint32_t curkeycount;

		/* Create our lookup key */
		keycount++;
		keystr[0] = _PW_KEYBYNUM;
		if(version == 3)
			curkeycount = keycount;
		else if(version == 4)
			curkeycount = htonl(keycount);
		bcopy((char *)&curkeycount, keystr+1, sizeof(curkeycount));
		keystr[0] = _PW_VERSIONED(keystr[0], version);
		lkey.data = keystr;
		lkey.size = sizeof(curkeycount)+1;

		/* Look it up */
		rval = (srcdb->get)(srcdb, &lkey, &ldata, 0);
		if(rval==-1)
			errx(1, "srcdb->get'ing");
		else if(rval==1)
			break;
		char *dp = ldata.data;

		/* Make sure our buffer is big enough */
		if(ldata.size > bufsz)
		{
			bufsz = ldata.size + 1024;
			buf = reallocf(buf, bufsz);
			if(buf==NULL)
				errx(1, "reallocf()'ing buf");
		}
		bzero(buf, bufsz);

		/*
		 * Bring in data.
		 * Shamelessly cribbed from FreeBSD getpwent(); see LICENCE.
		 */
		char *lp = buf;
#define EXPAND(e)	e=lp; while( (*lp++ = *dp++) );
#define SCALAR(v)	memmove(&(v), dp, sizeof(v)); dp += sizeof(v); \
		if(version == 4) (v) = ntohl((v));

		/* Quiet warnings about the empty while() in EXPAND */
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wempty-body"
		EXPAND(pstore.pw_name);
		EXPAND(pstore.pw_passwd);
		SCALAR(pstore.pw_uid);
		SCALAR(pstore.pw_gid);
		SCALAR(pw_change);
		EXPAND(pstore.pw_class);
		EXPAND(pstore.pw_gecos);
		EXPAND(pstore.pw_dir);
		EXPAND(pstore.pw_shell);
		SCALAR(pw_expire);
#pragma clang diagnostic pop
		/* This isn't used anywhere */
		bcopy(dp, (char *)&pstore.pw_fields, sizeof(pstore.pw_fields));
		dp += sizeof(pstore.pw_fields);

		/* Spit out */
		fprintf(outfile, "%s:%s:%lu:%lu:%s:%lu:%lu:%s:%s:%s\n",
				pstore.pw_name, pstore.pw_passwd, (long)pstore.pw_uid,
				(long)pstore.pw_gid, pstore.pw_class,
				(long)pw_change, (long)pw_expire,
				pstore.pw_gecos, pstore.pw_dir, pstore.pw_shell);

		/* Next! */
	}
	fprintf(stderr, "Done  (%d records)\n", keycount);

	fflush(outfile);
	fclose(outfile);
	(srcdb->close)(srcdb);

	exit(0);
}
