/* $Id: milter-regex.c,v 1.24 2007/06/10 16:49:55 ca Exp $ */

/*
 * Copyright (c) 2003-2004 Daniel Hartmeier
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *    - Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    - Redistributions in binary form must reproduce the above
 *      copyright notice, this list of conditions and the following
 *      disclaimer in the documentation and/or other materials provided
 *      with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */

static const char rcsid[] = "$Id: milter-regex.c,v 1.24 2007/06/10 16:49:55 ca Exp $";

#include "sm/generic.h"
SM_RCSID("@(#)$Id: milter-regex.c,v 1.24 2007/06/10 16:49:55 ca Exp $")
#include "sm/error.h"
#include "sm/assert.h"
#include "sm/types.h"
#include "sm/ctype.h"
#include "sm/sysexits.h"
#include "sm/fcntl.h"
#include "sm/io.h"
#include "sm/log.h"
#include "sm/regex.h"
#include "sm/signal.h"
#include "sm/sm_extracthdr.h"
#include "sm/smreplycodes.h"
#include "sm/pmilter.h"
#include "sm/pmfdef.h"
#include "sm/pmfapi.h"

#if MTA_USE_PMILTER

#include <stdio.h>

#include "milter-regex-eval.h"

extern int parse_ruleset(const char *, struct ruleset **, char *, size_t);

static const char	*rule_file_name = "/etc/meta1/milter-regex.conf";
static int		 debug = 0;
static int		 Xhdr_perm = 0;

struct context {
	struct ruleset	*rs;
	int		*res;
	char		 buf[2048];	/* longer body lines are wrapped */
	unsigned	 pos;		/* write position within buf */
	char		 host_name[128];
	char		 host_addr[64];
	char		 hdr_from[128];
	char		 hdr_to[128];
	char		 hdr_subject[128];
	char		 ta_id[20];
	char		*quarantine;

	uint32_t	 pmexse_flags;
	sm_xhdr_ctx_P	 pmexse_xhdr_ctx;

};
typedef struct context	context_T, *context_P, *pmexse_ctx_P;

#define PMSE_FL_NONE		0x0000
#define PMSE_FL_MSG_INIT	0x0001
#define PMSE_FL_MSG_HDR		0x0002
#define PMSE_FL_MSG_EOH		0x0004
#define PMSE_FL_MSG_BODY	0x0008

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

#define sfsistat sfsistat_T
#define SMFICTX pmse_ctx_T
#define MI_SUCCESS SM_PMI_SUCCESS
#define MI_FAILURE SM_PMI_FAILURE
#define _SOCK_ADDR sm_sockaddr_T
#define smfi_getpriv sm_pmfi_get_ctx_se
#define smfi_setpriv sm_pmfi_set_ctx_se
static pmg_ctx_P pmg_ctx;
#define smfi_stop() sm_pmfi_stop(pmg_ctx)

void  die(const char *);

static sfsistat		 setreply(SMFICTX *, struct context *,
			    const struct action *);
static struct ruleset	*get_ruleset(void);
static sfsistat		 cb_connect(SMFICTX *, const char *, _SOCK_ADDR *);
static sfsistat		 cb_helo(SMFICTX *, const char *, bool);
static sfsistat		 cb_envfrom(SMFICTX *, const char *, char **);
static sfsistat		 cb_envrcpt(SMFICTX *, const char *, char **);
static sfsistat		 cb_header(SMFICTX *, char *, char *);
static sfsistat		 cb_eoh(SMFICTX *);
static sfsistat		 cb_body(SMFICTX *, u_char *, size_t);
static sfsistat		 cb_eom(SMFICTX *);
static sfsistat		 cb_close(SMFICTX *);
static void		 usage(const char *);
static void		 msg(int, struct context *, const char *, ...);

#define OCONN		"/var/spool/milter-regex/sock"
#define RCODE_REJECT	"554"
#define RCODE_TEMPFAIL	"451"
#define XCODE_REJECT	"5.7.1"
#define XCODE_TEMPFAIL	"4.7.1"
#define	MAXRS		16

#if SM_STAT_st_mtimespec
#define	ST_MTIME st_mtimespec
#else
#define	ST_MTIME st_mtime
#endif

