/*
 * mreport.c
 *	Jason Armstrong <jason@datrix.co.za>
 *
 * $Id: mreport.c,v 1.22 2000/01/25 12:29:10 jason Exp $
 */

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>

/*
#ifndef _NO_GETOPT_H_
#include <getopt.h>
#endif
*/

#include "mreport.h"
#include "timer.h"
#include "hash.h"


struct timeval tv;


int
main(int argc, char *argv[])
{
	struct options o;

	if (m_getconfig(&o, argc, argv) < 0 || m_getdata(&o) < 0) {
		return -1;
	}

	return 0;
}



int
m_getdata(struct options *o)
{
	struct data d;
	char *p;
	int rc,
	    i = 0;

	if (m_init(o, &d) < 0) {
		return -1;
	}

	while ( (rc = m_getline(o, &d)) == 0) {
		if (m_getid(o, d.id, sizeof(d.id) - 1, d.mbuf) == 1) {
			continue;
		}

		if (!o->summary && !o->nostats && !(i++)) {
			strncpy(d.first, d.mbuf, sizeof(d.first) - 1);
		}

		memset(&(d.mfrom), 0, sizeof(struct from));
		memset(&(d.mto), 0, sizeof(struct to));

		if ( (p = strstr(d.mbuf, FROM))) {
			if (m_add_from(o, &d, &p) < 0) {
				return -1;
			}
		} else if ( (p = strstr(d.mbuf, STAT))) {
			if (m_process_stat(o, &d, &p) < 0) {
				return -1;
			}
		} else {
			if (m_last_resort(o, &d) < 0) {
				return -1;
			}
		}
	}

	if (rc < 0) {
		return rc;
	}

	if (!o->summary && !o->nostats) {
		strncpy(d.last, d.mbuf, sizeof(d.last) - 1);
	}

	if (m_report(o, &d) < 0) {
		return -1;
	}

	return 0;
}


int
m_process_stat(struct options *o, struct data *d, char **p)
{
	int rc;

	if ( (rc = m_get_stat(o, d, *p))) {
		return rc;
	}

	if ( (rc = m_get_ctrl(o, d)) < 0) {
		return rc;
	}

	if ( (rc = m_get_to(o, d)) < 0) {
		return rc;
	}

	return 0;
}


int
m_add_to(struct options *o, struct data *d)
{
	if (d->toi >= d->tocount) {
		if ( !(d->to = realloc( d->toi ? d->to : NULL,
		    (d->toi + 1 + M_INCREMENT) * sizeof(struct to *) ))) {
			return (m_err(__LINE__, "realloc", strerror(errno)));
		}

		d->tocount += M_INCREMENT;
	}

	if ( !(d->to[d->toi] = malloc(sizeof(struct to)))) {
		return (m_err(__LINE__, "malloc", strerror(errno)));
	}
	memcpy(d->to[d->toi], &(d->mto), sizeof(struct to));

	if (d->keytoi >= d->keytocount) {
		if ( !(d->keysto = realloc( d->keytoi ? d->keysto : NULL,
		      (d->keytoi + 1 + M_INCREMENT) * sizeof(char *) ))) {
			return (m_err(__LINE__, "realloc", strerror(errno)));
		}

		d->keytocount += M_INCREMENT;
	}

	if ( !(d->keysto[d->keytoi] = (char *) malloc(M_KEYSIZE + 1))) {
		return (m_err(__LINE__, "malloc", strerror(errno)));
	}


	if (d->duplicate && m_lookup_duplicate(o, d)) {

	} else {
		snprintf(d->keysto[d->keytoi], M_KEYSIZE - 1, d->id);
	}

	if (hash_insert_str(d->hto, d->keysto[d->keytoi], d->to[d->toi])) {
		return (m_err(__LINE__, "hash_insert_str", ""));
	}

	(d->keytoi)++;
	(d->toi)++;

	return 0;
}


int
m_get_to(struct options *o, struct data *d)
{
	char *p,
	     *cp;
	int i = 0,
	    s = sizeof(d->mto.email) - 1;

	p = strstr(d->mbuf, TO) + strlen(TO);

	while (*p) {
		if ( !(cp = strchr(p, ','))) {
			fprintf(stderr, 
				"We should have a comma here :\n[%s]\n", 
				d->mbuf);
			return 0;
		}
		*cp = 0;

		if (strstr(p, CTL) || strstr(p, DELAY)) {
			*cp = ',';
			break;
		}

		if ( !(i = m_get_email(p, d->mto.email, s))) {
			strncpy(d->mto.email, M_UNKNOWN, s);
		} else {
			while (d->mto.email[--i] == ' ') {
				d->mto.email[i] = 0;
			}
		}

		if (!o->from) {
			if (o->dom && 
			    !strchr(d->mto.email, '@') &&
			    !strchr(d->mto.email, '|') &&
			    !strchr(d->mto.email, '/')) {
				strncat(d->mto.email, "@",
				       s - strlen(d->mfrom.email));
				strncat(d->mto.email, o->domain,
				       s - strlen(d->mfrom.email));
			}

		}

		/*
		if (o->alias) {
			if (m_check_alias(o, d) < 0) {
				fprintf(stderr, "ERROR: m_check_alias()\n");
				return 0;
			}
		}
		*/

		if (o->dbg) {
			printf("TO   : [%s] [%s]\n",
					d->id, d->mto.email);
		}

		if (d->mto.status) {
			if (strcmp(d->mto.stat, HOST_UNKNOWN) == 0) {
				d->merrs.hostunknown++;
			} else if (strcmp(d->mto.stat, USER_UNKNOWN) == 0) {
				d->merrs.userunknown++;
			} else if (strcmp(d->mto.stat, NO_SERVICE) == 0) {
				d->merrs.noservice++;
			}
		}

		if (m_add_to(o, d) < 0) {
			return -1;
		}

		*cp = ',';
		p = cp + 1;
	}

	return 0;
}



