/*
 *	Copyright 1991 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 */

/* LINTLIBRARY */

#include "mailer.h"
#include <ctype.h>
#include "search.h"
#include "io.h"

#ifndef strchr
extern char *strchr();
#endif
extern int cistrcmp();
extern struct sptree *spt_headers, *spt_eheaders;
extern void sp_null();

/*
 * RFC822 header list maintenance.
 */


/* This initializes the list of headers to which we attach specific semantics */

struct headerinfo mandatory_hdrs[] = {
{ "bcc",		Addresses,	Recipient,	normal		},
{ "cc",			AddressList,	Recipient,	normal		},
{ "from",		AMailboxList,	Sender,		normal		},
{ "message-id",		MessageID,	nilUserType,	normal		},
{ "reply-to",		AddressList,	Sender,		normal		},
{ "resent-bcc",		Addresses,	Recipient,	Resent		},
{ "resent-cc",		AddressList,	Recipient,	Resent		},
{ "resent-from",	AMailboxList,	Sender,		Resent		},
{ "resent-message-id",	MessageID,	nilUserType,	Resent		},
{ "resent-reply-to",	AddressList,	Sender,		Resent		},
{ "resent-sender",	Mailbox,	Sender,		Resent		},
{ "resent-to",		AddressList,	Recipient,	Resent		},
{ "sender",		Mailbox,	Sender,		normal		},
{ "to",			AddressList,	Recipient,	normal		},
};

struct headerinfo optional_hdrs[] = {
{ "return-receipt-to",	AddressList,	Sender,		normal		},
{ "return-path",	Mailbox,	Sender,		normal		},
{ "date",		DateTime,	nilUserType,	normal		},
{ "resent-date",	DateTime,	nilUserType,	Resent		},
{ "encrypted",		Encrypted,	nilUserType,	normal		},
{ "errors-to",		AddressList,	Sender,		normal		},
/* { "keywords",	PhraseList,	nilUserType,	normal	    }, */
{ "obsoletes",		MessageIDList,	nilUserType,	normal		},
{ "references",		References,	nilUserType,	normal		},
#if 0
{ "in-reply-to",	References,	nilUserType,	normal		},
{ "received",		Received,	nilUserType,	normal		},
#endif
};

struct headerinfo envelope_hdrs[] = {
{ "authinfo",	nilHeaderSemantics,	nilUserType,	eIdentinfo	},
{ "bodytype",	nilHeaderSemantics,	nilUserType,	eBodytype	},
{ "channel",		AnyWord,	nilUserType,	eChannel	},
{ "env-end",	nilHeaderSemantics,	nilUserType,	eEnvEnd		},
{ "env-eof",	nilHeaderSemantics,	nilUserType,	eEnvEnd		},
{ "envid",	nilHeaderSemantics,	nilUserType,	eEnvid		},
{ "external",	nilHeaderSemantics,	nilUserType,	eExternal	},
{ "from",		AMailboxList,	Sender,		eFrom		},
{ "fullname",		Phrase,		nilUserType,	eFullname	},
{ "identinfo",	nilHeaderSemantics,	nilUserType,	eIdentinfo	},
{ "loginname",		UserAtDomain,	nilUserType,	ePrettyLogin	},
{ "rcvdfrom",		DomainName,	nilUserType,	eRcvdFrom	},
{ "to",			AddressList,	Recipient,	eTo		},
{ "todsn",	nilHeaderSemantics,	Recipient,	eToDSN		},
{ "user",		Mailbox,	nilUserType,	eUser		},
{ "verbose",		AnyWord,	nilUserType,	eVerbose	},
{ "via",		AnyWord,	nilUserType,	eVia		},
{ "with",		AnyWord,	nilUserType,	eWith		},
};

