/* $Id: milter-spamd.c,v 1.13 2006/10/05 04:27:35 ca Exp $ */

/*
 * Copyright (c) 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.
 *
 */

/*
 *  Modified for MeTA1 pmilter
 */

static const char rcsid[] = "$Id: milter-spamd.c,v 1.13 2006/10/05 04:27:35 ca Exp $";

#include "sm/generic.h"
SM_RCSID("@(#)$Id: milter-spamd.c,v 1.13 2006/10/05 04:27:35 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/regex.h"
#include "sm/signal.h"
#include "sm/syslog.h"
#include "sm/smreplycodes.h"
#include "sm/pmilter.h"
#include "sm/pmfdef.h"
#include "sm/pmfapi.h"

#if MTA_USE_PMILTER

#include <stdio.h>

#define MI_SUCCESS SM_PMI_SUCCESS

static const char	*spamd_user = "";
static const char	*ignore_connect = "";
static int		 debug = 0;
static int		 timeout = 30;
static regex_t		 re_ignore_connect;

/* when to add Spam: header: */
#define ADDHDR_NEVER	0	/* never */
#define ADDHDR_ONLY	1	/* only, don't reject */
#define ADDHDR_GT0	2	/* if score > 0 */
#define ADDHDR_ALWAYS	3	/* always */
static int		 AddHdr = ADDHDR_NEVER;

struct context {
	char		 ctx_host[128];
	char		 ctx_addr[64];
	char		 ctx_helo[128];
#if 0
	char		 hdr_from[128];
	char		 hdr_to[128];
	char		 hdr_subject[128];
#endif
	char		 *ctx_ta_id;
	int		 ctx_fd;
	int		 ctx_state;
	int		 ctx_flags;
	int		 ctx_spam;
	double		 ctx_score, ctx_threshold;
	char		 ctx_symbols[128];
};
typedef struct context	context_T, *context_P;

#define CTX_FL_NONE	0x0000
#define CTX_FL_FD_OPEN	0x0001	/* fd is ok to use */
#define CTX_FL_FD_FAIL	0x0002	/* don't try to open file */
#define CTX_FL_WR_FAIL	0x0004	/* write failed before */

#define CTX_SET_FLAG(context, fl)	(context)->ctx_flags |= (fl)
#define CTX_CLR_FLAG(context, fl)	(context)->ctx_flags &= ~(fl)
#define CTX_IS_FLAG(context, fl)	(((context)->ctx_flags & (fl)) != 0)


static void		 usage(const char *);
static void		 msg(int, context_P, const char *, ...);

#define USER		"_milter-spamd"
#define OCONN		"/var/spool/milter-spamd/sock"
#define	SPAMD_ADDR	"127.0.0.1"
#define	SPAMD_PORT	783

static short spamd_port = SPAMD_PORT;

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

static void
msg(int priority, context_P context, const char *fmt, ...)
{
	va_list ap;
	char msg[8192];

	va_start(ap, fmt);
	if (context != NULL)
		snprintf(msg, sizeof(msg), "%s: ", context->ctx_addr);
	else
		msg[0] = 0;
	vsnprintf(msg + strlen(msg), sizeof(msg) - strlen(msg), fmt, ap);
	if (debug)
		printf("syslog: %s\n", msg);
	else
		syslog(priority, "%s", msg);
	va_end(ap);
}

static int
get_spamd_fd(context_P context)
{
	int fd;
	struct sockaddr_in sa;

	if (CTX_IS_FLAG(context, CTX_FL_FD_OPEN) ||
	    CTX_IS_FLAG(context, CTX_FL_FD_FAIL))
		return (-1);
	if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		msg(LOG_ERR, context, "get_spamd_fd: socket: %s",
		    strerror(errno));
		return (-1);
	}
	memset(&sa, 0, sizeof(sa));
	sa.sin_family = AF_INET;
	sa.sin_addr.s_addr = inet_addr(SPAMD_ADDR);
	sa.sin_port = htons(spamd_port);
	if (connect(fd, (struct sockaddr *)&sa, sizeof(sa))) {
		msg(LOG_ERR, context, "get_spamd_fd: connect: %s",
		    strerror(errno));
		close(fd);
		CTX_SET_FLAG(context, CTX_FL_FD_FAIL);
		return (-1);
	}
	CTX_SET_FLAG(context, CTX_FL_FD_OPEN);
	return (fd);
}

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))
		fprintf(stderr,
			"sev=ERROR, where=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))
		fprintf(stderr,
			"sev=ERROR, where=cb_negotiate, sm_pmfi_setmaclist=%#x, macro=taid\n",
			ret);
	return SM_SUCCESS;
}

