/* $Id: milter-regex.c,v 1.3 2007/08/03 22:11:48 dhartmei Exp $ */

/*
 * Copyright (c) 2003-2006 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.3 2007/08/03 22:11:48 dhartmei Exp $";

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#ifdef __linux__
#include <grp.h>
#endif
#include <libmilter/mfapi.h>

#include "eval.h"

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

static const char	*rule_file_name = "/usr/pkg/etc/milter-regex.conf";
static int		 debug = 0;
static pthread_mutex_t	 mutex;

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		 helo[128];
	char		 hdr_from[128];
	char		 hdr_to[128];
	char		 hdr_subject[128];
	char		*quarantine;
};

static sfsistat		 setreply(SMFICTX *, struct context *,
			    const struct action *);
static struct ruleset	*get_ruleset(void);
static sfsistat		 cb_connect(SMFICTX *, char *, _SOCK_ADDR *);
static sfsistat		 cb_helo(SMFICTX *, char *);
static sfsistat		 cb_envfrom(SMFICTX *, char **);
static sfsistat		 cb_envrcpt(SMFICTX *, 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 USER		"_milter-regex"
#define OCONN		"unix:/var/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

/* Define what sendmail macros should be queried in what context (phase)
 * with smfi_getsymval(). Whether sendmail actually provides specific
 * values depends on configuration of confMILTER_MACROS_*
 */
struct {
	const char *phase;
	const char *name;
} macro[] = {
	{ "connect", "{daemon_name}" },
	{ "connect", "{if_name}" },
	{ "connect", "{if_addr}" },
	{ "connect", "j" },
	{ "connect", "_" },
	{ "helo", "{tls_version}" },
	{ "helo", "{cipher}" },
	{ "helo", "{cipher_bits}" },
	{ "helo", "{cert_subject}" },
	{ "helo", "{cert_issuer}" },
	{ "envfrom", "i" },
	{ "envfrom", "{auth_type}" },
	{ "envfrom", "{auth_authen}" },
	{ "envfrom", "{auth_ssf}" },
	{ "envfrom", "{auth_author}" },
	{ "envfrom", "{mail_mailer}" },
	{ "envfrom", "{mail_host}" },
	{ "envfrom", "{mail_addr}" },
	{ "envrcpt", "{rcpt_mailer}" },
	{ "envrcpt", "{rcpt_host}" },
	{ "envrcpt", "{rcpt_addr}" },
	{ NULL, NULL }
};

#if __linux__ || __sun__
#define	ST_MTIME st_mtime
extern size_t	 strlcpy(char *, const char *, size_t);
#else
#define	ST_MTIME st_mtimespec
#endif

static void
mutex_lock(void)
{
	if (pthread_mutex_lock(&mutex))
		die("pthread_mutex_lock");
}

static void
mutex_unlock(void)
{
	if (pthread_mutex_unlock(&mutex))
		die("pthread_mutex_unlock");
}

#ifdef __sun__
int
daemon(int nochdir, int noclose)
{
	pid_t pid;
	int fd;

	if ((pid = fork()) < 0) {
		fprintf(stderr, "fork: %s\n", strerror(errno));
		return (1);
	} else if (pid > 0)
		_exit(0);
	if ((pid = setsid()) == -1) {
		fprintf(stderr, "setsid: %s\n", strerror(errno));
		return (1);
	}
	if ((pid = fork()) < 0) {
		fprintf(stderr, "fork: %s\n", strerror(errno));
		return (1);
	} else if (pid > 0)
		_exit(0);
	if (!nochdir && chdir("/")) {
		fprintf(stderr, "chdir: %s\n", strerror(errno));
		return (1);
	}
	if (!noclose) {
		dup2(fd, fileno(stdout));
		dup2(fd, fileno(stderr));
		dup2(open("/dev/null", O_RDONLY, 0), fileno(stdin));
	}
	return (0);
}
#endif

