/*
 * Copyright (c) 2004-2006 Sendmail, Inc. and its suppliers.
 *	All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: pmilter_engine.c,v 1.34 2007/02/13 03:58:18 ca Exp $")
#include "sm/error.h"
#include "sm/assert.h"
#include "sm/ctype.h"
#include "sm/reccom.h"
#include "sm/pmilter.h"
#include "pmilter.h"
#include "sm/pmfdef.h"
#include "sm/pmfapi.h"
#include "util.h"

#if MTA_USE_PMILTER

/*
**  How to implement session timeouts?
**  That is, when the server aborts a connection will pmilter be informed?
**
**  Note: currently a reply text is only sent if it
**  - has an SMTP reply code
**  - the reply code matches the return value of the function
**  This means it can't be used for other things right now, e.g.,
**  changing an address.
*/

#define CHKREPLYTEXT					\
	sm_str_getlen(pmse_ctx->pmse_reply_text) > 0 &&	\
	IS_SMTP_REPLY(rcode) &&				\
	SMTP_REPLY_MATCHES_RCODE(pmse_ctx->pmse_reply_text, rcode, 0, rc)

/*
**  SM_PMILT_NSEID -- New SMTP session
**
**	Parameters:
**		pmss_ctx -- task context pmilter/SMTP server
**		pmse_ctx -- pmilter/SMTP server session context
**		rcbe -- RCB for reply
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_pmilt_nseid(pmss_ctx_P pmss_ctx, pmse_ctx_P pmse_ctx, sm_rcbe_P rcbe)
{
	sm_ret_T ret;
	int rcode;
	bool sendreplytext;
	pmilter_P pmilter;

	SM_IS_PMSE_CTX(pmse_ctx);
	SM_REQUIRE(pmse_ctx->pmse_pmg_ctx != NULL);
	pmilter = pmse_ctx->pmse_pmg_ctx->pmg_pmilter;
	SM_REQUIRE(pmilter != NULL);
	ret = SM_SUCCESS;
	rcode = SMTP_R_OK;
	sendreplytext = false;

	if (pmilter->pmfi_connect != NULL &&
	    SM_IS_FLAG(pmss_ctx->pmss_pmcap, SM_SCAP_PM_CNNCT))
	{
		int rc;
		sm_sockaddr_T sm_sockaddr;

		sm_memzero(&sm_sockaddr, sizeof(sm_sockaddr));
		sm_sockaddr.sin.sin_addr.s_addr = pmse_ctx->pmse_cltipv4;
		sm_sockaddr.sin.sin_family = AF_INET;
		sm_str_clr(pmse_ctx->pmse_reply_text);
		pm_clrreplies(pmse_ctx);
		rcode = (*pmilter->pmfi_connect)(pmse_ctx,
				(char *) sm_str_getdata(pmse_ctx->pmse_arg1),
				&sm_sockaddr);
		sendreplytext = CHKREPLYTEXT;
	}
	ret = sm_rcb_putv(&rcbe->rcbe_rcb, RCB_PUTV_FIRST,
		SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT,
		SM_RCBV_INT, RT_M2S_ID, pmss_ctx->pmss_id,
		SM_RCBV_BUF, RT_M2S_SEID, pmse_ctx->pmse_se_id, SMTP_STID_SIZE,
		SM_RCBV_INT, RT_M2S_RCODE, rcode,
		SM_RCBV_STR, sendreplytext ? RT_M2S_STATT : RT_NOSEND,
			pmse_ctx->pmse_reply_text,
		SM_RCBV_END);
	return ret;
}

/*
**  SM_PMILT_CSEID -- Close SMTP session
**
**	Parameters:
**		pmss_ctx -- task context pmilter/SMTP server
**		pmse_ctx -- pmilter/SMTP server session context
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_pmilt_cseid(pmss_ctx_P pmss_ctx, pmse_ctx_P pmse_ctx)
{
	sm_ret_T ret;
	pmilter_P pmilter;

	SM_IS_PMSE_CTX(pmse_ctx);
	SM_REQUIRE(pmse_ctx->pmse_pmg_ctx != NULL);
	pmilter = pmse_ctx->pmse_pmg_ctx->pmg_pmilter;
	SM_REQUIRE(pmilter != NULL);
	ret = SM_SUCCESS;

	if (pmilter->pmfi_close != NULL)
		ret = (*pmilter->pmfi_close)(pmse_ctx);
	return SM_SUCCESS;
}

/*
**  SM_PMILT_HELO -- EHLO/HELO
**
**	Parameters:
**		pmss_ctx -- task context pmilter/SMTP server
**		pmse_ctx -- pmilter/SMTP server session context
**		rcbe -- RCB for reply
**		ehlo -- was EHLO used?
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_pmilt_helo(pmss_ctx_P pmss_ctx, pmse_ctx_P pmse_ctx, sm_rcbe_P rcbe, bool ehlo)
{
	sm_ret_T ret;
	int rcode;
	bool sendreplytext;
	pmilter_P pmilter;

	SM_IS_PMSE_CTX(pmse_ctx);
	SM_REQUIRE(pmse_ctx->pmse_pmg_ctx != NULL);
	pmilter = pmse_ctx->pmse_pmg_ctx->pmg_pmilter;
	SM_REQUIRE(pmilter != NULL);
	ret = SM_SUCCESS;
	rcode = SMTP_R_OK;
	sendreplytext = false;

	if (pmilter->pmfi_helo != NULL) {
		int rc;

		sm_str_clr(pmse_ctx->pmse_reply_text);
		rcode = (*pmilter->pmfi_helo)(pmse_ctx,
				(char *) sm_str_getdata(pmse_ctx->pmse_arg1), ehlo);
		sendreplytext = CHKREPLYTEXT;
	}
	ret = sm_rcb_putv(&rcbe->rcbe_rcb, RCB_PUTV_FIRST,
		SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT,
		SM_RCBV_INT, RT_M2S_ID, pmss_ctx->pmss_id,
		SM_RCBV_BUF, RT_M2S_SEID, pmse_ctx->pmse_se_id, SMTP_STID_SIZE,
		SM_RCBV_INT, RT_M2S_RCODE, rcode,
		SM_RCBV_STR, sendreplytext ? RT_M2S_STATT : RT_NOSEND,
			pmse_ctx->pmse_reply_text,
		SM_RCBV_END);
	return ret;
}

/*
**  SM_PMILT_MAIL -- MAIL
**
**	Parameters:
**		pmss_ctx -- task context pmilter/SMTP server
**		pmse_ctx -- pmilter/SMTP server session context
**		rcbe -- RCB for reply
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_pmilt_mail(pmss_ctx_P pmss_ctx, pmse_ctx_P pmse_ctx, sm_rcbe_P rcbe)
{
	sm_ret_T ret;
	int rcode;
	unsigned int nelem;
	bool sendreplytext;
	char **argv;
	pmilter_P pmilter;

	SM_IS_PMSE_CTX(pmse_ctx);
	SM_REQUIRE(pmse_ctx->pmse_pmg_ctx != NULL);
	pmilter = pmse_ctx->pmse_pmg_ctx->pmg_pmilter;
	SM_REQUIRE(pmilter != NULL);
	ret = SM_SUCCESS;
	rcode = SMTP_R_OK;
	argv = NULL;
	sendreplytext = false;

	ret = args2argv(pmse_ctx->pmse_arg2, &nelem, &argv);
	if (sm_is_err(ret))
		goto error;
	if (pmilter->pmfi_mail != NULL) {
		int rc;

		sm_str_clr(pmse_ctx->pmse_reply_text);
		rcode = (*pmilter->pmfi_mail)(pmse_ctx,
				(char *) sm_str_getdata(pmse_ctx->pmse_arg1),
				argv);
		sendreplytext = CHKREPLYTEXT;
	}
	ret = sm_rcb_putv(&rcbe->rcbe_rcb, RCB_PUTV_FIRST,
		SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT,
		SM_RCBV_INT, RT_M2S_ID, pmss_ctx->pmss_id,
		SM_RCBV_BUF, RT_M2S_SEID, pmse_ctx->pmse_se_id, SMTP_STID_SIZE,
		SM_RCBV_INT, RT_M2S_RCODE, rcode,
		SM_RCBV_STR, sendreplytext ? RT_M2S_STATT : RT_NOSEND,
			pmse_ctx->pmse_reply_text,
		SM_RCBV_END);

  error:
	if (argv != NULL)
		sm_free(argv);
	return ret;
}

/*
**  SM_PMILT_RCPT -- RCPT
**
**	Parameters:
**		pmss_ctx -- task context pmilter/SMTP server
**		pmse_ctx -- pmilter/SMTP server session context
**		rcbe -- RCB for reply
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_pmilt_rcpt(pmss_ctx_P pmss_ctx, pmse_ctx_P pmse_ctx, sm_rcbe_P rcbe)
{
	sm_ret_T ret;
	int rcode;
	unsigned int nelem;
	bool sendreplytext;
	char **argv;
	pmilter_P pmilter;

	SM_IS_PMSE_CTX(pmse_ctx);
	SM_REQUIRE(pmse_ctx->pmse_pmg_ctx != NULL);
	pmilter = pmse_ctx->pmse_pmg_ctx->pmg_pmilter;
	SM_REQUIRE(pmilter != NULL);
	ret = SM_SUCCESS;
	rcode = SMTP_R_OK;
	argv = NULL;
	sendreplytext = false;

	ret = args2argv(pmse_ctx->pmse_arg2, &nelem, &argv);
	if (sm_is_err(ret))
		goto error;
	if (pmilter->pmfi_rcpt != NULL) {
		int rc;

		sm_str_clr(pmse_ctx->pmse_reply_text);
		rcode = (*pmilter->pmfi_rcpt)(pmse_ctx,
				(char *) sm_str_getdata(pmse_ctx->pmse_arg1),
				argv);
		sendreplytext = CHKREPLYTEXT;
	}
	ret = sm_rcb_putv(&rcbe->rcbe_rcb, RCB_PUTV_FIRST,
		SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT,
		SM_RCBV_INT, RT_M2S_ID, pmss_ctx->pmss_id,
		SM_RCBV_BUF, RT_M2S_SEID, pmse_ctx->pmse_se_id, SMTP_STID_SIZE,
		SM_RCBV_INT, RT_M2S_RCODE, rcode,
		SM_RCBV_STR, sendreplytext ? RT_M2S_STATT : RT_NOSEND,
			pmse_ctx->pmse_reply_text,
		SM_RCBV_END);

  error:
	if (argv != NULL)
		sm_free(argv);
	return ret;
}

/*
**  SM_PMILT_DATA -- DATA
**
**	Parameters:
**		pmss_ctx -- task context pmilter/SMTP server
**		pmse_ctx -- pmilter/SMTP server session context
**		rcbe -- RCB for reply
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_pmilt_data(pmss_ctx_P pmss_ctx, pmse_ctx_P pmse_ctx, sm_rcbe_P rcbe)
{
	sm_ret_T ret;
	int rcode;
	bool sendreplytext;
	pmilter_P pmilter;

	SM_IS_PMSE_CTX(pmse_ctx);
	SM_REQUIRE(pmse_ctx->pmse_pmg_ctx != NULL);
	pmilter = pmse_ctx->pmse_pmg_ctx->pmg_pmilter;
	SM_REQUIRE(pmilter != NULL);
	ret = SM_SUCCESS;
	rcode = SMTP_R_OK;
	sendreplytext = false;

	if (pmilter->pmfi_data != NULL) {
		int rc;

		sm_str_clr(pmse_ctx->pmse_reply_text);
		rcode = (*pmilter->pmfi_data)(pmse_ctx);
		sendreplytext = CHKREPLYTEXT;
	}
	ret = sm_rcb_putv(&rcbe->rcbe_rcb, RCB_PUTV_FIRST,
		SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT,
		SM_RCBV_INT, RT_M2S_ID, pmss_ctx->pmss_id,
		SM_RCBV_BUF, RT_M2S_SEID, pmse_ctx->pmse_se_id, SMTP_STID_SIZE,
		SM_RCBV_INT, RT_M2S_RCODE, rcode,
		SM_RCBV_STR, sendreplytext ? RT_M2S_STATT : RT_NOSEND,
			pmse_ctx->pmse_reply_text,
		SM_RCBV_END);
	return ret;
}

/*
**  SM_PMILT_MSG -- message chunck
**
**	Parameters:
**		pmss_ctx -- task context pmilter/SMTP server
**		pmse_ctx -- pmilter/SMTP server session context
**		buf -- message chunck
**		len -- size of message chunck
**		rcbe -- RCB for reply
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_pmilt_msg(pmss_ctx_P pmss_ctx, pmse_ctx_P pmse_ctx, uchar *buf, size_t len, sm_rcbe_P rcbe)
{
	sm_ret_T ret;
	int rcode;
	bool sendreplytext;
	pmilter_P pmilter;

	SM_IS_PMSE_CTX(pmse_ctx);
	SM_REQUIRE(pmse_ctx->pmse_pmg_ctx != NULL);
	pmilter = pmse_ctx->pmse_pmg_ctx->pmg_pmilter;
	SM_REQUIRE(pmilter != NULL);
	ret = SM_SUCCESS;
	rcode = SMTP_R_OK;
	sendreplytext = false;

	if (pmilter->pmfi_msg != NULL) {
		int rc;

		sm_str_clr(pmse_ctx->pmse_reply_text);
		rcode = (*pmilter->pmfi_msg)(pmse_ctx, buf, len);
		sendreplytext = SM_IS_FLAG(pmss_ctx->pmss_pmcap, SM_SCAP_PM_MSG_RC) &&
				CHKREPLYTEXT;
	}

	if (SM_IS_FLAG(pmss_ctx->pmss_pmcap, SM_SCAP_PM_MSG_RC)) {
		ret = sm_rcb_putv(&rcbe->rcbe_rcb, RCB_PUTV_FIRST,
			SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT,
			SM_RCBV_INT, RT_M2S_ID, pmss_ctx->pmss_id,
			SM_RCBV_BUF, RT_M2S_SEID, pmse_ctx->pmse_se_id, SMTP_STID_SIZE,
			SM_RCBV_INT, RT_M2S_RCODE, rcode,
			SM_RCBV_STR, sendreplytext ? RT_M2S_STATT : RT_NOSEND,
				pmse_ctx->pmse_reply_text,
			SM_RCBV_END);
	}

	return ret;
}

/*
**  SM_PMILT_DOT -- final message chunck
**
**	Parameters:
**		pmss_ctx -- task context pmilter/SMTP server
**		pmse_ctx -- pmilter/SMTP server session context
**		buf -- message chunck
**		len -- size of message chunck
**		rcbe -- RCB for reply
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_pmilt_dot(pmss_ctx_P pmss_ctx, pmse_ctx_P pmse_ctx, uchar *buf, size_t len, sm_rcbe_P rcbe)
{
	sm_ret_T ret;
	int rcode, rc;
	bool sendreplytext;
#if MTA_USE_RSAD
	bool sendreplies;
#endif
	pmilter_P pmilter;

	SM_IS_PMSE_CTX(pmse_ctx);
	SM_REQUIRE(pmse_ctx->pmse_pmg_ctx != NULL);
	pmilter = pmse_ctx->pmse_pmg_ctx->pmg_pmilter;
	SM_REQUIRE(pmilter != NULL);
	ret = SM_SUCCESS;
	rcode = SMTP_R_OK;
	sendreplytext = false;
#if MTA_USE_RSAD
	sendreplies = false;
#endif

	if (pmilter->pmfi_msg != NULL) {
		sm_str_clr(pmse_ctx->pmse_reply_text);
		pmse_ctx->pmse_state = PMSE_ST_MSG;
		rcode = (*pmilter->pmfi_msg)(pmse_ctx, buf, len);
		if (SM_IS_FLAG(pmss_ctx->pmss_pmcap, SM_SCAP_PM_MSG_RC)) {
			sendreplytext = CHKREPLYTEXT;
		}
		else
			rcode = SMTP_R_OK;
	}
	if (!(SM_IS_FLAG(pmss_ctx->pmss_pmcap, SM_SCAP_PM_MSG_RC) &&
	      (IS_SMTP_REPLY(rcode) || rcode == SMTP_R_ACCEPT)) &&
	    pmilter->pmfi_eom != NULL)
	{
		sm_str_clr(pmse_ctx->pmse_reply_text);
		pm_clrreplies(pmse_ctx);
		pmse_ctx->pmse_state = PMSE_ST_DOT;
		rcode = (*pmilter->pmfi_eom)(pmse_ctx);
		if (SMTP_R_RPLCMSG == rcode &&
		    SM_IS_FLAG(pmss_ctx->pmss_pmcap, SM_SCAP_PM_MSGREPLACE) &&
		    pmilter->pmfi_msg_rplc != NULL)
		{
			pmse_ctx->pmse_cmd_status = SM_SUCCESS;
			rcbe->rcbe_wr_cb = sm_pmilt_msg_rplc;
			rcbe->rcbe_ctx = pmse_ctx;
		}
		pmse_ctx->pmse_state = PMSE_ST_NONE;
		sendreplytext = CHKREPLYTEXT;
#if MTA_USE_RSAD
		sendreplies = (pmse_ctx->pmse_nreplies > 0
					&& pmse_ctx->pmse_rcodes != NULL);
#endif
	}
	ret = sm_rcb_putv(&rcbe->rcbe_rcb, RCB_PUTV_FIRST,
		SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT,
		SM_RCBV_INT, RT_M2S_ID, pmss_ctx->pmss_id,
		SM_RCBV_BUF, RT_M2S_SEID, pmse_ctx->pmse_se_id, SMTP_STID_SIZE,
		SM_RCBV_INT, RT_M2S_RCODE, rcode,
		SM_RCBV_STR, sendreplytext ? RT_M2S_STATT : RT_NOSEND,
			pmse_ctx->pmse_reply_text,
#if MTA_USE_RSAD
		SM_RCBV_INTA, sendreplies ? RT_M2S_RCODES : RT_NOSEND,
			pmse_ctx->pmse_nreplies, pmse_ctx->pmse_rcodes,
#endif
		SM_RCBV_END);

	if (pmse_ctx->pmse_mail_new != NULL) {
		if (SM_IS_FLAG(pmss_ctx->pmss_pmcap, SM_SCAP_PM_MAILMOD)) {
			ret = sm_rcb_putv(&rcbe->rcbe_rcb, 0,
				SM_RCBV_STR, RT_M2S_MAIL_NEW, pmse_ctx->pmse_mail_new,
				SM_RCBV_END);
		}
		else if (sm_is_success(ret))
			ret = sm_error_perm(SM_EM_PMILTER, EINVAL);
		SM_STR_FREE(pmse_ctx->pmse_mail_new);
	}

	/* send recipient mods */
	if (!PM_RCPTS_EMPTY(&pmse_ctx->pmse_rcpts_hd)) {
		if (SM_IS_FLAG(pmss_ctx->pmss_pmcap, SM_SCAP_PM_RCPTMOD)) {
			pm_rcpt_P pm_rcpt;

			for (pm_rcpt = PM_RCPTS_FIRST(&pmse_ctx->pmse_rcpts_hd);
			     pm_rcpt != PM_RCPTS_END(&pmse_ctx->pmse_rcpts_hd)
			     && sm_is_success(ret);
			     pm_rcpt = PM_RCPTS_NEXT(pm_rcpt))
			{
				if (PM_RCPT_ADD == pm_rcpt->pmr_type)
					ret = sm_rcb_putv(&rcbe->rcbe_rcb, 0,
						SM_RCBV_STR, RT_M2S_RCPT_ADD, pm_rcpt->pmr_pa,
						SM_RCBV_END);
				else
					ret = sm_rcb_putv(&rcbe->rcbe_rcb, 0,
						SM_RCBV_STR, RT_M2S_RCPT_DEL, pm_rcpt->pmr_pa,
						SM_RCBV_INT, RT_M2S_RCPT_IDX, pm_rcpt->pmr_idx,
						SM_RCBV_END);
			}
		}
		else if (sm_is_success(ret))
			ret = sm_error_perm(SM_EM_PMILTER, EINVAL);
		pm_rcpts_free(pmse_ctx);
	}

	/* send header mods */
	if (!HDRMODL_EMPTY(pmse_ctx->pmse_hdrmodhd)) {
		if (SM_IS_FLAG(pmss_ctx->pmss_pmcap, SM_SCAP_PM_HDRMOD)) {
			sm_hdrmodl_wr(pmse_ctx->pmse_hdrmodhd, &rcbe->rcbe_rcb,
				RT_M2S_HM_T_P, RT_M2S_HM_HDR);
		}
		else if (sm_is_success(ret))
			ret = sm_error_perm(SM_EM_PMILTER, EINVAL);
		sm_hdrmodl_free(&pmse_ctx->pmse_hdrmodhd);
	}

	return ret;
}