static sfsistat
setreply(SMFICTX *ctx, struct context *context, const struct action *action)
{
	int result = SMFIS_CONTINUE;
	char *what, *rhs;
	char m[64];
/*
**  strlen("454 4.7.1 \r\n")=12, hence restrict len for reply to 64-12=52
**  (2 for paranoia's sake)
*/

#define MR_TXT_LEN	"50"

	what = rhs = NULL;
	switch (action->type) {
	case ACTION_REJECT:
		what = "REJECT";
		rhs = action->msg;
		result = SMFIS_REJECT;
		break;
	case ACTION_REJECT_QUICK:
		what = "REJECT_QUICK";
		rhs = action->msg;
		result = SMTP_R_SET_QUICK(SMFIS_REJECT);
		break;
	case ACTION_TEMPFAIL:
		what = "TEMPFAIL";
		rhs = action->msg;
		result = SMFIS_TEMPFAIL;
		break;
	case ACTION_QUARANTINE:
		if (context->quarantine != NULL)
			free(context->quarantine);
		context->quarantine = strdup(action->msg);
		break;
	case ACTION_DISCARD:
		what = "status";
		rhs = "DISCARD";
		result = SMFIS_DISCARD;
		break;
	case ACTION_ACCEPT:
#if 0
		what = "status";
		rhs = "ACCEPT";
#endif
		result = SMFIS_ACCEPT;
		break;
	}
	if (what != NULL  && msg != NULL)
	{
		msg(LOG_NOTICE, context,
			"ta_id=%s, %s=%s, From=%@s, To=%@s, Subject=%@s",
			context->ta_id, what, rhs, context->hdr_from,
			context->hdr_to, context->hdr_subject);
	}
	if (action->type == ACTION_REJECT)
	{
		snprintf(m, sizeof(m), "554 5.7.1 %." MR_TXT_LEN "s\r\n",
			(char *)action->msg);
		if (sm_pmfi_setreply(ctx, m) != MI_SUCCESS)
			msg(LOG_ERR, context, "smfi_setreply");
	}
	if (action->type == ACTION_TEMPFAIL)
	{
		snprintf(m, sizeof(m), "454 4.7.1 %." MR_TXT_LEN "s\r\n",
			(char *)action->msg);
		if (sm_pmfi_setreply(ctx, m) != MI_SUCCESS)
			msg(LOG_ERR, context, "smfi_setreply");
	}
	return (result);
}

static struct ruleset *
get_ruleset(void)
{
	static struct ruleset *rs[MAXRS] = { NULL, NULL, 0, 0 };
	static int cur = 0;
	static time_t last_check = 0;
	static struct stat sbo;
	time_t t = time(NULL);
	int load = 0;

	if (!last_check)
		memset(&sbo, 0, sizeof(sbo));
	if (t - last_check >= 10) {
		struct stat sb;

		last_check = t;
		memset(&sb, 0, sizeof(sb));
		if (stat(rule_file_name, &sb))
			msg(LOG_ERR, NULL, "get_ruleset: stat: %s: %s",
			    rule_file_name, strerror(errno));
		else if (memcmp(&sb.ST_MTIME, &sbo.ST_MTIME,
		    sizeof(sb.ST_MTIME))) {
			memcpy(&sbo.ST_MTIME, &sb.ST_MTIME,
			    sizeof(sb.ST_MTIME));
			load = 1;
		}
	}
	if (load || rs[cur] == NULL) {
		int i;
		char err[8192];

		msg(LOG_DEBUG, NULL, "loading new configuration file");
		for (i = 0; i < MAXRS; ++i)
			if (rs[i] != NULL && rs[i]->refcnt == 0) {
				msg(LOG_DEBUG, NULL, "freeing unused ruleset "
				    "%d/%d", i, MAXRS);
				free_ruleset(rs[i]);
				rs[i] = NULL;
			}
		for (i = 0; i < MAXRS; ++i)
			if (rs[i] == NULL)
				break;
		if (i == MAXRS)
			msg(LOG_ERR, NULL, "all rulesets are in use, cannot "
			    "load new one", MAXRS);
		else if (parse_ruleset(rule_file_name, &rs[i], err,
		    sizeof(err)) || rs[i] == NULL)
			msg(LOG_ERR, NULL, "parse_ruleset: %s", err);
		else {
			msg(LOG_INFO, NULL, "configuration file %s loaded "
			    "successfully", rule_file_name);
			cur = i;
		}
	}
	return (rs[cur]);
}

