/************************************************************
** $Id: edit.c,v 0.9 2003/12/29 16:45:38 bcx Exp bcx $
**
** Copyright Bryan Costales and Perforce Software, Inc. 2003
**
** This code is "open source" as defined by version 1.9 of
** the Open Source Definition from:
**
**	http://www.opensource.org/docs/definition.php.
**
** The is the slowedit interface for the milter.
**
*************************************************************/

# define EXTERN extern
# include "slow.h"

extern PRIV *Priv;		/* in test.c */
extern struct smfi_str Ctx;	/* in test.c */

static int EditMode = 0;

typedef struct {
	char *token;
	int   mode;
} EDITCOMMANDS;

EDITCOMMANDS EditCommands[] = {
# define EDITCOM_ADD		1
	{"add", 	EDITCOM_ADD},		/* add an IP event */
# define EDITCOM_DELETE		2
	{"delete", 	EDITCOM_DELETE},	/* delete an IP and all its events */
# define EDITCOM_LIST		3
	{"list", 	EDITCOM_LIST},		/* list all or selected IP addresses */
# define EDITCOM_FIND		4
	{"find", 	EDITCOM_FIND},		/* find by IP or events */
# define EDITCOM_PURGE		5
	{"purge", 	EDITCOM_PURGE},		/* purge old records */
# define EDITCOM_RE		8
	{"rebuild", 	EDITCOM_RE},		/* rebuild the database */
# define EDITCOM_SUM		9
	{"summarize", 	EDITCOM_SUM},		/* summarize an IP address */
	{NULL,		0},
};

bool
slowedit(int opt, int argc, char **argv)
{
	int ret, mode;
	EDITCOMMANDS *e;
	char *user;
	extern char *optarg;

	/*
	 * Format of an edit command is "slowedit command switches IP
	 * On entry to this routine, argv[0] is the command.
	 */
	if (argv[opt] == NULL)
		goto usage;
	mode = 0;
	for (e = EditCommands; e->token != NULL; ++e)
	{
		if (strcasecmp(argv[opt], e->token) == 0)
		{
			mode = e->mode;
			break;
		}
	}
	if (mode == 0)
	{
usage:
		printf("Usage: slowedit ");
		for (e = EditCommands; e->token != NULL; ++e)
			printf("%s%s",
				e == EditCommands ? "" : "|",
				e->token);
		printf("\n");
		return FALSE;
	}

	ret = 0;
	user = cuserid(NULL);
	EditMode = mode;
	switch(mode)
	{
	    /* add IP event */
	    case EDITCOM_ADD:
		ret = slowadd(user, opt, argc, argv);
		break;

	    case EDITCOM_DELETE:
		ret = slowdelete(user, opt, argc, argv);
		break;

	    case EDITCOM_LIST:
		ret = slowlist(user, opt, argc, argv);
		break;

	    case EDITCOM_FIND:
		ret = slowfind(user, opt, argc, argv);
		break;

	    case EDITCOM_RE:
		ret = slowrebuild(user, opt, argc, argv);
		break;

	    case EDITCOM_PURGE:
	    case EDITCOM_SUM:
		ret = slowsummarize(user, opt, argc, argv);
		break;
	}
	return ret;
}

bool
slowadd(char *user, int opt, int argc, char **argv)
{
	char *iptext;
	char *event_str;
	char logprefix[BUFSIZ];

	test_init(TRUE);
	++opt;
	if ((argc - opt) < 2)
	{
		(void) printf("Usage: slowedit add IPnumber \"event,event,...\"\n");
		show_events("Combine events from");
		return FALSE;
	}
	iptext = try_strdup(__FILE__, __LINE__, argv[opt], FALSE);
	++opt;
	event_str = try_strdup(__FILE__, __LINE__, argv[opt], FALSE);
	if (slowsetpriv(iptext, event_str, FALSE) == FALSE)
		return FALSE;

	if (user == NULL)
		sprintf(logprefix, "edit add: ");
	else
		sprintf(logprefix, "%s added: ", user);
	LogPrefix = logprefix;

	Dp = open_database(DatabaseLoc, FALSE);

	Priv->ip = revbytes(inet_network(Priv->iptxt));
	(void)smfi_setpriv(&Ctx, Priv);
	/*
	 * A time of zero means use the current time.
	 */
	(void) push_to_db(&Ctx, (time_t)0, FALSE);

	if (! isdebug(BUG_DELETE))
	{
		(void) printf("slowedit: add %s by %s succeeded\n",
			iptext,
			user == NULL ? "noody" : user);
	}

	(void) free(event_str);
	return TRUE;
}

