/************************************************************
** $Id: milter.c,v 0.10 2004/01/24 15:01:19 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.
**
** 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"

/*
** Exiting early because of an abort or a problem requires
** that we log if we can.
*/
void
egress_log(SMFICTX *ctx, char *file, int line, char *err, char *why)
{
# if HAVE_SYSLOG
	PRIV *priv = (PRIV *)smfi_getpriv(ctx);
	char *iptxt;

	if (priv == NULL)
		return;
	/*
	 * If push_to_database failed to log something. And we are
	 * tracing to debug something, we do a fallback log here.
	 */
	if (isdebug(BUG_TRACE))
	{
		if (priv->iptxt == NULL)
			iptxt = "?.?.?.?";
		else
			iptxt = priv->iptxt;
		(void)syslog(LOG_INFO, "%s: %s(%s): %s: %s",
			iptxt, file, line, err, why);
# endif
	}
}

void
clear_priv(SMFICTX *ctx, int is_end_of_connect)
{
	PRIV *priv = (PRIV *)smfi_getpriv(ctx);

	if (priv == NULL)
		return;

	if (priv->midstr != NULL)
	{
		(void) free(priv->midstr);
		priv->midstr = NULL;
	}
	priv->bits	 = 0;
	priv->envrcpts	 = 0;
	priv->badrcpts	 = 0;
	priv->honeyrcpts = 0;
	priv->nhdrrcpts	 = 0;

	/*
	 * Don't deallocate connection information, there may
	 * be more envelopes. Only do this at end of connection.
	 */
	if (is_end_of_connect == TRUE)
	{
		if (priv->iptxt != NULL)
		{
			(void) free(priv->iptxt);
			priv->iptxt = NULL;
		}
		(void) free((char *)priv);
		(void) smfi_setpriv(ctx, NULL);
	}
	return;
}

sfsistat
egress_envelope(SMFICTX *ctx, sfsistat ret, char *file, int line, char *err, char *why)
{
	/*
	 * We get here only if neither xxfi_eom() nor xxfi_abort() were
	 * called.
	 */
	if (file != NULL && err != NULL && why != NULL)
	{
		if (push_to_db(ctx, 0, TRUE) == FALSE)
			egress_log(ctx, file, line, err, why);
	}
	clear_priv(ctx, FALSE);
	return ret;
}

sfsistat
egress_connect(SMFICTX *ctx, sfsistat ret, char *file, int line, char *err, char *why)
{
	egress_log(ctx, file, line, err, why);
	clear_priv(ctx, TRUE);
	return ret;
}


