/************************************************************
** $Id: milter.c,v 0.7 2003/11/24 18:38:35 bcx Exp $
**
** 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.
**
** Entry points defined in this file:
**
** All the milter entry points and:
** main():
** 	Process command line an launch the milter.
*************************************************************/

# define EXTERN extern
# include "slow.h"


void
free_priv(SMFICTX *ctx)
{
	PRIV *priv = (PRIV *)smfi_getpriv(ctx);

	if (priv == NULL)
		return;
	if (priv->iptxt != NULL)
		(void) free(priv->iptxt);
	if (priv->midstr != NULL)
		(void) free(priv->midstr);
	if (priv->hdrrcpts != NULL)
		(void) free_list(priv->hdrrcpts);
	(void) free((char *)priv);
	return;
}

/*
** Process Connect:
**
**	This is after sendmail has accepted the connection,
**	but before sendmail has sent its initial greeting.
**
** For the {client_resolv}  test to work, you need to add the following
** to your sendmail mc configuration file:
**
**	define(`confMILTER_MACROS_CONNECT', confMILTER_MACROS_CONNECT``,{client_resolve}'')
**
*/

sfsistat
xxfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *ha)
{
	PRIV *priv;
	struct in_addr in;
	char *sym;
	bool badfamily;

	/*
	 * Set up the private (thread specific) data that will persist
	 * throughout the connection. A connection may
	 * contain multiple envelopes.
	 */
	priv = (PRIV *)try_alloc(__FILE__, __LINE__, sizeof(PRIV), TRUE);
	if (priv == NULL)
		return SMFIS_TEMPFAIL;

	(void) memset((char *)priv, 0, sizeof(PRIV));
	priv->hdrrcpts = NULL;
	priv->midstr = NULL;
	if (smfi_setpriv(ctx, (void *)priv) == MI_FAILURE)
	{
		milterr(Prog, __FILE__, __LINE__, TRUE,
			errno, "ABORTED", "Register private data");
		return SMFIS_TEMPFAIL;
	}

	/*
	 * Gather the IP number of the connecting host and
	 * record it in the private data.
	 */
	badfamily = FALSE;
	if (ha == NULL || ha->sa_family != AF_INET)
	{
		/* 
		 * This is not an IPv4 address (sendmail may have
		 * been run with the -bs command-line switch.
		 * Since we cannot record it, we accept
		 * the whole connection.
		 */
		badfamily = TRUE;
		set_bit(BIT_NOTINET, priv->bits);
		/* XXX log what happened? */
	}
	else
	{
		(void) memcpy(&(priv->ip), ha->sa_data, sizeof(PRIMARY_DATUM));
		(void) memcpy(&in.s_addr, ha->sa_data, sizeof(in.s_addr));
# ifndef WORDS_BIGENDIAN
		in.s_addr = revbytes(in.s_addr);
# endif
		priv->iptxt = try_strdup(__FILE__, __LINE__, inet_ntoa(in), TRUE);
		if (priv->iptxt == NULL)
			return SMFIS_TEMPFAIL;

		/*
		 * If we are allowed to check RBL hosts,
		 * we do it here. We can check multiple
		 * RBL hosts, but beware: each takes time.
		 */
		if (RBLList != NULL)
		{
			char **p;

			for (p = RBLList; *p != NULL; ++p)
			{
				if (rbl_lookup(*p, priv->iptxt, TRUE) != 0)
					continue;
				set_bit(BIT_RBL_BAD, priv->bits);
				break;
			}
		}

		/*
		 * If this connection is from one of our MX hosts
		 * save that information for future use, and
		 * clear the saved ip data.
		 */
		if (is_in_list(priv->iptxt, MxHosts) == TRUE
			|| is_in_list(hostname, MxHosts) == TRUE)
		{
			/*
			 * Yes, from our MX server, so clear the IP
			 * information. It will be later snarfed
			 * from a Received: header.
			 * Note this will only happen if the config
			 * allows it.
			 */
			if (LookBackOne == TRUE)
			{
				priv->ip = (PRIMARY_DATUM)0;
				if (priv->iptxt != NULL)
					(void) free(priv->iptxt);
				priv->iptxt = NULL;
				set_bit(BIT_FROMMX, priv->bits);
			}
		}
	}

	/*
	 * sendmail looks up the real host name, to get its
	 * IP number. It then reverse looks up the IP number
	 * to get the host name. If the two don't match, sendmail
	 * consider the hostname forged.
	 *
	 * If the milter configuration file allows this
	 * test, perform it.
	 */
	if (CheckForged == TRUE)
	{
		sym = smfi_getsymval(ctx, "{client_resolve}");
		if (sym != NULL && strcasecmp(sym, "forged") == 0)
			set_bit(BIT_FORGED, priv->bits);
	}

	/*
	 * Hook for regression testing. If you change this
	 * output, change test.c too.
	 */
	if (isdebug(BUG_CONNECT))
	{
		(void)printf("%s [%s]: flags=%s%s%s\n",
			hostname==NULL?"<nil>":hostname,
			priv->iptxt==NULL?"<nil>":priv->iptxt,
			isbitset(BIT_FROMMX, priv->bits)?"[MMX]":"",
			isbitset(BIT_NOTINET, priv->bits)?"[NET]":"",
			isbitset(BIT_FORGED, priv->bits)?"[FOR]":"");
	}

	/*
	 * If this is a connection on standard input or some
	 * other non-IPv4 network. Accept it, because we cannot
	 * associate an IP number with it.
	 */
	if (badfamily == TRUE)
		return SMFIS_ACCEPT;

	return SMFIS_CONTINUE;
}

