/*
**  DBM -- General dbm management tool.
**  Copyright (c) 1987 Lennart Lovstrand
**  CIS Dept, Univ of Linkoping, Sweden
**
**  Use it, abuse it, but don't sell it.
*/

#include <ndbm.h>
#include <signal.h>
#include "sendmail.h"

#define DB_DIREXT	".dir"
#define DB_PAGEXT	".pag"

#ifndef lint
static char	SccsId[] = "@(#)dbm.c 2.2 (lel@ida.liu.se) 10/24/88";
static char	Rcsid[] = "@(#)$Id: dbm.c,v 1.12 1991/07/01 17:28:32 paul Exp $";
#endif /* !lint */

#define	SAMECASE	0
#define LOWERCASE	1
#define UPPERCASE	2

#define COMMENTCHAR	'#'
#define SENTENIEL	"@@@"

#define streq(s, t)	(strcmp(s, t) == 0)
#define MAKEDATUM(d, s)	{(d).dptr = s; (d).dsize = strlen(s) + 1;}
#define CHECKDATUM(d)	if (Nullwarn && ((d).dsize == 0 || \
					 (d).dptr[(d).dsize - 1] != '\0')) \
		fprintf(stderr, "[Warning: Entry not NULL terminated]\n");

#ifdef __STDC__
void opendbm(int, int);
FILE * openfile(const char *, const char *);
void closefile(FILE *);
void parsefile(FILE *, FILE *);
void loadfile(FILE *);
char * scantoken(const char *);
void casify(char *, int);
#else /* !__STDC__ */
# define	const
void opendbm();
FILE * openfile();
void closefile();
void parsefile();
void loadfile();
char * scantoken();
void casify();
#endif /* __STDC__ */
void closedbm();
void do_clear();
void do_delete();
void do_dump();
void do_fetch();
void do_store();
void do_parse();
void do_make();
void do_load();
char * index();

struct comtab {
    char *c_name;
    void (*c_func)();
    int LockType;
} CommandTable[] = {
    {"clear",	do_clear},
    {"delete",	do_delete},
    {"dump",	do_dump},
    {"fetch",	do_fetch},
    {"load",	do_load},
    {"make",	do_make},
    {"parse",	do_parse},
    {"store",	do_store}
};
    
/* global arguments */
int	Argc;
char	**Argv;

/* options */
int	Appending = FALSE;
int	Casing = SAMECASE;
bool	Debug = FALSE;
char	*Outfile = NULL;
int	Mode = 0644;
char	*Progname;
char	*Senteniel = NULL;
int	Storeflag = DBM_INSERT;
bool	Storewarn = TRUE;
bool	Nullwarn = FALSE;
bool	Valueonly = FALSE;

/* Dbm globals */
DBM	*Dbm;
char	*Dbmfile = NULL;
int	Dbmaccess;
datum	key, val;

main(argc, argv)
     int argc;
     char **argv;
{
    extern int optind;
    extern char *optarg;
    struct comtab *cmd;
    int c;

    Argc = argc;
    Argv = argv;

    Progname = Argv[0];

    while ((c = getopt(Argc, Argv, "ADILNRSUd:m:o:s:v")) != EOF)
	switch (c) {
	  case 'A':
	    Appending = TRUE;
	    break;
	  case 'D':
	    Debug = TRUE;
	    break;
	  case 'I':
	    Storeflag = DBM_INSERT;
	    Storewarn = FALSE;
	    break;
	  case 'L':
	    Casing = LOWERCASE;
	    break;
	  case 'N':
	    Nullwarn = TRUE;
	    break;
	  case 'R':
	    Storeflag = DBM_REPLACE;
	    break;
	  case 'S':
	    if (Senteniel == NULL)
		Senteniel = SENTENIEL;
	    break;
	  case 'U':
	    Casing = UPPERCASE;
	    break;
	  case 'd':
	    Dbmfile = optarg;
	    break;
	  case 'm':
	    if (optarg[0] == '0')
		(void) sscanf(optarg, "%o", &Mode);
	    else {
		(void) sscanf(optarg, "%d", &Mode);
		if (Mode == 0) {
		    (void) fprintf(stderr, "%s: non-numeric mode: %s\n",
				   Progname, optarg);
		    exit(1);
		}
	    }
	    break;
	  case 'o':
	    Outfile = optarg;
	    break;
	  case 's':
	    Senteniel = optarg;
	    break;
	  case 'v':
	    Valueonly = TRUE;
	    break;
	  default:
	    (void) fprintf(stderr,
			   "usage: %s [-ADILNRSU] [-d dbm_file] [-m mode] %s", 
			   Progname, "[-o output_file] [-v] command [args]\n");
	    exit(1);
	}

    Argc -= optind;
    Argv += optind;

    if (Argc > 0) {
	for (cmd = CommandTable; cmd < &CommandTable[sizeof(CommandTable) /
						     sizeof(*CommandTable)];
	     cmd++)
	    if (streq(*Argv, cmd->c_name)) {
		(*cmd->c_func)();
		exit(0);
	    }
	(void) fprintf(stderr, "%s: unknown dbm command %s", Progname, *Argv);
    } else
	(void) fprintf(stderr, "%s: missing dbm command", Progname);
    (void) fprintf(stderr, ", use one of the following:\n");
    for (cmd = CommandTable; cmd < &CommandTable[sizeof(CommandTable) /
						 sizeof(*CommandTable)]; cmd++)
	(void) fprintf(stderr, "%s%s", cmd == CommandTable ? "" : "\t",
		       cmd->c_name);
    (void) fprintf(stderr, "\n");
    exit(3);
}