bool
slowdelete(char *user, int opt, int argc, char **argv)
{
	time_t t;
	char *iptext;
	PRIMARY_KEY ip;
	int ret;
	int cnt;

	test_init(TRUE);

	++opt;
	if ((argc - opt) < 1)
	{
		(void) printf("Usage: slowedit delete IPnumber [date]\n");
		(void) printf("\tIf date missing, remove all the IP's records\n");
		(void) printf("\telse remove just that event record.\n");
		return FALSE;
	}

	iptext = try_strdup(__FILE__, __LINE__, argv[opt], FALSE);
	ip = inet_network(iptext);
	if ((int)ip == -1)
	{
		(void) printf("slowedit delete: %s: cannot interpret\n", iptext);
		return FALSE;
	}
	ip = revbytes(ip);

	++opt;
	if (argv[opt] == NULL)
		t = 0;
	else
		t = parse_date(argv[opt]);

	Dp = open_database(DatabaseLoc, FALSE);
	cnt = 0;
	ret = del_event_database(Dp, &ip, t);
	if (ret != 0)
	{
		struct tm *tt;
		char buf[BUFSIZ];

		tt = localtime(&t);
		(void) sprintf(buf, "%02d/%02d/%d.%02d:%02d:%02d",
			tt->tm_mon + 1, tt->tm_mday, tt->tm_year + 1900,
			tt->tm_hour, tt->tm_min, tt->tm_sec);

		(void) printf("slowedit: delete %s %s %s\n",
			iptext, buf,
			ret == DB_NOTFOUND ? "Entry specified not in datbase":
			strerror(errno));
		return FALSE;
	}

# if HAVE_SYSLOG
	if (DoLog == TRUE && LogEvents == TRUE)
		syslog(LOG_INFO, "%s deleted %s %s\n",
			user == NULL ? Prog : user,
			iptext,
			t == 0 ? "" : argv[opt]);
# endif
	(void) printf("slowedit: %s successfully deleted %s event %s\n",
		user == NULL ? Prog : user, iptext,
		t == 0 ? "all-events" : argv[opt]);
	return TRUE;
}

bool
slowlist(char *user, int opt, int argc, char **argv)
{
	char *iptext;
	PRIMARY_KEY ip;

	test_init(TRUE);

	++opt;
	if (argc <= opt)
	{
		printf("Usage: slowedit list [IPnumber | \"all\"]\n");
		return FALSE;
	}
	iptext = try_strdup(__FILE__, __LINE__, argv[opt], FALSE);

	/*
	** Close and reopen the database read-only.
	*/
	Dp = open_database(DatabaseLoc, DBREADONLY);

	if (strcasecmp(iptext, "all") == 0)
	{
		if (dump_events(Dp, stdout, FALSE, 0) != 0)
		{
			milterr(Prog, __FILE__, __LINE__, FALSE,
				errno, iptext, "list all failed");
		}
		return TRUE;
	}

	ip = inet_network(iptext);
	if ((int)ip == -1)
	{
		(void) printf("slowedit list: %s: cannot interpret\n",
			iptext);
		return FALSE;
	}
	ip = revbytes(ip);

	if (dump_events(Dp, stdout, FALSE, ip) != 0)
	{
		milterr(Prog, __FILE__, __LINE__, FALSE,
			errno, iptext, "list all failed");
	}

	return TRUE;
}

