/*
 * 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: example-pmilter-0.c,v 1.12 2006/10/05 04:27:37 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/smreplycodes.h"
#include "sm/pmilter.h"
#include "sm/pmfdef.h"
#include "sm/pmfapi.h"

#if MTA_USE_PMILTER

/* global context */
typedef struct pmexg_ctx_S	pmexg_ctx_T, *pmexg_ctx_P;
struct pmexg_ctx_S
{
	uint32_t	 pmexg_cap;
	uint32_t	 pmexg_flags;
};

/* context per SMTP server */
typedef struct pmexss_ctx_S	pmexss_ctx_T, *pmexss_ctx_P;
struct pmexss_ctx_S
{
	pmexg_ctx_P	 pmexss_pmexg_ctx;
	uint32_t	 pmexss_flags;
};

/* context per SMTP session */
typedef struct pmexse_ctx_S	pmexse_ctx_T, *pmexse_ctx_P;
struct pmexse_ctx_S
{
	pmexss_ctx_P	 pmexse_pmexss_ctx;
	uint32_t	 pmexse_flags;
	int		 pmexse_fd;
	char		 pmexse_fname[PATH_MAX];
};

#define PMSE_FL_NONE	0x0000
#define PMG_FL_W2F	0x0001	/* write to file */
#define PMSE_FL_W2F	PMG_FL_W2F

#define PMSE_FL_FD_OPEN	0x0002	/* fd is ok to use */
#define PMSE_FL_FD_FAIL	0x0004	/* don't try to open file */
#define PMSE_FL_WR_FAIL	0x0008	/* write failed before */

#define PMSE_SET_FLAG(pmexse_ctx, fl)	(pmexse_ctx)->pmexse_flags |= (fl)
#define PMSE_CLR_FLAG(pmexse_ctx, fl)	(pmexse_ctx)->pmexse_flags &= ~(fl)
#define PMSE_IS_FLAG(pmexse_ctx, fl)	(((pmexse_ctx)->pmexse_flags & (fl)) != 0)


static sm_ret_T
expm1_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;
	pmexss_ctx_P pmexss_ctx;
	pmexg_ctx_P pmexg_ctx;

	pmexg_ctx = sm_pmfi_get_ctx_g_ss(pmss_ctx);
	pmexss_ctx = (pmexss_ctx_P) malloc(sizeof(*pmexss_ctx));
	if (pmexss_ctx == NULL)
		return SMTP_R_TEMP;
	sm_pmfi_set_ctx_ss(pmss_ctx, pmexss_ctx);

	pmexss_ctx->pmexss_pmexg_ctx = pmexg_ctx;
	pmexss_ctx->pmexss_flags = pmexg_ctx->pmexg_flags;
	*pm_cap = pmexg_ctx->pmexg_cap;
	*pm_fct = 0;
	*pm_feat = 0;
	*pm_misc = 0;
	ret = sm_pmfi_setmaclist(pmss_ctx, PM_SMST_CONNECT,
				PMM_SRVHOSTNAME, PMM_SEID, PMM_END);
	if (sm_is_err(ret))
		sm_io_fprintf(smioerr,
			"sev=ERROR, where=expm1_negotiate, sm_pmfi_setmaclist=%#x, macro=hostname\n",
			ret);
	ret = sm_pmfi_setmaclist(pmss_ctx, PM_SMST_DOT,
				PMM_DOT_MSGID, PMM_END);
	if (sm_is_err(ret))
		sm_io_fprintf(smioerr,
			"sev=ERROR, where=expm1_negotiate, sm_pmfi_setmaclist=%#x, macro=msg_id\n",
			ret);
	sm_io_fprintf(smioerr, "where=expm1_negotiate, cap=%#X\n",
		pmexg_ctx->pmexg_cap);
	return SM_SUCCESS;
}

static sfsistat_T
expm1_connect(pmse_ctx_P pmse_ctx, const char *hostname, sm_sockaddr_T *hostaddr)
{
	sm_ret_T ret;
	pmexse_ctx_P pmexse_ctx;
	pmexss_ctx_P pmexss_ctx;
	char *srvhostname;

	/* create session context */
	pmexse_ctx = (pmexse_ctx_P) malloc(sizeof(*pmexse_ctx));
	if (pmexse_ctx == NULL)
		return SMTP_R_TEMP;
	pmexss_ctx = sm_pmfi_get_ctx_ss_se(pmse_ctx);
	pmexse_ctx->pmexse_pmexss_ctx = pmexss_ctx;
	pmexse_ctx->pmexse_flags = pmexss_ctx->pmexss_flags;
	sm_pmfi_set_ctx_se(pmse_ctx, pmexse_ctx);
	sm_io_fprintf(smioerr, "client=%s\n", hostname);

	ret = sm_pmfi_getmac(pmse_ctx, PMM_SRVHOSTNAME, &srvhostname);
	sm_io_fprintf(smioerr, "srvhostname=%s, ret=%#X\n",
		srvhostname, ret);

	return SMTP_R_CONT;
}