static sm_ret_T
cb_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;

	*pm_cap = SM_SCAP_PM_ALL;
	*pm_fct = 0;
	*pm_feat = 0;
	*pm_misc = 0;
	ret = sm_pmfi_setmaclist(pmss_ctx, PM_SMST_CONNECT,
				PMM_SRVHOSTNAME, PMM_END);
	if (sm_is_err(ret))
		msg(LOG_ERR, NULL,
			"sev=ERROR, func=cb_negotiate, sm_pmfi_setmaclist=%#x, macro=hostname\n",
			ret);
	ret = sm_pmfi_setmaclist(pmss_ctx, PM_SMST_MAIL,
				PMM_MAIL_TAID, PMM_END);
	if (sm_is_err(ret))
		msg(LOG_ERR, NULL,
			"sev=ERROR, func=cb_negotiate, sm_pmfi_setmaclist=%#x, macro=taid\n",
			ret);
	return SM_SUCCESS;
}


static sfsistat
cb_connect(SMFICTX *ctx, const char *name, _SOCK_ADDR *sa)
{
	struct context *context;

	context = calloc(1, sizeof(*context));
	if (context == NULL) {
		msg(LOG_ERR, NULL, "cb_connect: calloc: %s", strerror(errno));
		return (SMFIS_ACCEPT);
	}
	context->rs = get_ruleset();
	if (context->rs == NULL) {
		free(context);
		msg(LOG_ERR, NULL, "cb_connect: get_ruleset");
		return (SMFIS_ACCEPT);
	}
	context->res = calloc(context->rs->maxidx, sizeof(*context->res));
	if (context->res == NULL) {
		free(context);
		msg(LOG_ERR, NULL, "cb_connect: calloc: %s", strerror(errno));
		return (SMFIS_ACCEPT);
	}
	if (smfi_setpriv(ctx, context) != MI_SUCCESS) {
		free(context->res);
		free(context);
		msg(LOG_ERR, NULL, "cb_connect: smfi_setpriv");
		return (SMFIS_ACCEPT);
	}
	context->rs->refcnt++;
	context->pmexse_flags = 0;

	strlcpy(context->host_name, name, sizeof(context->host_name));
	strlcpy(context->host_addr, "unknown", sizeof(context->host_addr));
	switch (sa->sa.sa_family) {
	case AF_INET: {
		struct sockaddr_in *sin = (struct sockaddr_in *)sa;

		if (inet_ntop(AF_INET, &sin->sin_addr.s_addr,
		    context->host_addr, sizeof(context->host_addr)) ==
		    NULL)
			msg(LOG_ERR, NULL, "cb_connect: inet_ntop: %s",
			    strerror(errno));
		break;
	}
#if HAVE_INET6
	case AF_INET6: {
		struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;

		if (inet_ntop(AF_INET6, &sin6->sin6_addr,
		    context->host_addr, sizeof(context->host_addr)) ==
		    NULL)
			msg(LOG_ERR, NULL, "cb_connect: inet_ntop: %s",
			    strerror(errno));
		break;
	}
#endif
	}
	msg(LOG_DEBUG, context, "cb_connect('%s', '%s')",
	    context->host_name, context->host_addr);
	return (SMFIS_CONTINUE);
}

static sfsistat
cb_helo(SMFICTX *ctx, const char *arg, bool ehlo)
{
	struct context *context;
	const struct action *action;

	if ((context = (struct context *)smfi_getpriv(ctx)) == NULL) {
		msg(LOG_ERR, NULL, "cb_helo: smfi_getpriv");
		return (SMFIS_ACCEPT);
	}
	/* multiple HELO imply RSET in sendmail */
	/* evaluate connect arguments here, because we can't call */
	/* setreply from cb_connect */
	eval_clear(context->rs, context->res, COND_CONNECT);
	if ((action = eval_cond(context->rs, context->res, COND_CONNECT,
	    context->host_name, context->host_addr)) != NULL)
		return (setreply(ctx, context, action));
	if ((action = eval_end(context->rs, context->res, COND_CONNECT)) !=
	    NULL)
		return (setreply(ctx, context, action));
	msg(LOG_DEBUG, context, "cb_helo('%@s')", arg);
	eval_clear(context->rs, context->res, COND_HELO);
	if ((action = eval_cond(context->rs, context->res, COND_HELO,
	    arg, NULL)) != NULL)
		return (setreply(ctx, context, action));
	if ((action = eval_end(context->rs, context->res, COND_HELO)) !=
	    NULL)
		return (setreply(ctx, context, action));
	return (SMFIS_CONTINUE);
}

