/*
** addrsbr.c -- parse addresses 822-style
**
** This code is Copyright (c) 2002, by the authors of nmh.  See the
** COPYRIGHT file in the root directory of the nmh distribution for
** complete copyright information.
*/

#include <sysexits.h>
#include <h/mh.h>
#include <h/utils.h>
#include <h/addrsbr.h>
#include <h/mf.h>

/*
** High level parsing of addresses:
**
** The routines in sbr/mf.c parse the syntactic representations of
** addresses.  The routines in sbr/addrsbr.c associate semantics with those
** addresses.
**
** A full 822-style parser is called for syntax recongition. This breaks
** each address into its components. Note however that no semantics are
** assumed about the parts or their totality. This means that implicit
** hostnames aren't made explicit, and explicit hostnames aren't expanded
** to their "official" represenations.
**
** To summarize, when we're all done, here's what MH knows about the address:
**       type: local or network
**       host:  not locally defaulted, not explicitly expanded
**       everything else
*/


static int  ingrp = 0;
static char *pers = NULL;
static char *mbox = NULL;
static char *host = NULL;
static char *route = NULL;
static char *grp = NULL;
static char *note = NULL;
static char err[BUFSIZ];
static char adr[BUFSIZ];


char *
getname(char *addrs)
{
	struct adrx *ap;

	pers = mbox = host = route = grp = note = NULL;
	err[0] = '\0';

	if ((ap = getadrx(addrs ? addrs : "")) == NULL)
		return NULL;

	strncpy(adr, ap->text, sizeof(adr));
	pers = ap->pers;
	mbox = ap->mbox;
	host = ap->host;
	route = ap->path;
	grp = ap->grp;
	ingrp = ap->ingrp;
	note = ap->note;
	if (ap->err && *ap->err)
		strncpy(err, ap->err, sizeof(err));

	return adr;
}


struct mailname *
getm(char *str, char *dfhost, int dftype, int wanthost, char *eresult)
{
	struct mailname *mp;

	if (err[0]) {
		if (eresult)
			strcpy(eresult, err);
		else
			if (wanthost == AD_HOST)
				admonish(NULL, "bad address '%s' - %s", str, err);
		return NULL;
	}
	if (pers == NULL && mbox == NULL && host == NULL && route == NULL
		&& grp == NULL) {
		if (eresult)
			strcpy(eresult, "null address");
		else
			if (wanthost == AD_HOST)
				admonish(NULL, "null address '%s'", str);
		return NULL;
	}
	if (mbox == NULL && grp == NULL) {
		if (eresult)
			strcpy(eresult, "no mailbox in address");
		else if (wanthost == AD_HOST)
			admonish(NULL, "no mailbox in address '%s'", str);
		return NULL;
	}

	if (dfhost == NULL) {
		dfhost = LocalName();
		dftype = LOCALHOST;
	}

	mp = mh_xcalloc(1, sizeof(*mp));
	if (mp == NULL) {
		if (eresult)
			strcpy(eresult, "insufficient memory to represent address");
		else if (wanthost == AD_HOST)
			adios(EX_OSERR, NULL, "insufficient memory to represent address");
		return NULL;
	}

	mp->m_next = NULL;
	mp->m_text = mh_xstrdup(str);
	if (pers)
		mp->m_pers = mh_xstrdup(pers);

	if (mbox == NULL) {
		mp->m_type = BADHOST;
		mp->m_nohost = 1;
		mp->m_ingrp = ingrp;
		mp->m_gname = mh_xstrdup(grp);
		if (note)
			mp->m_note = mh_xstrdup(note);
		return mp;
	}

	if (host) {
		mp->m_mbox = mh_xstrdup(mbox);
		mp->m_host = mh_xstrdup(host);
	} else {
		mp->m_nohost = 1;
		mp->m_mbox = mh_xstrdup(mbox);
		if (route == NULL && dftype == LOCALHOST) {
			mp->m_host = NULL;
			mp->m_type = dftype;
		} else {
			mp->m_host = route ? NULL : mh_xstrdup(dfhost);
			mp->m_type = route ? NETHOST : dftype;
		}
		goto got_host;
	}

	if (wanthost == AD_NHST)
		mp->m_type = !mh_strcasecmp(LocalName(), mp->m_host)
			? LOCALHOST : NETHOST;
	else
		mp->m_type = mh_strcasecmp(LocalName(), mp->m_host) ?  NETHOST : LOCALHOST;

got_host: ;
	if (route)
		mp->m_path = mh_xstrdup(route);
	mp->m_ingrp = ingrp;
	if (grp)
		mp->m_gname = mh_xstrdup(grp);
	if (note)
		mp->m_note = mh_xstrdup(note);

	return mp;
}