/*
** Process the HELO/EHLO command
**
**	The argument contains the host name give in the
**	HELO/EHLO command. This is the literal argument
**	and cannot be trusted.
**
** Note to get the "advanced write" test to work, you need to patch
** V8.13 sendmail with the "patchs/sendmail.patch" file. Then paste
** the following define into your sendmail mc configuration file.
**
define(`confMILTER_MACROS_HELO', confMILTER_MACROS_HELO``, {illegalpipe}'')
**
*/
sfsistat
xxfi_helo(SMFICTX *ctx, char *host)
{
	PRIV *priv;
	char *sym;

	/* we ignore the passed argument "host" */

	priv = (PRIV *)smfi_getpriv(ctx);
	if (priv == NULL)
	{
		milterr(Prog, __FILE__, __LINE__, TRUE,
			errno, "ABORTED", "Private data is NULL");
		return SMFIS_TEMPFAIL;
	}

	/*
	 * If the milter configuration file allow it, check for
	 * an illegal advance write.
	 */
	if(AdvanceWrite == TRUE)
	{
		sym = smfi_getsymval(ctx, "{illegalpipe}");
		if (sym != NULL && strcasecmp(sym, "true") == 0)
			set_bit(BIT_ADVANCEWRITE, priv->bits);
	}

	/*
	 * Hook for regression testing. If you change this
	 * output, change test.c too.
	 */
	if (isdebug(BUG_HELO))
	{
		(void)printf("%s: flags=%s\n",
			host==NULL?"<nil>":host,
			isbitset(BIT_ADVANCEWRITE, priv->bits)?"[PIP]":"");
	}
	return SMFIS_CONTINUE;
}

/*
** Process MAIL FROM:
**
**	argv contains a single recipient address in argv[0]
**	the rest of argv is the MAIL FROM: arguments. These are
**	also captured in the macros passed by sendmail.
**
**	This function is usually called only once.
*/
sfsistat
xxfi_envfrom(SMFICTX *ctx, char **argv)
{
	char *cp, *ep;
	PRIV *priv;

	if (argv == NULL)
		return SMFIS_CONTINUE;
	/*
	 * If the milter configuration file prevents this
	 * test, skip it.
	 */
	if (CheckFromHeader == FALSE)
		return SMFIS_CONTINUE;

	priv = (PRIV *)smfi_getpriv(ctx);
	if (priv == NULL)
	{
		milterr(Prog, __FILE__, __LINE__, TRUE,
			errno, "ABORTED", "Private data is NULL");
		return SMFIS_TEMPFAIL;
	}

	/*
	 * Check the envelope sender's host.
	 */
	cp = strchr(argv[0], '@');
	/*
	 * Missing @ means either a local address (user)
	 * or an empty angle brace pair (<>),which
	 * is legal for bounced email.
	 */
	if (cp == NULL)
	{
		if (isdebug(BUG_MAILFROM))
		{
			if (strcmp("<>", argv[0]) == 0)
				(void) printf("%s: okay bounce sender\n", argv[0]);
			else
				(void) printf("%s: okay local\n", argv[0]);
		}
		return SMFIS_CONTINUE;
	}
	++cp;
	ep = strchr(cp, '>');
	if (ep != NULL)
	{
		*ep = '\0';
		--ep;
	}
	else
		ep = cp+strlen(cp);
	/*
	 * A missing host part after the @ (i.e. "user@") is bad.
	 */
	if (cp >= ep)
	{
		set_bit(BIT_BAD_FROM_HEAD, priv->bits);
	}
	else
	{
		/*
		 * We don't count it an error if the lookup returns
		 * a temporary error.
		 */
		if (gethostbyname(cp) == NULL)
		{
			if (h_errno != TRY_AGAIN)
				set_bit(BIT_BAD_FROM_HEAD, priv->bits);
		}
	}

	if (isdebug(BUG_MAILFROM))
		(void) printf("%s: flags=%s\n", argv[0],
		isbitset(BIT_BAD_FROM_HEAD, priv->bits)?"[FRM]":"" );

	return SMFIS_CONTINUE;
}