/*
** 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)
	{
		/* 
		** memory allocation failure here means
		** only xxfi_connect() will be called.
		*/
		return SMFIS_ACCEPT;
	}
	(void) memset((char *)priv, '\0', sizeof(PRIV));

	if (smfi_setpriv(ctx, (void *)priv) == MI_FAILURE)
	{
		/*
		** milterr() always exits.
		*/
		milterr(Prog, __FILE__, __LINE__, TRUE,
			errno, "xxfi_connect() 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;
		priv->ip = htonl(0x080000);
		priv->iptxt = try_strdup(__FILE__, __LINE__, "127.0.0.0", TRUE);
		set_bit(BIT_NOTINET, priv->bits);
	}
	else
	{
		(void) memcpy(&(priv->ip),
			(char *)&(((struct sockaddr_in *)ha)->sin_addr),
			sizeof(PRIMARY_DATUM));

		(void) memcpy(&in.s_addr, &(priv->ip), sizeof(PRIMARY_DATUM));
		priv->iptxt = try_strdup(__FILE__, __LINE__, inet_ntoa(in), TRUE);
		if (priv->iptxt == NULL)
		{
			/*
			** milterr() always exits.
			*/
			milterr(Prog, __FILE__, __LINE__, TRUE,
				errno, "xxfi_connect() ABORTED", strerror(errno));
		}

		/*
		 * 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 egress_connect(ctx, SMFIS_ACCEPT, 
			__FILE__, __LINE__,
			"Not an IPv4 network connection",
			"Accept envelope from xxfi_connect()");

	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)
	{
		return egress_connect(ctx, SMFIS_ACCEPT, 
			__FILE__, __LINE__, "Unexpected NULL private pointer",
			"Accept connection from xxfi_helo()");
	}

	/*
	 * If the milter configuration file allows it, check for
	 * an illegal advance write.
	 */
	if(AdvanceWrite == TRUE)
	{
		sym = smfi_getsymval(ctx, "{illegalpipe}");
		if (sym != NULL && *sym && 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.
*/
# ifndef HAVE_GETIPNODEBYNAME
pthread_mutex_t Envfrom_mutex;
# endif

sfsistat
xxfi_envfrom(SMFICTX *ctx, char **argv)
{
	char *cp, *ep;
	PRIV *priv;

	if (argv == NULL)
	{
		return egress_envelope(ctx, SMFIS_ACCEPT, 
			__FILE__, __LINE__, "NULL MAIL FROM sender",
			"Accept sender from xxfi_envfrom()");
	}

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

	priv = (PRIV *)smfi_getpriv(ctx);
	if (priv == NULL)
	{
		return egress_envelope(ctx, SMFIS_ACCEPT, 
			__FILE__, __LINE__,
			"Unexpected NULL private pointer",
			"Accept sender from xxfi_envfrom()");
	}

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

	/*
	 * A missing host part after the @ (i.e. "user@") is bad.
	 */
	if (cp >= ep)
	{
		set_bit(BIT_BAD_HOST, priv->bits);
	}
	else
	{
		/*
		 * We don't count it an error if the lookup returns
		 * a temporary error.
		 */
		int err;
# if HAVE_GETIPNODEBYNAME
		struct hostent *hp;

		hp = getipnodebyname(cp, AF_INET, AI_DEFAULT , &err);
		if (hp == NULL)
		{
			if (err != TRY_AGAIN)
				set_bit(BIT_BAD_HOST, priv->bits);
		}
		freehostent(hp);
# else
		/*
		 * Warning, this is not thread-safe!
		 */
		pthread_mutex_lock(&Envfrom_mutex);
		if (gethostbyname(cp) == NULL)
		{
			err = h_errno;
			if (err != TRY_AGAIN)
				set_bit(BIT_BAD_HOST, priv->bits);
		}
		pthread_mutex_unlock(&Envfrom_mutex);
# endif
	}

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

	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)
	{
		return egress_envelope(ctx, SMFIS_ACCEPT, 
			__FILE__, __LINE__,
			"Unexpected NULL private pointer",
			"Accept recipient from xxfi_envto()");
	}
	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.
	 */
	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);
		(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 recipient 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)
	{
		return egress_envelope(ctx, SMFIS_ACCEPT, 
			__FILE__, __LINE__,
			"Unexpected NULL private pointer",
			"Accept envelope from xxfi_header()");
	}

	/*
	 * If the XTrackHeader appears, accept the message,
	 * because this means it was already screened (probably
	 * by out MX host).
	 */
	if (XTrackHeader != NULL
		&& strcasecmp(field, XTrackHeader) == 0)
	{
		if (isdebug(BUG_HEADER))
			printf("%s: %s accepted\n", field, value);
		return egress_envelope(ctx, SMFIS_ACCEPT, 
			__FILE__, __LINE__,
			"Found XTrackHeader, already screened",
			"Accept envelope from xxfi_header()");
	}

	/*
	 * 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));
		in.s_addr = ntohl(in.s_addr);
		priv->iptxt = try_strdup(__FILE__, __LINE__, inet_ntoa(in), TRUE);
		if (priv->iptxt == NULL)
		{
			return egress_envelope(ctx, SMFIS_ACCEPT, 
				__FILE__, __LINE__, strerror(errno),
				"Accept envelope from xxfi_header()");
		}
		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->nhdrrcpts += header_recipient(value);
		if (isdebug(BUG_HEADER))
		{
			(void) printf("%s: %s\n", field, value);
		}
		return SMFIS_CONTINUE;
	}

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

		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 RFC2822 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.
*/
sfsistat
xxfi_eoh(SMFICTX *ctx)
{
	PRIV *priv;

	priv = (PRIV *)smfi_getpriv(ctx);
	if (priv == NULL)
	{
		return egress_envelope(ctx, SMFIS_ACCEPT, 
			__FILE__, __LINE__, strerror(errno),
			"Accept envelope from xxfi_eoh()");
	}

	/*
	 * 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]":"");
	}

	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.
*/
int
push_to_db(SMFICTX *ctx, time_t tt, int threading)
{
	PRIMARY_KEY p;
	SECONDARY_DATUM event;
	PRIMARY_DATUM   t;
	PRIMARY_KEY *ips;
	int cnt, i;
	PRIV *priv;
	int didlog;

	didlog = FALSE;
	priv = (PRIV *)smfi_getpriv(ctx);
	if (priv == NULL)
		return didlog;

	if (isdebug(BUG_DB) || isdebug(BUG_ADD) || isdebug(BUG_DELETE))
		t = BUG_NOW;
	else if (tt != 0)
		t = tt;
	else
		(void)time(&t);

	event.date = 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);

	ips = NULL;
	cnt = 0;
	ips = aliasips(ips, &cnt, priv->ip);
	if (cnt == 0)
		return didlog;
	for (i = 0; i < cnt; ++i)
	{
		if (i == 1)
			set_bit(BIT_ADDEDIP, event.eventmap);
		p = ips[i];
		if (put_event_database(Dp, p, &event) != 0)
		{
			/*
			** milterr never returns.
			*/
			milterr(Prog, __FILE__, __LINE__, TRUE,
				errno, "Database write",
				DatabaseLoc);
		}
# if HAVE_SYSLOG
		if (DoLog == TRUE && LogEvents == TRUE)
		{
			struct in_addr in;
			char *itext = NULL;

			if (priv->iptxt == NULL)
			{
				if (ips[i] == 0)
					itext = try_strdup(__FILE__, __LINE__, "0.0.0.0", TRUE);
				else
				{
					(void) memcpy(&in.s_addr, 
						(char *)&ips[i], sizeof(PRIMARY_KEY));
					in.s_addr = ntohl(in.s_addr);
					itext = try_strdup(__FILE__, __LINE__,
						inet_ntoa(in), TRUE ); 
				}
			}
			else
				itext = try_strdup(__FILE__, __LINE__, priv->iptxt, TRUE);

			if (itext == NULL)
				itext = try_strdup(__FILE__, __LINE__, "unknown", TRUE);

			syslog(LOG_INFO, "%s%s", LogPrefix,
				event_to_str(itext, t, &event, TRUE));
			didlog = TRUE;
			(void)free(itext);
		}
# endif
	}
	if (ips != NULL)
		(void)free(ips);

	return didlog;
}

/*
** 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_EOM', confMILTER_MACROS_EOM``, {nbadrcpts}'')
**
*/
sfsistat
xxfi_eom(SMFICTX *ctx)
{
	PRIV *priv;
	char *sym;

	priv = (PRIV *)smfi_getpriv(ctx);
	if (priv == NULL)
	{
		return egress_envelope(ctx, SMFIS_ACCEPT, 
			__FILE__, __LINE__,
			"Unexpected NULL private pointer",
			"Accept envelope from xxfi_eom()");
	}

	/*
	 * Record the number of bad recipients.
	 */
	priv->badrcpts = 0;
	sym = smfi_getsymval(ctx, "{nbadrcpts}");
	if (sym != NULL)
		priv->badrcpts = atoi(sym);
		
	/*
	 * Insert the tracking header if desired.
	 */
	if (XTrackHeader != NULL && priv->iptxt != NULL)
		(void)smfi_addheader(ctx, XTrackHeader, priv->iptxt);

	if (isdebug(BUG_EOM))
	{
		return SMFIS_ACCEPT;
	}
	(void) push_to_db(ctx, 0, TRUE);
	(void) clear_priv(ctx, FALSE);
	return SMFIS_ACCEPT;
}

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

	priv = (PRIV *)smfi_getpriv(ctx);
	if (priv != NULL)
	{
		if (MilterAbort == TRUE)
			set_bit(BIT_MILTER_ABORTED, priv->bits);

		if (! isdebug(BUG_ABORT))
		{
			(void) push_to_db(ctx, 0, TRUE);
			if (XTrackHeader != NULL && priv->iptxt != NULL)
				(void)smfi_addheader(ctx, XTrackHeader, priv->iptxt);
		}
	}
	if (! isdebug(BUG_ABORT))
		(void) clear_priv(ctx, FALSE);
	return SMFIS_ACCEPT;
}

sfsistat
xxfi_close(SMFICTX *ctx)
{
	clear_priv(ctx, TRUE);
	return SMFIS_ACCEPT;
}


struct smfiDesc SmfiDesc =
{
	NULL,		/* filter name */
	SMFI_VERSION,	/* version code -- do not change */
	SMFIF_ADDHDRS,	/* flags: This milter adds headers */
	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 headers */
	NULL,		/* This milter ignores the body */
	xxfi_eom,	/* either end of message */
	xxfi_abort,	/* or 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;
	int dbver;
	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;		/* global, set here, RO elsewhere */
	Verbose = FALSE;	/* global, set here, RO elsewhere */

	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;
			if (strcasecmp(cf, "/dev/null") == 0)
				break;
			if (getuid() == 0 || geteuid() == 0)
				break;
			printf("%s: -C requires root\n", Prog);
			exit(EINVAL);
			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 BOOTSTRAP\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
	 * A real chicken and egg problem here. How can we be sure
	 * the configuration file is safe, if we don't yet know
	 * the setting of the RunAsUser configuration item?
	 */
	if (cf == NULL)
		cf = CFFILE;
	(void) setdefaults();
	if (Verbose)
		printf("%s: using %s as config file.\n", Prog, cf);
	(void) readconf(Prog, cf);

	/*
	 * If we were run by root, and if the config file defines
	 * a different user to run as, switch now.
	 */
	if (geteuid() == 0 || getuid() == 0)
	{
		uid_t uid;
		gid_t gid;

		if (RunAsUser == NULL)
		{
			fprintf(stderr,
				"Run by root, but RunAsUser undefined. Abort.\n");
			exit(EINVAL);
		}
		if (isdigit((int)*RunAsUser))
			uid = atoi(RunAsUser);
		else
		{
			struct passwd *pw;

			pw = getpwnam(RunAsUser);
			if (pw == NULL)
			{
				fprintf(stderr,
					"RunAsUser=%s: no such login name\n",
					RunAsUser);
				exit(errno);
			}
			uid = pw->pw_uid;
		}

		if (RunAsGroup != NULL)
		{
			struct group *gp;

			if (isdigit((int)*RunAsGroup))
				gid = atoi(RunAsGroup);
			else
			{
				gp = getgrnam(RunAsGroup);
				if (gp == NULL)
				{
					fprintf(stderr,
						"RunAsGroup=%s: no such group name\n",
						RunAsGroup);
					exit(errno);
				}
				gid = gp->gr_gid;
			}
			(void)setgid(gid);
			(void)setegid(gid);
# if HAVE_SYSLOG
			if (DoLog == TRUE)
				syslog(LOG_INFO, "Reset group identity to %s", 
					RunAsGroup);
# endif
		}
		(void)setuid(uid);
		(void)seteuid(uid);
# if HAVE_SYSLOG
		if (DoLog == TRUE)
			syslog(LOG_INFO, "Reset user identity to %s", 
				RunAsUser);
# endif
	}

	/*
	 * Regression testing.
	 * Each test exits when done.
	 */
	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_SHOWNOW))	test_now(xtest);

	/*
	** A hack to speed up DNS lookups. There will be
	** no retries and no retransmissions.
	*/
	_res.retrans = 0;
	_res.retry = 0;

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


	/*
	 * Should the mx list be empty on our MX servers?
	 */
	if(mode != MODE_ADD_BY_HAND)
	{
		if (Verbose == TRUE)
			printf("%s: looking up MX Records\n",
				Prog);
		/*
		 * If we did not specify our hostname in the config,
		 * look it up with gethostname().
		 */
		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);
			if (Verbose == TRUE || isdebug(BUG_MX))
				print_list(buf, MxHosts,
					stdout);
			if (isdebug(BUG_MX))
				return EX_OK;
		}
		else
		{
			MxHosts = getmx(LocalHostName, MxHosts);
			if (Verbose == TRUE || isdebug(BUG_MX))
				print_list(LocalHostName, MxHosts,
					stdout);
			if (isdebug(BUG_MX))
				return EX_OK;
		}
	}

	/*
	 * Read into memory, a list of the honeypot addresses.
	 * Use -v to see what what was read.
	 */
	if (HoneyFile != NULL)
	{
		(void) read_honey();
		if (Verbose == TRUE)
		{
			(void) print_list(HoneyFile,
				HoneyHosts, stdout);
		}
	}

	/*
	 * The database stuff is always last.
	 * Open both databases. The db flags set are in
	 * database.c. Here we only supply the file name.
	 */
	if (mode == MODE_RUN_AS_MILTER || mode == MODE_BOOTSTRAP)
	{
		if (DatabaseLoc == NULL)
		{
			printf("%s: DatabaseLoc is NULL. Aborted\n", Prog);
			return EINVAL;
		}
		Dp = open_database(DatabaseLoc, force);

		if (mode == MODE_BOOTSTRAP)
		{
			PRIMARY_KEY p;
			SECONDARY_DATUM d;

			p = DATABASE_VERSION;
			memset((char *)&d, '\0', sizeof(SECONDARY_DATUM));
			ret = put_event_database(Dp, p, &d);
			if (ret != 0)
			{
				printf("%s: bootstrap: could not install database version\n", Prog);
				printf("%s: database creation aborted.\n", Prog);
				exit(ret);
			}
			if (Verbose == TRUE)
				printf("%s: succesful insert of version\n", Prog);
			return EX_OK;
		}

		dbver = get_db_version(Dp, FALSE);
		if (dbver != DATABASE_VERSION)
		{
			printf("%s: database version mismatch. Expected %d got %d\n",
				Prog, DATABASE_VERSION, dbver);
			exit(EINVAL);
		}

		if (Verbose == TRUE)
			printf("%s: using %s version %d\n", Prog, DatabaseLoc, dbver);


		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
		if (Verbose == TRUE)
			printf("%s: starting multi-threaded run\n",
				Prog);

# ifndef HAVE_GETIPNODEBYNAME
		pthread_mutex_init(&Envfrom_mutex, NULL);
# endif
		(void) smfi_main();

		(void)Dp->sync(Dp, 0);
		(void)close_database(Dp);
		if (Verbose == TRUE)
			printf("%s: all done.\n",
				Prog);
# if HAVE_SYSLOG
		if (DoLog == TRUE)
			syslog(LOG_INFO, "Version %s: normal shutdown", 
				VERSION);
# endif
		/*
		 * XXX Should we put auto-soft-restart code here?
		 */
	}
	else if(mode == MODE_ADD_BY_HAND)
	{
		slowedit(optind, argc, argv);
	}

	return EX_OK;
}