bool
slowfind(char *user, int opt, int argc, char **argv)
{
	LOGICSTRUCT *logic;
	DBT key, data;
	SECONDARY_DATUM *event;
	PRIMARY_KEY ip;
	DBC *curs;
	int ret;
	struct in_addr in;
	char ipbuf[MAXHOSTNAMELEN];
	char *cp;
	BITMAP set;

	test_init(TRUE);
	++opt;
	if (argc == opt)
	{
			(void) printf("Usage: slowedit find \"[and|or],relation,relation,...\"\n");
		return FALSE;
	}
	cp = try_strdup(__FILE__, __LINE__, argv[opt], FALSE);
	logic = slowlogicparse(cp);
	if (logic == NULL)
		return FALSE;

	Dp = open_database(DatabaseLoc, DBREADONLY);
	ret = Dp->cursor(Dp, NULL, &curs, 0);
	if (ret != 0)
	{
		Dp->err(Dp, ret, "%s\n", DatabaseLoc);
		return ret;
	}
	(void) memset(&data, 0, sizeof data);
	(void) memset(&key, 0, sizeof key);
	key.flags = DB_DBT_REALLOC;
	data.flags = DB_DBT_REALLOC;
	for (;;)
	{
		ret = curs->c_get(curs, &key, &data, DB_NEXT);
		if (ret == DB_NOTFOUND)
			break;
		if (ret != 0)
		{
			Dp->err(Dp, ret, "%s\n", DatabaseLoc);
			return ret;
		}
		if (key.data == NULL || data.data == NULL)
		{
			continue;
		}

		event = (SECONDARY_DATUM *)data.data;
		ip = *((PRIMARY_KEY *)key.data);
		(void) memcpy(&in.s_addr, ((char *)&ip), sizeof(in.s_addr));

		in.s_addr = htonl(in.s_addr);
		(void) sprintf(ipbuf, "%s", inet_ntoa(in));

		if (logic->andor == SLOWBLOGICAND)
		{
			/*
			 * Since logic AND means everything must match
			 * we continue (skip printing) on any mismatch.
			 */
			switch(logic->envrcpts.relation)
			{
				case SLOWLOGICEQUAL:
					if (event->envrcpts != logic->envrcpts.value)
						continue;
					break;
				case SLOWLOGICGREATER:
					if (event->envrcpts <= logic->envrcpts.value)
						continue;
					break;
				case SLOWLOGICLESSTHAN:
					if (event->envrcpts >= logic->envrcpts.value)
						continue;
					break;
				case SLOWLOGICNONE:
					break;
				default:
					continue;
			}
			switch(logic->hdrrcpts.relation)
			{
				case SLOWLOGICEQUAL:
					if (event->headrcpts != logic->hdrrcpts.value)
						continue;
					break;
				case SLOWLOGICGREATER:
					if (event->headrcpts <= logic->hdrrcpts.value)
						continue;
					break;
				case SLOWLOGICLESSTHAN:
					if (event->headrcpts >= logic->hdrrcpts.value)
						continue;
					break;
				case SLOWLOGICNONE:
					break;
				default:
					continue;
			}
			switch(logic->honeyrcpts.relation)
			{
				case SLOWLOGICEQUAL:
					if (event->honeyrcpts != logic->honeyrcpts.value)
						continue;
					break;
				case SLOWLOGICGREATER:
					if (event->honeyrcpts <= logic->honeyrcpts.value)
						continue;
					break;
				case SLOWLOGICLESSTHAN:
					if (event->honeyrcpts >= logic->honeyrcpts.value)
						continue;
					break;
				case SLOWLOGICNONE:
					break;
				default:
					continue;
			}
			switch(logic->bad.relation)
			{
				case SLOWLOGICEQUAL:
					if (event->badrcpts != logic->bad.value)
						continue;
					break;
				case SLOWLOGICGREATER:
					if (event->badrcpts <= logic->bad.value)
						continue;
					break;
				case SLOWLOGICLESSTHAN:
					if (event->badrcpts >= logic->bad.value)
						continue;
					break;
				case SLOWLOGICNONE:
					break;
				default:
					continue;
			}
			if (logic->bits[SLOWSETBITS] != 0)
			{
				/*
				 * The specified bits must be set 
				 */
				set = logic->bits[SLOWSETBITS] & event->eventmap;
				if (set == 0)
					continue;
			}
			if (logic->bits[SLOWONLYBITS] != 0)
			{
				/*
				 * The specified bits must be the only ones set 
				 */
				set = logic->bits[SLOWONLYBITS] - event->eventmap;
				if (set != 0)
					continue;
			}
			if (logic->bits[SLOWCLEARBITS] != 0)
			{
				/*
				 * The specified bits must be clear (0)
				 */
				set = logic->bits[SLOWCLEARBITS] & ~(event->eventmap);
				if (set == 0)
					continue;
			}
			if (logic->reg != NULL && event->msgid[0] != '\0')
			{
# if HAVE_REGEX_H && HAVE_REGCOMP
				regmatch_t pmatch;
				int rerr;

				rerr = regexec(logic->reg, event->msgid, 1, &pmatch, 0);
				if (rerr != 0)
					continue;
# else
				if (strstr(event->msgid, logic->reg) != 0)
					continue;
# endif
			}
			else if (logic->reg != NULL)
				continue;

			(void) printf("%s\n", event_to_str(ipbuf, event->date, event, FALSE));
			continue;
		}
		/*
		 * Since logic OR means anythin may match
		 * jump to print on any match.
		 */
		switch(logic->envrcpts.relation)
		{
			case SLOWLOGICEQUAL:
				if (event->envrcpts == logic->envrcpts.value)
					goto print;
				break;
			case SLOWLOGICGREATER:
				if (event->envrcpts > logic->envrcpts.value)
					goto print;
				break;
			case SLOWLOGICLESSTHAN:
				if (event->envrcpts < logic->envrcpts.value)
					goto print;
				break;
		}
		switch(logic->hdrrcpts.relation)
		{
			case SLOWLOGICEQUAL:
				if (event->headrcpts == logic->hdrrcpts.value)
					goto print;
				break;
			case SLOWLOGICGREATER:
				if (event->headrcpts > logic->hdrrcpts.value)
					goto print;
				break;
			case SLOWLOGICLESSTHAN:
				if (event->headrcpts < logic->hdrrcpts.value)
					goto print;
				break;
		}
		switch(logic->honeyrcpts.relation)
		{
			case SLOWLOGICEQUAL:
				if (event->honeyrcpts == logic->honeyrcpts.value)
					goto print;
				break;
			case SLOWLOGICGREATER:
				if (event->honeyrcpts > logic->honeyrcpts.value)
					goto print;
				break;
			case SLOWLOGICLESSTHAN:
				if (event->honeyrcpts < logic->honeyrcpts.value)
					goto print;
				break;
		}
		switch(logic->bad.relation)
		{
			case SLOWLOGICEQUAL:
				if (event->badrcpts == logic->bad.value)
					goto print;
				break;
			case SLOWLOGICGREATER:
				if (event->badrcpts > logic->bad.value)
					goto print;
				break;
			case SLOWLOGICLESSTHAN:
				if (event->badrcpts < logic->bad.value)
					goto print;
				break;
		}
		if (logic->bits[SLOWSETBITS] != 0)
		{
			/*
			 * The specified bits must be set 
			 */
			set = logic->bits[SLOWSETBITS] & event->eventmap;
			if (set != 0)
				goto print;
		}
		if (logic->bits[SLOWONLYBITS] != 0)
		{
			/*
			 * The specified bits must be the only ones set 
			 */
			set = logic->bits[SLOWONLYBITS] - event->eventmap;
			if (set == 0)
				goto print;
		}
		if (logic->bits[SLOWCLEARBITS] != 0)
		{
			/*
			 * The specified bits must be clear (0)
			 */
			set = logic->bits[SLOWCLEARBITS] & ~(event->eventmap);
			if (set != 0)
				goto print;
		}
		if (logic->reg != NULL && event->msgid[0] != '\0')
		{
# if HAVE_REGEX_H && HAVE_REGCOMP
			regmatch_t pmatch;

			if (regexec(logic->reg, event->msgid, 1, &pmatch, 0) == 0)
# else
			if (strstr(event->msgid, logic->reg) == 0)
# endif
				goto print;
			
		}
		continue;
print:
			(void) printf("%s\n", event_to_str(ipbuf, event->date, event, FALSE));
		continue;
	}
	(void) curs->c_close(curs);
	(void) close_database(Dp);
	return TRUE;
}