void
opendbm(access, LockType)
     int access, LockType;
{
    if (Dbmfile == NULL) {
	if (Argc > 1) {
	    /* use last argument */
	    Dbmfile = Argv[Argc-1];
	    Argc--;
	} else {
	    (void) fprintf(stderr, "%s: dbm file not specified\n", Progname);
	    exit(3);
	}
    }
    Dbm = dbm_open(Dbmfile, access, Mode);
    if (Dbm == NULL) {
	char *filename = (char *) malloc((unsigned) strlen(Dbmfile) + 20);
	(void) sprintf(filename, "%s{%s,%s}", Dbmfile, DB_DIREXT, DB_PAGEXT);
	perror(filename);
	exit(4);
    }
#ifndef GDBM
    if (flock(dbm_dirfno(Dbm), LockType) < 0) {
	    (void) fprintf(stderr, "%s: cannot lock %s\n",
		Progname, Dbmfile);
	    exit(3);
    }
#endif /* !GDBM */
    Dbmaccess = access;
}

void
closedbm()
{
    if ((Dbmaccess & O_RDONLY) == 0 && Senteniel != NULL) {
	MAKEDATUM(key, Senteniel);
	if (dbm_store(Dbm, key, key, DBM_REPLACE) != 0) {
	    (void) fprintf(stderr, "%s: could not store senteniel \"%s\"\n",
			   Progname, Senteniel);
	    perror(Progname);
	    exit(5);
	}
    }
#ifndef GDBM
    (void) flock(dbm_dirfno(Dbm), LOCK_UN);
#endif /* !GDBM */
    dbm_close(Dbm);
}

FILE *
openfile(filename, access)
     const char *filename;
     const char *access;
{
    FILE *f;

    if (streq(filename, "-"))
	if (streq(access, "r"))
	    return stdin;
	else
	    return stdout;
    else {
	f = fopen(filename, access);
	if (f == NULL) {
	    perror(filename);
	    exit(4);
	}
	return f;
    }
}

void
closefile(f)
     FILE *f;
{
    if (f != stdin && f != stdout)
	(void) fclose(f);
}
/*
**	DO_CLEAR -- Clear out database leaving it emtpy.
*/

void
do_clear()
{
    if (Dbmfile != NULL) {
	opendbm(O_RDWR | O_CREAT | O_TRUNC, LOCK_EX);
	closedbm();
    }
    while (Argc > 1) {
	opendbm(O_RDWR | O_CREAT | O_TRUNC, LOCK_EX);
	closedbm();
    }
}

   
/*
**	DO_DELETE -- Delete individual entries from the database.
*/

void
do_delete()
{
    opendbm(O_RDWR | O_CREAT, LOCK_EX);

    for (Argc--, Argv++; Argc > 0; Argc--, Argv++) {
	casify(*Argv, Casing);
	MAKEDATUM(key, *Argv);
	if (dbm_delete(Dbm, key) != 0) {
	    perror(*Argv);
	    exit(5);
	}
    }

    closedbm();
}

/*
**	DO_DUMP -- List all entries in the database.
*/
 