/*
** Process the RCPT TO: recipient address. This may be called
** multiple times, once for each RCPT TO: command.
** Each envelope recipient is in argv[0].
*/
sfsistat
xxfi_envto(SMFICTX *ctx, char **argv)
{
	PRIV *priv;
	char *cp, *ep;

	priv = (PRIV *)smfi_getpriv(ctx);
	if (priv == NULL)
	{
		milterr(Prog, __FILE__, __LINE__, TRUE,
			errno, "ABORTED", "Private data is NULL");
		return SMFIS_TEMPFAIL;
	}
	cp = argv[0];

	/*
	 * Strip surrounding angle braces if any.
	 */
	if (*cp == '<')
	{
		++cp;
		if ((ep = strchr(cp, '>')) != NULL)
			*ep ='0';
	}

	/*
	 * We reject any honeypot address, but count it.
	 * Note that since sendmail will count it as a bad recipient,
	 * we decrement the bad recipient count ahead of time.
	 */
	if (is_in_list(cp, HoneyHosts) == TRUE)
	{
		char buf[BUFSIZ];

		set_bit(BIT_GOT_HONEYPOT, priv->bits);
		if (isdebug(BUG_RCPTTO))
			(void)printf("%s: flags=%s\n", argv[0],
				isbitset(BIT_GOT_HONEYPOT,priv->bits)?"[HON]":"");
		++(priv->honeyrcpts);
		--(priv->badrcpts);
		(void) snprintf(buf, BUFSIZ, "%s... User unknown", argv[0]);
		smfi_setreply(ctx, "550", "5.1.1", buf);
		return SMFIS_REJECT;
	}

	/*
	 * If it is not a honey pot address, we count it as
	 * good, with the caveat that a later milter or sendmail
	 * may yet reject it.
	 */
	++(priv->envrcpts);
	if (isdebug(BUG_RCPTTO))
		(void)printf("%s: count=%d\n", argv[0], priv->envrcpts);
	return SMFIS_CONTINUE;
}

/*
** End of the envlope recipient list is marked by
** the start of the DATA phase with the collection
** of headers. Note that if there are no good recipients
** left in the recipeint list, sendmail will jump right 
** to the xxfi_abort() routine instead.
*/