bool
slowrebuild(char *user, int opt, int argc, char **argv)
{

	char buf[BUFSIZ];
	char *cp;
	char *ep;
	char *ipdb;
	char *prdate;
	int dolog;
	time_t t, s, e;
	int count;

	++opt;
	if ((argc - opt) < 2)
	{
		(void) printf("Usage: slowedit rebuild newdb < textfile\n");
		return FALSE;
	}
	ipdb = try_strdup(__FILE__, __LINE__, argv[opt], FALSE);

	/*
	 * Open and initialize the new database.
	 */
	Dp = open_database(ipdb, TRUE);

# if HAVE_SYSLOG
	dolog = DoLog;
	DoLog = FALSE;
	LogEvents = FALSE;
# endif

	count = 0;
	while (fgets(buf, BUFSIZ, stdin) != NULL)
	{
		if ((cp = strchr(buf, '\n')) != NULL)
			*cp = '\0';

		cp = strchr(buf, ':');
		if (cp == NULL)
			continue;
		*cp = '\0';

		cp +=2;
		ep = strchr(cp, ' ');
		if (ep == NULL)
			continue;
		*ep = '\0';
		prdate = cp;
		t = parse_date(cp);
		cp = ep+1;

		if (Verbose)
			(void)time(&s);
		test_init(TRUE);
		Priv->ip = revbytes(inet_network(buf));
		if (slowsetpriv(buf, cp, TRUE) == FALSE)
			continue;
		(void)smfi_setpriv(&Ctx, Priv);
		(void)push_to_db(&Ctx, (time_t)t, FALSE);
		if (Verbose)
		{
			(void)time(&e);
			printf("%s %s added in %d seconds\n",
				buf, prdate, (int)(e-s));
		}
		if ((++count % 25) == 0)
			printf("%d records added\n", count);
	}
# if HAVE_SYSLOG
	if (dolog == TRUE)
	{
		syslog(LOG_INFO, "rebuild %s ipdb=%s",
			user == NULL ? "" : user,
			ipdb);
	}
# endif
	printf("%d total records added\n", count);
	return TRUE;
}