void
do_dump()
{
    FILE *output;
    
    opendbm(O_RDONLY, LOCK_SH);

    if (Outfile == NULL)
	output = stdout;
    else
	output = openfile(Outfile, "w");

    for (key = dbm_firstkey(Dbm); key.dptr != NULL; key = dbm_nextkey(Dbm)) {
	val = dbm_fetch(Dbm, key);
	CHECKDATUM(key);
	CHECKDATUM(val);
	if (val.dptr == NULL)
	    perror(key.dptr);
	else if (Valueonly)
	    (void) fprintf(output, "%.*s\n", val.dsize, val.dptr);
	else
	    (void) fprintf(output, "%.*s\t%.*s\n", key.dsize, key.dptr,
			   val.dsize, val.dptr);
    }
}
/*
**	DO_FETCH -- Lookup individual keys in the database.
*/
 
void
do_fetch()
{
    char buf[BUFSIZ];
    register char *nl;

    opendbm(O_RDONLY, LOCK_SH);

    if (Argc == 1) {
	while (fgets(buf, sizeof(buf), stdin) != NULL) {
	    if ((nl = index(buf, '\n')) != NULL)
		*nl = '\0';
	    casify(buf, Casing);
	    MAKEDATUM(key, buf);
	    val = dbm_fetch(Dbm, key);
	    CHECKDATUM(val);
	    if (val.dptr == NULL)
		(void) printf("%s\t[NOT FOUND]\n", buf);
	    else if (Valueonly)
		(void) printf("%.*s\n", val.dsize, val.dptr);
	    else
		(void) printf("%s\t%.*s\n", buf, val.dsize, val.dptr);
	}
    }
    else
    for (Argc--, Argv++; Argc > 0; Argc--, Argv++) {
	casify(*Argv, Casing);
	MAKEDATUM(key, *Argv);
	val = dbm_fetch(Dbm, key);
	CHECKDATUM(val);
	if (val.dptr == NULL)
	    (void) printf("%s\t[NOT FOUND]\n", *Argv);
	else if (Valueonly)
	    (void) printf("%.*s\n", val.dsize, val.dptr);
	else
	    (void) printf("%s\t%.*s\n", *Argv, val.dsize, val.dptr);
    }

    closedbm();
}

/*
**	DO_STORE -- Insert individual entries into the database.
*/

void
do_store()
{
    /* barf if # of args - 1 is even and no dbm file has been specified */
    if ((Argc & 1) == 1 && Dbmfile == NULL) {
	(void) fprintf(stderr, "%s: no dbm file specified\n", Progname);
	exit(3);
    }

    opendbm(O_RDWR | O_CREAT, LOCK_EX);

    for (Argc--, Argv++; Argc > 1; Argc -= 2, Argv += 2) {
	casify(Argv[0], Casing);
	MAKEDATUM(key, Argv[0]);
	MAKEDATUM(val, Argv[1]);
	if (dbm_store(Dbm, key, val, Storeflag) != 0) {
	    extern int errno;

	    if (errno != 0) {
		perror(Argv[0]);
		exit(5);
	    } else if (Storewarn)
		(void) fprintf(stderr,
			       "%s: duplicate key \"%s\" => \"%s\" ignored\n",
			       Progname, Argv[0], Argv[1]);
	}
    }
    if (Argc > 0)
	(void) fprintf(stderr, "%s: no value for last key \"%s\"--ignored\n",
		       Progname, Argv[0]);

    closedbm();
}

/*
**	DO_PARSE -- Parse a textual database file and produce key-value
**		pairs separated by a tab (suitable for input to the ``load''
**		function).
*/

void
do_parse()
{
    FILE *input, *output;
    
    if (Outfile == NULL)
	output = stdout;
    else
	output = openfile(Outfile, "w");

    if (Argc == 1)
	parsefile(stdin, output);
    else
	for (Argc--, Argv++; Argc > 0; Argc--, Argv++) {
	    input = openfile(*Argv, "r");
	    parsefile(input, output);
	    closefile(input);
	}
}

/*
**	DO_MAKE -- Parse the textual input and load the result into
**		the database.
*/