static sfsistat
cb_envfrom(SMFICTX *ctx, const char *mail, char **args)
{
	struct context *context;
	const struct action *action;
	char *ta_id;

	if ((context = (struct context *)smfi_getpriv(ctx)) == NULL) {
		msg(LOG_ERR, NULL, "cb_envfrom: smfi_getpriv");
		return (SMFIS_ACCEPT);
	}
	context->pmexse_flags = 0;
	/* multiple MAIL FROM indicate separate messages */
	eval_clear(context->rs, context->res, COND_ENVFROM);
	ta_id = NULL;
	sm_pmfi_getmac(ctx, PMM_MAIL_TAID, &ta_id);
	if (ta_id == NULL)
		ta_id = "unknown_ta_id";
	strlcpy(context->ta_id, ta_id, sizeof(context->ta_id));

	if (mail != NULL) {
		msg(LOG_DEBUG, context, "cb_envfrom('%@s')", mail);
		if ((action = eval_cond(context->rs, context->res, COND_ENVFROM,
		    mail, NULL)) != NULL)
			return (setreply(ctx, context, action));
	}
	if ((action = eval_end(context->rs, context->res, COND_ENVFROM)) !=
	    NULL)
		return (setreply(ctx, context, action));
	return (SMFIS_CONTINUE);
}

static sfsistat
cb_envrcpt(SMFICTX *ctx, const char *rcpt, char **args)
{
	struct context *context;
	const struct action *action;

	if ((context = (struct context *)smfi_getpriv(ctx)) == NULL) {
		msg(LOG_ERR, NULL, "cb_envrcpt: smfi_getpriv");
		return (SMFIS_ACCEPT);
	}
	/* multiple RCPT TO: possible */
	eval_clear(context->rs, context->res, COND_ENVRCPT);
	if (rcpt != NULL) {
		msg(LOG_DEBUG, context, "cb_envrcpt('%@s')", rcpt);
		if ((action = eval_cond(context->rs, context->res, COND_ENVRCPT,
		    rcpt, NULL)) != NULL)
			return (setreply(ctx, context, action));
	}
	if ((action = eval_end(context->rs, context->res, COND_ENVRCPT)) !=
	    NULL)
		return (setreply(ctx, context, action));
	return (SMFIS_CONTINUE);
}

static sfsistat
cb_header(SMFICTX *ctx, char *name, char *value)
{
	struct context *context;
	const struct action *action;

	if ((context = (struct context *)smfi_getpriv(ctx)) == NULL) {
		msg(LOG_ERR, context, "cb_header: smfi_getpriv");
		return (SMFIS_ACCEPT);
	}
	msg(LOG_DEBUG, context, "cb_header('%@s', '%@s')", name, value);
	if (!strcasecmp(name, "From"))
		strlcpy(context->hdr_from, value, sizeof(context->hdr_from));
	else if (!strcasecmp(name, "To"))
		strlcpy(context->hdr_to, value, sizeof(context->hdr_to));
	else if (!strcasecmp(name, "Subject"))
		strlcpy(context->hdr_subject, value,
		    sizeof(context->hdr_subject));
	if ((action = eval_cond(context->rs, context->res, COND_HEADER,
	    name, value)) != NULL)
		return (setreply(ctx, context, action));
	return (SMFIS_CONTINUE);
}

static sfsistat
cb_eoh(SMFICTX *ctx)
{
	struct context *context;
	const struct action *action;

	if ((context = (struct context *)smfi_getpriv(ctx)) == NULL) {
		msg(LOG_ERR, NULL, "cb_eoh: smfi_getpriv");
		return (SMFIS_ACCEPT);
	}
	msg(LOG_DEBUG, context, "cb_eoh()");
	memset(context->buf, 0, sizeof(context->buf));
	context->pos = 0;
	if ((action = eval_end(context->rs, context->res, COND_HEADER)) !=
	    NULL)
		return (setreply(ctx, context, action));
	return (SMFIS_CONTINUE);
}

