/* Copyright 1993 - Matti Aarnio, Turku University, Turku, Finland
   This will be free software, but only when it is finished.

   The way the Zmailer uses DBM entries is by using strings with
   their terminating NULL as keys, and as data..  Thus the length
   is strlen(string)+1, not strlen(string) !
 */

#include "hostenv.h"
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <fcntl.h>
#ifdef HAVE_NDBM_H
#define datum Ndatum
#include <ndbm.h>
#undef datum
#endif
#ifdef HAVE_GDBM_H
#define datum Gdatum
#include <gdbm.h>
#undef datum
#endif
#ifdef HAVE_DB_H
#include <db.h>
#endif

#define PROG "makendbm"

int aliasinput = 0;
int policyinput = 0;

#include <errno.h>
extern int errno;		/* Not all systems declare it in <errno.h> ... */

/* extern char *strchr(); */

extern void create_dbase __((FILE *, void *, int));

extern char *skip821address __((char *));

extern void usage __((const char *, const char *, int));
void usage(prog, errs, err)
const char *prog, *errs;
int err;
{
    fprintf(stderr, "Usage: %s [-a|-p] dbtype database.name [infilename|-]\n", prog);
    fprintf(stderr, "  where supported dbtypes are:");
#ifdef HAVE_NDBM_H
    fprintf(stderr, " ndbm");
#endif
#ifdef HAVE_GDBM_H
    fprintf(stderr, " gdbm");
#endif
#ifdef HAVE_DB_H
    fprintf(stderr, " btree");
#endif
    fprintf(stderr, "\n");
    fprintf(stderr, "  If no infilename is defined, database.name is assumed.\n");
#ifdef HAVE_NDBM_H
    fprintf(stderr, "  (NDBM appends  .pag, and .dir  into actual db file names..)\n");
#endif
#ifdef HAVE_GDBM_H
    fprintf(stderr, "  (GDBM appends .gdbm, to the actual db file name..)\n");
#endif
#ifdef HAVE_DB_H
    fprintf(stderr, "  (BTREE appends .db, to the actual db file name..)\n");
#endif
    fprintf(stderr, "\n");
    fprintf(stderr, "  The '-a' option is for parsing input that comes in\n");
    fprintf(stderr, "  'aliases' format:  'key: data,in,single,long,line'\n");
    fprintf(stderr, " Error now: %s", errs);
    fprintf(stderr, ", errno=%d (%s)", err, strerror(err));
    fprintf(stderr, "\n");
    exit(1);
}


char inputline[64 * 1024];	/* 64 kB for SINGLE line is rather much :-) */

void create_dbase(infile, dbf, typ)
FILE *infile;
void *dbf;
int typ;
{
#ifdef HAVE_NDBM_H
    DBM *ndbmfile = dbf;
#endif
#ifdef HAVE_GDBM_H
    GDBM_FILE gdbmfile = dbf;
#endif
#ifdef HAVE_DB_H
    DB *dbfile = dbf;
#endif
    int rc, tlen, slen;
    char *s, *t, *str1, *str2;

    char policykeybuf[256];	/* Plenty enough ? */
    char policydata[sizeof(inputline) + 256];
    int linenum = 0;


    while (!feof(infile) && !ferror(infile)) {
	*inputline = 0;
	++linenum;
	fgets(inputline, sizeof(inputline) - 1, infile);
	if (*inputline == 0)
	    break;		/* Last! */
	s = (char *) strchr(inputline, '\n');
	if (s)
	    *s = 0;		/* Zap \n from the end -- if it exists.. */
	if (*inputline == '#')
	    continue;		/* Comment! */

	/* Scan first white-space separated token, point its start with t! */
	t = inputline;
	while (*t == '\t' || *t == ' ')
	    ++t;

	if (*t == 0)
	    continue;		/* Blank line! */
	if (*t == '#')
	    continue;		/* Comment.. */

	if (policyinput) {	/* Policy-line */
	    int policydatalen = 0;

	    s = t;
	    /* scan over first word */
	    while (*s && *s != '\t' && *s != ' ')
		++s;
	    /* Stopped without line-end NIL ?  Trunc, and advance! */
	    if (*s)
		*s++ = 0;

	    strlower(t); /* Lowercasify the key */

	    rc = parsepolicykey((void *) policykeybuf, t);
	    /* XX: rc != 0  ==> error */
	    if (rc != 0) {
		printf("Error: line %d: bad policykey, rc = %d\n",
		       linenum, rc);
		continue;
	    }
	    /* Skip LWSP */
	    /*      while (*s == ' ' || *s == '\t') ++s;
	     */


	    /* Collect attribute pairs */
	    str1 = strtok(s, " \t");
	    if (str1 == NULL) {
		printf("Error: No attribute pair on line %d.\n", linenum);
	    } else {
		while (1) {
		    str2 = strtok(NULL, " \t");
		    if (str2 == NULL) {
			printf("Error: Invalid attribute pair on line %d.\n",
			       linenum);
			break;
		    }
		    rc = parseattributepair((void *) &policydata[policydatalen],
					    str1, str2);
		    policydatalen += policydata[policydatalen] & 0xFF;

		    str1 = strtok(NULL, " \t");
		    if (str1 == NULL)
			break;
		}
	    }

	    t = (void *) &policykeybuf[0];
	    tlen = (policykeybuf[0] & 0xFF);

	    s = (void *) &policydata[0];
	    slen = policydatalen;


	} else {

	    if (aliasinput) {
		s = skip821address(t);
		/* We are now at the white-space -- possibly the last
		   char of the line prefix is a double-colon (:) */
		if (s > t) {
		    if (s[-1] == ':')
			s[-1] = 0;
		    else if (s[0] != 0)
			*s++ = 0;	/* Zero-terminate the key, scan for input */
		}
	    } else {
		s = t;
		/* Scan over the non-LWSP text */
		while (*s && *s != '\t' && *s != ' ')
		    ++s;
		/* Stopped without line-end NIL ?  Trunc, and advance! */
		if (*s)
		    *s++ = 0;
	    }
	    /* Scan forward, if we have some LWSP here, and then more data ? */
	    while (*s && (*s == '\t' || *s == ' '))
		++s;

	    strlower(t); /* Lowercasify the key */

	    tlen = strlen(t) + 1;
	    slen = strlen(s) + 1;
	}

	rc = -2;
#ifdef HAVE_NDBM_H
	if (typ == 1) {
	    Ndatum Ndat, Nkey;
	    Nkey.dptr = t;
	    Nkey.dsize = tlen;
	    Ndat.dptr = s;
	    Ndat.dsize = slen;
	    rc = dbm_store(ndbmfile, Nkey, Ndat, DBM_INSERT);
	}
#endif
#ifdef HAVE_GDBM_H
	if (typ == 2) {
	    Gdatum Gdat, Gkey;
	    Gkey.dptr = t;
	    Gkey.dsize = tlen;
	    Gdat.dptr = s;
	    Gdat.dsize = slen;
	    rc = gdbm_store(gdbmfile, Gkey, Gdat, GDBM_INSERT);
	}
#endif
#ifdef HAVE_DB_H
	if (typ == 3) {
	    DBT Bkey, Bdat;
	    Bkey.data = t;
	    Bkey.size = tlen;
	    Bdat.data = s;
	    Bdat.size = slen;
	    rc = (dbfile->put) (dbfile, &Bkey, &Bdat, R_NOOVERWRITE);
	}
#endif
	if (rc > 0) {
	    fprintf(stderr, "Error: Duplicate key at line %d.\n", linenum);
	} else if (rc < 0) {
	    break;		/* Some error -- any, just quit */
	}
    }
}