struct semtrans {
	char		*name;
	HeaderSemantics semantics;	/* S/SL rule name in rfc822.ssl */
} hdrsemtable[] = {
{	"AMailboxList",		AMailboxList		},
{	"Address",		Address			},
{	"Addresses",		Addresses		},
{	"AddressList",		AddressList		},
{	"AnyWord",		AnyWord			},
{	"DateTime",		DateTime		},
{	"DomainName",		DomainName		},
{	"Encrypted",		Encrypted		},
{	"LocalPart",		LocalPart		},
{	"Mailbox",		Mailbox			},
{	"Mailboxes",		Mailboxes		},
{	"MailboxList",		MailboxList		},
{	"MessageID",		MessageID		},
{	"MessageIDList",	MessageIDList		},
{	"Phrase",		Phrase			},
{	"Phrases",		Phrases			},
{	"PhraseList",		PhraseList		},
{	"Received",		Received		},
{	"References",		References		},
{	"Route",		Route			},
{	"RouteAddress",		RouteAddress		},
{	"RouteAddressInAngles",	RouteAddressInAngles	},
{	"SubDomain",		SubDomain		},
{	"UserAtDomain",		UserAtDomain		},
};

HeaderSemantics
semname2enum(name)
	char *name;
{
	int i;

	for (i = 0; i < (sizeof hdrsemtable / sizeof hdrsemtable[0]); ++i) {
		if (cistrcmp(name, hdrsemtable[i].name) == 0)
			return hdrsemtable[i].semantics;
	}
	return nilHeaderSemantics;
}

char *
semenum2name(d)
	HeaderSemantics d;
{
	int i;

	for (i = 0; i < (sizeof hdrsemtable / sizeof hdrsemtable[0]); ++i) {
		if (d == hdrsemtable[i].semantics)
			return hdrsemtable[i].name;
	}
	return NULL;
}

void
init_header()
{
	struct headerinfo rh, *rhp;
	int i;
	memtypes oval;

	oval = stickymem;
	for (i = 0; i < (sizeof mandatory_hdrs / sizeof mandatory_hdrs[0]); ++i) {
		rh.hdr_name = mandatory_hdrs[i].hdr_name;
		rh.semantics = mandatory_hdrs[i].semantics;
		rh.user_type = mandatory_hdrs[i].user_type;
		rh.class = mandatory_hdrs[i].class;
		stickymem = MEM_MALLOC;
		rhp = (struct headerinfo *)tmalloc(sizeof (struct headerinfo));
		stickymem = oval;
		*rhp = rh;
		sp_install(symbol((u_char *)rhp->hdr_name),
			   (u_char *)rhp, 1, spt_headers);
	}
	for (i = 0; i < (sizeof optional_hdrs / sizeof optional_hdrs[0]); ++i) {
		rh.hdr_name = optional_hdrs[i].hdr_name;
		rh.semantics = optional_hdrs[i].semantics;
		rh.user_type = optional_hdrs[i].user_type;
		rh.class = optional_hdrs[i].class;
		stickymem = MEM_MALLOC;
		rhp = (struct headerinfo *)tmalloc(sizeof (struct headerinfo));
		*rhp = rh;
		stickymem = oval;
		sp_install(symbol((u_char *)rhp->hdr_name),
			   (u_char *)rhp, 0, spt_headers);
	}
	for (i = 0; i < (sizeof envelope_hdrs / sizeof envelope_hdrs[0]); ++i) {
		rh.hdr_name = envelope_hdrs[i].hdr_name;
		rh.semantics = envelope_hdrs[i].semantics;
		rh.user_type = envelope_hdrs[i].user_type;
		rh.class = envelope_hdrs[i].class;
		stickymem = MEM_MALLOC;
		rhp = (struct headerinfo *)tmalloc(sizeof (struct headerinfo));
		*rhp = rh;
		stickymem = oval;
		sp_install(symbol((u_char *)rhp->hdr_name),
			   (u_char *)rhp, 0, spt_eheaders);
	}
}

struct sptree *
open_header(sip)
	struct search_info *sip;
{
	if (sip->subtype == NULL)
		return NULL;
	return (struct sptree *)sip->subtype;
}

