/*
 * Copyright (c) 2005, 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: t-pmilter-1.c,v 1.65 2007/06/18 16:20:38 ca Exp $")
#include "sm/error.h"
#include "sm/assert.h"
#include "sm/types.h"
#include "sm/sysexits.h"
#include "sm/fcntl.h"
#include "sm/io.h"
#include "sm/ctype.h"
#include "sm/reccom.h"
#include "sm/mta.h"
#define PMILTER_DEBUG_DEFINE 1
#include "pmilter.h"
#include "sm/pmfdef.h"
#include "sm/pmfapi.h"
#include "sm/pmilter.h"
#include "util.h"
#include "t-pmilter.h"
#include "sm/test.h"

#if MTA_USE_PMILTER

static sm_hdrmodhd_P sm_hdrmodhd = NULL;
static char *rcpt_add_pa = NULL;
static char *rcpt_del_pa = NULL;
static rcpt_idx_T rcpt_del_idx = 0;
static char *mail_new = NULL;

/* replace all occurrences of a in str with b... just a hack */
static void
strreplace(char *str, char a, char b)
{
	char *s;

	if (NULL == str)
		return;
	if (a == b)
		return;
	s = str;
	while (*s != '\0') {
		if (*s == a)
			*s = b;
		++s;
	}
}

/* hack: copy str, replace '_' with ' ', and append \r\n */
static char *
getreply(char *str)
{
	size_t len;
	char *new;

	if (NULL == str)
		return NULL;
	len = strlen(str);
	if (len > 1000)
		return NULL;
	new = malloc(len + 4);
	if (NULL == new)
		return NULL;
	sm_memcpy(new, str, len + 1);
	strreplace(new, '_', ' ');
	new[len] = '\r';
	new[len + 1] = '\n';
	new[len + 2] = '\0';
	return new;
}

/*
**  pmilter test program; uses "native" API
*/

static pmt_ctx_T pmt_ctx;
#define SM_BUFSIZE	8192
static uint rbufsize = SM_BUFSIZE;

#define SETREPLY(str) do {							\
		if ((str) != NULL && *(str) != '\0')		\
			ret = sm_pmfi_setreply(pmse_ctx, str);	\
	} while (0)

#define T_DELAY(delay) do {	\
		if ((delay) > 0)	\
			sleep(delay);	\
	} while (0)

/* common function code; use only at the end of a function */
#define TPM_END(stage)					\
	if (PMT_EXIT(pmt_ctx.pmt_rcode[stage])) \
		exit(0);	\
	SETREPLY(pmt_ctx.pmt_reply[stage]);	\
	T_DELAY(pmt_ctx.pmt_delay[stage]);	\
	return pmt_ctx.pmt_rcode[stage]

static sm_ret_T
tpm1_negotiate(pmss_ctx_P pmss_ctx, uint32_t srv_cap, uint32_t srv_fct, uint32_t srv_feat, uint32_t srv_misc, uint32_t *pm_cap, uint32_t *pm_fct, uint32_t *pm_feat, uint32_t *pm_misc)
{
	sm_ret_T ret;
	pmt_ctx_P pmt_ctx_l;

	SM_REQUIRE(pm_cap != NULL);
	SM_REQUIRE(pm_fct != NULL);
	SM_REQUIRE(pm_feat != NULL);
	SM_REQUIRE(pm_misc != NULL);

	pmt_ctx_l = (pmt_ctx_P) sm_pmfi_get_ctx_g_ss(pmss_ctx);
	SM_TEST(pmt_ctx_l != NULL);
	SM_TEST(pmt_ctx_l == &pmt_ctx);

	if (SM_IS_FLAG(pmt_ctx_l->pmt_flags, PMT_FL_M_HOSTN) ||
	    SM_IS_FLAG(pmt_ctx_l->pmt_flags, PMT_FL_M_SEID))
	{
		ret = sm_pmfi_setmaclist(pmss_ctx, PM_SMST_CONNECT,
					PMM_SRVHOSTNAME, PMM_SEID, PMM_END);
		SM_TEST(SM_SUCCESS == ret);
	}

	if (SM_IS_FLAG(pmt_ctx_l->pmt_flags, PMT_FL_M_MSGID)) {
		ret = sm_pmfi_setmaclist(pmss_ctx, PM_SMST_DOT,
					PMM_DOT_MSGID, PMM_END);
		SM_TEST(SM_SUCCESS == ret);
	}

	if (SM_IS_FLAG(pmt_ctx_l->pmt_flags, PMT_FL_M_TAID)) {
		ret = sm_pmfi_setmaclist(pmss_ctx, PM_SMST_MAIL,
					PMM_MAIL_TAID, PMM_END);
		SM_TEST(SM_SUCCESS == ret);
	}

	/* check that all bits in pmt_cap are also set in srv_cap... */
	if ((pmt_ctx.pmt_cap & srv_cap) != pmt_ctx.pmt_cap)
		sm_io_fprintf(smioerr,
			"sev=WARN, where=tpm1_negotiate, pm_cap=%#X, srv_cap=%#X\n",
			pmt_ctx_l->pmt_cap, srv_cap);

	*pm_cap = pmt_ctx_l->pmt_cap;
	*pm_fct = 0;
	*pm_feat = 0;
	*pm_misc = 0;
	sm_io_fprintf(smioerr, "sev=DBG, where=tpm1_negotiate, cap=%#X\n",
		pmt_ctx_l->pmt_cap);
	return SM_SUCCESS;
}