static sfsistat_T
cb_connect(pmse_ctx_P pmse_ctx, const char *name, sm_sockaddr_T *sa)
{
	context_P context;
	char host[64];

	context = calloc(1, sizeof(*context));
	if (context == NULL) {
		msg(LOG_ERR, NULL, "cb_connect: calloc: %s", strerror(errno));
		return (SMFIS_ACCEPT);
	}
	context->ctx_fd = -1;
	if (sm_pmfi_set_ctx_se(pmse_ctx, context) != MI_SUCCESS) {
		free(context);
		msg(LOG_ERR, NULL, "cb_connect: smfi_setpriv");
		return (SMFIS_ACCEPT);
	}

	strlcpy(host, "unknown", sizeof(host));
	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, host,
		    sizeof(host)) == 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, host,
		    sizeof(host)) == NULL)
			msg(LOG_ERR, NULL, "cb_connect: inet_ntop: %s",
			    strerror(errno));
		break;
	}
#endif
	}
	strlcpy(context->ctx_host, name, sizeof(context->ctx_host));
	strlcpy(context->ctx_addr, host, sizeof(context->ctx_addr));
	msg(LOG_DEBUG, context, "cb_connect('%s', '%s')", name, host);
	if (ignore_connect[0] && (
	    !regexec(&re_ignore_connect, name, 0, NULL, 0) ||
	    !regexec(&re_ignore_connect, host, 0, NULL, 0))) {
		msg(LOG_DEBUG, context, "cb_connect: matches host ignore RE");
		return (SMFIS_ACCEPT);
	}
	return (SMFIS_CONTINUE);
}

static sfsistat_T
cb_helo(pmse_ctx_P pmse_ctx, const char *arg, bool ehlo)
{
	context_P context;

	if ((context = (context_P)sm_pmfi_get_ctx_se(pmse_ctx)) == NULL) {
		msg(LOG_ERR, NULL, "cb_helo: smfi_getpriv");
		return (SMFIS_ACCEPT);
	}
	strlcpy(context->ctx_helo, arg, sizeof(context->ctx_helo));
	msg(LOG_DEBUG, context, "cb_helo('%s')", arg);
	return (SMFIS_CONTINUE);
}

#if MILTER_SPAMD_DEBUG
static sfsistat_T
cb_mail(pmse_ctx_P pmse_ctx, const char *mail, char **argv)
{
	context_P context;

	if ((context = (context_P)sm_pmfi_get_ctx_se(pmse_ctx)) == NULL) {
		msg(LOG_ERR, NULL, "cb_envfrom: smfi_getpriv");
		return (SMFIS_ACCEPT);
	}
	if (mail != NULL)
		msg(LOG_DEBUG, context, "cb_envfrom('%s')", mail);
	return (SMFIS_CONTINUE);
}