struct headerinfo *
find_header(db, name)
	struct sptree *db;
	char *name;
{
	struct spblk *spl;
	register char *cp;
#ifdef	USE_ALLOCA
	int len = strlen(name);
	char *buffer = alloca(len+1);
#else
	static int blen = 0;
	static char *buffer = NULL;
	int len;

	if (buffer == NULL) {
		blen = 64;
		buffer = malloc(blen);
	}
	len = strlen(name);
	if ( len >= blen ) {
	  while (len < blen)
	    blen <<= 1;
	  buffer = realloc(buffer, blen);
	}
#endif
	strcpy(buffer, name);
	for (cp = buffer; *cp != '\0'; ++cp) {
		if (isascii(*cp) && isupper(*cp))
			*cp = tolower(*cp);
	}

	if ((spl = sp_lookup(symbol((u_char *)buffer), db)) == NULL)
		return NULL;
	return (struct headerinfo *)spl->data;
}

/*
 * Search headers database for a key.
 */

struct conscell *
search_header(sip)
	struct search_info *sip;
{
	struct sptree *db;
	struct conscell *tmp;
	struct headerinfo *rhp;
	char buf[1024];

	if ((db = open_header(sip)) == NULL)
		return NULL;
	rhp = find_header(db, sip->key);
	if (rhp == NULL)
		return NULL;
	sprintf(buf, "%s:%s:%s",
		     semenum2name(rhp->semantics),
		     rhp->user_type == Sender ? "Sender" :
		     rhp->user_type == Recipient ? "Recipient" : "",
		     rhp->class == Resent ? "Resent" : "");
	return newstring((u_char *)strsave(buf));
}

/*
 * Free any information stored in this database.
 */

int
hdfreedata(spl)
	struct spblk *spl;
{
	if (spl->data)
		free((char *)spl->data);
	return 0;
}

void
close_header(sip)
	struct search_info *sip;
{
	struct sptree *db;

	if ((db = open_header(sip)) == NULL)
		return;
	sp_scan(hdfreedata, (struct spblk *)NULL, db);
	sp_null(db);
}

int
enormal(s)
	char *s;
{
	return (s == NULL || *s == '\0' || cistrcmp(s, "none") == 0 ||
		cistrcmp(s, "normal") == 0 || cistrcmp(s, "-") == 0);
}

/*
 * Add the indicated key/value pair to the database.
 */

