/************************************************************
** $Id: milter.c,v 0.5 2003/11/03 03:08:10 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"

/**********************************************************
The tests to be made:
	Good deeds (green flags):

	    mail delivered from sender
	    mail delivered to sender

	Questionable deeds (yellow flags):

	    mail received for multiple recipients
	    mail received at secondary MX
	    mail received for bad address (bounce)

	Bad deeds (red flags):

	    attempt to relay
	    attempt to pipeline SMTP commands illegally 
	    mail received for honeypot address
	    mail received from bogus/mismatched sender domain

    There are also some events that can be logged from outside the
    MTA, so people can rat out a bad sender:

	    sender imported from a blacklist (yellow flag)
	    sender imported from a whitelist (green)
	    sender failed open relay check (yellow)
	    report of spam content from a user (yellow)
***********************************************************/

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->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));

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

	/*
	 * 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;
		setbit(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));
		priv->iptxt = try_strdup(__FILE__, __LINE__, inet_ntoa(in));
	}

	/*
	 * 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;
			setbit(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)
			setbit(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,
			bitset(BIT_FROMMX, priv->bits)?"[MMX]":"",
			bitset(BIT_NOTINET, priv->bits)?"[NET]":"",
			bitset(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" */

	/*
	 * If the milter configuration file allow it, check for
	 * an illegal advance write.
	 */
	if(AdvanceWrite == TRUE)
	{
		priv = (PRIV *)smfi_getpriv(ctx);
		if (priv == NULL)
		{
			milterr(Prog, __FILE__, __LINE__, TRUE,
				errno, "ABORTED", "Private data is NULL");
		}

		sym = smfi_getsymval(ctx, "{illegalpipe}");
		if (sym != NULL && strcasecmp(sym, "true") == 0)
			setbit(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,
			bitset(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 host[MAXHOSTNAMELEN];
	int herr;
	struct hostent res;
	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");
	}

	/*
	 * 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)
	{
		setbit(BIT_BAD_FROM_HEAD, priv->bits);
	}
	else
	{
		/*
		 * We don't count it an error if the lookup returns
		 * a temporary error.
		 */
		if (gethostbyname_r(cp, &res, host, MAXHOSTNAMELEN, &herr) == NULL)
		{
			if (herr != TRY_AGAIN)
				setbit(BIT_BAD_FROM_HEAD, priv->bits);
		}
	}

	if (isdebug(BUG_MAILFROM))
		(void) printf("%s: flags=%s\n", argv[0],
		bitset(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");
	}
	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];

		setbit(BIT_GOT_HONEYPOT, priv->bits);
		if (isdebug(BUG_RCPTTO))
			(void)printf("%s: flags=%s\n", argv[0],
				bitset(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");
	}
	/*
	 * 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));
		priv->iptxt = try_strdup(__FILE__, __LINE__, inet_ntoa(in));
		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 (CheckMessageId == TRUE && strcasecmp(field, "Message-Id") == 0)
	{
		priv->gotmid = TRUE;

		if (header_messageid(value) == FALSE)
			setbit(BIT_BAD_MSG_ID, priv->bits);

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

	}
	if (CheckFromHeader == TRUE && strcasecmp(field, "From") == 0)
	{
		char host[MAXHOSTNAMELEN];
		int herr;
		struct hostent res;

		/*
		 * 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_r(cp, &res, 
				host, MAXHOSTNAMELEN, &herr) == NULL)
			{
				if (herr != TRY_AGAIN)
					setbit(BIT_BAD_FROM_HEAD, priv->bits);
			}
		}
		if (isdebug(BUG_HEADER))
			(void) printf("%s: %s flags=%s\n", field, value,
				bitset(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");
	}

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

	/*
	 * Here we count up the number of header recipients.
	 */
	priv->nhdrrcpts = 0;
	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)
{
	SECONDARY_KEY k;
	SECONDARY_DATUM event;
	PRIMARY_DATUM   t;

	(void)time(&t);

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

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

	if (adddate_db(Dp1, Prog, priv->ip, t) != 0)
		milterr(Prog, __FILE__, __LINE__, TRUE,
			errno, "Primary database write", PrimaryDatabaseLoc);
	k.ip = priv->ip;
	k.date = t;
	if (ipputdateinfo_db(Dp2, Prog, &k, &event) != 0)
		milterr(Prog, __FILE__, __LINE__, TRUE,
			errno, "Secondary database write", SecondaryDatabaseLoc);

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

	return SMFIS_ACCEPT;
}

/*
** 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. Also note that this only gives an approximation
** of the number of bad envelope recipients.
**
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");
	}
	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);
}

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)
	{
		setbit(BIT_MILTER_ABORTED, priv->bits);
		if (! isdebug(BUG_ABORT))
			return push_to_db(ctx, priv);
	}

	return SMFIS_ACCEPT;
}


struct smfiDesc SmfiDesc =
{
	NULL,		/* filter name */
	SMFI_VERSION,	/* version code -- do not change */
	0,		/* 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;
	const char *version = "$Revision: 0.5 $";
	bool force;
	int ret;
	int mode;
	char *xtest;
	char buf[BUFSIZ];
	extern int errno;

	Prog = try_strdup(__FILE__, __LINE__, basename(argv[0]));
	SmfiDesc.xxfi_name = Prog;
	
	mode = MODE_RUN_AS_MILTER;
	if (strcmp(Prog, "slowadd"))		/* synonym for -a */
		mode = MODE_ADD_BY_HAND;
	else if (strcmp(Prog, "slowweed"))	/* synonym for -w */
		mode = MODE_GARBAGE_COLLECT;
	else if (strcmp(Prog, "slowdump"))	/* synonym for -d */
		mode = MODE_GARBAGE_COLLECT;

	/* Process command line options */
	xtest = NULL;
	force = FALSE;
	Debugbits = 0L;
	while ((c = getopt(argc, argv, "dX:D:fC:V")) != -1)
	{
		switch (c)
		{
		    /* Specify configuration file */
		    case 'C':
			cf = optarg;
			break;
		    
		    /* Dump the database to stdout */
		    case 'd':
			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':
			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;
		}
	}

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

	if (isdebug(BUG_DB))	test_db(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)
	{
		printf("%s: successfully created.\n", PrimaryDatabaseLoc);
		printf("%s: successfully created.\n", SecondaryDatabaseLoc);
		return EX_OK;
	}

	if (mode == MODE_DUMP_DATABASE)
	{
		ret = dump_database(Dp1, Dp2, PrimaryDatabaseLoc,
			SecondaryDatabaseLoc, 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 (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,
				EPROTO, 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;
	}

	/*
	 * 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 (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);
		}

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

		(void) smfi_main();
	}

	return EX_OK;
}