static sfsistat_T
cb_rcpt(pmse_ctx_P pmse_ctx, const char *rcpt, char **argv)
{
	context_P context;

	if ((context = (context_P)sm_pmfi_get_ctx_se(pmse_ctx)) == NULL) {
		msg(LOG_ERR, NULL, "cb_envrcpt: smfi_getpriv");
		return (SMFIS_ACCEPT);
	}
	if (rcpt != NULL)
		msg(LOG_DEBUG, context, "cb_envrcpt('%s')", rcpt);
	return (SMFIS_CONTINUE);
}
#endif /* MILTER_SPAMD_DEBUG */

static int
writefd(context_P context, unsigned char *chunk, size_t len)
{
	extern sm_ret_T sm_write_wait(int _fd, int _timeout);

	if (!CTX_IS_FLAG(context, CTX_FL_FD_OPEN) ||
	    CTX_IS_FLAG(context, CTX_FL_WR_FAIL))
		return -1;
	if (chunk == NULL || len <= 0)
		return 0;

	while (len > 0)
	{
		sm_ret_T r;
		ssize_t written;
		size_t wr;

		r = sm_write_wait(context->ctx_fd, timeout);
		if (r != 0)
			return r;
		written = write(context->ctx_fd, chunk, len);
		if (written == -1)
		{
			sm_io_fprintf(smioerr,
				"sev=ERROR, where=msg, write=%d\n",
				(int) written);
			CTX_SET_FLAG(context, CTX_FL_WR_FAIL);
			close(context->ctx_fd);
			context->ctx_fd = -1;
			CTX_CLR_FLAG(context, CTX_FL_FD_OPEN);
			return -1;
		}
		wr = (size_t) written;
		assert(len >= wr);
		len -= wr;
		chunk += wr;
	}
	return 0;
}

static void
fdprintf(context_P context, const char *fmt, ...)
{
	va_list ap;
	char s[2048];

	va_start(ap, fmt);
	vsnprintf(s, sizeof(s), fmt, ap);
	va_end(ap);
	(void) writefd(context, (unsigned char *) s, strlen(s));
}

static sm_ret_T
cb_msg(pmse_ctx_P pmse_ctx, unsigned char *chunk, size_t size)
{
	context_P context;

	if ((context = (context_P)sm_pmfi_get_ctx_se(pmse_ctx)) == NULL) {
		msg(LOG_ERR, NULL, "cb_msg: smfi_getpriv");
		return (SMFIS_ACCEPT);
	}
	if (CTX_IS_FLAG(context, CTX_FL_FD_FAIL))
		return -1;

	/* first call? */
	if (!CTX_IS_FLAG(context, CTX_FL_FD_OPEN) &&
	    !CTX_IS_FLAG(context, CTX_FL_WR_FAIL) &&
	    context->ctx_fd < 0) {
		char *sendmail_name;
		char *sendmail_queue;
		char *sendmail_date;

		sm_pmfi_getmac(pmse_ctx, PMM_SRVHOSTNAME, &sendmail_name);
		sm_pmfi_getmac(pmse_ctx, PMM_MAIL_TAID, &sendmail_queue);
		context->ctx_ta_id = sendmail_queue;

		/* FIX: $b */
		sendmail_date = NULL;

		if ((context->ctx_fd = get_spamd_fd(context)) < 0)
			return (SMFIS_ACCEPT);
		fdprintf(context, "SYMBOLS SPAMC/1.2\r\n");
		if (spamd_user[0])
			fdprintf(context, "User: %s\r\n", spamd_user);
		fdprintf(context, "\r\n");
		/* send fake Received: header */
		fdprintf(context, "Received: from %s (%s [%s])",
		    context->ctx_helo, context->ctx_host, context->ctx_addr);
		if (sendmail_name != NULL && sendmail_name[0]) {
			fdprintf(context, "\r\n\tby %s (milter-spamd)",
			    sendmail_name);
			if (sendmail_queue != NULL && sendmail_queue[0])
				fdprintf(context, " id %s", sendmail_queue);
		}
		if (sendmail_date != NULL && sendmail_date[0])
			fdprintf(context, "; %s", sendmail_date);
		else {
			char d[128];
			time_t t = time(NULL);

			if (strftime(d, sizeof(d), "%a, %e %b %Y %H:%M:%S %z",
			    localtime(&t)))
				fdprintf(context, "; %s", d);
		}
		fdprintf(context, "\r\n");
	}
#if 0
	fdprintf(context, "%s: %s\r\n", 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));