/*
** Process a header
**
**	This routine is called once per header.
**	This routine is usually called many times.
**	In this routine we screen:
**		XTrackHeader: to see if we already saw this message.
**		To:, Cc:, and Bcc: to count header recipients
**		Received (if BIT_FROMMX is set, see xxfi_connect)
**		Message-Id: to detect bad Message-Ids
*/
sfsistat
xxfi_header(SMFICTX *ctx, char *field, char *value)
{
	PRIV *priv;
	char *cp;

	priv = (PRIV *)smfi_getpriv(ctx);
	if (priv == NULL)
	{
		milterr(Prog, __FILE__, __LINE__, TRUE,
			errno, "ABORTED", "Private data is NULL");
		return SMFIS_TEMPFAIL;
	}
	/*
	 * If the XTrackHeader appears, accept the message,
	 * because this means it was already screened.
	 */
	if (XTrackHeader != NULL
		&& strcasecmp(field, XTrackHeader) == 0)
	{
		if (isdebug(BUG_HEADER))
			printf("%s: %s accepted\n", field, value);
		/* XXX log what happened? */
		return SMFIS_ACCEPT;
	}

	/*
	 * If this is a Received: header, and if we got this
	 * message from one of our MX servers, try to snarf
	 * the sending IP number.
	 */
	if (priv->ip == 0
		&& LookBackOne == TRUE
		&& strcasecmp(field, "Received") == 0)
	{
		struct in_addr in;

		priv->ip = header_received(value);
		if (priv->ip == 0)
		{
			if (isdebug(BUG_HEADER))
				printf("Received: not from our MX\n");
			return SMFIS_CONTINUE;
		}
		/*
		 * Got the sending IP number, so save it to
		 * the text form too.
		 */
		(void) memcpy(&in.s_addr, &priv->ip, sizeof(in.s_addr));
# ifndef WORDS_BIGENDIAN
		in.s_addr = revbytes(in.s_addr);
# endif
		priv->iptxt = try_strdup(__FILE__, __LINE__, inet_ntoa(in), TRUE);
		if (priv->iptxt == NULL)
			return SMFIS_TEMPFAIL;
		if (isdebug(BUG_HEADER))
			printf("Received: our MX said %s\n", priv->iptxt);
		return SMFIS_CONTINUE;
	}

	/*
	 * Is the header one that contains recipients?
	 */
	if (strcasecmp(field, "To") == 0
		|| strcasecmp(field, "Cc") == 0
		|| strcasecmp(field, "Bcc") == 0)
	{
		/*
		 * Yes, so add to the list of header recipients.
		 */
		priv->hdrrcpts = header_recipient(priv->hdrrcpts, value);
		if (isdebug(BUG_HEADER))
		{
			if (strcasecmp(field, "To") == 0)
				(void)print_list("To", priv->hdrrcpts, stdout);
			else
				(void) printf("%s: %s\n", field, (priv->hdrrcpts)[0]);
		}
		return SMFIS_CONTINUE;
	}

	if (strcasecmp(field, "Message-Id") == 0)
	{
		priv->midstr = try_strdup(__FILE__, __LINE__, field, TRUE);
		if (priv->iptxt == NULL)
			return SMFIS_TEMPFAIL;

		if (CheckMessageId == TRUE)
		{
			if (header_messageid(value) == FALSE)
				set_bit(BIT_BAD_MSG_ID, priv->bits);
		}

		if (isdebug(BUG_HEADER))
			(void) printf("%s: %s flags=%s\n", field, value,
				isbitset(BIT_BAD_MSG_ID, priv->bits)?"[MID]":"");

	}
	if (CheckFromHeader == TRUE && strcasecmp(field, "From") == 0)
	{
		/*
		 * Strip away the RFC 822 cruft from the address
		 * and move the pointer to the first char following
		 * the @ so that we can look up the host name.
		 */
		cp = deRFC822(value);
		if (cp != NULL 
			&& (cp = strchr(cp, '@')) != NULL 
			&& *(++cp) != '\0')
		{
			/*
			 * We don't count it an error if the lookup returns
			 * a temporary error.
			 * Note that we cannot check if it is addressed to
			 * the local host because we may be relaying to
			 * other hosts or domains (perhaps as MX servers).
			 * So we only check to see if it exists.
			 */
			if (gethostbyname(cp) == NULL)
			{
				if (h_errno != TRY_AGAIN)
					set_bit(BIT_BAD_FROM_HEAD, priv->bits);
			}
		}
		if (isdebug(BUG_HEADER))
			(void) printf("%s: %s flags=%s\n", field, value,
				isbitset(BIT_BAD_FROM_HEAD, priv->bits)?"[FRM]":"");

	}
	return SMFIS_CONTINUE;
}

/*
** After all headers have been processed, we can check
** to see if any were missing.
** Also we can count the header recipients here.
*/
sfsistat
xxfi_eoh(SMFICTX *ctx)
{
	PRIV *priv;
	char **rp;

	priv = (PRIV *)smfi_getpriv(ctx);
	if (priv == NULL)
	{
		milterr(Prog, __FILE__, __LINE__, TRUE,
			errno, "ABORTED", "Private data is NULL");
		return SMFIS_TEMPFAIL;
	}

	/*
	 * If there was no Message-ID header, we count this as bad too.
	 */
	if (CheckMessageId == TRUE && priv->midstr == NULL)
		set_bit(BIT_BAD_MSG_ID, priv->bits);
	{
		if (isdebug(BUG_EOH))
			(void) printf("eoh: flags=%s\n",
				isbitset(BIT_BAD_MSG_ID, priv->bits)?"[MID]":"");
	}

	/*
	 * Here we count up the number of header recipients.
	 */
	priv->nhdrrcpts = 0;
	if (priv->hdrrcpts != NULL)
	{
		for (rp = priv->hdrrcpts; *rp != NULL; ++rp)
			++(priv->nhdrrcpts);
		priv->hdrrcpts = free_list(priv->hdrrcpts);
	}
	if (isdebug(BUG_EOH))
		(void) printf("eoh: nhdrrcpts=%d\n", priv->nhdrrcpts);

	return SMFIS_CONTINUE;
}