static sfsistat_T
tpm1_connect(pmse_ctx_P pmse_ctx, const char *hostname, sm_sockaddr_T *hostaddr)
{
	sfsistat_T ret;

	if (PMT_EXIT(pmt_ctx.pmt_rcode[SM_STAGE_NSEID]))
		exit(0);

	/* HACK; should get pmt_ctx from pmse_ctx (indirectly) */
	ret = sm_pmfi_set_ctx_se(pmse_ctx, &pmt_ctx);
	SM_TEST(SM_SUCCESS == ret);
	++pmt_ctx.pmt_se_cnt;

	if (SM_IS_FLAG(pmt_ctx.pmt_flags, PMT_FL_M_HOSTN)) {
		char *srvhostname;

		ret = sm_pmfi_getmac(pmse_ctx, PMM_SRVHOSTNAME, &srvhostname);
		SM_TEST(SM_SUCCESS == ret);
		SM_TEST(srvhostname != NULL);
		sm_io_fprintf(smioerr,
			"sev=DBG, seid=%s, srvhostname=%s, where=connect, ret=%X\n",
			pmse_ctx->pmse_se_id, srvhostname, ret);
	}

	if (SM_IS_FLAG(pmt_ctx.pmt_flags, PMT_FL_M_SEID)) {
		char *se_id;

		ret = sm_pmfi_getmac(pmse_ctx, PMM_SEID, &se_id);
		SM_TEST(SM_SUCCESS == ret);
		SM_TEST(se_id != NULL);
		sm_io_fprintf(smioerr,
			"sev=DBG, seid=%s, se_id=%s, where=connect, ret=%X\n",
			pmse_ctx->pmse_se_id, se_id, ret);
	}

	ret = pmt_ctx.pmt_rcode[SM_STAGE_NSEID];
	sm_io_fprintf(smioerr,
		"sev=DBG, seid=%s, host=%s, where=connect, rc=%d\n",
		pmse_ctx->pmse_se_id, hostname, ret);
	TPM_END(SM_STAGE_NSEID);
}

static sm_ret_T
tpm1_close(pmse_ctx_P pmse_ctx)
{
	void *actx;

	sm_io_fprintf(smioerr, "sev=DBG, seid=%s, where=close\n",
		pmse_ctx->pmse_se_id);
	actx = sm_pmfi_get_ctx_se(pmse_ctx);
	SM_TEST(actx == &pmt_ctx);
	return SM_SUCCESS;
}

static sfsistat_T
tpm1_helo(pmse_ctx_P pmse_ctx, const char *helohost, bool ehlo)
{
	sfsistat_T ret;

	sm_io_fprintf(smioerr, "sev=DBG, seid=%s, helo=%s, ehlo=%d, rc=%d\n",
		pmse_ctx->pmse_se_id, helohost, ehlo,
		pmt_ctx.pmt_rcode[SM_STAGE_HELO]);
	TPM_END(SM_STAGE_HELO);
}

static sfsistat_T
tpm1_mail(pmse_ctx_P pmse_ctx, const char *mail, char **argv)
{
	sfsistat_T ret;

	++pmt_ctx.pmt_ta_cnt;
	pmt_rcpts_free(&pmt_ctx);
	pmt_ctx.pmt_ta_rcpts_ok = 0;
	if (SM_IS_FLAG(pmt_ctx.pmt_flags, PMT_FL_M_TAID)) {
		char *ta_id;

		ret = sm_pmfi_getmac(pmse_ctx, PMM_MAIL_TAID, &ta_id);
		SM_TEST(SM_SUCCESS == ret);
		SM_TEST(ta_id != NULL);
		sm_io_fprintf(smioerr,
			"sev=DBG, seid=%s, ta_id=%s, ret=%X\n",
			pmse_ctx->pmse_se_id, ta_id, ret);
	}

	if (SM_IS_FLAG(pmt_ctx.pmt_flags, PMT_FL_M_2ND) &&
	    pmt_ctx.pmt_ta_cnt > 1 &&
	    SM_IS_FLAG(pmt_ctx.pmt_flags, PMT_FL_M_MSGID))
	{
		char *msgid;

		ret = sm_pmfi_getmac(pmse_ctx, PMM_DOT_MSGID, &msgid);
		SM_TEST(SM_SUCCESS == ret);
		SM_TEST(NULL == msgid);
		sm_io_fprintf(smioerr,
			"sev=DBG, func=tpm1_mail, seid=%s, msgid=%s, ret=%X\n",
			pmse_ctx->pmse_se_id, msgid, ret);
	}

	if (argv != NULL && argv[0] != NULL && strcasecmp(argv[0], "prdr") == 0)
		PMT_SET_FLAG(&pmt_ctx, PMT_FL_RD_RSAD);
	else
		PMT_CLR_FLAG(&pmt_ctx, PMT_FL_RD_RSAD);

	sm_io_fprintf(smioerr,
		"sev=DBG, seid=%s, mail=%s, argv[0]=%s, rc=%d\n",
		pmse_ctx->pmse_se_id, mail,
		(argv != NULL && argv[0] != NULL) ? argv[0] : "NULL",
		pmt_ctx.pmt_rcode[SM_STAGE_MAIL]);
	TPM_END(SM_STAGE_MAIL);
}