int
add_header(sip, value)
	struct search_info *sip;
	char *value;
{
	struct sptree *db;
	struct spblk *spl;
	memtypes oval;
	char *cp;
	struct headerinfo rh, *rhp;

	if ((db = open_header(sip)) == NULL)
		return EOF;

	/* parse a line like:  rulename:sender/recipient/-:resent/normal */
	if (*value == '\0') {
		fprintf(stderr, "add_header: null header specification\n");
		return EOF;
	}
	if ((cp = strchr(value, ':')) == NULL) {
		fprintf(stderr,
			"add_header: missing sender/recipient and resent/normal fields in value\n");
		return EOF;
	}
	*cp++ = '\0';
	rh.semantics = semname2enum(value);
	if (rh.semantics == nilHeaderSemantics) {
		fprintf(stderr, "add_header: unknown parse rule '%s'\n", value);
		return EOF;
	}
	value = cp;
	if (*value == '\0')
		return EOF;
	if ((cp = strchr(value, ':')) == NULL) {
		fprintf(stderr,
			"add_header: missing resent/normal field in value\n");
		return EOF;
	}
	*cp++ = '\0';
	if (enormal(value))
		rh.user_type = nilUserType;
	else if (cistrcmp(value, "Sender") == 0)
		rh.user_type = Sender;
	else if (cistrcmp(value, "Recipient") == 0)
		rh.user_type = Recipient;
	else {
		fprintf(stderr,
			"add_header: sender/recipient misspecified as '%s'\n",
			value);
		return EOF;
	}
	value = cp;
	if ((cp = strchr(value, ':')) != NULL) {
		fprintf(stderr, "add_header: junk at end of value: '%s'\n",
				value);
		return EOF;
	}
	if (enormal(value))
		rh.class = normal;
	else if (cistrcmp(value, "Resent") == 0)
		rh.class = Resent;
	else {
		fprintf(stderr,
			"add_header: resent/normal misspecified as '%s'\n",
			value);
		return EOF;
	}

	/* make sure the key is lowercase and has only appropriate characters */

	for (cp = (char *)sip->key; *cp != '\0'; ++cp) {
		if (*cp != '-'
		    && (!isalpha(*cp) || isspace(*cp) || *cp == ':')) {
			fprintf(stderr,
				"add_header: bad character in key '%s'\n",
				sip->key);
			return EOF;
		}
		if (isupper(*cp))
			*cp = tolower(*cp);
	}

	rh.hdr_name = (char *)sip->key;

	oval = stickymem;
	stickymem = MEM_MALLOC;
	rhp = (struct headerinfo *)tmalloc(sizeof (struct headerinfo));
	*rhp = rh;
	stickymem = oval;

	if ((spl = sp_lookup(symbol(sip->key), db)) == NULL)
		sp_install(symbol(sip->key), (void*)rhp, 0, db);
	else if (spl->mark == 0) {
		hdfreedata(spl);
		spl->data = (u_char *)rhp;
	} else {
		free((char *)rhp);
		fprintf(stderr,
			"add_header: cannot change permanent definition of '%s'\n",
			sip->key);
		return EOF;
	}

	return 0;
}

/*
 * Remove the indicated key from the database.
 */

int
remove_header(sip)
	struct search_info *sip;
{
	struct sptree *db;
	struct spblk *spl;

	if ((db = open_header(sip)) == NULL)
		return EOF;
	if ((spl = sp_lookup(symbol(sip->key), db)) == NULL) {
		fprintf(stderr, "remove_header: no such key as \"%s\"!\n",
				sip->key);
		return EOF;
	} else if (spl->mark == 1) {
		fprintf(stderr, "remove_header: cannot remove permanent definition of '%s'\n", sip->key);
		return EOF;
	}
	hdfreedata(spl);
	sp_delete(spl, db);
	return 0;
}

/*
 * Print the database.
 */

static FILE *pcfp;

static int
hdprintdata(spl)
	struct spblk *spl;
{
	struct headerinfo *rhp;

	rhp = (struct headerinfo *)spl->data;
	fprintf(pcfp, "%-16s\t%s:%s:%s (%s)\n", pname(spl->key),
		     semenum2name(rhp->semantics),
		     rhp->user_type == Sender ? "Sender" :
		     rhp->user_type == Recipient ? "Recipient" : "-",
		     rhp->class == Resent ? "Resent" : "-",
		     spl->mark == 0 ? "optional" : "permanent");
	return 0;
}


void
print_header(sip, outfp)
	struct search_info *sip;
	FILE *outfp;
{
	struct sptree *db;

	if ((db = open_header(sip)) == NULL)
		return;
	pcfp = outfp;
	sp_scan(hdprintdata, (struct spblk *)NULL, db);
	fflush(outfp);
}

/* Count the database */

static int   pc_cnt;

static int
hdcountdata(spl)
	struct spblk *spl;
{
	++pc_cnt;
	return 0;
}

void
count_header(sip, outfp)
	struct search_info *sip;
	FILE *outfp;
{
	struct sptree *db;
	pc_cnt = 0;

	if ((db = open_header(sip)) != NULL)
	  sp_scan(hdcountdata, (struct spblk *)NULL, db);
	fprintf(outfp,"%d\n",pc_cnt);
	fflush(outfp);
}

void
owner_header(sip, outfp)
	struct search_info *sip;
	FILE *outfp;
{
	fprintf(outfp, "%d\n", getuid());
	fflush(outfp);
}