int
m_get_ctrl(struct options *o, struct data *d)
{
	char *cp;
	int i,
	    size = sizeof(d->mto.ctrl) - 1;

	if ( !(cp = strstr(d->mbuf, CTL))) {
		return 1;
	}

	cp += strlen(CTL);

	if ( !(i = m_get_email(cp, d->mto.ctrl, size))) {
		strncpy(d->mto.ctrl, M_UNKNOWN, size);
	} else {
		while (d->mto.ctrl[--i] == ' ') {
			d->mto.ctrl[i] = 0;
		}

		if (o->dom && !strchr(d->mto.ctrl, '@')) {
			strncat(d->mto.ctrl, "@", size - strlen(d->mto.ctrl) - 1);
			strncat(d->mto.ctrl, o->domain, size - strlen(d->mto.ctrl) - 1);
		}
	}

	return 0;
}


int
m_get_stat(struct options *o, struct data *d, char *p)
{
	char *cp;

	if (!p) {
		return -1;
	}

	cp = p + strlen(STAT);

	if (strncasecmp(cp, SENT, strlen(SENT)) == 0) {
		snprintf(d->mto.stat, sizeof(d->mto.stat) - 1, STAT);
		return 0;
	} else if (strncasecmp(cp, DEFERRED, strlen(DEFERRED)) == 0) {
		d->merrs.deferred++;
	} else if (strncasecmp(cp, USER_UNKNOWN, strlen(USER_UNKNOWN)) == 0) {
		snprintf(d->mto.stat, sizeof(d->mto.stat) - 1, USER_UNKNOWN);
		d->mto.status = 1;
		return 0;
	} else if (strncasecmp(cp, HOST_UNKNOWN, strlen(HOST_UNKNOWN)) == 0) {
		snprintf(d->mto.stat, sizeof(d->mto.stat) - 1, HOST_UNKNOWN);
		d->mto.status = 1;
		return 0;
	} else if (strncasecmp(cp, NO_SERVICE, strlen(NO_SERVICE)) == 0) {
		snprintf(d->mto.stat, sizeof(d->mto.stat) - 1, NO_SERVICE);
		d->mto.status = 1;
		return 0;
	} else if (strncasecmp(cp, IO_ERROR, strlen(IO_ERROR)) == 0) {
		d->merrs.ioerror++;
	} else {
		if (o->dbg) {
			printf("STAT : %s\n", cp);
		}
	}

	return 1;
}


int
m_add_from(struct options *o, struct data *d, char **p)
{
	if (!strstr(d->mbuf, SIZE) || !strstr(d->mbuf, NRCPTS)) {
		return 0;
	}

	memset( &(d->mfrom), 0, sizeof(struct from));

	if (m_get_from_email(o, p, d->mfrom.email, sizeof(d->mfrom.email) - 1) < 0) {
		return -1;
	}

	if (m_get_from_size(o, *p, &(d->mfrom.size)) < 0) {
		return -1;
	}

	if (o->dbg) {
		printf("FROM : [%s] [%s] [%lu]\n", 
			d->id, d->mfrom.email, d->mfrom.size);
	}

	/*
	if (m_get_nrcpts(o, d) < 0) {
		return -1;
	}
	*/


	if (d->fromi >= d->fromcount) {
		if ( !(d->from = realloc( d->fromi ? d->from : NULL,
		    (d->fromi + 1 + M_INCREMENT) * sizeof(struct from *) ))) {
			return (m_err(__LINE__, "realloc", strerror(errno)));
		}

		d->fromcount += M_INCREMENT;
	}

	if ( !(d->from[d->fromi] = malloc(sizeof(struct from)))) {
		return (m_err(__LINE__, "malloc", strerror(errno)));
	}
	memcpy(d->from[d->fromi], &(d->mfrom), sizeof(struct from));

	if (d->keyi >= d->keycount) {
		if ( !(d->keys = realloc( d->keyi ? d->keys : NULL,
		      (d->keyi + 1 + M_INCREMENT) * sizeof(char *) ))) {
			return (m_err(__LINE__, "realloc", strerror(errno)));
		}

		d->keycount += M_INCREMENT;
	}

	if ( !(d->keys[d->keyi] = (char *) malloc(M_KEYSIZE + 1))) {
		return (m_err(__LINE__, "malloc", strerror(errno)));
	}

	if (hash_lookup_str(d->hfrom, d->id)) {
		if (m_register_duplicate(o, d) < 0) {
			return -1;
		}
	} else {
		snprintf(d->keys[d->keyi], M_KEYSIZE - 1, d->id);
	}

	if (hash_insert_str(d->hfrom, d->keys[d->keyi], d->from[d->fromi])) {
		return (m_err(__LINE__, "hash_insert_str", ""));
	}

	(d->keyi)++;
	(d->fromi)++;

	return 0;
}


int
m_register_duplicate(struct options *o, struct data *d)
{
	if (d->dupi >= d->dupcount) {
		if ( !(d->dup = realloc( d->dupi ? d->dup : NULL,
		      (d->dupi + 1 + M_INCREMENT) * sizeof(struct duplicate *) ))) {
			return (m_err(__LINE__, "realloc", strerror(errno)));
		}

		d->dupcount += M_INCREMENT;
	}

	if ( !(d->dup[d->dupi] = malloc(sizeof(struct duplicate)))) {
		return (m_err(__LINE__, "malloc", strerror(errno)));
	}

	snprintf(d->dup[d->dupi]->id1, sizeof(d->dup[d->dupi]->id1) - 1, d->id);
	snprintf(d->dup[d->dupi]->id2, sizeof(d->dup[d->dupi]->id2) - 1, 
		 "%d%s", d->dupi, d->id);

	snprintf(d->keys[d->keyi], M_KEYSIZE - 1, d->dup[d->dupi]->id2);

	if (o->dbg) {
		printf("DUPLICATE REGISTERED: %s\n", d->id);
	}

	(d->dupi)++;
	(d->duplicate)++;

	return 0;
}