static sfsistat_T
tpm1_rcpt(pmse_ctx_P pmse_ctx, const char *rcpt, char **argv)
{
	sm_ret_T ret, st, rcode;

	st = -1;
	ret = SMTP_OK;
	if (SM_IS_FLAG(pmt_ctx.pmt_flags, PMT_FL_R_ST))
		ret = sm_pmfi_getstatus(pmse_ctx, &st);
	sm_io_fprintf(smioerr,
		"sev=DBG, seid=%s, rcpt=%s, argv[0]=%s, st=%d, rc=%d\n",
		pmse_ctx->pmse_se_id, rcpt,
		(argv != NULL && argv[0] != NULL) ? argv[0] : "NULL", st,
		pmt_ctx.pmt_rcode[SM_STAGE_RCPT]);

	if (rcpt != NULL && strlen(rcpt) > 5 &&
	    'r' == rcpt[1] && IS_SMTP_CODE(rcpt, 2))
		rcode = strtoul(rcpt + 2, NULL, 10);
	else
		rcode = SMTP_NO_REPLY;

	if ((smtp_reply_type(pmt_ctx.pmt_rcode[SM_STAGE_RCPT]) == SMTP_RTYPE_OK
	     || pmt_ctx.pmt_rcode[SM_STAGE_RCPT] == SMTP_R_CONT
	     || pmt_ctx.pmt_rcode[SM_STAGE_RCPT] == SMTP_OK) &&
	    (smtp_reply_type(ret) == SMTP_RTYPE_OK
	     || SMTP_OK == ret) &&
	    (smtp_reply_type(rcode) == SMTP_RTYPE_OK
	     || SMTP_NO_REPLY == rcode))
	{
		pmt_rcpt_P pmt_rcpt;

		pmt_rcpt_new(&pmt_ctx, rcpt, &pmt_rcpt);
		++pmt_ctx.pmt_ta_rcpts_ok;
	}
	TPM_END(SM_STAGE_RCPT);
}

static sfsistat_T
tpm1_data(pmse_ctx_P pmse_ctx)
{
	sm_ret_T ret;

	sm_io_fprintf(smioerr, "sev=DBG, seid=%s, where=data, rc=%d\n",
		pmse_ctx->pmse_se_id, pmt_ctx.pmt_rcode[SM_STAGE_DATA]);
	TPM_END(SM_STAGE_DATA);
}

static void
pm_open_wr(pmse_ctx_P pmse_ctx)
{
	if (PMT_IS_FLAG(&pmt_ctx, PMT_FL_W2F) &&
	    !PMT_IS_FLAG(&pmt_ctx, PMT_FL_FD_OPEN) &&
	    !PMT_IS_FLAG(&pmt_ctx, PMT_FL_FD_FAIL))
	{
		pmt_ctx.pmt_fd = open(pmt_ctx.pmt_fname,
					O_WRONLY|O_APPEND|O_CREAT,
					0660);
		if (pmt_ctx.pmt_fd >= 0)
			PMT_SET_FLAG(&pmt_ctx, PMT_FL_FD_OPEN);
		else
			PMT_SET_FLAG(&pmt_ctx, PMT_FL_FD_FAIL);
		sm_io_fprintf(smioerr,
			"sev=DBG, seid=%s, where=pm_open_wr, open=%d\n",
			pmse_ctx->pmse_se_id, pmt_ctx.pmt_fd);
	}
}

static void
pm_close_wr(pmse_ctx_P pmse_ctx)
{
	if (PMT_IS_FLAG(&pmt_ctx, PMT_FL_FD_OPEN)) {
		close(pmt_ctx.pmt_fd);
		pmt_ctx.pmt_fd = INVALID_FD;
		PMT_CLR_FLAG(&pmt_ctx, PMT_FL_FD_OPEN);
	}
}


static sfsistat_T
tpm1_unknown(pmse_ctx_P pmse_ctx, const char *cmd)
{
	sm_io_fprintf(smioerr,
		"sev=DBG, seid=%s, where=unknown, cmd=%s\n",
		pmse_ctx->pmse_se_id, cmd);
	return SMTP_R_CONT;
}

static sfsistat_T
tpm1_msg(pmse_ctx_P pmse_ctx, unsigned char *buf, size_t len)
{
	sm_ret_T ret;

	pm_open_wr(pmse_ctx);
	if (PMT_IS_FLAG(&pmt_ctx, PMT_FL_W2F) &&
	    PMT_IS_FLAG(&pmt_ctx, PMT_FL_FD_OPEN) &&
	    !PMT_IS_FLAG(&pmt_ctx, PMT_FL_WR_FAIL) &&
	    buf != NULL && len > 0
	   )
	{
		ssize_t written;

		written = write(pmt_ctx.pmt_fd, buf, len);
		if (written == -1) {
			sm_io_fprintf(smioerr,
				"sev=ERROR, seid=%s, where=msg, write=%d\n",
				pmse_ctx->pmse_se_id, (int) written);
			PMT_SET_FLAG(&pmt_ctx, PMT_FL_WR_FAIL);
			pm_close_wr(pmse_ctx);
		}
	}

	sm_io_fprintf(smioerr, "sev=DBG, seid=%s, where=msg, len=%d, rc=%d\n",
		pmse_ctx->pmse_se_id, (int) len, pmt_ctx.pmt_rcode[SM_STAGE_MSG]);
	TPM_END(SM_STAGE_MSG);
}

