edit.c #6

  • //
  • guest/
  • bryan_costales/
  • edit.c
  • View
  • Commits
  • Open Download .zip Download (31 KB)
/************************************************************
** $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;
}

# Change User Description Committed
#6 4222 bryan_costales Massive rewrite to speed up the database writes.
Using a single database now with duplicate keys
where the keys are the IP numbers. Added a purge
command and removed the garbage command. Fixed
some leaking memory bugs and properly closed the
database in a few places were it was not
properly closed. Updated the docs to reflect
this and bumped both the database version and
release number. Running on a FreeBSD 3.x machine
and a Solaris 9 machine.
#5 4052 bryan_costales Implimented:
    whitelisting
    AddMXHost for MX servers that lie
    Converted to thread safe DNS routines
    garbage collection
    RunAsUser and RunAsGroup for root startups
    rebuild the database
    summarize by IP number
Finished all documentation.
Moved release from alpha to beta
#4 4030 bryan_costales Finished documenting the configuration file
Fixed a race condition and a core dump bug.
Added hooks for whitelisting and IP aliasing
Added support for Berkeley DB 4.2
Converted to htonl() and ntohl()

Known Bugs:
    ip.db cannot be shared over NFS
    IP tracking from MX hosts can fail
    A RunAsUser config option is needed.
#3 3998 bryan_costales Brought the whole distribution up to V0.9
Added a huge abount of documentation.
Added slowedit find
Created startup scripts to launch for testing
Fixed numerous bugs.
Fixed a few portablity issues.
Installed hooks for whitelisting and IP aliases.
#2 3957 bryan_costales Added rbl lookup support and testing for same.
Folded in support for smfi_stop().
Added lots of slowedit commands
Fixed a serious bug in MX lookups.
Added to documentation.
#1 3890 bryan_costales This is the 0.6 release. The following have been
added with the uses indicated:

    Source files: edit.c -- the slowedit functions
              compat.c -- missing system files
    Autoconf:     configure.ac, makefile.am config.h
              aclocal.m4 acinclude.m4 build/
    Documents:    doc/ -- html and man(1) documents
    Testing:      tests/ -- regressive testing
    TODO:          -- revised to show actual progress