#endif

	if (CTX_IS_FLAG(context, CTX_FL_FD_OPEN) &&
	    !CTX_IS_FLAG(context, CTX_FL_WR_FAIL) &&
	    context->ctx_fd >= 0)
		writefd(context, chunk, size);
	return (SMFIS_CONTINUE);
}

static void
spamd_reply(const char *line, context_P context, sfsistat_T *action)
{
	const char *p;

	switch (context->ctx_state) {
	case 0:
		if (strncmp(line, "SPAMD/", 6)) {
			msg(LOG_ERR, context, "spamd_reply: first reply "
			    "not SPAMD version: %s", line);
			*action = SMFIS_ACCEPT;
			break;
		}
		p = line + 6;
		while (*p && *p != ' ')
			++p;
		while (*p == ' ')
			++p;
		if (strncmp(p, "0 EX_OK", 7)) {
			msg(LOG_ERR, context, "spamd_reply: first reply "
			    "not 0 EX_OK: %s", line);
			*action = SMFIS_ACCEPT;
			break;
		}
		context->ctx_state = 1;
		break;
	case 1:
		if (!strncmp(line, "Spam: ", 6)) {
			char decision[16];
			double score, threshold;

			if (sscanf(line + 6, "%15s ; %lf / %lf", decision,
			    &score, &threshold) != 3) {
				msg(LOG_ERR, context, "spamd_reply: malformed "
				    "decision reply: %s", line);
				*action = SMFIS_ACCEPT;
				break;
			}
			context->ctx_spam = !strcmp(decision, "True");
			context->ctx_score = score;
			context->ctx_threshold = threshold;
			context->ctx_state = 2;
		}
		break;
	case 2:
		if (!line[0])
			context->ctx_state = 3;
		break;
	case 3:
		strlcat(context->ctx_symbols, line,
			sizeof(context->ctx_symbols));
		break;
	default:
		msg(LOG_ERR, context,
			"spamd_reply: invalid context->ctx_state");
		*action = SMFIS_ACCEPT;
	}
}