int main(argc, argv)
int argc;
char *argv[];
{
    char *dbasename = NULL;
    FILE *infile = NULL;
    int c;
    int typ = 0;
#ifdef HAVE_NDBM_H
    DBM *ndbmfile = NULL;
#endif
#ifdef HAVE_GDBM_H
    GDBM_FILE gdbmfile = NULL;
#endif
#ifdef HAVE_DB_H
    DB *dbfile = NULL;
#endif
    char *dbtype = NULL;
    void *dbf = NULL;
    char *argv0 = argv[0];

    aliasinput = 0;
    policyinput = 0;

    while ((c = getopt(argc, argv, "ap")) != EOF) {
	switch (c) {
	case 'a':
	    aliasinput = 1;
	    break;
	case 'p':
	    policyinput = 1;
	    break;
	default:
	    break;
	}
    }

    /* Usage: */
    /*  makendbm [-ap] dbtype database.name [infilename|-] */
    /* argv[] 0    1      2           3          4           */

    printf("optind = %d, argc = %d\n", optind, argc);

    if ((argc - optind) < 2)
	usage(argv0, "too few arguments", 0);
    if ((argc - optind) > 3)
	usage(argv0, "too many arguments", 0);
    dbasename = argv[optind + 1];


    if ((argc - optind) == 3) {
	if (strcmp(argv[optind + 2], "-") == 0)
	    infile = stdin;
	else
	    infile = (FILE *) fopen(argv[optind + 2], "r");
    } else
	infile = stdin;
    dbtype = argv[optind];

    if (infile == NULL)
	usage(argv0, "bad infile", errno);

    typ = 0;
#ifdef HAVE_NDBM_H
    if (cistrcmp(dbtype, "ndbm") == 0)
	typ = 1;
    else
#endif
#ifdef HAVE_GDBM_H
    if (cistrcmp(dbtype, "gdbm") == 0)
	typ = 2;
    else
#endif
#ifdef HAVE_DB_H
    if (cistrcmp(dbtype, "btree") == 0)
	typ = 3;
#endif
    if (typ == 0)
	usage(argv0, "unknown dbtype", 0);

#ifdef HAVE_NDBM_H
    if (typ == 1) {
	ndbmfile = dbm_open(dbasename, O_RDWR | O_CREAT | O_TRUNC, 0644);
	dbf = ndbmfile;
    }
#endif
#ifdef HAVE_GDBM_H
    if (typ == 2) {
	/* Play loose .. don't do syncs while writing */
	dbasename = strcpy(malloc(strlen(dbasename) + 8), dbasename);
	strcat(dbasename, ".gdbm");	/* ALWAYS append this */
	gdbmfile = gdbm_open(dbasename, 0, GDBM_NEWDB | GDBM_FAST, 0644, NULL);
	dbf = gdbmfile;
    }
#endif
#ifdef HAVE_DB_H
    if (typ == 3) {
	dbasename = strcpy(malloc(strlen(dbasename) + 8), dbasename);
	strcat(dbasename, ".db");	/* ALWAYS append this */
	dbfile = dbopen(dbasename, O_RDWR | O_CREAT | O_TRUNC, 0644,
			DB_BTREE, NULL);
	dbf = dbfile;
    }
#endif
    if (dbf == NULL)
	usage(argv0, "Can't open dbase file", errno);

    create_dbase(infile, dbf, typ);

#ifdef HAVE_NDBM_H
    if (typ == 1)
	dbm_close(ndbmfile);
#endif
#ifdef HAVE_GDBM_H
    if (typ == 2)
	gdbm_close(gdbmfile);
#endif
#ifdef HAVE_DB_H
    if (typ == 3) {
	(dbfile->sync) (dbfile, 0);
	(dbfile->close) (dbfile);
    }
#endif

    return 0;
}