/*
**  SM_PMILT_ABORT_TA -- abort current transaction
**
**	Parameters:
**		pmss_ctx -- task context pmilter/SMTP server
**		pmse_ctx -- pmilter/SMTP server session context
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_pmilt_abort_ta(pmss_ctx_P pmss_ctx, pmse_ctx_P pmse_ctx)
{
	sm_ret_T ret;
	pmilter_P pmilter;

	SM_IS_PMSE_CTX(pmse_ctx);
	SM_REQUIRE(pmse_ctx->pmse_pmg_ctx != NULL);
	pmilter = pmse_ctx->pmse_pmg_ctx->pmg_pmilter;
	SM_REQUIRE(pmilter != NULL);
	ret = SM_SUCCESS;

	if (pmilter->pmfi_abort != NULL)
		ret = (*pmilter->pmfi_abort)(pmse_ctx);

	return ret;
}

/*
**  SM_PMILT_MSG_RPLC_STAT -- message replacement status
**
**	Parameters:
**		pmss_ctx -- task context pmilter/SMTP server
**		pmse_ctx -- pmilter/SMTP server session context
**		status -- message replacement status
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_pmilt_msg_rplc_stat(pmss_ctx_P pmss_ctx, pmse_ctx_P pmse_ctx, sm_ret_T status)
{
	sm_ret_T ret;
	pmilter_P pmilter;

	SM_IS_PMSE_CTX(pmse_ctx);
	SM_REQUIRE(pmse_ctx->pmse_pmg_ctx != NULL);
	pmilter = pmse_ctx->pmse_pmg_ctx->pmg_pmilter;
	SM_REQUIRE(pmilter != NULL);
	ret = SM_SUCCESS;

	if (pmilter->pmfi_msg_rplc_stat != NULL)
		ret = (*pmilter->pmfi_msg_rplc_stat)(pmse_ctx, status);

	return ret;
}
#endif /* MTA_USE_PMILTER */


syntax highlighted by Code2HTML, v. 0.9.1