static sm_ret_T
expm1_close(pmse_ctx_P pmse_ctx)
{
	pmexse_ctx_P pmexse_ctx;

	sm_io_fprintf(smioerr, "where=close\n");
	pmexse_ctx = (pmexse_ctx_P) sm_pmfi_get_ctx_se(pmse_ctx);
	if (pmexse_ctx != NULL)
		sm_free(pmexse_ctx);
	sm_pmfi_set_ctx_se(pmse_ctx, NULL);
	return SM_SUCCESS;
}

static sfsistat_T
expm1_helo(pmse_ctx_P pmse_ctx, const char *helohost, bool ehlo)
{
	sm_io_fprintf(smioerr, "helo=%s\n", helohost);
	return SMTP_R_CONT;
}

static sfsistat_T
expm1_mail(pmse_ctx_P pmse_ctx, const char *mail, char **argv)
{
	sm_io_fprintf(smioerr, "mail=%s, argv[0]=%s\n", mail,
		(argv != NULL && argv[0] != NULL) ? argv[0] : "NULL");
	return SMTP_R_CONT;
}

static sfsistat_T
expm1_rcpt(pmse_ctx_P pmse_ctx, const char *rcpt, char **argv)
{
	sm_io_fprintf(smioerr, "rcpt=%s, argv[0]=%s\n", rcpt,
		(argv != NULL && argv[0] != NULL) ? argv[0] : "NULL");
	return SMTP_R_CONT;
}

static sfsistat_T
expm1_data(pmse_ctx_P pmse_ctx)
{
	sm_io_fprintf(smioerr, "where=data\n");
	return SMTP_R_CONT;
}

static void
pm_open(pmse_ctx_P pmse_ctx, pmexse_ctx_P pmexse_ctx)
{
	if (PMSE_IS_FLAG(pmexse_ctx, PMSE_FL_W2F) &&
	    !PMSE_IS_FLAG(pmexse_ctx, PMSE_FL_FD_OPEN) &&
	    !PMSE_IS_FLAG(pmexse_ctx, PMSE_FL_FD_FAIL))
	{
		(void) strlcpy(pmexse_ctx->pmexse_fname, "temp.XXXXXX",
				sizeof(pmexse_ctx->pmexse_fname));
		pmexse_ctx->pmexse_fd = mkstemp(pmexse_ctx->pmexse_fname);
		if (pmexse_ctx->pmexse_fd >= 0)
			PMSE_SET_FLAG(pmexse_ctx, PMSE_FL_FD_OPEN);
		else
			PMSE_SET_FLAG(pmexse_ctx, PMSE_FL_FD_FAIL);
		sm_io_fprintf(smioerr, "where=open, mkstemp=%d\n",
			pmexse_ctx->pmexse_fd);
	}
}

static sfsistat_T
expm1_unknown(pmse_ctx_P pmse_ctx, const char *cmd)
{
	sm_io_fprintf(smioerr, "where=unknown, cmd=%s\n", cmd);
	return SMTP_R_CONT;
}

static sm_ret_T
expm1_abort(pmse_ctx_P pmse_ctx)
{
	sm_io_fprintf(smioerr, "where=abort_ta\n");
	return SM_SUCCESS;
}

static sfsistat_T
expm1_msg(pmse_ctx_P pmse_ctx, unsigned char *buf, size_t len)
{
	pmexse_ctx_P pmexse_ctx;

	pmexse_ctx = (pmexse_ctx_P) sm_pmfi_get_ctx_se(pmse_ctx);
	pm_open(pmse_ctx, pmexse_ctx);
	if (PMSE_IS_FLAG(pmexse_ctx, PMSE_FL_W2F) &&
	    PMSE_IS_FLAG(pmexse_ctx, PMSE_FL_FD_OPEN) &&
	    !PMSE_IS_FLAG(pmexse_ctx, PMSE_FL_WR_FAIL) &&
	    buf != NULL && len > 0
	   )
	{
		ssize_t written;

		written = write(pmexse_ctx->pmexse_fd, buf, len);
		if (written == -1)
		{
			sm_io_fprintf(smioerr,
				"sev=ERROR, where=msg, write=%d\n",
				(int) written);
			PMSE_SET_FLAG(pmexse_ctx, PMSE_FL_WR_FAIL);
			close(pmexse_ctx->pmexse_fd);
			pmexse_ctx->pmexse_fd = INVALID_FD;
			PMSE_CLR_FLAG(pmexse_ctx, PMSE_FL_FD_OPEN);
		}
	}

	sm_io_fprintf(smioerr, "where=msg, len=%lu\n", (unsigned long) len);
	return SM_SUCCESS;
}