int
m_lookup_duplicate(struct options *o, struct data *d)
{
	int i;

	for (i = 0; i < d->dupi; i++) {
		if (strcmp(d->dup[i]->id1, d->id) == 0) {
			snprintf(d->keysto[d->keytoi], M_KEYSIZE - 1, d->dup[i]->id2);
			if (o->dbg) {
				printf("DUPLICATE TO: %s\n", d->keysto[d->keytoi]);
			}

			return 1;
		}
	}

	return 0;
}




int
m_getline(struct options *o, struct data *d)
{
	long pos;
	int c = 0,
	    i = 0,
	    perbyte = 0;

	if ( (pos = ftell(d->in)) < 0) {
		return (m_err(__LINE__, "ftell", strerror(errno)));
	}


	for (;8;) {
		if (perbyte) {
			if (fseek(d->in, pos, SEEK_SET) < 0) {
				return (m_err(__LINE__, "fseek", strerror(errno)));
			}

			while ( (c = fgetc(d->in)) != EOF) {
				if (i >= d->mbufsize - 1) {
					if ( !(d->mbuf = realloc(d->mbuf, 
						         d->mbufsize + (M_BUFSIZE * sizeof(char)) + 1))) {
						return (m_err(__LINE__, "realloc", strerror(errno)));
					}
					d->mbufsize += (M_BUFSIZE * sizeof(char));

					if (o->dbg) {
						printf("Buffer size change [%d]\n", 
								d->mbufsize);
					}
				}

				if ( (d->mbuf[i] = c) == '\n') {
					break;
				} else {
					i++;
				}
			}

			d->mbuf[i] = 0;
			break;

		} else {
			if (!fgets(d->mbuf, d->mbufsize, d->in)) {
				if (feof(d->in)) {
					return 1;
				} else {
					return (m_err(__LINE__, "fgets", strerror(errno)));
				}
			}

			if ( !strchr(d->mbuf, '\n')) {
				DBG("Switching to Per Byte mode (long line)");
				perbyte++;
				continue;
			}

			break;
		}
	}

	return 0;
}

int
m_check_alias(struct options *o, struct data *d)
{
	void *v;

	if ( (v = hash_lookup_str(d->halias, d->mto.email))) {
		if (o->dbg) {
			printf("ALIAS: [%s] [%s]\n", 
				((struct alias *)(v))->alias,
				((struct alias *)(v))->account);
		}

		snprintf(d->mto.email, 
			 sizeof(d->mto.email), 
			 ((struct alias *)(v))->account);
	}

	return 0;
}


int
m_load_aliases(struct options *o, struct data *d)
{
	FILE *f;
	char buf[256];

	if ( !(f = fopen(o->aliases, "r"))) {
		return (m_err(__LINE__, o->aliases, strerror(errno)));
	}

	while (fgets(buf, sizeof(buf), f)) {
		if (buf[0] == '#' || buf[0] == '\n' || !buf[0]) {
			continue;
		}
		buf[strlen(buf) - 1] = 0;

		m_process_alias(o, d, buf);
	}

	fclose(f);

	return 0;
}

int
m_process_alias(struct options *o, struct data *d, char *buf)
{
	char *p = buf,
	     account[128],
	     alias[128];
	int i = 0,
	    gotcolon = 0,
	    needq = 0;

	while (*p && i < sizeof(alias) - 1) {
		if (*p == ':') {
			p++;
			gotcolon = 1;
			break;
		}
		/*
		if (!isspace(*p)) {
			alias[i++] = *p;
		}
		*/
		alias[i++] = *p;
		p++;
	}
	alias[i] = 0;

	if (!i || !gotcolon) {
		return (m_err(__LINE__, "Corrupt Alias", buf));
	}
	if (o->dom && 
	    !strchr(alias, '@') && 
	    !strchr(alias, '|') && 
	    !strchr(alias, '/')) {
		strncat(alias, "@", sizeof(alias) - strlen(alias));
		strncat(alias, o->domain, sizeof(alias) - strlen(alias));
	}


	while (isspace(*p)) {
		p++;
	}

	i = 0;

	if (*p == '"') {
		needq = 1;
		p++;
	}

	while (*p && i < sizeof(account) - 1) {
		if (*p == ',') {

			if (!i) {
				return (m_err(__LINE__, "Corrupt Alias", buf));
			}

			account[i] = 0;

			if (m_add_alias(o, d, account, alias) < 0) {
				return -1;
			}

			i = 0;
			p++;
			while (isspace(*p)) {
				p++;
			}
			if (*p == '"') {
				needq = 1;
				p++;
			}
			continue;
		}

		if (needq && *p == '"') {
			needq = 0;
			p++;
			continue;
		}

		account[i++] = *p++;
	}
	account[i] = 0;

	if (!i) {
		return (m_err(__LINE__, "Corrupt Alias", buf));
	}

	if (m_add_alias(o, d, account, alias) < 0) {
		return -1;
	}

	return 0;
}

int
m_add_alias(struct options *o, struct data *d, char *account, char *alias)
{
	if (d->ali >= d->alcount) {
		if ( !(d->al = realloc( d->ali ? d->al : NULL,
		    (d->ali + 1 + M_INCREMENT) * sizeof(struct alias *) ))) {
			return (m_err(__LINE__, "realloc", strerror(errno)));
		}

		d->alcount += M_INCREMENT;
	}

	if ( !(d->al[d->ali] = malloc(sizeof(struct alias)))) {
		return (m_err(__LINE__, "malloc", strerror(errno)));
	}

	snprintf(d->al[d->ali]->account, 
	         sizeof(d->al[d->ali]->account) - 1,
		 account);

	snprintf(d->al[d->ali]->alias, 
	         sizeof(d->al[d->ali]->alias) - 1,
		 alias);

	if (hash_insert_str(d->halias, d->al[d->ali]->alias, d->al[d->ali])) {
		return (m_err(__LINE__, "hash_insert_str", ""));
	}

	(d->ali)++;

	return 0;
}