static sfsistat_T
tpm1_eom(pmse_ctx_P pmse_ctx)
{
	sm_ret_T ret;
	bool writefailed;
	int rcode;
	sm_hdrmod_P sm_hdrmod;

	writefailed = PMT_IS_FLAG(&pmt_ctx, PMT_FL_W2F) &&
			PMT_IS_FLAG(&pmt_ctx, PMT_FL_WR_FAIL);
	if (PMT_IS_FLAG(&pmt_ctx, PMT_FL_W2F) &&
	    PMT_IS_FLAG(&pmt_ctx, PMT_FL_FD_OPEN) &&
	    !PMT_IS_FLAG(&pmt_ctx, PMT_FL_WR_FAIL))
	{
		pm_close_wr(pmse_ctx);
		sm_io_fprintf(smioerr,
			"sev=DBG, seid=%s, where=dot, status=closed\n",
			pmse_ctx->pmse_se_id);
	}

	if (SM_IS_FLAG(pmt_ctx.pmt_flags, PMT_FL_M_MSGID)) {
		char *msgid;

		ret = sm_pmfi_getmac(pmse_ctx, PMM_DOT_MSGID, &msgid);
		SM_TEST(SM_SUCCESS == ret);
		SM_TEST(msgid != NULL);
		sm_io_fprintf(smioerr,
			"sev=DBG, seid=%s, msgid=%s, ret=%X\n",
			pmse_ctx->pmse_se_id, msgid, ret);
	}

	rcode = pmt_ctx.pmt_rcode[SM_STAGE_DOT];

	/* HACK !!! */
#define PMT_MAX_REPLIES	32
	if (PMT_IS_FLAG(&pmt_ctx, PMT_FL_RD_RSAD)
	    && SMTP_R_D_EOM == pmt_ctx.pmt_rcode[SM_STAGE_DOT]
	    && pmt_ctx.pmt_ta_rcpts_ok < PMT_MAX_REPLIES)
	{
		uint u;
		pmt_rcpt_P pmt_rcpt;
		int rcodes[PMT_MAX_REPLIES];

		for (u = 0, pmt_rcpt = PMT_RCPTS_FIRST(&pmt_ctx.pmt_rcpts);
	         u < pmt_ctx.pmt_ta_rcpts_ok
		     && pmt_rcpt != PMT_RCPTS_END(&pmt_ctx.pmt_rcpts);
		     u++, pmt_rcpt = PMT_RCPTS_NEXT(pmt_rcpt))
		{
			char *pa;

			rcodes[u] = pmt_ctx.pmt_rcode[SM_STAGE_RSAD];
			pa = pmt_rcpt->pmtr_pa;
			if (pa != NULL && strlen(pa) > 5 &&
			    'd' == pa[1] && IS_SMTP_CODE(pa, 2))
				rcodes[u] = strtoul(pa + 2, NULL, 10);
		}
		ret = sm_pmfi_setreplies(pmse_ctx, pmt_ctx.pmt_ta_rcpts_ok, rcodes, NULL);
		sm_io_fprintf(smioerr,
			"sev=DBG, seid=%s, rcpts_ok=%d, rcode=%d, setreplies=%r\n"
			, pmse_ctx->pmse_se_id, pmt_ctx.pmt_ta_rcpts_ok
			, pmt_ctx.pmt_rcode[SM_STAGE_RSAD], ret);
	}
	else if (SMTP_R_OK == rcode && writefailed)
		rcode = SMTP_R_TEMP;
	else if (PMT_IS_FLAG(&pmt_ctx, PMT_FL_RPLC_MSG))
		rcode = SMTP_R_RPLCMSG;

	if (mail_new != NULL) {
		ret = sm_pmfi_mail_rplc(pmse_ctx, mail_new, NULL);
		SM_TEST(SM_SUCCESS == ret);
		sm_io_fprintf(smioerr,
			"sev=DBG, seid=%s, mail_new=%s, ret=%#x\n",
			pmse_ctx->pmse_se_id, mail_new, ret);
	}

	if (rcpt_add_pa != NULL) {
		ret = sm_pmfi_rcpt_add(pmse_ctx, rcpt_add_pa, NULL);
		SM_TEST(SM_SUCCESS == ret);
		sm_io_fprintf(smioerr,
			"sev=DBG, seid=%s, rcpt_add_pa=%s, ret=%#x\n",
			pmse_ctx->pmse_se_id, rcpt_add_pa, ret);
	}
	if (rcpt_del_pa != NULL) {
		ret = sm_pmfi_rcpt_del(pmse_ctx, rcpt_del_pa, rcpt_del_idx);
		SM_TEST(SM_SUCCESS == ret);
		sm_io_fprintf(smioerr,
			"sev=DBG, seid=%s, rcpt_del_pa=%s, ret=%#x\n",
			pmse_ctx->pmse_se_id, rcpt_del_pa, ret);
	}
	sm_hdrmod = HDRMODL_FIRST(sm_hdrmodhd);
	while (sm_hdrmod != NULL) {
		ret = sm_pmfi_hdr_mod(pmse_ctx, sm_hdrmod->sm_hm_type,
				sm_hdrmod->sm_hm_pos,
				sm_hdrmod->sm_hm_hdr != NULL
					? sm_cstr_data(sm_hdrmod->sm_hm_hdr)
					: NULL);
		SM_TEST(SM_SUCCESS == ret);
		sm_io_fprintf(smioerr, "sev=DBG, seid=%s, hdr=%#C, ret=%#x\n",
			pmse_ctx->pmse_se_id, sm_hdrmod->sm_hm_hdr, ret);
		sm_hdrmod_rm(&sm_hdrmodhd);
		sm_hdrmod = HDRMODL_FIRST(sm_hdrmodhd);
	}
	sm_io_fprintf(smioerr, "sev=DBG, seid=%s, where=dot, rc=%d\n",
		pmse_ctx->pmse_se_id, rcode);

	if (PMT_EXIT(rcode))
		exit(0);
	pmt_rcpts_free(&pmt_ctx);
	SETREPLY(pmt_ctx.pmt_reply[SM_STAGE_DOT]);
	T_DELAY(pmt_ctx.pmt_delay[SM_STAGE_DOT]);
	return rcode;
}