static sfsistat
setreply(SMFICTX *ctx, struct context *context, const struct action *action)
{
	int result = SMFIS_CONTINUE;

	switch (action->type) {
	case ACTION_REJECT:
		msg(LOG_NOTICE, context, "REJECT: %s, Helo: %s, From: %s, "
		    "To: %s, Subject: %s", action->msg, context->helo,
		    context->hdr_from, context->hdr_to, context->hdr_subject);
		result = SMFIS_REJECT;
		break;
	case ACTION_TEMPFAIL:
		msg(LOG_NOTICE, context, "TEMPFAIL: %s, Helo: %s, From: %s, "
		    "To: %s, Subject: %s", action->msg, context->helo,
		    context->hdr_from, context->hdr_to, context->hdr_subject);
		result = SMFIS_TEMPFAIL;
		break;
	case ACTION_QUARANTINE:
		if (context->quarantine != NULL)
			free(context->quarantine);
		context->quarantine = strdup(action->msg);
		break;
	case ACTION_DISCARD:
		msg(LOG_NOTICE, context, "DISCARD, Helo: %s, From: %s, "
		    "To: %s, Subject: %s", context->helo, context->hdr_from,
		    context->hdr_to, context->hdr_subject);
		result = SMFIS_DISCARD;
		break;
	case ACTION_ACCEPT:
		msg(LOG_INFO, context, "ACCEPT, Helo: %s, From: %s, "
		    "To: %s, Subject: %s", context->helo, context->hdr_from,
		    context->hdr_to, context->hdr_subject);
		result = SMFIS_ACCEPT;
		break;
	}
	if (action->type == ACTION_REJECT &&
	    smfi_setreply(ctx, RCODE_REJECT, XCODE_REJECT,
	    (char *)action->msg) != MI_SUCCESS)
		msg(LOG_ERR, context, "smfi_setreply");
	if (action->type == ACTION_TEMPFAIL &&
	    smfi_setreply(ctx, RCODE_TEMPFAIL, XCODE_TEMPFAIL,
	    (char *)action->msg) != MI_SUCCESS)
		msg(LOG_ERR, context, "smfi_setreply");
	return (result);
}

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

	mutex_lock();
	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;
		}
	}
	mutex_unlock();
	return (rs[cur]);
}

static struct action *
check_macros(SMFICTX *ctx, struct context *context, const char *phase)
{
	struct action *action;
	int i;
	const char *v;

	for (i = 0; macro[i].phase != NULL; ++i) {
		if (strcmp(macro[i].phase, phase))
			continue;
		if ((v = smfi_getsymval(ctx, (char *)macro[i].name)) == NULL)
			v = "";
		msg(LOG_DEBUG, context, "macro %s = %s", macro[i].name, v);
		if ((action = eval_cond(context->rs, context->res, COND_MACRO,
		    macro[i].name, v)) != NULL)
			return (action);
	}
	return (NULL);
}

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

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

	strlcpy(context->host_name, name, sizeof(context->host_name));
	strlcpy(context->host_addr, "unknown", sizeof(context->host_addr));
	if (sa) {
		switch (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;
		}
		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;
		}
		}
	}
	msg(LOG_DEBUG, context, "cb_connect('%s', '%s')",
	    context->host_name, context->host_addr);
	if ((action = check_macros(ctx, context, "connect")) != NULL) {
		/* can't really do this, delay */
		/*return (setreply(ctx, context, action)); */
	}
	return (SMFIS_CONTINUE);
}

static sfsistat
cb_helo(SMFICTX *ctx, char *arg)
{
	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);
	}
	strlcpy(context->helo, arg, sizeof(context->helo));
	msg(LOG_DEBUG, context, "cb_helo('%s')", arg);
	/* 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,
	    COND_MACRO)) !=
	    NULL)
		return (setreply(ctx, context, action));
	if ((action = check_macros(ctx, context, "helo")) != NULL)
		return (setreply(ctx, context, action));
	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,
	    COND_MACRO)) != NULL)
		return (setreply(ctx, context, action));
	return (SMFIS_CONTINUE);
}

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

	if ((context = (struct context *)smfi_getpriv(ctx)) == NULL) {
		msg(LOG_ERR, NULL, "cb_envfrom: smfi_getpriv");
		return (SMFIS_ACCEPT);
	}
	/* multiple MAIL FROM indicate separate messages */
	eval_clear(context->rs, context->res, COND_ENVFROM);
	if (*args != NULL) {
		msg(LOG_DEBUG, context, "cb_envfrom('%s')", *args);
		if ((action = eval_cond(context->rs, context->res, COND_ENVFROM,
		    *args, NULL)) != NULL)
			return (setreply(ctx, context, action));
	}
	if ((action = eval_end(context->rs, context->res, COND_ENVFROM,
	    COND_MACRO)) != NULL)
		return (setreply(ctx, context, action));
	if ((action = check_macros(ctx, context, "envfrom")) != NULL)
		return (setreply(ctx, context, action));
	return (SMFIS_CONTINUE);
}