int
m_init(struct options *o, struct data *d)
{
	memset(d, 0, sizeof(struct data));

	if ( !(d->in = fopen(o->in, "r"))) {
		return (m_err(__LINE__, o->in, strerror(errno)));
	}

	if (!strlen(o->out)) {
		d->out = stdout;
	} else {
		if ( !(d->out = fopen(o->out, "w"))) {
			return (m_err(__LINE__, o->out, strerror(errno)));
		}
	}

	if ( !(d->mbuf = malloc( (M_BUFSIZE * sizeof(char)) + 1)) ) {
		return (m_err(__LINE__, "malloc", strerror(errno)));
	}
	d->mbufsize = M_BUFSIZE * sizeof(char);

	if ( !(d->hfrom = hash_alloc(2)) || 
	     !(d->hto = hash_alloc(2))) {
		return (m_err(__LINE__, "hash_alloc", "No memory"));
	}

	/*
	if (o->alias) {
		m_load_aliases(o, d);
	}
	*/

	return 0;
}


int
m_last_resort(struct options *o, struct data *d)
{
	char *p;

	if (strstr(d->mbuf, USER_UNKNOWN) && 
	    !(strstr(d->mbuf, DSN)) && 
	    !(strstr(d->mbuf, RETURN))) {
		d->merrs.userunknown++;
		if (m_add_problem(o, d, M_NO_USER) < 0) {
			return -1;
		}
	} else if (strstr(d->mbuf, NORELAY) && !(strstr(d->mbuf, RULESET))) {
		d->merrs.norelay++;
		if (m_add_problem(o, d, M_NO_RELAY) < 0) {
			return -1;
		}
	} else if (strstr(d->mbuf, LOSTINPUT)) {
		snprintf(d->mto.stat, sizeof(d->mto.stat) - 1, LOSTINPUT);
		d->mto.status = 1;

		if (m_add_to(o, d) < 0) {
			return -1;
		}
	} else if ( (p = strstr(d->mbuf, CLONE))) {
		snprintf(d->mto.stat, sizeof(d->mto.stat) - 1, CLONE);
		d->mto.status = 1;

		if (m_get_clone_id(o, d, p) < 0) {
			return -1;
		}

		if (m_add_to(o, d) < 0) {
			return -1;
		}
	} else if (strstr(d->mbuf, EOM_ERROR)) {
		d->merrs.eomerror++;
		snprintf(d->mto.stat, sizeof(d->mto.stat) - 1, EOM_ERROR);
		d->mto.status = 1;

		if (m_add_to(o, d) < 0) {
			return -1;
		}
	}

	return 0;
}

int
m_get_clone_id(struct options *o, struct data *d, char *p)
{
	int i = 0;

	if (!p) {
		return -1;
	}

	p += strlen(CLONE);
	while (isspace(*p)) {
		p++;
	}

	while (*p && *p != ',' && i < sizeof(d->id) - 1) {
		d->id[i++] = *p++;
	}
	d->id[i] = 0;

	if (!i) {
		fprintf(stderr, "Error getting clone id\n%s\n", d->mbuf);
	}

	if (o->dbg) {
		printf("CLONE: %s\n", d->id);
	}

	return 0;

}



int
m_add_problem(struct options *o, struct data *d, int flag)
{
	char *p;
	int needgt,
	    i,
	    size = sizeof(d->mto.email) - 1;

	if ( !(p = strstr(d->mbuf, d->id))) {
		return (m_err(__LINE__, "No ID", d->mbuf));
	}

	p += strlen(d->id) + 1;

	while (isspace(*p)) {
		p++;
	}

	needgt = i = 0;

	if (*p == '<') {
		needgt = 1;
		p++;
	}

	while (*p && strncmp(p, "...", 3) != 0 && i < size) {
		if (needgt && *p == '>') {
			break;
		}
		d->mto.email[i++] = *p++;
	}
	d->mto.email[i] = 0;

	if (!i) {
		strncpy(d->mto.email, M_UNKNOWN, size);
	} else {
		while (d->mto.email[--i] == ' ') {
			d->mto.email[i] = 0;
		}

		if (o->dom && !strchr(d->mto.email, '@')) {
			strncat(d->mto.email, "@", size - strlen(d->mto.email) - 1);
			strncat(d->mto.email, o->domain, size - strlen(d->mto.email) - 1);
		}
	}

	if (flag == M_NO_USER) {
		snprintf(d->mto.stat, sizeof(d->mto.stat) - 1, USER_UNKNOWN);
		if (o->dbg) {
			printf("TO   : [%s] (User Unknown)\n", d->mto.email);
		}
	} else if (flag == M_NO_RELAY) {
		snprintf(d->mto.stat, sizeof(d->mto.stat) - 1, NORELAY);
		if (o->dbg) {
			printf("TO   : [%s] (No Relay)\n", d->mto.email);
		}
	}

	d->mto.status = 1;


	if (m_add_to(o, d) < 0) {
		return -1;
	}

	return 0;
}




int
m_get_from_email(struct options *o, char **p, char *email, int size)
{
	char *cp = *p + strlen(FROM);
	int i;

	if ( !(i = m_get_email(cp, email, size))) {
		strncpy(email, M_UNKNOWN, size);
	} else {
		while (email[--i] == ' ') {
			email[i] = 0;
		}

		if (o->dom && !strchr(email, '@')) {
			strncat(email, "@", size - strlen(email) - 1);
			strncat(email, o->domain, size - strlen(email) - 1);
		}
	}

	*p = strstr(cp, SIZE);

	return 0;
}