/*
** Gather up the information needed to write to the database.
** Do the database writes, and return what to do next.
** This is called from xxfi_eom and xxfi_abort.
*/
sfsistat
push_to_db(SMFICTX *ctx, PRIV *priv, int log)
{
	SECONDARY_KEY k;
	SECONDARY_DATUM event;
	PRIMARY_DATUM   t;

	if (isdebug(BUG_DB) || isdebug(BUG_ADD) || isdebug(BUG_DELETE))
		t = 1234000000;
	else
		(void)time(&t);

	event.eventmap = priv->bits;
	event.envrcpts = priv->envrcpts;
	event.headrcpts = priv->nhdrrcpts;
	event.honeyrcpts = priv->honeyrcpts;
	event.badrcpts = priv->badrcpts;

	(void) memset(event.msgid, '\0', MAX_MESSAGE_ID);
	if (priv->midstr != NULL)
		(void) memcpy(event.msgid, priv->midstr, MAX_MESSAGE_ID -1);

	if (event.headrcpts > MaxHeaderRcpts)
		set_bit(BIT_TOO_MANY_HDR_RCPTS, event.eventmap);
	if (event.envrcpts > MaxRcptsPerEnvelope)
		set_bit(BIT_TOO_MANY_ENV_RCPTS, event.eventmap);

	if (put_ip_database(Dp1, priv->ip, t, FALSE, log) != 0)
	{
		milterr(Prog, __FILE__, __LINE__, TRUE,
			errno, "Primary database write", PrimaryDatabaseLoc);
		return SMFIS_TEMPFAIL;
	}
	k.ip = priv->ip;
	k.date = t;
	if (put_event_database(Dp2, &k, &event) != 0)
	{
		milterr(Prog, __FILE__, __LINE__, TRUE,
			errno, "Secondary database write", SecondaryDatabaseLoc);
		return SMFIS_TEMPFAIL;
	}

# if HAVE_SYSLOG
	if (DoLog == TRUE && LogEvents == TRUE)
		syslog(LOG_INFO, "%s%s", LogPrefix,
			event_to_str(priv->iptxt, t, &event, TRUE));
# endif

	(void) free_priv(ctx);
	(void) smfi_setpriv(ctx, NULL);

	return SMFIS_CONTINUE;
}

/*
** Process the end-of-envelope. Here we summarize everything and
** update the database. Note that we update the database here,
** because an earlier Milter may have rejected the message based
** on the body contents.
**
** Note to get the ${nbadrcpts} value, you need be running V8.13 sendmail
** and you need to paste the following define into your sendmail 
** mc configuration file.
**
define(`confMILTER_MACROS_ENVRCPT', confMILTER_MACROS_ENVRCPT``, {nbadrcpts}'')
**
*/
sfsistat
xxfi_eom(SMFICTX *ctx)
{
	PRIV *priv;
	char *sym;

	priv = (PRIV *)smfi_getpriv(ctx);
	if (priv == NULL)
	{
		milterr(Prog, __FILE__, __LINE__, TRUE,
			errno, "ABORTED", "Private data is NULL");
		return SMFIS_TEMPFAIL;
	}
	priv->badrcpts = 0;
	sym = smfi_getsymval(ctx, "{nbadrcpts}");
	if (sym != NULL)
		priv->badrcpts = atoi(sym);
		
	if (isdebug(BUG_EOM))
	{
		return SMFIS_ACCEPT;
	}
	return push_to_db(ctx, priv, TRUE);
}

sfsistat
xxfi_close(SMFICTX *ctx)
{
	PRIV *priv;

	priv = (PRIV *)smfi_getpriv(ctx);
	if (priv != NULL)
	{
		(void) free_priv(ctx);
		(void) smfi_setpriv(ctx, NULL);
	}
	return SMFIS_ACCEPT;
}