static sfsistat
cb_body(SMFICTX *ctx, u_char *chunk, size_t size)
{
	struct context *context;

	if ((context = (struct context *)smfi_getpriv(ctx)) == NULL) {
		msg(LOG_ERR, NULL, "cb_body: smfi_getpriv");
		return (SMFIS_ACCEPT);
	}
	for (; size > 0; size--, chunk++) {
		context->buf[context->pos] = *chunk;
		if (context->buf[context->pos] == '\n' ||
		    context->pos == sizeof(context->buf) - 1) {
			const struct action *action;

			if (context->pos > 0 &&
			    context->buf[context->pos - 1] == '\r')
				context->buf[context->pos - 1] = 0;
			else
				context->buf[context->pos] = 0;
			context->pos = 0;
			msg(LOG_DEBUG, context, "cb_body('%@s')", context->buf);
			if ((action = eval_cond(context->rs, context->res,
			    COND_BODY, context->buf, NULL)) != NULL)
				return (setreply(ctx, context, action));
		} else
			context->pos++;
	}
	return (SMFIS_CONTINUE);
}

static sfsistat
cb_eom(SMFICTX *ctx)
{
	struct context *context;
	const struct action *action;
	int result = SMFIS_ACCEPT;

	if ((context = (struct context *)smfi_getpriv(ctx)) == NULL) {
		msg(LOG_ERR, NULL, "cb_eom: smfi_getpriv");
		return (SMFIS_ACCEPT);
	}
	msg(LOG_DEBUG, context, "cb_eom()");
	if ((action = eval_end(context->rs, context->res, COND_BODY)) !=
	    NULL)
		result = setreply(ctx, context, action);
	else
		msg(LOG_DEBUG, context, "ACCEPT, From: %@s, To: %@s, "
		    "Subject: %s", context->hdr_from, context->hdr_to,
		    context->hdr_subject);
#ifdef SMFIF_QUARANTINE
	if (context->quarantine != NULL) {
		msg(LOG_NOTICE, context, "QUARANTINE: %@s, From: %@s, To: %@s, "
		    "Subject: %@s", action->msg, context->hdr_from,
		    context->hdr_to, context->hdr_subject);
		if (smfi_quarantine(ctx, context->quarantine) != MI_SUCCESS)
			msg(LOG_ERR, context, "cb_eom: smfi_quarantine");
	}
#endif
	context->pmexse_flags = 0;
	return (result);
}

static sfsistat
cb_close(SMFICTX *ctx)
{
	struct context *context;

	context = (struct context *)smfi_getpriv(ctx);
	msg(LOG_DEBUG, context, "cb_close()");
	if (context != NULL) {
		smfi_setpriv(ctx, NULL);
		free(context->res);
		if (context->quarantine != NULL)
			free(context->quarantine);
		context->rs->refcnt--;
		free(context);
	}
	return (SMFIS_CONTINUE);
}