int
m_get_nrcpts(struct options *o, struct data *d)
{
	char *cp,
	     *cp2;

	if ( !(cp = strstr(d->mbuf, NRCPTS))) {
		DBG("Possibly incorrect data line, no nrcpts=");
		return 1;
	}

	cp += strlen(NRCPTS);
	if ( !(cp2 = strchr(cp, ','))) {
		DBG("Returning, no comma found");
		return 1;
	}

	*cp2 = 0;
	d->mfrom.nrcpts = atoi(cp);
	*cp2 = ',';

	if (o->dbg) {
		printf("NRCPT: [%d]\n", d->mfrom.nrcpts);
	}

	return 0;
}


int
m_get_from_size(struct options *o, char *p, unsigned long *size)
{
	char *cp,
	     *cp2;

	if (!p) {
		DBG("Possibly incorrect data line, no size");
		return 1;
	}

	cp = p + strlen(SIZE);

	if ( !(cp2 = strchr(cp, ','))) {
		DBG("Returning, no comma found");
		return 1;
	}
	*cp2 = 0;
	*size = atol(cp);
	*cp2 = ',';


	return 0;
}

int
m_getid(struct options *o, char *id, int size, char *buf)
{
	char *p;
	int i = 0;

	if ( !(p = strchr(buf, ']'))) {
		m_corruptline(o, buf);
		return 1;
	}

	while (!isalnum(*p)) {
		p++;
	}

	if (strncasecmp(p, M_LOGIN, strlen(M_LOGIN)) == 0 ||
	    strncasecmp(p, M_LOGOUT, strlen(M_LOGOUT)) == 0 ||
	    strncasecmp(p, M_DAEMON, strlen(M_DAEMON)) == 0 ||
	    strncasecmp(p, M_ALIAS, strlen(M_ALIAS)) == 0) {
		return 1;
	}

	while (*p != ':') {
		if (i >= size - 1) {
			id[i] = 0;
			return 1;
		}

		if (!isspace(*p)) {
			id[i++] = *p;
		}
		p++;
	}
	id[i] = 0;

	if (!i) {
		m_corruptline(o, buf);
		return 1;
	}

	return 0;
}


int
m_report(struct options *o, struct data *d)
{
	if (m_get_final(o, d) < 0) {
		return -1;
	}

	m_sort(o, d);

	if (m_print_output(o, d) < 0) {
		return -1;
	}

	if (o->showerrs) {
		m_show_errors(o, d);
	}

	if (!o->summary && !o->nostats) {
		m_show_stats(o, d);
	}

	return 0;
}


int
m_print_output(struct options *o, struct data *d)
{
	int i;

	if (!d->finali) {
		fprintf(stderr, "\
\n\
+++++++++++++++++++++++++++++++++++++++++++++\n\
+            No records found               +\n\
+ Are you sure this is a sendmail log file? +\n\
+++++++++++++++++++++++++++++++++++++++++++++\n\n");
		fflush(stderr);
		return -1;
	}

	if (o->dbg) {
		printf("\n---------[ OUTPUT FOLLOWS ]--------------\n\n");
	}

	if (!o->summary) {
                fprintf(d->out, "      [%s] [%s] ", o->machine, o->in);
                if (o->from) {
                        fprintf(d->out, "[Summary: From]");
                } else if (o->to) {
                        fprintf(d->out, "[Summary: To]");
		}
                fprintf(d->out, "\n\n");
	}


	for (i = 0; i < ( (o->max && o->max < d->finali) ? o->max : d->finali); i++) {

		d->total += d->mfinal[i]->size;

		if (o->max && !o->summary) {
			fprintf(d->out, "%3d. ", i + 1);
		}

		fprintf(d->out, 
		        "%s %c%4d%c", 
			!o->summary && d->mfinal[i]->size > o->big ? "*" : " ",
			o->summary ? ' ' : '[',
			d->mfinal[i]->num,
			o->summary ? ' ' : ']');
		fprintf(d->out, "%9lu ", d->mfinal[i]->size);
		
		if (o->from) {
			fprintf(d->out, "%-30s ", d->mfinal[i]->from);
		} else if (o->to) {
			fprintf(d->out, "%-30s ", d->mfinal[i]->to);
		} else {
			fprintf(d->out, "%-30s %-30s", 
					d->mfinal[i]->from, d->mfinal[i]->to);
		}
		fprintf(d->out, "\n");
	}

	return 0;
}


int
m_show_errors(struct options *o, struct data *d)
{
	int i;
	char tmp[32] = "";

	if (!d->erri) {
		return 0;
	}

	fprintf(d->out, "\n\n\tError information\n");
	fprintf(d->out, "\t-----------------\n");

	qsort(d->merrinfo, d->erri, sizeof(struct errinfo *), m_cmperrs);

	for (i = 0; i < d->erri; i++) {
		if (strcmp(d->merrinfo[i]->stat, tmp) != 0) {
			fprintf(d->out, "\n[%s]\n", d->merrinfo[i]->stat);
			strncpy(tmp, d->merrinfo[i]->stat, sizeof(tmp) - 1);
		}

		fprintf(d->out, "    o [%d] [%s]->[%s]\n",
			d->merrinfo[i]->num,
			d->merrinfo[i]->from,
			d->merrinfo[i]->to);
	}

	return 0;
}