sfsistat
xxfi_abort(SMFICTX *ctx)
{
	PRIV *priv;

	priv = (PRIV *)smfi_getpriv(ctx);
	if (priv != NULL)
	{
		set_bit(BIT_MILTER_ABORTED, priv->bits);
		if (! isdebug(BUG_ABORT))
			return push_to_db(ctx, priv, TRUE);
	}

	return SMFIS_ACCEPT;
}


struct smfiDesc SmfiDesc =
{
	NULL,		/* filter name */
	SMFI_VERSION,	/* version code -- do not change */
	SMFIF_NONE,	/* flags */
	xxfi_connect,	/* connection info filter */
	xxfi_helo,	/* SMTP HELO command filter */
	xxfi_envfrom,	/* envelope sender filter */
	xxfi_envto,	/* envelope recipient filter */
	xxfi_header,	/* header filter */
	xxfi_eoh,	/* end of header */
	NULL,		/* This milter ignores the body */
	xxfi_eom,	/* end of message */
	xxfi_abort,	/* message aborted */
	xxfi_close	/* connection cleanup */
};


int
main(argc, argv)
	int argc;
	char *argv[];
{
	int c;
	char *cf = NULL;
	bool force;
	int ret;
	int mode;
	char *xtest;
	char buf[BUFSIZ];
	extern int errno;

	Prog = try_strdup(__FILE__, __LINE__, basename(argv[0]), FALSE);
	SmfiDesc.xxfi_name = Prog;
	
	/*
	 * The run-by-hand interface is by name only.
	 */
	mode = MODE_RUN_AS_MILTER;
	if (strcmp(Prog, "slowedit") == 0)
		mode = MODE_ADD_BY_HAND;

# if HAVE_SYSLOG
	openlog(Prog, LOG_NDELAY, LOG_MAIL);
# endif

	/* Process command line options */
	xtest = NULL;
	force = FALSE;
	Debugbits = 0L;
	Verbose = FALSE;
	while ((c = getopt(argc, argv, "EdX:D:fC:Vv")) != -1)
	{
		switch (c)
		{
		    /* Verbose let's you see what is happening */
		    /* Note that verbose is not debug */
		    case 'v':
			Verbose = TRUE;
			break;

		    /* Edit mode, break and let slowedit() handle it. */
		    case 'E':
			if (Verbose == TRUE)
				(void) printf("Reset Mode to EDIT\n");
			mode = MODE_ADD_BY_HAND;
			goto skip;

		    /* Specify configuration file */
		    case 'C':
			cf = optarg;
			break;
		    
		    /* Dump the database to stdout */
		    case 'd':
			if (Verbose == TRUE)
				(void) printf("Reset Mode to DBDUMP\n");
			mode = MODE_DUMP_DATABASE;
			break;
		    
		    /* Print the program's version and exit */
		    case 'V':
			printf("%s: %s\n", Prog, VERSION);
			exit(0);
			break;

		    /*
		     * To bootstrap the system, use -f to
		     * force the databases to be created if
		     * they do not already exists.
		     */
		    case 'f':
			if (Verbose == TRUE)
				(void) printf("Reset Mode to FORCE\n");
			mode = MODE_BOOTSTRAP;
			force = TRUE;
			break;

		    /*
		     * Debugging is by catagory. e.g. -Dmx prints
		     * the mx records found for the LocalHostName.
		     */
		    case 'D':
		    	(void) set_debug(optarg);
		    	break;
			
		    case 'X':
			xtest = optarg;
			break;
		}
	}
	/*
	 * If slowedit mode, leave the rest of argc, argv available.
	 */
skip:

	/* Read the configuration file */
	if (cf == NULL)
		cf = CFFILE;
	(void) setdefaults();
	(void) readconf(Prog, cf);

	/*
	 * Regression testing.
	 */
	if (isdebug(BUG_CONNECT)) 	test_connect(xtest);
	if (isdebug(BUG_HELO))    	test_helo(xtest);
	if (isdebug(BUG_MAILFROM))	test_mailfrom(xtest);
	if (isdebug(BUG_MAILFROM))	test_mailfrom(xtest);
	if (isdebug(BUG_RCPTTO))	test_rcptto(xtest);
	if (isdebug(BUG_HEADER))	test_header(xtest);
	if (isdebug(BUG_EOH))		test_eoh(xtest);
	if (isdebug(BUG_EOM))		test_eom(xtest);
	if (isdebug(BUG_CLOSE))		test_close(xtest);
	if (isdebug(BUG_ABORT))		test_abort(xtest);
	if (isdebug(BUG_DB))		test_db(xtest);
	if (isdebug(BUG_ADD))		test_add(xtest);
	if (isdebug(BUG_DELETE))	test_delete(xtest);
	if (isdebug(BUG_RBL))		test_rbl(xtest);

	/*
	 * Open both databases. The db flags used are in
	 * database.c. Here we only supply the file name.
	 */
	Dp1 = open_database(PrimaryDatabaseLoc, force);
	if (Dp1 == NULL)
	{
		milterr(Prog, __FILE__, __LINE__, FALSE,
			errno, "Primary database open", PrimaryDatabaseLoc);
	}
	Dp2 = open_database(SecondaryDatabaseLoc, force);
	if (Dp2 == NULL)
	{
		milterr(Prog, __FILE__, __LINE__, FALSE,
			errno, "Secondary database open", SecondaryDatabaseLoc);
	}

	if (mode == MODE_BOOTSTRAP)
	{
		if (Verbose == TRUE)
		{
			printf("%s: successfully created.\n",
				PrimaryDatabaseLoc);
			printf("%s: successfully created.\n",
				SecondaryDatabaseLoc);
		}
# if HAVE_SYSLOG
		if (DoLog == TRUE)
			syslog(LOG_INFO, "%s%s created, %s created", 
				LogPrefix,
				PrimaryDatabaseLoc, SecondaryDatabaseLoc);
# endif
		return EX_OK;
	}

	if (mode == MODE_DUMP_DATABASE)
	{
		if (Verbose == TRUE)
		{
			printf("Dumping %s and %s\n",
				PrimaryDatabaseLoc,
				SecondaryDatabaseLoc);

		}
		ret = dump_database(Dp1, Dp2, stdout);
		exit(ret);
	}

	/*
	 * Read into memory, a list of the honeypot addresses.
	 * Use -Dhoney to print what was read.
	 */
	(void) read_honey();
	if (isdebug(BUG_HONEY))
	{
		(void) print_list("Honey Pot Addresses", HoneyHosts, stdout);
		return EX_OK;
	}

	/*
	 * ??? Should the mx list be empty on our MX servers?
	 */
	if(mode != MODE_ADD_BY_HAND)
	{
		if (LocalHostName == NULL)
		{
			if (gethostname(buf, BUFSIZ) <0)
				milterr(Prog, __FILE__, __LINE__, FALSE,
					errno, "Define LocalHostName",
					"Get Host Name");
			if (strchr(buf, '.') == NULL)
				milterr(Prog, __FILE__, __LINE__, FALSE,
					EINVAL, buf, "Not fully qualified");
			MxHosts = getmx(buf, MxHosts);
		}
		else
			MxHosts = getmx(LocalHostName, MxHosts);
	}

	/*
	 * This cannot be used for regression testing. But it
	 * is a handy way to see how mx lookups are being done.
	 */
	if (isdebug(BUG_MX))
	{
		if (xtest == NULL)
		{
			(void) print_list(LocalHostName, 
				MxHosts, stdout);
			return EX_OK;
		}
		MxHosts = NULL;
		MxHosts = getmx(xtest, MxHosts);
		(void) print_list(xtest, MxHosts, stdout);
		return EX_OK;
	}


	if (mode == MODE_RUN_AS_MILTER)
	{
		if (smfi_register(SmfiDesc) == MI_FAILURE)
		{
			milterr(Prog, __FILE__, __LINE__, FALSE,
				errno, "ABORT", "Milter Listen failed");
		}

		if (smfi_setconn(ListenPort) == MI_FAILURE)
		{
			milterr(Prog, __FILE__, __LINE__, FALSE,
				errno, "Start to listen", ListenPort);
		}

		/*
		 * TODO -- add code to put into the background
		 */
# if HAVE_SYSLOG
		if (DoLog == TRUE)
			syslog(LOG_INFO, "Version %s: starting up on socket %s", 
				VERSION, ListenPort);
# endif
		(void) smfi_main();
	}
	else if(mode == MODE_ADD_BY_HAND)
	{
		slowedit(optind, argc, argv);
	}

	return EX_OK;
}