bool
slowsummarize(char *user, int opt, int argc, char **argv)
{
	DBT key, data;
	PRIMARY_KEY ip = 0;
	int ret;
	time_t t, now;
	char *iptext = NULL;
	PRIMARY_KEY p;
	SECONDARY_DATUM *event;
	SECONDARY_SUMMARY *sum;
	int c;
	DBC *curs;
	extern int errno;

	test_init(TRUE);
	++opt;
	if (argv[opt] == NULL)
	{
		if (EditMode == EDITCOM_PURGE)
			printf("Usage: slowedit purge interval units\n");
		else
			printf("Usage: slowedit summarize IPnumber interval units\n");
		return FALSE;
	}
	if (EditMode == EDITCOM_SUM)
	{
		ip = revbytes(inet_network(argv[opt]));
		iptext = argv[opt];
		++opt;
	}
	if (argv[opt] == NULL || (int)(t = atoi(argv[opt])) <= 0)
	{
		printf("slowedit %s: interval may not be <= 0\n",
			EditMode == EDITCOM_SUM ? "summarize":"purge");
		return FALSE;
	}

	if (argv[++opt] != NULL)
	{
		c = argv[opt][0];
		if (isupper(c))
			c = tolower(c);
		switch((int)argv[opt][0])
		{
			case 's':	/* seconds */
				break;
			case 'm':	/* minutes */
				t = t * 60;
				break;
			case 'h':	/* hours */
				t = t * 60 * 60;
				break;
			case 'd':	/* days */
				t = t * 60 * 60 * 24;
				break;
			case 'w':	/* weeks */
				t = t * 60 * 60 * 24 * 7;
				break;
			default:
				printf("slowedit summarize: %c unrecognized units\n", c);
				return FALSE;
		}
	}
	else
		t = t * 24 * 60 * 60; /* default is days */

	(void)time(&now);
	if (t > 0)
		t = now - t;

	test_init(TRUE);
	if (EditMode == EDITCOM_PURGE)
	{
		int cnt;

		Dp = open_database(DatabaseLoc, FALSE);
		ret = Dp->cursor(Dp, NULL, &curs, 0);
		if (ret != 0)
		{
			Dp->err(Dp, ret, "%s\n", DatabaseLoc);
			return ret;
		}
		(void) memset(&data, 0, sizeof data);
		(void) memset(&key, 0, sizeof key);
		key.flags = DB_DBT_REALLOC;
		data.flags = DB_DBT_REALLOC;

		cnt = 0;
		for (;;)
		{
			ret = curs->c_get(curs, &key, &data, DB_NEXT);
			if (ret == DB_NOTFOUND)
					break;
			if (ret != 0)
			{
				Dp->err(Dp, ret, "%s\n", DatabaseLoc);
				(void) curs->c_close(curs);
				(void) close_database(Dp);
				return ret;
			}
			if (key.data == NULL || data.data == NULL)
				continue;

			event = (SECONDARY_DATUM *)data.data;
			p = *((PRIMARY_KEY *)key.data);

			if (p == DATABASE_VERSION)
				continue;

			if (event->date >= t)
				continue;

			ret = curs->c_del(curs, 0);
			if (ret != 0)
			{
				Dp->err(Dp, ret, "%s\n", DatabaseLoc);
				(void) curs->c_close(curs);
				(void) close_database(Dp);
				return ret;
			}
			++cnt;
		}
		printf("slowedit purge: removed %d old records\n", cnt);
		(void) curs->c_close(curs);
		(void) close_database(Dp);
		return TRUE;

	}
	sum = try_alloc(__FILE__, __LINE__, sizeof(SECONDARY_SUMMARY), FALSE);
	(void) memset((char *)sum, '\0', sizeof(SECONDARY_SUMMARY));

	Dp = open_database(DatabaseLoc, DBREADONLY);
	ret = Dp->cursor(Dp, NULL, &curs, 0);
	if (ret != 0)
	{
		Dp->err(Dp, ret, "%s\n", DatabaseLoc);
		return ret;
	}
	(void) memset(&data, 0, sizeof data);
	(void) memset(&key, 0, sizeof key);
	key.flags = DB_DBT_REALLOC;
	data.flags = DB_DBT_REALLOC;

	for (;;)
	{
		ret = curs->c_get(curs, &key, &data, DB_NEXT);
		if (ret == DB_NOTFOUND)
				break;
		if (ret != 0)
		{
			Dp->err(Dp, ret, "%s\n", DatabaseLoc);
			(void) curs->c_close(curs);
			(void) close_database(Dp);
			return ret;
		}
		if (key.data == NULL || data.data == NULL)
			continue;

		event = (SECONDARY_DATUM *)data.data;
		p = *((PRIMARY_KEY *)key.data);

		if (p != ip)
			continue;

		if (p == DATABASE_VERSION)
			continue;

		if (event->date < t)
			continue;

		sum->count      += 1;
		sum->envrcpts   += event->envrcpts;
		sum->headrcpts  += event->headrcpts;
		sum->honeyrcpts += event->honeyrcpts;
		sum->badrcpts   += event->badrcpts;
		if (event->eventmap != 0)
		{
			if (isbitset(event->eventmap, BIT_TOO_MANY_HDR_RCPTS))
				sum->hed += 1;
			if (isbitset(event->eventmap, BIT_TOO_MANY_ENV_RCPTS))
				sum->env += 1;
			if (isbitset(event->eventmap, BIT_GOT_HONEYPOT))
				sum->hon += 1;
			if (isbitset(event->eventmap, BIT_BAD_MSG_ID))
				sum->mid += 1;
			if (isbitset(event->eventmap, BIT_BAD_FROM_HEAD))
				sum->frm += 1;
			if (isbitset(event->eventmap, BIT_BAD_HOST))
				sum->bho += 1;
			if (isbitset(event->eventmap, BIT_RBL_BAD))
				sum->rbl += 1;
			if (isbitset(event->eventmap, BIT_MILTER_ABORTED))
				sum->abo += 1;
			if (isbitset(event->eventmap, BIT_ADVANCEWRITE))
				sum->pip += 1;
			if (isbitset(event->eventmap, BIT_FORGED))
				sum->frg += 1;
			if (isbitset(event->eventmap, BIT_FROMMX))
				sum->mmx += 1;
			if (isbitset(event->eventmap, BIT_NOTINET))
				sum->net += 1;
			if (isbitset(event->eventmap, BIT_ADDEDIP))
				sum->aip += 1;
		}
		
	}
	(void) curs->c_close(curs);
	(void) close_database(Dp);

	printf("%s:%33d record%s:\n", iptext, sum->count,
		sum->count == 1 ? "" : "s");
	if (sum->count == 0)	/* prevent division by zero */
		sum->count = 1;
	printf("\t%-30.30s=%9d (%d%%)\n",
		"Whitelisted Address", sum->whitelisted,
		(int)(((float)sum->whitelisted/(float)sum->count)*100.0) );
	printf("\t%-30.30s=%9d (%d%%)\n",
		"Envelope Recipients", sum->envrcpts,
		(int)(((float)sum->envrcpts/(float)sum->count)*100.0) );
	printf("\t%-30.30s=%9d (%d%%)\n",
		"Header Recipients", sum->headrcpts,
		(int)(((float)sum->headrcpts/(float)sum->count)*100.0) );
	printf("\t%-30.30s=%9d (%d%%)\n",
		"Honey Pot Recipients", sum->honeyrcpts,
		(int)(((float)sum->honeyrcpts/(float)sum->count)*100.0) );
	printf("\t%-30.30s=%9d (%d%%)\n",
		"Bad Recipients", sum->badrcpts,
		(int)(((float)sum->badrcpts/(float)sum->count)*100.0) );
	printf("\t%-30.30s=%9d (%d%%)\n",
		"Excess Header Recipients", sum->hed,
		(int)(((float)sum->hed/(float)sum->count)*100.0) );
	printf("\t%-30.30s=%9d (%d%%)\n",
		"Excess Envelope Recipients", sum->env,
		(int)(((float)sum->env/(float)sum->count)*100.0) );
	printf("\t%-30.30s=%9d (%d%%)\n",
		"Any Honey Pot Recipients", sum->hon,
		(int)(((float)sum->hon/(float)sum->count)*100.0) );
	printf("\t%-30.30s=%9d (%d%%)\n",
		"Bad Message-Id Headers", sum->mid,
		(int)(((float)sum->mid/(float)sum->count)*100.0) );
	printf("\t%-30.30s=%9d (%d%%)\n",
		"Bad Connecting Hosts", sum->bho,
		(int)(((float)sum->bho/(float)sum->count)*100.0) );
	printf("\t%-30.30s=%9d (%d%%)\n",
		"Realtime Black Hole Rejects", sum->rbl,
		(int)(((float)sum->rbl/(float)sum->count)*100.0) );
	printf("\t%-30.30s=%9d (%d%%)\n",
		"Milter Aborts", sum->abo,
		(int)(((float)sum->abo/(float)sum->count)*100.0) );
	printf("\t%-30.30s=%9d (%d%%)\n",
		"Illegal Pipe-Lining Attempts", sum->pip,
		(int)(((float)sum->pip/(float)sum->count)*100.0) );
	printf("\t%-30.30s=%9d (%d%%)\n",
		"Sendmail said \"forged\"", sum->frg,
		(int)(((float)sum->frg/(float)sum->count)*100.0) );
	printf("\t%-30.30s=%9d (%d%%)\n",
		"Received from our MX server", sum->mmx,
		(int)(((float)sum->mmx/(float)sum->count)*100.0) );
	printf("\t%-30.30s=%9d (%d%%)\n",
		"Not An IP Network", sum->net,
		(int)(((float)sum->net/(float)sum->count)*100.0) );
	printf("\t%-30.30s=%9d (%d%%)\n",
		"An Aliased IP entry", sum->aip,
		(int)(((float)sum->aip/(float)sum->count)*100.0) );

	printf("::\n");

	return TRUE;
}