static int
pm_open_rd(pmse_ctx_P pmse_ctx)
{
	int rcode;

	rcode = SMTP_R_OK;
	if (PMT_IS_FLAG(&pmt_ctx, PMT_FL_RPLC_MSG) &&
	    !PMT_IS_FLAG(&pmt_ctx, PMT_FL_RFD_OPEN) &&
	    !PMT_IS_FLAG(&pmt_ctx, PMT_FL_RFD_FAIL))
	{
		pmt_ctx.pmt_rfd = open(pmt_ctx.pmt_rfname, O_RDONLY);
		if (pmt_ctx.pmt_rfd >= 0) {
			if (NULL == pmt_ctx.pmt_buf)
				pmt_ctx.pmt_buf = malloc(rbufsize);
			if (NULL == pmt_ctx.pmt_buf) {
				close(pmt_ctx.pmt_rfd);
				pmt_ctx.pmt_rfd = INVALID_FD;
				PMT_SET_FLAG(&pmt_ctx, PMT_FL_RFD_FAIL);
				rcode = SMTP_R_TEMP;
			}
			else
				PMT_SET_FLAG(&pmt_ctx, PMT_FL_RFD_OPEN);
		}
		else {
			PMT_SET_FLAG(&pmt_ctx, PMT_FL_RFD_FAIL);
			rcode = SMTP_R_TEMP;
		}
		sm_io_fprintf(smioerr,
			"sev=DBG, seid=%s, where=pm_open_rd, open=%d\n",
			pmse_ctx->pmse_se_id, pmt_ctx.pmt_rfd);
	}
	return rcode;
}

static void
pm_close_rd(pmse_ctx_P pmse_ctx)
{
	if (PMT_IS_FLAG(&pmt_ctx, PMT_FL_RFD_OPEN)) {
		close(pmt_ctx.pmt_rfd);
		pmt_ctx.pmt_rfd = INVALID_FD;
		PMT_CLR_FLAG(&pmt_ctx, PMT_FL_RFD_OPEN);
	}
	if (pmt_ctx.pmt_buf != NULL) {
		free(pmt_ctx.pmt_buf);
		pmt_ctx.pmt_buf = NULL;
	}
}

static sfsistat_T
tpm1_msg_rplc_stat(pmse_ctx_P pmse_ctx, sm_ret_T status)
{
	pm_close_rd(pmse_ctx);
	sm_io_fprintf(smioerr,
		"sev=DBG, seid=%s, tpm1_msg_rplc_stat=%#x\n",
		pmse_ctx->pmse_se_id, status);
	return SMTP_R_OK;
}

static sfsistat_T
tpm1_msg_rplc(pmse_ctx_P pmse_ctx, const unsigned char **pmsgchunk, size_t *pmsglen)
{
	int rcode;

	if (PMT_EXIT(pmt_ctx.pmt_rcode[SM_STAGE_RPLC_MSG])) {
		sm_io_fprintf(smioerr,
			"sev=DBG, seid=%s, tpm1_msg_rplc=exiting\n",
			pmse_ctx->pmse_se_id);
		exit(0);
	}
	SM_REQUIRE(pmsgchunk != NULL);
	SM_REQUIRE(pmsglen != NULL);
	*pmsgchunk = NULL;
	*pmsglen = 0;
	rcode = pm_open_rd(pmse_ctx);
	sm_io_fprintf(smioerr,
		"sev=DBG, func=tpm1_msg_rplc, seid=%s, flags=%#x\n",
		pmse_ctx->pmse_se_id, pmt_ctx.pmt_flags);
	if (PMT_IS_FLAG(&pmt_ctx, PMT_FL_RPLC_MSG) &&
	    PMT_IS_FLAG(&pmt_ctx, PMT_FL_RFD_OPEN) &&
	    !PMT_IS_FLAG(&pmt_ctx, PMT_FL_RD_FAIL)
	   )
	{
		ssize_t rd;
		char *msg;

		msg = pmt_ctx.pmt_buf;
		if (NULL == msg)
			rd = 0;
		else
			rd = read(pmt_ctx.pmt_rfd, msg, rbufsize);
		if (rd <= 0) {
			sm_io_fprintf(smioerr,
				"sev=DBG, seid=%s, rd=%#x\n",
				pmse_ctx->pmse_se_id, rd);
			pm_close_rd(pmse_ctx);
		}
		else {
			if (rd >= rbufsize)
				rcode = SMTP_R_CONT;
			*pmsgchunk = (unsigned char *) msg;
			*pmsglen = rd;
			if (rd < rbufsize) {
				msg[rd] = '\0';
				sm_io_fprintf(smioerr,
					"sev=DBG, seid=%s, rplcmsg=%.*#s, rd=%#x, rcode=%d\n",
					pmse_ctx->pmse_se_id, (int) rd, msg, rd, rcode);
			}
		}
	}
	return rcode;
}

static sm_ret_T
tpm1_signal(pmg_ctx_P pmg_ctx, int sig)
{
	sm_io_fprintf(smioerr, "sev=DBG, signal=%d\n", sig);
	return SM_SUCCESS;
}

static sm_ret_T
tpm1_abort(pmse_ctx_P pmse_ctx)
{
	sm_io_fprintf(smioerr,
		"sev=DBG, seid=%s, where=abort_ta\n",
		pmse_ctx->pmse_se_id);
	pm_close_wr(pmse_ctx);
	pm_close_rd(pmse_ctx);
	pmt_rcpts_free(&pmt_ctx);
	return SM_SUCCESS;
}

/*
**  USAGE -- Print usage message to smioerr
**
**	Parameters:
**		prg -- program name
**
**	Returns:
**		exits
*/