static sfsistat_T
cb_eom(pmse_ctx_P pmse_ctx)
{
	context_P context;
	sfsistat_T action = SMFIS_CONTINUE;
	char buf[2048];
	int pos = 0, retry = 0;

	if ((context = (context_P)sm_pmfi_get_ctx_se(pmse_ctx)) == NULL) {
		msg(LOG_ERR, NULL, "cb_eom: smfi_getpriv");
		return (SMFIS_ACCEPT);
	}
	msg(LOG_DEBUG, context, "cb_eom()");
	if (context->ctx_fd < 0)
		goto done;
	context->ctx_symbols[0] = 0;
	/* no more writing data to spamd, want to read result now */
	if (shutdown(context->ctx_fd, SHUT_WR)) {
		msg(LOG_ERR, context, "cb_eom: shutdown: %s", strerror(errno));
		goto done;
	}
	if (fcntl(context->ctx_fd, F_SETFL, fcntl(context->ctx_fd, F_GETFL) |
	    O_NONBLOCK)) {
		msg(LOG_ERR, context, "cb_eom: fcntl: %s", strerror(errno));
		goto done;
	}
	/* try at most 6 times (10 seconds timeout each) */
	while (action == SMFIS_CONTINUE && retry < 6) {
		fd_set fds;
		struct timeval tv;
		int r, i;
		char b[8192];

		FD_ZERO(&fds);
		FD_SET(context->ctx_fd, &fds);
		tv.tv_sec = 10;
		tv.tv_usec = 0;
		r = select(context->ctx_fd + 1, &fds, NULL, NULL, &tv);
		if (r < 0) {
			if (errno != EINTR) {
				msg(LOG_ERR, context, "cb_eom: select: %s",
				    strerror(errno));
				break;
			}
			continue;
		} else if (r == 0 || !FD_ISSET(context->ctx_fd, &fds)) {
			retry++;
			msg(LOG_DEBUG, context, "cb_eom: waiting for "
			    "spamd reply (retry %d)", retry);
			continue;
		}
		r = read(context->ctx_fd, b, sizeof(b));
		if (r < 0) {
			if (errno != EINTR) {
				msg(LOG_ERR, context, "cb_eom: read: %s",
				    strerror(errno));
				break;
			}
			continue;
		} else if (r == 0)
			/* connection closed by spamd */
			break;
		for (i = 0; i < r; ++i)
			if (b[i] == '\n' || pos == sizeof(buf) - 1) {
				if (pos > 0 && buf[pos - 1] == '\r')
					buf[pos - 1] = 0;
				else
					buf[pos] = 0;
				/* sets action when done */
				spamd_reply(buf, context, &action);
				pos = 0;
			} else
				buf[pos++] = b[i];
	}
	if (retry == 6)
		msg(LOG_ERR, context, "cb_eom: spamd connection timed out");
done:
	if (context->ctx_fd >= 0) {
		close(context->ctx_fd);
		context->ctx_fd = -1;
	}
	/* either way, we don't want to continue */
	if (action == SMFIS_CONTINUE)
	{
		action = (context->ctx_spam && ADDHDR_ONLY != AddHdr)
				? SMFIS_REJECT : SMFIS_ACCEPT;
	}
#if 0
	msg(action == SMFIS_REJECT ? LOG_NOTICE : LOG_INFO, context,
	    "%s (%s %.1f/%.1f%s%s), From: %s, To: %s, Subject: %s",
	    (action == SMFIS_REJECT ? "REJECT" : "ACCEPT"),
	    (context->ctx_spam ? "SPAM" : "ham"), context->ctx_score,
	    context->ctx_threshold,
	    (context->ctx_symbols[0] ? " " :  ""), context->ctx_symbols,
	    context->hdr_from, context->hdr_to, context->hdr_subject);
#else
	msg(action == SMFIS_REJECT ? LOG_NOTICE : LOG_INFO, context,
	    "%s (%s %.1f/%.1f%s%s), ta_id=%s",
	    (action == SMFIS_REJECT ? "REJECT" : "ACCEPT"),
	    (context->ctx_spam ? "SPAM" : "ham"),
	    context->ctx_score, context->ctx_threshold,
	    (context->ctx_symbols[0] ? " " :  ""), context->ctx_symbols,
	    context->ctx_ta_id);
#endif
	if (action == SMFIS_REJECT) {
		char m[64];

		snprintf(m, sizeof(m), "554 5.7.1 Spam (score %.1f)\r\n",
			context->ctx_score);
		if (sm_pmfi_setreply(pmse_ctx, m) != MI_SUCCESS)
			msg(LOG_ERR, context, "sm_pmfi_setreply");
	}
	if (ADDHDR_ALWAYS == AddHdr ||
	    ADDHDR_ONLY == AddHdr ||
	    (ADDHDR_GT0 == AddHdr && context->ctx_score > 0)) {
		char m[64];

		snprintf(m, sizeof(m), "X-Spam: score %.1f\r\n",
			context->ctx_score);
		if (sm_pmfi_hdr_mod(pmse_ctx, SM_HDRMOD_T_APPEND, 0,
				(const uchar *) m) != MI_SUCCESS)
			msg(LOG_ERR, context, "sm_pmfi_hdrmod");
	}
#if 0
	context->pos =
	    context->hdr_from[0] = context->hdr_to[0] =
	    context->hdr_subject[0] =
#endif
	    context->ctx_state = context->ctx_spam =
	    context->ctx_flags =
	    context->ctx_symbols[0] = 0;
	context->ctx_score = context->ctx_threshold = 0.0;
	return (action);
}