static sfsistat
cb_envrcpt(SMFICTX *ctx, 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 (*args != NULL) {
		msg(LOG_DEBUG, context, "cb_envrcpt('%s')", *args);
		if ((action = eval_cond(context->rs, context->res, COND_ENVRCPT,
		    *args, NULL)) != NULL)
			return (setreply(ctx, context, action));
	}
	if ((action = eval_end(context->rs, context->res, COND_ENVRCPT,
	    COND_MACRO)) != NULL)
		return (setreply(ctx, context, action));
	if ((action = check_macros(ctx, context, "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 ((action = eval_end(context->rs, context->res, COND_MACRO,
	    COND_HEADER)) != NULL)
		return (setreply(ctx, context, action));
	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,
	    COND_BODY)) != 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,
	    COND_MAX)) != NULL)
		result = setreply(ctx, context, action);
	else
		msg(LOG_DEBUG, context, "ACCEPT, Helo: %s, From: %s, To: %s, "
		    "Subject: %s", context->helo, context->hdr_from,
		    context->hdr_to, context->hdr_subject);
	if (context->quarantine != NULL) {
		msg(LOG_NOTICE, context, "QUARANTINE: %s, Helo: %s, From: %s, "
		    "To: %s, Subject: %s", action->msg, context->helo,
		    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");
	}
	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);
}

struct smfiDesc smfilter = {
	"milter-regex",	/* filter name */
	SMFI_VERSION,	/* version code -- do not change */
	SMFIF_QUARANTINE, /* flags */
	cb_connect,	/* connection info filter */
	cb_helo,	/* SMTP HELO command filter */
	cb_envfrom,	/* envelope sender filter */
	cb_envrcpt,	/* envelope recipient filter */
	cb_header,	/* header filter */
	cb_eoh,		/* end of header */
	cb_body,	/* body block */
	cb_eom,		/* end of message */
	NULL,		/* message aborted */
	cb_close	/* connection cleanup */
};

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

	va_start(ap, fmt);
	if (context != NULL)
		snprintf(msg, sizeof(msg), "%s: ", context->host_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 void
usage(const char *argv0)
{
	fprintf(stderr, "usage: %s [-d] [-c config] [-u user] "
	    "[-p pipe]\n", argv0);
	exit(1);
}

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;
	const char *oconn = OCONN;
	const char *user = USER;
	sfsistat r = MI_FAILURE;
	const char *ofile = NULL;

	tzset();
	openlog("milter-regex", LOG_PID | LOG_NDELAY, LOG_MAIL);

	while ((ch = getopt(argc, argv, "c:dp:u:")) != -1) {
		switch (ch) {
		case 'c':
			rule_file_name = optarg;
			break;
		case 'd':
			debug = 1;
			break;
		case 'p':
			oconn = optarg;
			break;
		case 'u':
			user = optarg;
			break;
		default:
			usage(argv[0]);
		}
	}
	if (argc != optind) {
		fprintf(stderr, "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;
	if (ofile != NULL)
		unlink(ofile);

	/* drop privileges */
	if (!getuid()) {
		struct passwd *pw;

		if ((pw = getpwnam(user)) == NULL) {
			fprintf(stderr, "getpwnam: %s: %s\n", user,
			    strerror(errno));
			return (1);
		}
		setgroups(1, &pw->pw_gid);
		if (setegid(pw->pw_gid) || setgid(pw->pw_gid)) {
			fprintf(stderr, "setgid: %s\n", strerror(errno));
			return (1);
		}
		if (
#if ! ( __linux__ || __sun__ || __NetBSD__ || defined(__DragonFly__) )
		    seteuid(pw->pw_uid) ||
#endif
		    setuid(pw->pw_uid)) {
			fprintf(stderr, "setuid: %s\n", strerror(errno));
			return (1);
		}
	}

	if (pthread_mutex_init(&mutex, 0)) {
		fprintf(stderr, "pthread_mutex_init\n");
		goto done;
	}

	if (smfi_setconn((char *)oconn) != MI_SUCCESS) {
		fprintf(stderr, "smfi_setconn: %s: failed\n", oconn);
		goto done;
	}

	if (smfi_register(smfilter) != MI_SUCCESS) {
		fprintf(stderr, "smfi_register: failed\n");
		goto done;
	}

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

	/* daemonize (detach from controlling terminal) */
	if (!debug && daemon(0, 0)) {
		fprintf(stderr, "daemon: %s\n", strerror(errno));
		goto done;
	}
	umask(0177);

	msg(LOG_INFO, NULL, "started: %s", rcsid);
	r = smfi_main();
	if (r != MI_SUCCESS)
		msg(LOG_ERR, NULL, "smfi_main: terminating due to error");
	else
		msg(LOG_INFO, NULL, "smfi_main: terminating without error");

done:
	return (r);
}