static void
usage(const char *prg)
{
	sm_io_fprintf(smioerr, "usage: %s [options]\n"
		"-A cap           add capabilities\n"
		"   R             get RCPT status\n"
		"   r             send generated Received: header in message\n"
		"-B file          use content of file to replace message\n"
		"-b n             use n as buffer size when reading file (-B)\n"
		"-C cap           disable capabilities\n"
		"   cap is a sequence of characters:\n"
		"   C: CNNCT\n"
		"   E: EHLO\n"
#if 0
		"   S: STTLS\n"
		"   A: AUTH\n"
#endif
		"   M: MAIL\n"
		"   R: RCPT\n"
		"   D: DATA\n"
		"   B: MSG\n"
		"-c n             set requested capabilities\n"
		"-D n             delete header n\n"
#if PMILTER_DEBUG
		"-d n             set debug level\n"
#endif
		"-e pos=header    replace header at pos\n"
		"-F sender        change MAIL (must be in RFC 2821 format)\n"
		"-f name          write msg to file\n"
		"-i pos=header    insert a header at pos\n"
		"-M action=header prepend or append a header\n"
		"                 action=p/a\n"
		"-m macros        set list of macros to receive\n"
		"    H:hostname\n"
		"    M:message-id\n"
		"    S:session-id\n"
		"    T:transaction-id\n"
		"-R stage=reply   set SMTP reply text for stage\n"
		"   stage:\n"
		"     c: new session (connect)\n"
		"     h: HELO\n"
		"     m: MAIL\n"
		"     r: RCPT\n"
		"     d: DATA\n"
		"     D: Deferred RCPT status (RSAD)\n"
		"     B: Body\n"
		"     b: End of Body\n"
		"     R: message replacement\n"
		"-r stage=rcode   set SMTP reply code for stage to rcode\n"
		"   stage: see above\n"
		"   rcode=%d will invoke exit() to simulate a fatal error,\n"
		"   which can be applied to\n"
		"     R: replace message (see -B)\n"
		"-T recipient     add recipient (must be in RFC 2821 format)\n"
		"-w stage=delay   set a delay for replying in stage\n"
		"   stage: see above\n"
		"-X idx=recipient remove recipient (must be in RFC 2821 format)\n"
		, prg
		, PMT_EXIT_CODE
		);
	exit(EX_USAGE);
}

static pmilter_T
pmilter =
{
	"t-pmilter-1",
	LPMILTER_VERSION,
	SM_SCAP_PM_BASIC,
	0,
	0,
	0,
	tpm1_negotiate,
	tpm1_connect,
	tpm1_helo,
	tpm1_mail,
	tpm1_rcpt,
	tpm1_data,
	tpm1_msg,
	tpm1_eom,
	tpm1_abort,
	tpm1_close,
	tpm1_unknown,
	tpm1_signal,
	(pmfi_starttls_F) NULL,
	(pmfi_auth_F) NULL,
	tpm1_msg_rplc,
	tpm1_msg_rplc_stat
};

/*
**  MAIN -- PMILTER test server
**
**	Parameters:
**		argc -- number of arguments
**		argv -- vector of arguments
**
**	Returns:
**		exit code
*/