typedef struct {
	char *event;
	int   index;
} EVENT_LIST;

EVENT_LIST EventList[] = {
# define EVENT_ENVRCPTS		1
	{"er", EVENT_ENVRCPTS},	/* envelope recipients */
# define EVENT_HDRRCPTS		2
	{"hr", EVENT_HDRRCPTS},	/* header recipients */
# define EVENT_HONEYRCPTS	3
	{"ho", EVENT_HONEYRCPTS},	/* honey pot recipients */
# define EVENT_BADRCPTS		4
	{"br", EVENT_BADRCPTS},	/* bad envelope recipients */
# define EVENT_TOOHDR		5
	{"hed", EVENT_TOOHDR},	/* too many header recipients */
# define EVENT_TOOENV		6
	{"env", EVENT_TOOENV},	/* too many envelope recipients */
# define EVENT_HONEY		7
	{"hon", EVENT_HONEY},	/* one or more honey pots rcpts */
# define EVENT_MSGID		8
	{"mid", EVENT_MSGID},	/* bad or missing message-id header */
# define EVENT_FROMH		9
	{"frm", EVENT_FROMH},	/* bad from header */
# define EVENT_BADHO		10
	{"bho", EVENT_BADHO},	/* bad envelope sender host */
# define EVENT_RBL		11
	{"rbl", EVENT_RBL},	/* reported bad by an RBL host */
# define EVENT_MILT		12
	{"abo", EVENT_MILT},	/* The milter aborted */
# define EVENT_ADVW		13
	{"pip", EVENT_ADVW},	/* Attempt at an advanced write */
# define EVENT_FORGE		14
	{"for", EVENT_FORGE},	/* sendmail reported this as forged */
# define EVENT_FROMMX		15
	{"mmx", EVENT_FROMMX},	/* received from one of our MX servers */
# define EVENT_NOTINET		16
	{"net", EVENT_NOTINET},	/* not received via a TCP/IP network socket */
# define EVENT_ADDEDIP		17
	{"aip", EVENT_ADDEDIP},	/* This is an alias of another IP address */
# define EVENT_MESGID		18
	{"msgid", EVENT_MESGID},	/* The text that is the message-id header */
	{NULL,	0},
};

void
show_events(char *str)
{
	EVENT_LIST *e;
	int count;

	printf("%s:\n\t", str);
	count = 0;
	for (e = EventList; e->event != NULL; ++e)
	{
		if (++count == 6)
		{
			printf("\n\t");
			count = 0;
		}
		else if (e != EventList)
			printf(", ");
		if (strlen(e->event) == 2)
			printf("%s=#", e->event);
		else
			printf("%s", e->event);
	}
	printf("\n");
}
int
is_event(char *str)
{
	EVENT_LIST *e;

	for (e = EventList; e->event != NULL; ++e)
	{
		if (strcasecmp(e->event, str) == 0)
			return e->index;
	}
	return 0;
}