static sfsistat_T
pm_hdr(pmse_ctx_P pmse_ctx, pmexse_ctx_P pmexse_ctx, unsigned char *buf, size_t len)
{
	sm_ret_T ret, rv;
	bool newchunk;
	sm_xhdr_ctx_P sm_xhdr_ctx;
	struct context *context;

	sm_xhdr_ctx = pmexse_ctx->pmexse_xhdr_ctx;
	if ((context = (struct context *)smfi_getpriv(pmse_ctx)) == NULL) {
		msg(LOG_ERR, NULL, "pm_hdr: smfi_getpriv");
		return (SMFIS_ACCEPT);
	}
	sm_xhdr_ctx->sm_xhdr_chunk_off = 0;
	newchunk = true;
	do
	{
		ret = sm_xhdr(buf, len, newchunk, sm_xhdr_ctx);
		newchunk = false;
		if (sm_is_err(ret))
		{
			int plen;
			bool perm;
			char m[64];

			plen = (len > 256) ? 256 : (int) len;
			msg(LOG_NOTICE, context,
				"ta_id=%s, sm_xhdr=%m, buf=\"%.*@s\", len=%lu"
				", off=%lu, state=%#x, name=%@S, value=%@S",
				context->ta_id, ret, plen, buf, (unsigned long) len,
				(unsigned long) sm_xhdr_ctx->sm_xhdr_chunk_off,
				sm_xhdr_ctx->sm_xhdr_state,
				sm_xhdr_ctx->sm_xhdr_name,
				sm_xhdr_ctx->sm_xhdr_value);
			perm = sm_is_perm_err(ret) && Xhdr_perm;

			snprintf(m, sizeof(m),
				"%d %d.7.1 error parsing headers\r\n",
				perm ? 554 : 451, perm ? 5 : 4);
			if (sm_pmfi_setreply(pmse_ctx, m) != MI_SUCCESS)
				msg(LOG_ERR, context, "smfi_setreply");

			return perm ? SMFIS_REJECT : SMFIS_TEMPFAIL;
		}
		if (ret == SM_XHDR_GOT1 || ret == SM_XHDR_GOTA ||
		    ret == SM_XHDR_GOTL)
		{
			char *hdr_name, *hdr_value;

			hdr_name = (char *) sm_str_getdata(
						sm_xhdr_ctx->sm_xhdr_name);
			hdr_value = (char *) sm_str_getdata(
						sm_xhdr_ctx->sm_xhdr_value);
			if (hdr_name == NULL || hdr_value == NULL)
			{
				msg(LOG_NOTICE, context,
					"ta_id=%s, hdr_name=%p, hdr_value=%p",
					context->ta_id, hdr_name, hdr_value);
				return SMFIS_TEMPFAIL;
			}
			rv = cb_header(pmse_ctx, hdr_name, hdr_value); 
			if (rv != SMFIS_CONTINUE)
				return rv;
		}
	} while (!sm_is_err(ret) && ret == SM_XHDR_GOTA);
	if (ret == SM_XHDR_EOHDR || ret == SM_XHDR_GOTL)
	{
		PMSE_CLR_FLAG(pmexse_ctx, PMSE_FL_MSG_HDR);
		PMSE_SET_FLAG(pmexse_ctx, PMSE_FL_MSG_EOH);
		/* invoke end-of-header function */

		rv = cb_eoh(pmse_ctx);
		PMSE_CLR_FLAG(pmexse_ctx, PMSE_FL_MSG_EOH);
		PMSE_SET_FLAG(pmexse_ctx, PMSE_FL_MSG_BODY);
		if (rv != SMFIS_CONTINUE)
			return rv;
	}
	return SMFIS_CONTINUE;
}

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

	pmexse_ctx = (pmexse_ctx_P) sm_pmfi_get_ctx_se(pmse_ctx);
	if (pmexse_ctx == NULL)
		return SMTP_R_TEMP;

	ret = SMTP_R_CONT;
	if (!PMSE_IS_FLAG(pmexse_ctx, PMSE_FL_MSG_INIT))
	{
		ret = sm_xhdr_init(SM_XHDR_FL_SKIP_FIRST_BLANK,
				&(pmexse_ctx->pmexse_xhdr_ctx));
		if (sm_is_err(ret))
		{
			return SMTP_R_TEMP;
		}
		PMSE_SET_FLAG(pmexse_ctx, PMSE_FL_MSG_INIT|PMSE_FL_MSG_HDR);
	}

	if (PMSE_IS_FLAG(pmexse_ctx, PMSE_FL_MSG_HDR))
	{
		ret = pm_hdr(pmse_ctx, pmexse_ctx, buf, len);
		if (ret != SMFIS_CONTINUE)
			return ret;
		if (PMSE_IS_FLAG(pmexse_ctx, PMSE_FL_MSG_BODY))
		{
			sm_xhdr_ctx_P sm_xhdr_ctx;

			sm_xhdr_ctx = pmexse_ctx->pmexse_xhdr_ctx;
			if (sm_xhdr_ctx->sm_xhdr_chunk_off < len)
			{
				len -= sm_xhdr_ctx->sm_xhdr_chunk_off;
				buf += sm_xhdr_ctx->sm_xhdr_chunk_off;
				ret = sm_xhdr_end(sm_xhdr_ctx);
			}
			else
				len = 0;
		}
	}
	if (PMSE_IS_FLAG(pmexse_ctx, PMSE_FL_MSG_BODY) && len > 0)
		ret = cb_body(pmse_ctx, buf, len);
	return ret;
}