int
m_show_stats(struct options *o, struct data *d)
{
	char t[32];
	int line = 0;

	fprintf(d->out, "\n\
=====================\n\
Total Bytes         : %lu\n\
Number of Records   : %d\n\
---------------------\n",
		d->total, 
		(o->max && o->max < d->finali) ? o->max : d->finali);

	m_errs_summary(d, d->merrs.deferred, &line, "Deferred Messages   :");
	m_errs_summary(d, d->merrs.hostunknown, &line, "Host Unknown        :");
	m_errs_summary(d, d->merrs.userunknown, &line, "User Unknown        :");
	m_errs_summary(d, d->merrs.eomerror, &line, "Premature EOM Error :");
	m_errs_summary(d, d->merrs.ioerror, &line, "I/O Error           :");
	m_errs_summary(d, d->merrs.noservice, &line, "Service Unavailable :");
	m_errs_summary(d, d->merrs.norelay, &line, "We Don't Relay      :");

	if (line) {
		fprintf(d->out, "---------------------\n");
	}

	fprintf(d->out, "\
Host Name           : %s\n\
Input File          : %s\n\
Output File         : %s\n\
First Record        : %s\n\
Last Record         : %s\n\
---------------------\n\
Time Taken          : %s\n\
=====================\n",
		o->machine,
		o->in,
		strlen(o->out) ? o->out : "stdout", 
		d->first, d->last,
		dx_timerStopPrint(&tv, t, sizeof(t) - 1));

	fprintf(d->out, "%s-%s by %s\n\n", 
				o->prog, M_VERSION, M_DEVELOPER);

	return 0;
}

int
m_errs_summary(struct data *d, int n, int *l, char *s)
{
	if (n) {
		fprintf(d->out, "%s %d\n", s, n);
		(*l)++;
	}

	return 0;
}


int
m_get_final(struct options *o, struct data *d)
{
	unsigned int x;
	int found = 0;
	char t[32];
	void *vp;
	struct hash_node *hn,
	                 *hn2;

	if (o->timer) {
		printf("Time [scanning file] : %s\n", 
			dx_timerStopPrint(&tv, t, sizeof(t) - 1));
	}

	for (x = 0; x < d->hfrom->h_hashsize; ++x) {
		for (hn = d->hfrom->h_hash[x]; hn; hn = hn->h_next) {
			found = 0;
			hn2 = NULL;

			while ( (vp = hash_lookup_str2(d->hto, 
						       (char *) hn->h_key,
						       &found,
						       &hn2))) {

				/*
				if (strlen( ((struct to *)(vp))->ctrl) &&
				    strcmp( ((struct from *)(hn->h_data))->email,
				    	    ((struct to *)(vp))->ctrl) != 0) {
					printf("ERROR_DUPLICATE\n");
					continue;
				}
				*/

				if ( !((struct to *)(vp))->status) {
					if (m_add_final(o, d, hn->h_data, vp) < 0) {
						return -1;
					}
				} else {
					if (m_add_error(o, d, hn->h_data, vp) < 0) {
						return -1;
					}
				}
			} 

			if (!found && o->dbg) {
				printf("NOT FOUND: %s [%9lu] [%s]\n",
					(char *) hn->h_key,
					((struct from *)(hn->h_data))->size,
					((struct from *)(hn->h_data))->email);
			}
		}
	}

	if (o->timer) {
		printf("Time [processing %d records]  : %s\n", 
			(o->max && o->max < d->finali) ? o->max : d->finali,
			dx_timerStopPrint(&tv, t, sizeof(t) - 1));
	}

	return 0;
}

int
m_add_error(struct options *o, struct data *d, void *from, void *to)
{
	int i;

	if ( (o->str || o->ex) && m_checkstring(o, 
				    ((struct from *)(from))->email,
				    ((struct to *)(to))->email)) {
		return 0;
	}

	for (i = 0; i < d->erri; i++) {
		if (strcasecmp(d->merrinfo[i]->from, 
			       ((struct from *)(from))->email) == 0 &&
		    strcasecmp(d->merrinfo[i]->to,
		               ((struct to *)(to))->email) == 0 && 
		    strcasecmp(d->merrinfo[i]->stat, 
		 	       ((struct to *)(to))->stat) == 0) {
			(d->merrinfo[i]->num)++;
			return 0;
		}
	}


	if (d->erri >= d->errcount) {
		if ( !(d->merrinfo = realloc( d->erri ? d->merrinfo : NULL,
		    (d->erri + 1 + M_INCREMENT) * sizeof(struct errinfo *) ))) {
			return (m_err(__LINE__, "realloc", strerror(errno)));
		}

		d->errcount += M_INCREMENT;
	}

	if ( !(d->merrinfo[d->erri] = malloc(sizeof(struct errinfo)))) {
		return (m_err(__LINE__, "malloc", strerror(errno)));
	}

	snprintf(d->merrinfo[d->erri]->from, 
		 sizeof(d->merrinfo[d->erri]->from) - 1,
		 ((struct from *)(from))->email);
	snprintf(d->merrinfo[d->erri]->to, 
		 sizeof(d->merrinfo[d->erri]->to) - 1,
		 ((struct to *)(to))->email);
	snprintf(d->merrinfo[d->erri]->stat,
		 sizeof(d->mfinal[d->finali]->to) - 1,
		 ((struct to *)(to))->stat);
	d->merrinfo[d->erri]->num = 1;

	(d->erri)++;

	return 0;
}