bool
slowsetpriv(char *iptext, char *estr, int rebuild)
{
	char **list, **l;
	char **equate;
	char *item;
	int   value;
	int   index;

	list = mkargv(estr, ',', FALSE);
	equate = NULL;
	if (list == NULL)
	{
		if (rebuild == TRUE)
			return TRUE;
		(void) printf("slowedit: events missing\n");
		return FALSE;
	}
	if (isdebug(BUG_EDIT))
		print_list("slowsetpriv", list, stdout);

	for (l = list; *l != NULL; l++)
	{
		value = -1;
		if (strchr(*l, '=') != NULL)
		{
			equate = mkargv(*l, '=', FALSE);
			if (equate == NULL)
				continue;
			item = try_strdup(__FILE__, __LINE__, equate[0], FALSE);
			if (equate[1] != NULL)
				value = atoi(equate[1]);
			else
				value = 0;
		}
		else
			item = try_strdup(__FILE__, __LINE__, *l, FALSE);

		index = is_event(item);
		if (index == 0)
		{
			(void) printf("%s: Unrecognized event keyword\n", item);
			show_events("Combine events from");
			return FALSE;
		}

		if (strlen(item) == 3 && value >= 0)
		{
			(void) printf("slowedit: %s: Three letter events are boolean.", item);
			(void) printf(" Do not use %s=value form.\n", item);
			return FALSE;
		}

		switch(index)
		{
		    /* count of envelope recipients */
		    case EVENT_ENVRCPTS:
			if (value < 0 && rebuild == FALSE)
			{
				milterr(Prog, __FILE__, __LINE__, FALSE,
					EINVAL, item,
					"May not be negative");
			}
			else if (value < 0 && rebuild == TRUE)
				value = 0;
			Priv->envrcpts = value;
			break;

		    case EVENT_HDRRCPTS:
			if (value < 0 && rebuild == FALSE)
			{
				milterr(Prog, __FILE__, __LINE__, FALSE,
					EINVAL, item,
					"May not be negative");
			}
			else if (value < 0 && rebuild == TRUE)
				value = 0;
			Priv->nhdrrcpts = value;
			break;

		    case EVENT_HONEYRCPTS:
			if (value < 0 && rebuild == FALSE)
			{
				milterr(Prog, __FILE__, __LINE__, FALSE,
					EINVAL, item,
					"May not be negative");
			}
			else if (value < 0 && rebuild == TRUE)
				value = 0;
			Priv->honeyrcpts = value;
			break;

		    case EVENT_BADRCPTS:
			if (value < 0 && rebuild == FALSE)
			{
				milterr(Prog, __FILE__, __LINE__, FALSE,
					EINVAL, item,
					"May not be negative");
			}
			else if (value < 0 && rebuild == TRUE)
				value = 0;
			Priv->badrcpts = value;
			break;

		    case EVENT_TOOHDR:
			set_bit(BIT_TOO_MANY_HDR_RCPTS, Priv->bits);
			break;

		    case EVENT_TOOENV:
			set_bit(BIT_TOO_MANY_ENV_RCPTS, Priv->bits);
			break;

		    case EVENT_HONEY:
			set_bit(BIT_GOT_HONEYPOT, Priv->bits);
			break;

		    case EVENT_MSGID:
			set_bit(BIT_BAD_MSG_ID, Priv->bits);
			break;

		    case EVENT_FROMH:
			set_bit(BIT_BAD_FROM_HEAD, Priv->bits);
			break;

		    case EVENT_BADHO:
			set_bit(BIT_BAD_HOST, Priv->bits);
			break;

		    case EVENT_RBL:
			set_bit(BIT_RBL_BAD, Priv->bits);
			break;

		    case EVENT_MILT:
			set_bit(BIT_MILTER_ABORTED, Priv->bits);
			break;

		    case EVENT_ADVW:
			set_bit(BIT_ADVANCEWRITE, Priv->bits);
			break;

		    case EVENT_FORGE:
			set_bit(BIT_FORGED, Priv->bits);
			break;

		    case EVENT_FROMMX:
			set_bit(BIT_FROMMX, Priv->bits);
			break;

		    case EVENT_NOTINET:
			set_bit(BIT_NOTINET, Priv->bits);
			break;
		    
		    case EVENT_ADDEDIP:
			set_bit(BIT_ADDEDIP, Priv->bits);
			break;
		    
		    case EVENT_MESGID:
			if (equate != NULL && equate[1] != NULL)
				Priv->midstr = try_strdup(__FILE__, __LINE__, equate[1], FALSE);
			break;
		}
		(void) free(item);
	}
	Priv->iptxt = try_strdup(__FILE__, __LINE__, iptext, FALSE);
	return TRUE;
}