void
mnfree(struct mailname *mp)
{
	if (!mp)
		return;

	if (mp->m_text)
		mh_free0(&(mp->m_text));
	if (mp->m_pers)
		mh_free0(&(mp->m_pers));
	if (mp->m_mbox)
		mh_free0(&(mp->m_mbox));
	if (mp->m_host)
		mh_free0(&(mp->m_host));
	if (mp->m_path)
		mh_free0(&(mp->m_path));
	if (mp->m_gname)
		mh_free0(&(mp->m_gname));
	if (mp->m_note)
		mh_free0(&(mp->m_note));

	mh_free0(&mp);
}


#define empty(s) ((s) ? (s) : "")

char *
adrformat(struct mailname *mp)
{
	static char addr[BUFSIZ];
	static char buffer[BUFSIZ];

	if (mp->m_nohost)
		strncpy(addr, mp->m_mbox ? mp->m_mbox : "", sizeof(addr));
	else
		snprintf(addr, sizeof(addr), mp->m_host ? "%s%s@%s" : "%s%s",
			empty(mp->m_path), empty(mp->m_mbox), mp->m_host);

	if (mp->m_pers || mp->m_path) {
		if (mp->m_note)
			snprintf(buffer, sizeof(buffer), "%s %s <%s>",
				legal_person(mp->m_pers ? mp->m_pers : mp->m_mbox),
				mp->m_note, addr);
		else
			snprintf(buffer, sizeof(buffer), "%s <%s>",
				legal_person(mp->m_pers ? mp->m_pers : mp->m_mbox),
				addr);
	} else if (mp->m_note)
		snprintf(buffer, sizeof(buffer), "%s %s", addr, mp->m_note);
	else
		strncpy(buffer, addr, sizeof(buffer));

	return buffer;
}


#define W_NIL   0x0000
#define W_MBEG  0x0001
#define W_MEND  0x0002
#define W_MBOX  (W_MBEG | W_MEND)
#define W_HBEG  0x0004
#define W_HEND  0x0008
#define W_HOST  (W_HBEG | W_HEND)
#define WBITS   "\020\01MBEG\02MEND\03HBEG\04HEND"

/*
** Check if this is my address
*/