int
m_add_final(struct options *o, struct data *d, void *from, void *to)
{
	int i,
	    found;

	if ( (o->str || o->ex) && m_checkstring(o, 
				    ((struct from *)(from))->email,
				    ((struct to *)(to))->email)) {
		return 0;
	}

	for (i = found = 0; i < d->finali; i++) {
		if (o->from) {
			if (strcasecmp(d->mfinal[i]->from,
				((struct from *)(from))->email) == 0) {
				found = 1;
				break;
			}
		} else if (o->to) {
			if (strcasecmp(d->mfinal[i]->to,
				((struct to *)(to))->email) == 0) {
				found = 1;
				break;
			}
		} else {
			if (strcasecmp(d->mfinal[i]->to,
			             ((struct to *)(to))->email) == 0 &&
			    strcasecmp(d->mfinal[i]->from,
			    	      ((struct from *)(from))->email) == 0) {
				found = 1;
				break;
			}
		}
	}

	if (found) {
		d->mfinal[i]->size += ((struct from *)(from))->size;
		d->mfinal[i]->num++;
	} else {
		if (d->finali >= d->finalcount) {
			if ( !(d->mfinal = realloc( d->finali ? d->mfinal : NULL,
			    (d->finali + 1 + M_INCREMENT) * sizeof(struct final *) ))) {
				return (m_err(__LINE__, "realloc", strerror(errno)));
			}

			d->finalcount += M_INCREMENT;
		}

		if ( !(d->mfinal[d->finali] = malloc(sizeof(struct final)))) {
			return (m_err(__LINE__, "malloc", strerror(errno)));
		}

		memcpy(d->mfinal[d->finali]->from, 
		 	((struct from *)(from))->email, 
		 	sizeof(d->mfinal[d->finali]->from) - 1);
		d->mfinal[d->finali]->from[(strlen((char *)((struct from *)(from))->email))] = 0;
		memcpy(d->mfinal[d->finali]->to, 
		 	((struct to *)(to))->email, 
		 	sizeof(d->mfinal[d->finali]->to) - 1);
		d->mfinal[d->finali]->to[(strlen((char *)((struct to *)(to))->email))] = 0;

		d->mfinal[d->finali]->size = ((struct from *)(from))->size;
		d->mfinal[d->finali]->num = 1;

		(d->finali)++;
	}

	return 0;
}


int
m_sort(struct options *o, struct data *d)
{
	char t[32];

	switch (o->order) {
		case 2 :
			qsort(d->mfinal, 
			      d->finali, 
			      sizeof(struct final *), 
			      o->to ? m_cmpemailto : m_cmpemailfrom);
			break;
		case 1 :
			qsort(d->mfinal, 
			      d->finali, 
			      sizeof(struct final *), 
			      m_cmpnum);
			break;
		case 0 :
		default:
			qsort(d->mfinal, 
			      d->finali, 
			      sizeof(struct final *), 
			      m_cmpsize);
	}

	if (o->timer) {
		printf("Time [sorting]       : %s\n",
			dx_timerStopPrint(&tv, t, sizeof(t) - 1));
	}

	return 0;
}

int
m_cmpnum(const void *n1, const void *n2)
{
	struct final **ms1 = (struct final **) n1;
	struct final **ms2 = (struct final **) n2;
	int num1 = (*ms1)->num;
	int num2 = (*ms2)->num;

	if (num1 != num2) {
		return (num2 - num1);
	}

	return 0;
}

int
m_cmpsize(const void *s1, const void *s2)
{
	struct final **ms1 = (struct final **) s1;
	struct final **ms2 = (struct final **) s2;
	unsigned long n1 = (*ms1)->size;
	unsigned long n2 = (*ms2)->size;

	if (n1 == n2) {
		return 0;
	} else if (n1 > n2) {
		return -1;
	} else {
		return 1;
	}
}

int
m_cmpemailfrom(const void *s1, const void *s2)
{
	struct final **ms1 = (struct final**) s1;
	struct final **ms2 = (struct final **) s2;
	char *p1 = (*ms1)->from;
	char *p2 = (*ms2)->from;

	return (strcasecmp(p1, p2));
}


int
m_cmpemailto(const void *s1, const void *s2)
{
	struct final **ms1 = (struct final**) s1;
	struct final **ms2 = (struct final **) s2;
	char *p1 = (*ms1)->to;
	char *p2 = (*ms2)->to;

	return (strcasecmp(p1, p2));
}

int
m_get_email(char *str, char *email, int size)
{
	char *cp = str;
	int i = 0,
	    needgt = 0,
	    needq = 0;

	if (*cp == '<') {
		needgt = 1;
		cp++;
	}
	if (*cp == '"') {
		needq = 1;
		cp++;
	}

	while (*cp && *cp != ',' && i < size) {
		if ( (needgt && *cp == '>') ||
		     (needq && *cp == '"')) {
			break;
		}
		email[i++] = *cp++;
	}
	email[i] = 0;

	return (i);
}

int
m_cmperrs(const void *s1, const void *s2)
{
	struct errinfo **ms1 = (struct errinfo**) s1;
	struct errinfo **ms2 = (struct errinfo **) s2;
	char *p1 = (*ms1)->stat;
	char *p2 = (*ms2)->stat;

	return (strcasecmp(p1, p2));
}


int
m_checkstring(struct options *o, char *from, char *to)
{
	char *p,
	     *cp;

	if (o->to) {

		if (o->ex) {
			if (strstr(to, o->exclude)) {
				return 1;
			}
		}

		p = strrchr(to, '@');

		switch (o->str) {
			case 1 :
				if (!strstr(p ? p : to, o->string)) {
					return 1;
				}
				break;

			case 2 :
				if (!strstr(to, o->string2)) {
					return 1;
				}
				break;

			case 3 :
				if (!strstr(p ? p : to, o->string) ||
				    !strstr(to, o->string2)) {
					return 1;
				}
				break;

			default :
				return 0;
		}
	} else if (o->from) {

		if (o->ex) {
			if (strstr(from, o->exclude)) {
				return 1;
			}
		}

		p = strrchr(from, '@');

		switch (o->str) {
			case 1 :
				if (!strstr(p ? p : from, o->string)) {
					return 1;
				}
				break;

			case 2 :
				if (!strstr(from, o->string2)) {
					return 1;
				}
				break;

			case 3 :
				if (!strstr(p ? p : from, o->string) ||
				    !strstr(from, o->string2)) {
					return 1;
				}
				break;

			default :
				return 0;
		}
	} else {

		if (o->ex) {
			if ( (strstr(to, o->exclude)) ||
			    (strstr(from, o->exclude)) ) {
				return 1;
			}
		}

		p = strrchr(from, '@');
		cp = strrchr(to, '@');

		switch (o->str) {
			case 1 :
				if (!strstr(p ? p : from, o->string) &&
				    !strstr(cp ? cp : to, o->string)) {
					return 1;
				}
				break;

			case 2 :
				if (!strstr(from, o->string2) &&
				    !strstr(to, o->string2)) {
					return 1;
				}
				break;

			case 3 :
				if ( (!strstr(p ? p : from, o->string) ||
				      !strstr(from, o->string2)) &&
				    (!strstr(cp ? cp : to, o->string) ||
				     !strstr(to, o->string2)) ) {
					return 1;
				}
				break;

			default :
				return 0;
		}
	}

	return 0;
}