LOGICSTRUCT *
slowlogicparse(char *estr)
{
	char **list, **l;
	char **equate;
	char *item;
	int   val;
	int   index;
	int   ch, rel, whichbits;
	LOGICSTRUCT *logic;

	list = mkargv(estr, ',', FALSE);
	if (list == NULL)
	{
		(void) printf("slowedit: events missing\n");
		return FALSE;
	}
	if (isdebug(BUG_EDIT))
		print_list("slowsetpriv", list, stdout);

	logic = (LOGICSTRUCT *)try_alloc(__FILE__, __LINE__, sizeof(*logic), FALSE);
	if (logic == NULL)
		return NULL;
	(void)memset((char *)logic, '\0', sizeof(*logic));

	logic->andor = SLOWBLOGICAND; /* default to AND */
	logic->whitelisted = FALSE;

	for (l = list; *l != NULL; l++)
	{
		if (strcasecmp(*l, "and") == 0)
		{
			logic->andor = SLOWBLOGICAND;
			continue;
		}
		if (strcasecmp(*l, "or") == 0)
		{
			logic->andor = SLOWBLOGICOR;
			continue;
		}
		if (strcasecmp(*l, "whitelisted") == 0)
		{
			logic->andor = SLOWBLOGICOR;
			logic->whitelisted = TRUE;
				continue;
		}
			
		val = -1;
		rel = SLOWLOGICNONE;
		whichbits = SLOWSETBITS;

		if (strchr(*l, '=') != NULL)
		{
			rel = SLOWLOGICEQUAL;
			ch = '=';
		}
		else if (strchr(*l, '>') != NULL)
		{
			rel = SLOWLOGICGREATER;
			ch = '>';
		}
		else if (strchr(*l, '<') != NULL)
		{
			rel = SLOWLOGICLESSTHAN;
			ch = '<';
		}
		else
		{
			(void) printf("%s: Unrecognized or missing comparitor\n", *l);
			(void) printf("\tselect from: = (equal to), < (less than), or > (greater than\n");
			exit (EINVAL);
		}
	
		/*
		 * Other than "and" and "or", everyting is in the format
		 *     keyword [<>=] value,
		 * For non-bitmap items, value is numeric.
		 * For bitmap items, value must be =[set,only,clear]
		 * For msgid value must be ="regular expression"
		 */
		equate = mkargv(*l, ch, FALSE);
		if (equate == NULL)
			continue;
		if (equate[1] == NULL)
		{
			(void) printf("%s: Value portion missing from relationshipe.\n", *l);
			exit(EINVAL);
		}
		item = try_strdup(__FILE__, __LINE__, equate[0], FALSE);
		index = is_event(item);
		if (index == 0)
		{
			(void) printf("%s: Unrecognized event keyword\n", item);
			show_events("Combine events from");
			return FALSE;
		}

		if (isdigit((int)(equate[1][0])))
		{
			val = atoi(equate[1]);
			whichbits = SLOWSETBITS;
		}
		else
		{
			if (index == EVENT_MESGID)
			{
# if HAVE_REGEX_H && HAVE_REGCOMP
				int rerr;

				logic->reg = try_alloc(__FILE__, __LINE__, sizeof(regex_t), FALSE);
				memset((char *)(logic->reg), '\0', sizeof(regex_t));
				rerr = regcomp(logic->reg, equate[1], REG_EXTENDED|REG_ICASE);
				if (rerr != 0)
				{
					char rerrbuf[BUFSIZ];

					(void) regerror(rerr, logic->reg, rerrbuf, BUFSIZ);
					(void) printf("%s: %s\n", item, rerrbuf);
					exit (rerr);
				}

# else
				logic->reg = try_strdup(__FILE__, __LINE__, equate[1], FALSE);
# endif
				continue;
			}
			val = 0;
			if (strcasecmp(equate[1], "set") == 0)
				whichbits = SLOWSETBITS;
			else if (strcasecmp(equate[1], "only") == 0)
				whichbits = SLOWONLYBITS;
			else if (strcasecmp(equate[1], "clear") == 0)
				whichbits = SLOWCLEARBITS;
			else
			{
				(void) printf("%s: Unrecognized bit keyword\n", item);
				(void) printf("\tselect from: set, only, or clear\n");
				exit (EINVAL);
			}
		}

		switch(index)
		{
		    case EVENT_ENVRCPTS:
			logic->envrcpts.relation = rel;
			logic->envrcpts.value = val;
			break;
		    case EVENT_HDRRCPTS:
			logic->hdrrcpts.relation = rel;
			logic->hdrrcpts.value = val;
			break;
		    case EVENT_HONEYRCPTS:
			logic->honeyrcpts.relation = rel;
			logic->honeyrcpts.value = val;
			break;
		    case EVENT_BADRCPTS:
			logic->bad.relation = rel;
			logic->bad.value = val;
			break;

		    case EVENT_TOOHDR:
			set_bit(BIT_TOO_MANY_HDR_RCPTS, logic->bits[whichbits]);
			break;

		    case EVENT_TOOENV:
			set_bit(BIT_TOO_MANY_ENV_RCPTS, logic->bits[whichbits]);
			break;

		    case EVENT_HONEY:
			set_bit(BIT_GOT_HONEYPOT, logic->bits[whichbits]);
			break;

		    case EVENT_MSGID:
			set_bit(BIT_BAD_MSG_ID, logic->bits[whichbits]);
			break;

		    case EVENT_FROMH:
			set_bit(BIT_BAD_FROM_HEAD, logic->bits[whichbits]);
			break;

		    case EVENT_BADHO:
			set_bit(BIT_BAD_HOST, logic->bits[whichbits]);
			break;

		    case EVENT_RBL:
			set_bit(BIT_RBL_BAD, logic->bits[whichbits]);
			break;

		    case EVENT_MILT:
			set_bit(BIT_MILTER_ABORTED, logic->bits[whichbits]);
			break;

		    case EVENT_ADVW:
			set_bit(BIT_ADVANCEWRITE, logic->bits[whichbits]);
			break;

		    case EVENT_FORGE:
			set_bit(BIT_FORGED, logic->bits[whichbits]);
			break;

		    case EVENT_FROMMX:
			set_bit(BIT_FROMMX, logic->bits[whichbits]);
			break;

		    case EVENT_NOTINET:
			set_bit(BIT_NOTINET, logic->bits[whichbits]);
			break;

		}
		(void) free(item);
	}
	return logic;
}