void
do_make()
{
    FILE *input, *pipin, *pipout;
    int pipes[2];

    opendbm(O_RDWR | O_CREAT | (Appending ? 0 : O_TRUNC), LOCK_EX);

    if (pipe(pipes) != 0) {
	perror("pipe");
	exit(9);
    }
    pipin = fdopen(pipes[0], "r");
    pipout = fdopen(pipes[1], "w");

    if (fork() == 0) {
	/* child process */
	(void) fclose(pipout);

	loadfile(pipin);
    } else {
	/* parent process */
	(void) fclose(pipin);

	if (Argc == 1)
	    parsefile(stdin, pipout);
	else
	    for (Argc--, Argv++; Argc > 0; Argc--, Argv++) {
		input = openfile(*Argv, "r");
		parsefile(input, pipout);
		closefile(input);
	    }
    }
    closedbm();
}

/*
**	DO_LOAD -- Load the dbm database from a text file.  The input should
**		be key-value pairs separated by a tab, each on a single line.
*/

void
do_load()
{
    FILE *input;

    opendbm(O_RDWR | O_CREAT | (Appending ? 0 : O_TRUNC), LOCK_EX);

    if (Argc == 1)
	loadfile(stdin);
    else
	for (Argc--, Argv++; Argc > 0; Argc--, Argv++) {
	    input = openfile(*Argv, "r");
	    loadfile(input);
	    closefile(input);
	}
    closedbm();
}    

/*
**	PARSEFILE, LOADFILE
*/ 

void
parsefile(input, output)
     FILE *input, *output;
{
    char buf[BUFSIZ], *key, *val = NULL;
    register char *p;

    while (fgets(buf, sizeof(buf), input) != NULL) {
	if (buf[0] == COMMENTCHAR || buf[0] == '\n' || buf[0] == '\0')
	    continue;
	p=buf;
	if (!isspace(buf[0])) {
	    /* extract value */
	    p = scantoken(buf);
	    if (val != NULL)
		free(val);
	    val = (char *) malloc((unsigned) (p - buf + 1));
	    (void) strncpy(val, buf, p - buf);
	    val[p - buf] = '\0';
	    p++;
	}
	casify(buf, Casing);
	while ( *p != '\0') {
	    while (*p != '\0' && isspace(*p)) p++;
	    if (*p == '\0' || *p == COMMENTCHAR)
		break;
	    key = p;
	    p = scantoken(p);
	    if (*p == COMMENTCHAR)
		*p = '\0';
	    else if (*p != '\0')
		*p++ = '\0';
	    (void) fprintf(output, "%s\t%s\n", key, val);
	}
    }
}

void
loadfile(input)
     FILE *input;
{
    char buf[BUFSIZ];
    register char *tab, *nl;

    while (fgets(buf, sizeof(buf), input) != NULL) {
	nl = index(buf, '\n');
	if (nl != NULL)
	    *nl = '\0';

	tab = index(buf, '\t');
	if (tab == NULL) {
	    (void) fprintf(stderr, "%s: missing tab in \"%s\"--ignored\n",
			   Progname, buf);
	    continue;
	}
	*tab++ = '\0';
	casify(buf, Casing);
	MAKEDATUM(key, buf);
	MAKEDATUM(val, tab);
	if (dbm_store(Dbm, key, val, Storeflag) != 0 && Storewarn) {
	    extern int errno;

	    if (errno != 0) {
		perror(buf);
		exit(5);
	    } else if (Storewarn)
		(void) fprintf(stderr,
			       "%s: duplicate key \"%s\" => \"%s\" ignored\n",
			       Progname, buf, tab);
	}
    }
}

char *
scantoken(p)
     register const char *p;
{
  register bool quotedchar = FALSE, insidestring = FALSE, insideroute = FALSE;

  /* hidious address scanner */
  while (*p != '\0' && (quotedchar || insidestring || insideroute || 
			(*p != COMMENTCHAR && !isspace(*p)))) {
    /* special quote character handling */
    if (quotedchar)
      quotedchar = FALSE;
    else {
      quotedchar = (*p == '\\');
      if (!insidestring)
	if (*p == '<')
	  insideroute = TRUE;
	else if (*p == '>')
	  insideroute = FALSE;
      if (*p == '"')
	insidestring = !insidestring;
    }
    p++;
  }

  return (char *)p;
}

void
casify(p, c)
     register char *p;
     int c;
{
    switch (c) {
      case LOWERCASE:
	for (; *p != '\0'; p++)
	    if (isupper(*p))
		*p = tolower(*p);
	break;
      case UPPERCASE:
	for (; *p != '\0'; p++)
	    if (islower(*p))
		*p = toupper(*p);
	break;
    }
}