int
ismymbox(struct mailname *np)
{
	int oops;
	int len, i;
	char *cp;
	char *pp;
	char buffer[BUFSIZ];
	struct mailname *mp;
	static char *am = NULL;
	static struct mailname mq;

	/*
	** If this is the first call, initialize
	** list of alternate mailboxes.
	*/
	if (am == NULL) {
		mq.m_next = NULL;
		mq.m_mbox = getusername();
		mp = &mq;
		if ((am = context_find("alternate-mailboxes")) == NULL) {
			am = getusername();
		} else {
			oops = 0;
			while ((cp = getname(am))) {
				if ((mp->m_next = getm(cp, NULL, 0, AD_NAME, NULL)) == NULL) {
					admonish(NULL, "illegal address: %s", cp);
					oops++;
				} else {
					mp = mp->m_next;
					mp->m_type = W_NIL;
					if (*mp->m_mbox == '*') {
						mp->m_type |= W_MBEG;
						mp->m_mbox++;
					}
					if (*(cp = mp->m_mbox + strlen(mp->m_mbox) - 1) == '*') {
						mp->m_type |= W_MEND;
						*cp = '\0';
					}
					if (mp->m_host) {
						if (*mp->m_host == '*') {
							mp->m_type |= W_HBEG;
							mp->m_host++;
						}
						if (*(cp = mp->m_host + strlen(mp->m_host) - 1) == '*') {
							mp->m_type |= W_HEND;
							*cp = '\0';
						}
					}
					if ((cp = getenv("MHWDEBUG")) && *cp) {
						fprintf(stderr, "mbox=\"%s\" host=\"%s\" %s\n",
							mp->m_mbox, mp->m_host,
							snprintb(buffer, sizeof(buffer), (unsigned) mp->m_type, WBITS));
					}
				}
			}
			if (oops != 0) {
				advise(NULL, "please fix the profile entry %s",
						"alternate-mailboxes");
			}
		}

		if ((cp = context_find("Default-From")) != NULL) {
			int i = 0;
			char *cp2;
			oops = 0;

			while ((cp2 = getname(cp)) != NULL) {
				i++;
				if ((mp->m_next = getm(cp2, NULL, 0, AD_NAME, NULL)) == NULL) {
					admonish(NULL, "illegal address: %s", cp);
					oops++;
				} else {
					mp = mp->m_next;
					if ((cp = getenv("MHWDEBUG")) && *cp) {
						fprintf(stderr, "mbox=\"%s\" host=\"%s\" %s\n",
							mp->m_mbox, mp->m_host,
							snprintb(buffer, sizeof(buffer), (unsigned) mp->m_type, WBITS));
					}
				}

			}

			if (oops != 0 || i < 1) {
				advise(NULL, "please fix the profile entry %s",
						"Default-From");
			}

		}
	}

	if (np == NULL) /* XXX */
		return 0;

	switch (np->m_type) {
	case NETHOST:
		len = strlen(cp = LocalName());
		if (!uprf(np->m_host, cp) || np->m_host[len] != '.')
			break;
		goto local_test;

	case LOCALHOST:
local_test: ;
		if (!mh_strcasecmp(np->m_mbox, mq.m_mbox))
			return 1;
		break;

	default:
		break;
	}

	/*
	** Now scan through list of alternate
	** mailboxes, and check for a match.
	*/
	for (mp = &mq; mp->m_next;) {
		mp = mp->m_next;
		if (!np->m_mbox)
			continue;
		if ((len = strlen(cp = np->m_mbox))
			< (i = strlen(pp = mp->m_mbox)))
			continue;
		switch (mp->m_type & W_MBOX) {
		case W_NIL:
			if (mh_strcasecmp(cp, pp))
				continue;
			break;
		case W_MBEG:
			if (mh_strcasecmp(cp + len - i, pp))
				continue;
			break;
		case W_MEND:
			if (!uprf(cp, pp))
				continue;
			break;
		case W_MBEG | W_MEND:
			if (stringdex(pp, cp) < 0)
				continue;
			break;
		}

		if (mp->m_nohost)
			return 1;
		if (np->m_host == NULL)
			continue;
		if ((len = strlen(cp = np->m_host))
			< (i = strlen(pp = mp->m_host)))
			continue;
		switch (mp->m_type & W_HOST) {
		case W_NIL:
			if (mh_strcasecmp(cp, pp))
				continue;
			break;
		case W_HBEG:
			if (mh_strcasecmp (cp + len - i, pp))
				continue;
			break;
		case W_HEND:
			if (!uprf(cp, pp))
				continue;
			break;
		case W_HBEG | W_HEND:
			if (stringdex(pp, cp) < 0)
				continue;
			break;
		}
		return 1;
	}

	return 0;
}

/*
 * Insert mailname after element and returns the
 * number of parsed addresses. element is set to
 * the last parsed addresse.
 */
ssize_t
getmboxes(char *line, struct mailname **element)
{
	struct mailname *mp, *next, *first;
	char *cp;
	size_t i = 0;

	first = *element;
	next = first->m_next;

	while ((cp = getname(line))) {
		mp = getm(cp, NULL, 0, AD_HOST, NULL);
		if (mp == NULL) {
			goto error;
		}
		(*element)->m_next = mp;
		*element = mp;
		i++;
	}

	(*element)->m_next = next;
	return i;
error:
	while (first->m_next != NULL && first->m_next != next) {
		mp = first->m_next;
		first->m_next = mp->m_next;
		mh_free0(&mp);
	}
	first->m_next = next;
	return -1;
}