static sfsistat_T
cb_abort(pmse_ctx_P pmse_ctx)
{
	context_P context;

	if ((context = (context_P)sm_pmfi_get_ctx_se(pmse_ctx)) == NULL) {
		msg(LOG_ERR, NULL, "cb_abort: smfi_getpriv");
		return (SMFIS_ACCEPT);
	}
	msg(LOG_DEBUG, context, "cb_abort()");
	if (context->ctx_fd >= 0) {
		close(context->ctx_fd);
		context->ctx_fd = -1;
	}
#if 0
	context->pos =
	    context->hdr_from[0] = context->hdr_to[0] =
	    context->hdr_subject[0] =
#endif
	    context->ctx_state = context->ctx_spam =
	    context->ctx_flags =
	    context->ctx_symbols[0] = 0;
	context->ctx_score = context->ctx_threshold = 0.0;
	return (SMFIS_CONTINUE);
}

static sfsistat_T
cb_close(pmse_ctx_P pmse_ctx)
{
	context_P context;

	context = (context_P)sm_pmfi_get_ctx_se(pmse_ctx);
	msg(LOG_DEBUG, context, "cb_close()");
	if (context != NULL) {
		sm_pmfi_set_ctx_se(pmse_ctx, NULL);
		if (context->ctx_fd >= 0) {
			close(context->ctx_fd);
			context->ctx_fd = -1;
		}
		free(context);
	}
	return (SMFIS_CONTINUE);
}

static pmilter_T
pmilter =
{
	"milter-spamd",	/* 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,
#if MILTER_SPAMD_DEBUG
	cb_mail,
	cb_rcpt,
#else
	NULL,
	NULL,
#endif
	NULL,
	cb_msg,
	cb_eom,
	cb_abort,
	cb_close,
	NULL, /* cb_unknown, */
	NULL /* cb_signal */
};


static void
usage(const char *argv0)
{
	fprintf(stderr, "usage: %s [-d] [-i RE] [-U spamd_user] "
	    "[-p spamd_port] [-p pipe]\n", argv0);
	exit(1);
}

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

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

	tzset();
	openlog("milter-spamd", LOG_PID | LOG_NDELAY, LOG_DAEMON);

	while ((ch = getopt(argc, argv, "di:P:p:t:U:")) != -1) {
		switch (ch) {
		case 'd':
			debug = 1;
			break;
		case 'i':  {
			int r;

			ignore_connect = optarg;
			r = regcomp(&re_ignore_connect, ignore_connect,
			    REG_EXTENDED | REG_ICASE);
			if (r) {
				char e[8192];

				regerror(r, &re_ignore_connect, e, sizeof(e));
				fprintf(stderr, "regcomp: %s: %s\n",
				    ignore_connect, e);
				usage(prog);
			}
			break;
		}
		case 'P':
			spamd_port = (short) strtol(optarg, NULL, 0);
			break;
		case 'p':
			oconn = optarg;
			break;
		case 't':
			timeout = strtol(optarg, NULL, 0);
			break;
		case 'U':
			spamd_user = optarg;
			break;
		default:
			usage(prog);
		}
	}
	if (argc != optind) {
		fprintf(stderr, "unknown command line argument: %s ...",
		    argv[optind]);
		usage(prog);
	}

	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))
	{
		fprintf(stderr, "sev=ERROR, sm_pmfi_init=%#x\n", ret);
		goto done;
	}

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

	ret = sm_pmfi_setconn(pmg_ctx, (char *)ofile);
	if (sm_is_err(ret))
	{
		fprintf(stderr, "sev=ERROR, sm_pmfi_setconn=%#x\n", ret);
		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))
	{
		fprintf(stderr, "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