#define smfilter pmilter

static pmilter_T
pmilter =
{
	"milter-regex",	/* filter name */
	LPMILTER_VERSION,
	SM_SCAP_PM_CNNCT|SM_SCAP_PM_EHLO|SM_SCAP_PM_DATA|SM_SCAP_PM_MSG,
	0,
	0,
	0,
	cb_negotiate,
	cb_connect,
	cb_helo,
	cb_envfrom,
	cb_envrcpt,
	NULL,
	pm_msg,
	cb_eom,
	NULL,
	cb_close,
	NULL, /* cb_unknown, */
	NULL /* cb_signal */
};

static void
msg(int priority, struct context *context, const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	if (debug)
	{
		char msg[8192];

		sm_vsnprintf(msg, sizeof(msg), fmt, ap);
		sm_io_fprintf(smioerr, "syslog: %s\n", msg);
	}
	else
		sm_log_vwrite(NULL, NULL, NULL, priority, 1, fmt, ap);
	va_end(ap);
}

static void
usage(const char *argv0)
{
	sm_io_fprintf(smioerr, "usage: %s [-ds] [-c config] [-p socket]\n",
		argv0);
	exit(EX_USAGE);
}

void
die(const char *reason)
{
	msg(LOG_ERR, NULL, "die: %s", reason);
	smfi_stop();
	sleep(60);
	/* not reached, smfi_stop() kills thread */
	abort();
}

int
main(int argc, char **argv)
{
	int ch, dbg;
	sfsistat_T ret = SM_PMI_FAILURE;
	const char *oconn = OCONN;
	const char *ofile = NULL;
	uint32_t major, minor, patchlevel;
	char *prog;

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

	tzset();
	(void) sm_log_opensyslog("milter-regex", LOG_PID|LOG_NDELAY, LOG_MAIL);
	dbg = 0;
	while ((ch = getopt(argc, argv, "c:D:dp:s")) != -1) {
		switch (ch) {
		case 'c':
			rule_file_name = optarg;
			break;
		case 'D':
			dbg = strtoul(optarg, NULL, 0);
			break;
		case 'd':
			debug = 1;
			break;
		case 'p':
			oconn = optarg;
			break;
		case 's':
			Xhdr_perm = 1;
			break;
		default:
			usage(argv[0]);
		}
	}
	if (argc != optind) {
		sm_io_fprintf(smioerr, "unknown command line argument: %s ...",
		    argv[optind]);
		usage(argv[0]);
	}

	if (!strncmp(oconn, "unix:", 5))
		ofile = oconn + 5;
	else if (!strncmp(oconn, "local:", 6))
		ofile = oconn + 6;
	else
		ofile = oconn;
	if (ofile != NULL)
		(void) unlink(ofile);

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

	ret = sm_pmfi_version(pmg_ctx, &major, &minor, &patchlevel);
	if (sm_is_err(ret))
	{
		sm_io_fprintf(smioerr, "sev=ERROR, sm_pmfi_version=%m\n", ret);
		goto done;
	}
	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 done;
	}
	if (dbg > 0)
		ret = sm_pmfi_setdbg(pmg_ctx, dbg);

	ret = sm_pmfi_setconn(pmg_ctx, (char *)ofile);
	if (sm_is_err(ret))
	{
		sm_io_fprintf(smioerr, "sev=ERROR, sm_pmfi_setconn=%m\n", ret);
		goto done;
	}

	if (eval_init(ACTION_ACCEPT)) {
		sm_io_fprintf(smioerr, "eval_init: failed\n");
		goto done;
	}

	umask(0177);
	signal(SIGPIPE, SIG_IGN);
	msg(LOG_INFO, NULL, "started: %s", rcsid);

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

	if (ret != MI_SUCCESS)
		msg(LOG_ERR, NULL, "smfi_main: terminating due to error");
	else
		msg(LOG_INFO, NULL, "smfi_main: terminating without error");

done:
	return 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