int
m_corruptline(struct options *o, char *buf)
{
	if (o->dbg) {
		printf(" -> Corrupt Line (ID), skipping : \n%s\n", buf);
	}

	return 0;
}

int
m_getconfig(struct options *o, int ac, char *av[])
{
	char *optstr = "i:o:c:hd:ftrsx:b:-vgm:el:p:an:yzu:",
	     *p,
	     t[32];
	int opt;

	dx_startTimer(&tv);

	memset(o, 0, sizeof(struct options));

	strncpy(o->prog,
		(p = strrchr(av[0], '/')) ? p + 1 : av[0],
		sizeof(o->prog) - 1);
		
	while ( (opt = getopt(ac, av, optstr)) != EOF) {
		switch (opt) {
			case 'i' :
				strncpy(o->in, optarg, sizeof(o->in) - 1);
				break;
			case 'o' :
				strncpy(o->out, optarg, sizeof(o->out) - 1);
				break;
			case 'c' :
				strncpy(o->config, optarg, sizeof(o->config) - 1);
				break;
			case 'd' :
				strncpy(o->domain, optarg, sizeof(o->domain) - 1);
				o->dom = 1;
				break;
			case 'f' :
				o->from = 1;
				break;
			case 't' :
				o->to = 1;
				break;
			case 'l' :
				strncpy(o->string, optarg, sizeof(o->string) - 1);
				o->str += 1;
				break;
			case 'p' :
				strncpy(o->string2, optarg, sizeof(o->string2) - 1);
				o->str += 2;
				break;
			case 'x' :
				strncpy(o->exclude, optarg, sizeof(o->exclude) - 1);
				o->ex = 1;
				break;
			case 'g' :
				o->dbg = 1;
				break;
			case 'm' :
				o->max = atoi(optarg);
				break;
			case 'n' :
				o->nice = atoi(optarg);
				break;
			case 'z' :
				o->timer = 1;
				break;
			case 'r' :
				o->order = 1;
				break;
			case 'e' :
				o->order = 2;
				break;
			case 's' :
				o->summary = 1;
				break;
			case 'b' :
				o->big = atoi(optarg);
				break;
			case 'v' :
				version(o->prog);
				return -1;
			case 'a' :
				o->showerrs = 1;
				break;
			case 'y' :
				o->nostats = 1;
				break;
			case 'u' :
				strncpy(o->aliases, optarg, sizeof(o->aliases) - 1);
				o->alias = 1;
				break;
			case '-' :
			case 'h' :
			default  :
				usage(o->prog);
				return -1;
		}
	}

	if (m_checkopt(o) < 0) {
		usage(o->prog);
		return -1;
	}

	if (o->timer) {
		printf("Time [getconfig]     : %s\n",
			dx_timerStopPrint(&tv, t, sizeof(t) - 1));
	}

	return 0;
}



int
m_checkopt(struct options *o)
{
	if (!strlen(o->in)) {
		DBG("Using Default Maillog");
		strncpy(o->in, DEFAULT_MAILLOG, sizeof(o->in) - 1);
	}

	if (o->from && o->to) {
		printf("Error: Summarise either -f <from> or -t <to>, not both\n\n");
		return -1;
	}

	if (gethostname(o->machine, sizeof(o->machine) - 1) < 0) {
		strncpy(o->machine, "Unknown", sizeof(o->machine) - 1);
	}

	if (o->big <= 0) {
		o->big = DEFAULT_BIG;
	}

        if (setpriority(PRIO_PROCESS, 0, o->nice) < 0) {
                fprintf(stderr, "Error : setpriority(%d): %s\n",
                        o->nice, strerror(errno));
        }

	DBG("- - - - - - - ");
	DBG(o->in);
	DBG(o->out);
	DBG("- - - - - - - ");

	return 0;
}

int
m_err(int line, char *s, char *err)
{
	fprintf(stderr, "[%d] Error: %s: %s\n", line, s, err);
	fflush(stderr);

	return -1;
}


void
version(char *progname)
{
	printf("%s-%s\n", progname, M_VERSION);
}


void
usage(char *progname) 
{
	printf("\
usage: %s [options]\n\
\n\
   -f | -t            Summarise from or to\n\
   -i <input file>    Mail Log File   (default: %s)\n\
   -o <output file>   Output File     (default: stdout)\n\
   -d <domain>        Append <domain> to addresses without one\n\
   -p <string>        Only addresses with <string> in them\n\
   -l <string>        Only addresses with <string> in domain\n\
   -x <string>        Exclude addresses containing <string>\n\
   -r                 Order by number of messages, not by size\n\
   -e                 Order by email address, not by size\n\
   -b <number>        Flag msgs >     (default: %d)\n\
   -n <number>        Nice (priority) (default: %d)\n\
   -m <number>        Show only <number> records\n\
   -s                 Short form (summary only)\n\
   -a                 Show error summary\n\
   -y                 Don't show statistical information\n\
   -v                 Version\n\
   -h                 Help\n\
\n\
Version: %s\n\
Author : %s\n",
	progname,
	DEFAULT_MAILLOG, DEFAULT_BIG, DEFAULT_NICE,
	M_VERSION, M_DEVELOPER);
}




syntax highlighted by Code2HTML, v. 0.9.1