int
main(int argc, char *argv[])
{
	sm_ret_T ret;
	int c, ch, stage;
	unsigned int u, u2;
	unsigned long l;
	uint32_t major, minor, patchlevel;
	pmg_ctx_P pmg_ctx;
	char *prg, *str;
	sm_hdrmod_P sm_hdrmod;

	prg = argv[0];
	pmg_ctx = NULL;
	if (getuid() == 0 || geteuid() == 0) {
		sm_io_fprintf(smioerr,
			"%s: ERROR: do not run this as super-user!\n",
			prg);
		exit(EX_USAGE);
	}

	/* initialize test context */
	sm_memzero(&pmt_ctx, sizeof(pmt_ctx));
	for (c = 0; c < SM_STAGES; c++)
		pmt_ctx.pmt_rcode[c] = SMTP_R_CONT;
	pmt_ctx.pmt_fname[0] = '\0';
	pmt_ctx.pmt_fd = INVALID_FD;
	pmt_ctx.pmt_cap = SM_SCAP_PM_BASIC;
	PMT_RCPTS_INIT(&pmt_ctx.pmt_rcpts);

#if SM_HEAP_CHECK
	SmHeapCheck = 1;
	if (HEAP_CHECK)
		sm_heap_report(smioerr, 3);
#endif

	while ((c = getopt(argc, argv, "2A:B:b:C:c:D:d:e:F:f:i:M:m:R:r:T:w:X:"))
		!= -1)
	{
		switch (c) {
		  case '2':
			pmt_ctx.pmt_flags |= PMT_FL_M_2ND;
			break;
		  case 'B':
			strlcpy(pmt_ctx.pmt_rfname, optarg,
				sizeof(pmt_ctx.pmt_rfname));
			PMT_SET_FLAG(&pmt_ctx, PMT_FL_RPLC_MSG);
			pmt_ctx.pmt_cap |= SM_SCAP_PM_MSGREPLACE;
			break;
		  case 'b':
			rbufsize = strtoul(optarg, NULL, 0);
			break;
		  case 'A':
			for (u = 0, u2 = 0; (ch = optarg[u]) != '\0'; u++) {
				switch (ch) {
				  case 'r': u2 |= SM_SCAP_PM_SND_RCVD;
					break;
				  case 'R': u2 |= SM_SCAP_PM_RCPT_ST;
					pmt_ctx.pmt_flags |= PMT_FL_R_ST;
					break;
				  default: usage(prg); break;
				}
			}
			pmt_ctx.pmt_cap |= u2;
			break;
		  case 'C':
			for (u = 0, u2 = 0; (ch = optarg[u]) != '\0'; u++) {
				switch (ch) {
				  case 'C': u2 |= SM_SCAP_PM_CNNCT; break;
				  case 'E': u2 |= SM_SCAP_PM_EHLO; break;
				  case 'S': u2 |= SM_SCAP_PM_STTLS; break;
				  case 'A': u2 |= SM_SCAP_PM_AUTH; break;
				  case 'M': u2 |= SM_SCAP_PM_MAIL; break;
				  case 'R': u2 |= SM_SCAP_PM_RCPT; break;
				  case 'D': u2 |= SM_SCAP_PM_DATA; break;
				  case 'B': u2 |= SM_SCAP_PM_MSG; break;
				  default: usage(prg); break;
				}
			}
			pmt_ctx.pmt_cap &= ~u2;
			break;
		  case 'c':
			pmt_ctx.pmt_cap = strtoul(optarg, NULL, 0);
			break;

		  case 'D':
			sm_hdrmod = NULL;
			ret = sm_hdrmod_new(&sm_hdrmodhd, true, &sm_hdrmod);
			SM_TEST(SM_SUCCESS == ret);
			if (SM_SUCCESS != ret)
				return(EX_OSERR);
			SM_TEST(sm_hdrmod != NULL);
			if (NULL == sm_hdrmod)
				return(EX_OSERR);
			sm_hdrmod->sm_hm_type = SM_HM_TYPE_REMOVE;
			sm_hdrmod->sm_hm_pos = strtoul(optarg, NULL, 0);
			SM_TEST(SM_SUCCESS == ret);
			if (SM_SUCCESS != ret)
				return(EX_OSERR);
			pmt_ctx.pmt_cap |= SM_SCAP_PM_HDRMOD;
			break;

		  case 'd':
#if PMILTER_DEBUG
			pm_debug = strtoul(optarg, NULL, 0);
#endif
#if RCBCOMM_DEBUG
			rcbcomm_debug = strtoul(optarg, NULL, 0);
#endif
			break;

		  case 'e':
			l = strtoul(optarg, &str, 0);
			if (ULONG_MAX == l || l > UINT_MAX)
				usage(argv[0]);
			if (NULL == str || *str != '=')
				usage(argv[0]);

			sm_hdrmod = NULL;
			str = getreply(str + 1);
			ret = sm_hdrmod_new(&sm_hdrmodhd, true, &sm_hdrmod);
			SM_TEST(SM_SUCCESS == ret);
			if (SM_SUCCESS != ret)
				return(EX_OSERR);
			SM_TEST(sm_hdrmod != NULL);
			if (NULL == sm_hdrmod)
				return(EX_OSERR);
			sm_hdrmod->sm_hm_hdr = sm_cstr_scpyn0((const uchar *)str
							, strlen(str));
			sm_hdrmod->sm_hm_pos = l;
			sm_hdrmod->sm_hm_type = SM_HM_TYPE_REPLACE;
			free(str);
			pmt_ctx.pmt_cap |= SM_SCAP_PM_HDRMOD;
			break;

		  case 'F':
			mail_new = strdup(optarg);
			if (NULL == mail_new) {
				sm_io_fprintf(smioerr,
					"sev=ERROR, strdup=NULL");
				exit(EX_OSERR);
			}
			pmt_ctx.pmt_cap |= SM_SCAP_PM_MAILMOD;
			break;

		  case 'f':
			strlcpy(pmt_ctx.pmt_fname, optarg,
				sizeof(pmt_ctx.pmt_fname));
			PMT_SET_FLAG(&pmt_ctx, PMT_FL_W2F);
			break;

		  case 'i':
			l = strtoul(optarg, &str, 0);
			if (ULONG_MAX == l || l > UINT_MAX)
				usage(argv[0]);
			if (NULL == str || *str != '=')
				usage(argv[0]);

			sm_hdrmod = NULL;
			str = getreply(str + 1);
			ret = sm_hdrmod_new(&sm_hdrmodhd, true, &sm_hdrmod);
			SM_TEST(SM_SUCCESS == ret);
			if (SM_SUCCESS != ret)
				return(EX_OSERR);
			SM_TEST(sm_hdrmod != NULL);
			if (NULL == sm_hdrmod)
				return(EX_OSERR);
			sm_hdrmod->sm_hm_hdr = sm_cstr_scpyn0((const uchar *)str
							, strlen(str));
			sm_hdrmod->sm_hm_pos = l;
			sm_hdrmod->sm_hm_type = SM_HM_TYPE_INSERT;
			free(str);
			pmt_ctx.pmt_cap |= SM_SCAP_PM_HDRMOD;
			break;

		  case 'M':
			if (NULL == optarg ||
			    (optarg[0] != 'p' && optarg[0] != 'a')
			    || optarg[1] != '=')
			{
				usage(prg);
				break;
			}

			str = getreply(optarg + 2);
			SM_TEST(str != NULL);
			if (NULL == str)
				return(EX_OSERR);
			sm_hdrmod = NULL;
			ret = sm_hdrmod_new(&sm_hdrmodhd, true, &sm_hdrmod);
			SM_TEST(SM_SUCCESS == ret);
			if (SM_SUCCESS != ret)
				return(EX_OSERR);
			SM_TEST(sm_hdrmod != NULL);
			if (NULL == sm_hdrmod)
				return(EX_OSERR);
			sm_hdrmod->sm_hm_hdr = sm_cstr_scpyn0((const uchar *)str
							, strlen(str));
			SM_TEST(sm_hdrmod->sm_hm_hdr != NULL);
			if (NULL == sm_hdrmod->sm_hm_hdr)
				return(EX_OSERR);
			if (optarg[0] == 'p')
				sm_hdrmod->sm_hm_type = SM_HM_TYPE_PREPEND;
			else
				sm_hdrmod->sm_hm_type = SM_HM_TYPE_APPEND;
			free(str);
			pmt_ctx.pmt_cap |= SM_SCAP_PM_HDRMOD;
			break;

		  case 'm':
			for (u = 0; (ch = optarg[u]) != '\0'; u++) {
				switch (ch) {
				  case 'H':
					pmt_ctx.pmt_flags |= PMT_FL_M_HOSTN;
					break;
				  case 'M':
					pmt_ctx.pmt_flags |= PMT_FL_M_MSGID;
					break;
				  case 'S':
					pmt_ctx.pmt_flags |= PMT_FL_M_SEID;
					break;
				  case 'T':
					pmt_ctx.pmt_flags |= PMT_FL_M_TAID;
					break;
				  default: usage(prg); break;
				}
			}
			break;
		  case 'R':
			if (optarg != NULL && ISALPHA(optarg[0])
			    && optarg[1] == '=')
				u2 = 2;
			else {
				usage(prg);
				break;
			}

			str = getreply(optarg + u2);
			if (NULL == str) {
				sm_io_fprintf(smioerr,
					"sev=ERROR, getreply=NULL");
				exit(1);
			}
			stage = sm_getstage(optarg[0]);
			if (stage < 0 || stage >= SM_STAGES) {
				usage(prg);
				/* NOTREACHED */
				SM_ASSERT(false);
			}
			pmt_ctx.pmt_reply[stage] = str;
			if (SM_STAGE_MSG == stage)
				pmt_ctx.pmt_cap |= SM_SCAP_PM_MSG_RC;
			break;

		  case 'r':
			if (optarg != NULL && ISALPHA(optarg[0])
			    && optarg[1] == '=')
				u2 = 2;
			else {
				usage(prg);
				break;
			}

			u = (unsigned int) strtoul(optarg + u2, NULL, 0);
			stage = sm_getstage(optarg[0]);
			if (stage < 0 || stage >= SM_STAGES) {
				usage(prg);
				/* NOTREACHED */
				SM_ASSERT(false);
			}
			pmt_ctx.pmt_rcode[stage] = u;
			if (SM_STAGE_MSG == stage)
				pmt_ctx.pmt_cap |= SM_SCAP_PM_MSG_RC;
			break;

		  case 'T':
			rcpt_add_pa = strdup(optarg);
			if (NULL == rcpt_add_pa) {
				sm_io_fprintf(smioerr,
					"sev=ERROR, strdup=NULL");
				exit(EX_OSERR);
			}
			pmt_ctx.pmt_cap |= SM_SCAP_PM_RCPTMOD;
			break;

		  case 'w':
			if (optarg != NULL && ISALPHA(optarg[0])
			    && optarg[1] == '=')
				u2 = 2;
			else {
				usage(prg);
				break;
			}

			u = (unsigned int) strtoul(optarg + u2, NULL, 0);
			stage = sm_getstage(optarg[0]);
			if (stage < 0 || stage >= SM_STAGES) {
				usage(prg);
				/* NOTREACHED */
				SM_ASSERT(false);
			}
			pmt_ctx.pmt_delay[stage] = u;
			break;

		  case 'X':
			rcpt_del_idx = strtoul(optarg, &str, 0);
			if (NULL == str || *str != '=') {
				usage(prg);
				/* NOTREACHED */
				SM_ASSERT(false);
			}
			rcpt_del_pa = strdup(str + 1);
			if (NULL == rcpt_del_pa) {
				sm_io_fprintf(smioerr,
					"sev=ERROR, strdup=NULL");
				exit(EX_OSERR);
			}
			pmt_ctx.pmt_cap |= SM_SCAP_PM_RCPTMOD;
			break;

		  default:
			usage(prg);
			break;
		}
	}

	ret = sm_pmfi_init(&pmg_ctx);
	if (sm_is_err(ret)) {
		sm_io_fprintf(smioerr, "sev=ERROR, sm_pmfi_init=%x\n", ret);
		goto error;
	}

	sm_test_begin(argc, argv, "test pmilter 1");

	argc -= optind;
	argv += optind;

	/* XXX HACK */
	if (argc > 0)
		pmg_ctx->pmg_sockname = argv[0];
	else
		pmg_ctx->pmg_sockname = "pmilter.sock";

	ret = sm_pmfi_version(pmg_ctx, &major, &minor, &patchlevel);
	SM_TEST(SM_SUCCESS == ret);
	SM_TEST(1 == major);
	SM_TEST(0 == minor);
	SM_TEST(0 == patchlevel);

	ret = sm_pmfi_set_ctx_g(pmg_ctx, &pmt_ctx);
	SM_TEST(SM_SUCCESS == ret);

	ret = sm_pmfi_start(pmg_ctx, &pmilter);
	if (sm_is_err(ret)) {
		sm_io_fprintf(smioerr, "sev=ERROR, sm_pmfi_start=%x\n", ret);
		goto error;
	}

#if SM_HEAP_CHECK
	if (HEAP_CHECK)
		sm_heap_report(smioerr, 3);
#endif
	return sm_test_end();

  error:
	/* ignore shutdown errors... */
	(void) sm_pmilt_stop(pmg_ctx);

	/* select an appropriate error here... */
	return sm_error_value(ret);
}
#else /* MTA_USE_PMILTER */
int
main(int argc, char *argv[])
{
	return 0;
}
#endif /* MTA_USE_PMILTER */


syntax highlighted by Code2HTML, v. 0.9.1