static sfsistat_T
expm1_eom(pmse_ctx_P pmse_ctx)
{
	sm_ret_T ret;
	bool writefailed;
	pmexse_ctx_P pmexse_ctx;
	char *msgid;

	pmexse_ctx = (pmexse_ctx_P) sm_pmfi_get_ctx_se(pmse_ctx);
	writefailed = PMSE_IS_FLAG(pmexse_ctx, PMSE_FL_W2F) &&
			PMSE_IS_FLAG(pmexse_ctx, PMSE_FL_WR_FAIL);
	if (PMSE_IS_FLAG(pmexse_ctx, PMSE_FL_W2F) &&
	    PMSE_IS_FLAG(pmexse_ctx, PMSE_FL_FD_OPEN) &&
	    !PMSE_IS_FLAG(pmexse_ctx, PMSE_FL_WR_FAIL))
	{
		int r;

		r = close(pmexse_ctx->pmexse_fd);
		sm_io_fprintf(smioerr, "where=dot, write=%d\n", r);
		pmexse_ctx->pmexse_fd = INVALID_FD;
		PMSE_CLR_FLAG(pmexse_ctx, PMSE_FL_FD_OPEN);
	}

	ret = sm_pmfi_getmac(pmse_ctx, PMM_DOT_MSGID, &msgid);
	sm_io_fprintf(smioerr, "msgid=%s, ret=%#X\n", msgid, ret);

	sm_io_fprintf(smioerr, "where=dot\n");
	return SMTP_R_CONT;
}

static sm_ret_T
expm1_signal(pmg_ctx_P pmg_ctx, int sig)
{
	sm_io_fprintf(smioerr, "signal=%d\n", sig);
	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"
		"-f name          write msg to file\n"
		, prg
		);
	exit(EX_USAGE);
}

static pmilter_T
pmilter =
{
	"example-pmilter-0",
	LPMILTER_VERSION,
	SM_SCAP_PM_ALL,
	0,
	0,
	0,
	expm1_negotiate,
	expm1_connect,
	expm1_helo,
	expm1_mail,
	expm1_rcpt,
	expm1_data,
	expm1_msg,
	expm1_eom,
	expm1_abort,
	expm1_close,
	expm1_unknown,
	expm1_signal
};

/*
**  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;
	uint32_t major, minor, patchlevel;
	pmg_ctx_P pmg_ctx;
	char *prg;
	pmexg_ctx_T pmexg_ctx;


	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(&pmexg_ctx, sizeof(pmexg_ctx));
	pmexg_ctx.pmexg_cap = SM_SCAP_PM_ALL;

	while ((c = getopt(argc, argv, "fh")) != -1)
	{
		switch (c)
		{
		  case 'f':
			pmexg_ctx.pmexg_flags |= PMG_FL_W2F;
			break;
		  default:
			usage(prg);
			break;
		}
	}

	argc -= optind;
	argv += optind;

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

	ret = sm_pmfi_version(pmg_ctx, &major, &minor, &patchlevel);
	if (sm_is_err(ret))
	{
		sm_io_fprintf(smioerr, "sev=ERROR, sm_pmfi_version=%#x\n", ret);
		goto error;
	}
	if (major != LPMILTER_VERSION_MAJOR)
	{
		sm_io_fprintf(smioerr,
			"sev=ERROR, status=version_mismatch, compile_time=%d, run_time=%d\n"
			, LPMILTER_VERSION_MAJOR, major);
		goto error;
	}

	ret = sm_pmfi_setconn(pmg_ctx, "pmilter.sock");
	if (sm_is_err(ret))
	{
		sm_io_fprintf(smioerr, "sev=ERROR, sm_pmfi_setconn=%#x\n", ret);
		goto error;
	}

	ret = sm_pmfi_set_ctx_g(pmg_ctx, &pmexg_ctx);

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

	return 0;

  error:
	/* select an appropriate error here... */
	return sm_error_value(ret);
}
#else /* MTA_USE_PMILTER */
int
main(int argc, char *argv[])
{
	sm_io_fprintf(smioerr, "sev=ERROR, MTA_USE_PMILTER=not_set\n");
	return -1;
}
#endif /* MTA_USE_PMILTER */


syntax highlighted by Code2HTML, v. 0.9.1