/*
**  Copyright (c) 2005-2007 Sendmail, Inc. and its suppliers.
**	All rights reserved.
**
**  $Id: dkim-filter.c,v 1.290 2007/12/18 22:17:31 msk Exp $
*/

#ifndef lint
static char dkim_filter_c_id[] = "@(#)$Id: dkim-filter.c,v 1.290 2007/12/18 22:17:31 msk Exp $";
#endif /* !lint */

/* system includes */
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#if SOLARIS
# if SOLARIS > 20700
#  include <iso/limits_iso.h>
# else /* SOLARIS > 20700 */
#  include <limits.h>
# endif /* SOLARIS > 20700 */
#endif /* SOLARIS */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <sysexits.h>
#include <errno.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <assert.h>
#include <ctype.h>
#include <fcntl.h>
#include <pthread.h>
#include <netdb.h>
#include <signal.h>
#include <regex.h>
#include <openssl/sha.h>
#include <openssl/err.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#if SOLARIS
# define _PATH_DEVNULL		"/dev/null"
# ifndef _PATH_SENDMAIL
#  define _PATH_SENDMAIL	"/usr/sbin/sendmail"
# endif /* ! _PATH_SENDMAIL */
#else /* SOLARIS */
# include <paths.h>
#endif /* SOLARIS */

/* sendmail includes */
#include <sm/cdefs.h>
#include <sm/string.h>

/* libmilter includes */
#include "libmilter/mfapi.h"

/* libdkim includes */
#include <dkim.h>
#ifdef _FFR_VBR
# include <vbr.h>
#endif /* _FFR_VBR */

#if VERIFY_DOMAINKEYS
/* libdk includes */
#include <dk.h>
#endif /* VERIFY_DOMAINKEYS */

#if POPAUTH
/* libdb includes */
# include <db.h>
#endif /* POPAUTH */

/* dkim-filter includes */
#include "config.h"
#include "dkim-config.h"
#include "dkim-filter.h"
#include "dkim-ar.h"
#include "util.h"
#include "test.h"
#ifdef _FFR_STATS
# include "stats.h"
#endif /* _FFR_STATS */

/*
**  Header -- a handle referring to a header
*/

typedef struct Header * Header;
struct Header
{
	char *		hdr_hdr;
	char *		hdr_val;
	struct Header *	hdr_next;
};

/*
**  KEYTABLE -- table of keys
*/

struct keytable
{
	char *		key_selector;		/* selector */
	char *		key_domain;		/* domain */
	regex_t		key_re;			/* regex for matching */
	size_t		key_len;		/* key length */
	unsigned char *	key_data;		/* private key data */
	struct keytable * key_next;		/* next record */
};

/*
**  MSGCTX -- message context, containing transaction-specific data
*/

typedef struct msgctx * msgctx;
struct msgctx
{
	bool		mctx_addheader;		/* Authentication-Results: */
	bool		mctx_signing;		/* true iff signing */
	bool		mctx_headeronly;	/* in EOM, only add headers */
#if VERIFY_DOMAINKEYS
	bool		mctx_dksigned;		/* DK signature present */
#endif /* VERIFY_DOMAINKEYS */
#if _FFR_CAPTURE_UNKNOWN_ERRORS
	bool		mctx_capture;		/* capture message? */
#endif /* _FFR_CAPTURE_UNKNOWN_ERRORS */
	int		mctx_status;		/* status to report back */
	dkim_canon_t	mctx_hdrcanon;		/* header canonicalization */
	dkim_canon_t	mctx_bodycanon;		/* body canonicalization */
	dkim_alg_t	mctx_signalg;		/* signature algorithm */
	int		mctx_queryalg;		/* query algorithm */
	int		mctx_hdrbytes;		/* header space allocated */
	struct dkimf_dstring * mctx_tmpstr;	/* temporary string */
	char *		mctx_jobid;		/* job ID */
	char *		mctx_domain;		/* domain doing the signing */
	DKIM *		mctx_dkim;		/* DKIM handle */
#if VERIFY_DOMAINKEYS
	DK *		mctx_dk;		/* DK handle */
#endif /* VERIFY_DOMAINKEYS */
#ifdef _FFR_VBR
	VBR *		mctx_vbr;		/* VBR handle */
#endif /* _FFR_VBR */
	struct Header *	mctx_hqhead;		/* header queue head */
	struct Header *	mctx_hqtail;		/* header queue tail */
	struct keytable * mctx_key;		/* key information */
	char		mctx_hlist[MAXHEADERS];	/* header buffer */
};

/*
**  CONNCTX -- connection context, containing thread-specific data
*/

typedef struct connctx * connctx;
struct connctx
{
	bool		cctx_noleadspc;		/* no leading spaces */
	char		cctx_host[DKIM_MAXHOSTNAMELEN + 1];
						/* hostname */
	_SOCK_ADDR	cctx_ip;		/* IP info */
	struct msgctx *	cctx_msg;		/* message context */
};

/*
**  CONFIG -- configuration information
*/

typedef struct Config * Config;
struct Config
{
	int		cfg_nosig;		/* no signature */
	int		cfg_badsig;		/* bad signature */
	int		cfg_sigmissing;		/* missing signature */
	int		cfg_dnserr;		/* DNS error */
	int		cfg_internal;		/* internal error */
	int		cfg_security;		/* security concerns */
};

struct Config defaults =
{
	SMFIS_ACCEPT,
	/* SMFIS_REJECT, */ SMFIS_ACCEPT,
	/* SMFIS_REJECT, */ SMFIS_ACCEPT,
	SMFIS_TEMPFAIL,
	SMFIS_TEMPFAIL,
	SMFIS_TEMPFAIL
};

/*
**  LOOKUP -- lookup table
*/

struct lookup
{
	char *		str;
	int		code;
};

#define	CFG_DEFAULT		0
#define	CFG_NOSIGNATURE		1
#define	CFG_BADSIGNATURE	2
#define	CFG_SIGMISSING		3
#define	CFG_DNSERROR		4
#define	CFG_INTERNAL		5
#define	CFG_SECURITY		6

#define	DKIMF_MODE_SIGNER	0x01
#define	DKIMF_MODE_VERIFIER	0x02
#define	DKIMF_MODE_DEFAULT	(DKIMF_MODE_SIGNER|DKIMF_MODE_VERIFIER)

#define	DKIMF_STATUS_GOOD	0
#define	DKIMF_STATUS_BAD	1
#define	DKIMF_STATUS_NOKEY	2
#define	DKIMF_STATUS_REVOKED	3
#define	DKIMF_STATUS_NOSIGNATURE 4
#define	DKIMF_STATUS_BADFORMAT	5
#define	DKIMF_STATUS_PARTIAL	6
#define	DKIMF_STATUS_VERIFYERR	7
#define	DKIMF_STATUS_SUSPICIOUS	8
#define	DKIMF_STATUS_UNKNOWN	9

#define SIGMIN_BYTES		0
#define SIGMIN_PERCENT		1
#define SIGMIN_MAXADD		2

#define	SSPDENYSMTP		"550"
#define	SSPDENYESC		"5.7.1"
#define	SSPDENYTEXT		"rejected due to DKIM SSP evaluation"

struct lookup dkimf_params[] =
{
	{ "no",			CFG_NOSIGNATURE },
	{ "nosignature",	CFG_NOSIGNATURE },
	{ "bad",		CFG_BADSIGNATURE },
	{ "badsignature",	CFG_BADSIGNATURE },
	{ "miss",		CFG_SIGMISSING },
	{ "signaturemissing",	CFG_SIGMISSING },
	{ "dns",		CFG_DNSERROR },
	{ "dnserror",		CFG_DNSERROR },
	{ "int",		CFG_INTERNAL },
	{ "internal",		CFG_INTERNAL },
	{ "sec",		CFG_SECURITY },
	{ "security",		CFG_SECURITY },
	{ "def",		CFG_DEFAULT },
	{ "default",		CFG_DEFAULT },
	{ NULL,			-1 },
};

struct lookup dkimf_values[] =
{
	{ "a",			SMFIS_ACCEPT },
	{ "accept",		SMFIS_ACCEPT },
	{ "d",			SMFIS_DISCARD },
	{ "discard",		SMFIS_DISCARD },
	{ "r",			SMFIS_REJECT },
	{ "reject",		SMFIS_REJECT },
	{ "t",			SMFIS_TEMPFAIL },
	{ "tempfail",		SMFIS_TEMPFAIL },
	{ NULL,			-1 },
};

struct lookup dkimf_canon[] =
{
	{ "relaxed",		DKIM_CANON_RELAXED },
	{ "simple",		DKIM_CANON_SIMPLE },
	{ NULL,			-1 },
};

struct lookup dkimf_sign[] =
{
	{ "rsa-sha1",		DKIM_SIGN_RSASHA1 },
#ifdef SHA256_DIGEST_LENGTH
	{ "rsa-sha256",		DKIM_SIGN_RSASHA256 },
#endif /* SHA256_DIGEST_LENGTH */
	{ NULL,			-1 },
};

/* sender headers, in order */
char *senderhdr[] =
{
	"Resent-Sender",
	"Resent-From",
	"Sender",
	"From"
};
#define	NSENDERHDRS	4

/* default internal list */
char *defilist[] =
{
	"127.0.0.1",
	NULL
};

/* PROTOTYPES */
sfsistat mlfi_abort __P((SMFICTX *));
sfsistat mlfi_body __P((SMFICTX *, u_char *, size_t));
sfsistat mlfi_close __P((SMFICTX *));
sfsistat mlfi_connect __P((SMFICTX *, char *, _SOCK_ADDR *));
sfsistat mlfi_envfrom __P((SMFICTX *, char **));
sfsistat mlfi_envrcpt __P((SMFICTX *, char **));
sfsistat mlfi_eoh __P((SMFICTX *));
sfsistat mlfi_eom __P((SMFICTX *));
sfsistat mlfi_header __P((SMFICTX *, char *, char *));

static void dkimf_cleanup __P((SMFICTX *));
static Header dkimf_findheader __P((msgctx, char *, int));
void *dkimf_getpriv __P((SMFICTX *));
char * dkimf_getsymval __P((SMFICTX *, char *));
sfsistat dkimf_insheader __P((SMFICTX *, int, char *, char *));
static void dkimf_report __P((msgctx, char *, char *));
void dkimf_sendprogress __P((void *));
sfsistat dkimf_setpriv __P((SMFICTX *, void *));
sfsistat dkimf_setreply __P((SMFICTX *, char *, char *, char *));

/* GLOBALS */
bool addxhdr;					/* add identifying header? */
bool dolog;					/* syslog interesting stuff? */
bool dolog_success;				/* syslog successes too? */
bool quarantine;				/* quarantine failures? */
bool no_i_whine;				/* noted ${i} is undefined */
bool send_reports;				/* send failure reports */
bool use_ssp_deny;				/* use SSP "deny" directive? */
#if _FFR_REQUIRED_HEADERS
bool req_hdrs;					/* required header checks */
#endif /* _FFR_REQUIRED_HEADERS */
bool subdomains;				/* sign subdomains */
bool die;					/* global "die" flag */
bool remarall;					/* remove all matching ARs? */
bool remsigs;					/* remove current signatures? */
bool testmode;					/* test mode */
bool milterv2;					/* using milter v2? */
#ifdef QUERY_CACHE
bool querycache;				/* local query cache */
#endif /* QUERY_CACHE */
unsigned int tmo;				/* DNS timeout */
unsigned int mode;				/* operating mode */
int maxhdrsz;					/* max header bytes */
int diesig;					/* signal to distribute */
size_t keylen;					/* size of secret key */
size_t sigmin;					/* signature minimum */
int sigmintype;					/* signature minimum type */
int thread_count;				/* thread count */
#ifdef QUERY_CACHE
time_t cache_lastlog;				/* last cache stats logged */
#endif /* QUERY_CACHE */
dkim_canon_t hdrcanon;				/* canon. method for headers */
dkim_canon_t bodycanon;				/* canon. method for body */
dkim_alg_t signalg;				/* signing algorithm */
off_t signbytes;				/* bytes to sign */
char *sock;					/* listening socket */
char *progname;					/* program name */
dkim_sigkey_t seckey;				/* secret key data */
char *selector;					/* key selector */
#ifdef _FFR_ZTAGS
char *diagdir;					/* diagnostics directory */
#endif /* _FFR_ZTAGS */
#ifdef _FFR_SELECTOR_HEADER
char *selectorhdr;				/* selector header */
#endif /* _FFR_SELECTOR_HEADER */
#ifdef _FFR_VBR
char *vbr_defcert;				/* default VBR type */
char *vbr_deftype;				/* default VBR certifiers */
char **vbr_trusted;				/* trusted certifiers */
#endif /* _FFR_VBR */
struct Config conf;				/* configuration */
DKIM_LIB *libdkim;				/* libdkim handle */
#if VERIFY_DOMAINKEYS
DK_LIB *libdk;					/* libdk handle */
#endif /* VERIFY_DOMAINKEYS */
struct keytable *keyhead;			/* key list */
struct keytable *keytail;			/* key list */
#ifdef _FFR_REPLACE_RULES
struct replace *replist;			/* replacement list */
#endif /* _FFR_REPLACE_RULES */
Peer peerlist;					/* queue of "peers" */
Peer internal;					/* queue of "internal" hosts */
Peer exignore;					/* "external ignore" hosts */
char **domains;					/* domains to sign */
regex_t **dompats;				/* domain patterns */
char **mtas;					/* MTA ports to sign */
char **omithdrs;				/* headers to omit */
char **alwayshdrs;				/* always include headers */
char **macros;					/* macros/values to check */
char **values;					/* macros/values to check */
char **remar;					/* A-R header removal list */
#if POPAUTH
DB *popdb;					/* POP auth DB */
#endif /* POPAUTH */
pthread_mutex_t count_lock;			/* counter lock */
pthread_mutex_t popen_lock;			/* popen() lock */
#ifdef _FFR_STATS
char *statspath;				/* path for stats DB */
#endif /* _FFR_STATS */

/* Other useful definitions */
#define CRLF			"\r\n"		/* CRLF */

#ifndef SENDMAIL_OPTIONS
# define SENDMAIL_OPTIONS	""		/* options for reports */
#endif /* SENDMAIL_OPTIONS */

/* MACROS */
#define	DKIM_DEBUG(x)	(getenv("DKIMDEBUG") != NULL && \
			 strchr(getenv("DKIMDEBUG"), (x)) != NULL)
#define	JOBID(x)	((x) == NULL ? JOBIDUNKNOWN : (x))
#define	TRYFREE(x)	do { \
				if ((x) != NULL) \
				{ \
					free(x); \
					(x) = NULL; \
				} \
			} while (0)
#define	DKIMF_EOHMACROS	"i {daemon_name} {auth_type}"



/*
**  ==================================================================
**  BEGIN private section
*/

#if NO_SMFI_INSHEADER
/*
**  SMFI_INSHEADER -- stub for smfi_insheader() which didn't exist before
**                    sendmail 8.13.0
**
**  Parameters:
**  	ctx -- milter context
**  	idx -- insertion index
**  	hname -- header name
**  	hvalue -- header value
**
**  Return value:
**  	An sfsistat.
*/

sfsistat 
smfi_insheader(SMFICTX *ctx, int idx, char *hname, char *hvalue)
{
	assert(ctx != NULL);
	assert(hname != NULL);
	assert(hvalue != NULL);

	return smfi_addheader(ctx, hname, hvalue);
}
#endif /* NO_SMFI_INSHEADER */

/*
**  DKIMF_GETPRIV -- wrapper for smfi_getpriv()
**
**  Parameters:
**  	ctx -- milter (or test) context
**
**  Return value:
**  	The stored private pointer, or NULL.
*/

void *
dkimf_getpriv(SMFICTX *ctx)
{
	assert(ctx != NULL);

	if (testmode)
		return dkimf_test_getpriv((void *) ctx);
	else
		return smfi_getpriv(ctx);
}

/*
**  DKIMF_SETPRIV -- wrapper for smfi_setpriv()
**
**  Parameters:
**  	ctx -- milter (or test) context
**
**  Return value:
**  	An sfsistat.
*/

sfsistat
dkimf_setpriv(SMFICTX *ctx, void *ptr)
{
	assert(ctx != NULL);

	if (testmode)
		return dkimf_test_setpriv((void *) ctx, ptr);
	else
		return smfi_setpriv(ctx, ptr);
}

/*
**  DKIMF_INSHEADER -- wrapper for smfi_insheader()
**
**  Parameters:
**  	ctx -- milter (or test) context
**  	idx -- index at which to insert
**  	hname -- header name
**  	hvalue -- header value
**
**  Return value:
**  	An sfsistat.
*/

sfsistat
dkimf_insheader(SMFICTX *ctx, int idx, char *hname, char *hvalue)
{
	assert(ctx != NULL);
	assert(hname != NULL);
	assert(hvalue != NULL);

	if (testmode)
		return dkimf_test_insheader(ctx, idx, hname, hvalue);
	else
		return smfi_insheader(ctx, idx, hname, hvalue);
}

/*
**  DKIMF_SETREPLY -- wrapper for smfi_setreply()
**
**  Parameters:
**  	ctx -- milter (or test) context
**  	rcode -- SMTP reply code
**  	xcode -- SMTP enhanced status code
**  	replytxt -- reply text
**
**  Return value:
**  	An sfsistat.
*/

sfsistat
dkimf_setreply(SMFICTX *ctx, char *rcode, char *xcode, char *replytxt)
{
	assert(ctx != NULL);

	if (testmode)
		return dkimf_test_setreply(ctx, rcode, xcode, replytxt);
	else
		return smfi_setreply(ctx, rcode, xcode, replytxt);
}

/*
**  DKIMF_GETSYMVAL -- wrapper for smfi_getsymval()
**
**  Parameters:
**  	ctx -- milter (or test) context
**  	sym -- symbol to retrieve
**
**  Return value:
**  	Pointer to the value of the requested MTA symbol.
*/

char *
dkimf_getsymval(SMFICTX *ctx, char *sym)
{
	assert(ctx != NULL);
	assert(sym != NULL);

	if (testmode)
		return dkimf_test_getsymval(ctx, sym);
	else
		return smfi_getsymval(ctx, sym);
}

/*
**  DKIMF_GETDKIM -- retrieve DKIM handle in use
**
**  Parameters:
**  	vp -- opaque pointer (from test.c)
**
**  Return value:
**  	DKIM handle in use, or NULL.
*/

DKIM *
dkimf_getdkim(void *vp)
{
	struct connctx *cc;

	assert(vp != NULL);

	cc = vp;
	if (cc->cctx_msg != NULL)
		return cc->cctx_msg->mctx_dkim;
	else
		return NULL;
}

/*
**  DKIMF_SIGHANDLER -- signal handler
**
**  Parameters:
**  	sig -- signal received
**
**  Return value:
**  	None.
*/

static void
dkimf_sighandler(int sig)
{
	if (sig == SIGINT || sig == SIGTERM || sig == SIGHUP)
	{
		diesig = sig;
		die = TRUE;
	}
}

/*
**  DKIMF_KILLCHILD -- kill child process
**
**  Parameters:
**  	pid -- process ID to signal
**  	sig -- signal to use
**
**  Return value:
**  	None.
*/

static void
dkimf_killchild(pid_t pid, int sig)
{
	if (kill(pid, sig) == -1 && dolog)
	{
		syslog(LOG_ERR, "kill(%d, %d): %s", pid, sig,
		       strerror(errno));
	}
}

/*
**  DKIMF_ZAPKEY -- clobber the copy of the private key
**
**  Parameters:
**  	None.
**
**  Return value:
**  	None.
*/

static void
dkimf_zapkey(void)
{
	if (seckey != NULL)
	{
		memset(seckey, '\0', keylen);
		free(seckey);
	}

	if (keyhead != NULL)
	{
		struct keytable *key;
		struct keytable *next;

		key = keyhead;
		while (key != NULL)
		{
			next = key->key_next;

			memset(key->key_data, '\0', key->key_len);
			free(key->key_data);
			free(key->key_domain);
			free(key);

			key = next;
		}
	}
}

/*
**  DKIMF_STDIO -- set up the base descriptors to go nowhere
**
**  Parameters:
**  	None.
**
**  Return value:
**  	None.
*/

static void
dkimf_stdio(void)
{
	int devnull;

	/* this only fails silently, but that's OK */
	devnull = open(_PATH_DEVNULL, O_RDWR, 0);
	if (devnull != -1)
	{
		(void) dup2(devnull, 0);
		(void) dup2(devnull, 1);
		(void) dup2(devnull, 2);
		if (devnull > 2)
			(void) close(devnull);
	}

	(void) setsid();
}

/*
**  DKIMF_SENDPROGRESS -- tell the MTA "we're working on it!"
**
**  Parameters:
**  	ctx -- context
**
**  Return value:
**  	None (yet).
*/

void
dkimf_sendprogress(void *ctx)
{
	assert(ctx != NULL);

	(void) smfi_progress((SMFICTX *) ctx);
}

/*
**  DKIMF_INITCONTEXT -- initialize filter context
**
**  Parameters:
**  	None.
**
**  Return value:
**  	A pointer to an allocated and initialized filter context, or NULL
**  	on failure.
**
**  Side effects:
**  	Crop circles near Birmingham.
*/

static msgctx
dkimf_initcontext(void)
{
	msgctx ctx;

	ctx = (msgctx) malloc(sizeof(struct msgctx));
	if (ctx == NULL)
		return NULL;

	(void) memset(ctx, '\0', sizeof(struct msgctx));

	ctx->mctx_status = DKIMF_STATUS_UNKNOWN;
	ctx->mctx_hdrcanon = hdrcanon;
	ctx->mctx_bodycanon = bodycanon;
	ctx->mctx_signalg = DKIM_SIGN_DEFAULT;
	ctx->mctx_queryalg = DKIM_QUERY_DEFAULT;

	return ctx;
}

/*
**  DKIMF_LOG_SSL_ERRORS -- log any queued SSL library errors
**
**  Parameters:
**  	jobid -- job ID to include in log messages
**
**  Return value:
**  	None.
*/

static void
dkimf_log_ssl_errors(char *jobid)
{
	assert(jobid != NULL);

	/* log any queued SSL error messages */
	if (ERR_peek_error() != 0 && dolog)
	{
		int n;
		int saveerr;
		u_long e;
		char errbuf[BUFRSZ + 1];
		char tmp[BUFRSZ + 1];

		saveerr = errno;

		memset(errbuf, '\0', sizeof errbuf);
		for (n = 0; ; n++)
		{
			e = ERR_get_error();
			if (e == 0)
				break;

			memset(tmp, '\0', sizeof tmp);
			(void) ERR_error_string_n(e, tmp, sizeof tmp);
			if (n != 0)
				sm_strlcat(errbuf, "; ", sizeof errbuf);
			sm_strlcat(errbuf, tmp, sizeof errbuf);
		}

		syslog(LOG_INFO, "%s SSL %s", jobid, errbuf);

		errno = saveerr;
	}
}

/*
**  DKIMF_CLEANUP -- release local resources related to a message
**
**  Parameters:
**  	ctx -- milter context
**
**  Return value:
**  	None.
*/

static void
dkimf_cleanup(SMFICTX *ctx)
{
	msgctx dfc;
	connctx cc;

	assert(ctx != NULL);

	cc = (connctx) dkimf_getpriv(ctx);

	if (cc == NULL)
		return;

	dfc = cc->cctx_msg;

	/* release memory */
	if (dfc != NULL)
	{
		if (dfc->mctx_hqhead != NULL)
		{
			Header hdr;
			Header prev;

			hdr = dfc->mctx_hqhead;
			while (hdr != NULL)
			{
				TRYFREE(hdr->hdr_hdr);
				TRYFREE(hdr->hdr_val);
				prev = hdr;
				hdr = hdr->hdr_next;
				TRYFREE(prev);
			}
		}

		if (dfc->mctx_dkim != NULL)
			dkim_free(dfc->mctx_dkim);
#ifdef _FFR_VBR
		if (dfc->mctx_vbr != NULL)
			vbr_close(dfc->mctx_vbr);
#endif /* _FFR_VBR */

#if VERIFY_DOMAINKEYS
		if (dfc->mctx_dk != NULL)
			dk_free(dfc->mctx_dk);
#endif /* VERIFY_DOMAINKEYS */

		if (dfc->mctx_tmpstr != NULL)
			dkimf_dstring_free(dfc->mctx_tmpstr);

		free(dfc);
		cc->cctx_msg = NULL;
	}
}

/*
**  DKIMF_CONFIGLOOKUP -- look up the integer code for a config option or value
**
**  Parameters:
**  	opt -- option to look up
**  	table -- lookup table to use
**
**  Return value:
**  	Integer version of the option, or -1 on error.
*/

static int
dkimf_configlookup(char *opt, struct lookup *table)
{
	int c;

	for (c = 0; ; c++)
	{
		if (table[c].str == NULL ||
		    strcasecmp(opt, table[c].str) == 0)
			return table[c].code;
	}
}

/*
**  DKIMF_PARSECONFIG2 -- parse a single configuration value
**
**  Parameters:
**  	cfg -- pointer to data loaded from a configuration file
**  	name -- name of the parameter to query
**  	code -- equivalent code for the -C option
**  	str -- string to update
**  	len -- size of "str"
**
**  Return value:
**  	None.
**
**  Notes:
**  	This is transitional, for use during the time that the -C option
**  	and the configuration file coexist.  The -C option overrides
**  	the values found in the file.
*/

static void
dkimf_parseconfig2(struct config *cfg, const char *name, const char *code,
                   char *str, size_t len)
{
	char *v;
	size_t offset;

	assert(cfg != NULL);
	assert(name != NULL);
	assert(code != NULL);
	assert(str != NULL);
	assert(len > 0);

	if (config_get(cfg, name, &v, sizeof(char *)) == 1)
	{
		offset = strlen(str);

		snprintf(str + offset, len - offset, "%s%s=%s",
		         offset == 0 ? "" : ",", code, v);
	}
}

/*
**  DKIMF_PARSECONFIG -- parse configuration and/or apply defaults
**
**  Parameters:
** 	config -- configuration string, or NULL to apply defaults only
**
**  Return value:
**  	TRUE on success, FALSE on failure.
*/

static bool
dkimf_parseconfig(char *confstr)
{
	int vs;
	char *p;
	char *v;
	char *tmp;

	/* load defaults */
	memcpy(&conf, &defaults, sizeof(conf));

	if (confstr == NULL)
		return TRUE;

	tmp = strdup(confstr);
	if (tmp == NULL)
	{
		fprintf(stderr, "%s: strdup(): %s\n", progname,
		        strerror(errno));
		return FALSE;
	}

	/* process configuration */
	for (p = strtok(tmp, ","); p != NULL; p = strtok(NULL, ","))
	{
		v = strchr(p, '=');
		if (v == NULL)
		{
			fprintf(stderr,
			        "%s: syntax error in configuration string\n",
			        progname);
			return FALSE;
		}
		*v = '\0';
		v++;

		vs = dkimf_configlookup(v, dkimf_values);
		if (vs == -1)
		{
			fprintf(stderr,
			        "%s: invalid configuration value `%s'\n",
			        progname, v);
			return FALSE;
		}

		/* apply what's been found */
		switch (dkimf_configlookup(p, dkimf_params))
		{
		  case CFG_NOSIGNATURE:
			conf.cfg_nosig = vs;
			break;

		  case CFG_BADSIGNATURE:
			conf.cfg_badsig = vs;
			break;

		  case CFG_SIGMISSING:
			conf.cfg_sigmissing = vs;
			break;

		  case CFG_DNSERROR:
			conf.cfg_dnserr = vs;
			break;

		  case CFG_INTERNAL:
			conf.cfg_internal = vs;
			break;

		  case CFG_SECURITY:
			conf.cfg_security = vs;
			break;

		  case CFG_DEFAULT:
			conf.cfg_nosig = vs;
			conf.cfg_badsig = vs;
			conf.cfg_sigmissing = vs;
			conf.cfg_dnserr = vs;
			conf.cfg_internal = vs;
			conf.cfg_security = vs;
			break;

		  default:
			*v = '=';
			fprintf(stderr,
			        "%s: invalid configuration parameter `%s'\n",
			        progname, p);
			return FALSE;
		}
	}

	return TRUE;
}

/*
**  DKIMF_LIBSTATUS -- process a final status returned from libdkim
**
**  Parameters:
**  	ctx -- milter context
**  	where -- what function reported the error
**  	status -- status returned by a libdk call (DKIM_STAT_*)
**
**  Return value:
**   	An smfistat value to be returned to libmilter.
*/

static sfsistat
dkimf_libstatus(SMFICTX *ctx, char *where, int status)
{
	int retcode = SMFIS_CONTINUE;
	msgctx dfc;
	connctx cc;
	char *rcode = NULL;
	char *xcode = NULL;
	char *replytxt = NULL;

	assert(ctx != NULL);

	cc = dkimf_getpriv(ctx);
	assert(cc != NULL);
	dfc = cc->cctx_msg;
	assert(dfc != NULL);

	switch (status)
	{
	  case DKIM_STAT_OK:
		retcode = SMFIS_CONTINUE;
		break;

	  case DKIM_STAT_INTERNAL:
		retcode = conf.cfg_internal;
#ifdef _FFR_CAPTURE_UNKNOWN_ERRORS
		dfc->mctx_capture = TRUE;
#endif /* _FFR_CAPTURE_UNKNOWN_ERRORS */
		if (dolog)
		{
			const char *err;

			err = dkim_geterror(dfc->mctx_dkim);
			if (err == NULL)
				err = strerror(errno);

			syslog(LOG_ERR,
			       "%s: %s%sinternal error from libdkim: %s",
			       JOBID(dfc->mctx_jobid),
			       where == NULL ? "" : where,
			       where == NULL ? "" : ": ", err);
		}
		replytxt = "internal DKIM error";
		break;

	  case DKIM_STAT_BADSIG:
		retcode = conf.cfg_badsig;
		if (dolog)
		{
			syslog(LOG_ERR, "%s: bad signature data",
			       JOBID(dfc->mctx_jobid));
		}
		replytxt = "bad DKIM signature data";
		break;

	  case DKIM_STAT_NOSIG:
		retcode = conf.cfg_nosig;
		if (dolog)
		{
			syslog(LOG_ERR, "%s: no signature data",
			       JOBID(dfc->mctx_jobid));
		}
		replytxt = "no DKIM signature data";
		break;

	  case DKIM_STAT_NORESOURCE:
		retcode = conf.cfg_internal;
#ifdef _FFR_CAPTURE_UNKNOWN_ERRORS
		dfc->mctx_capture = TRUE;
#endif /* _FFR_CAPTURE_UNKNOWN_ERRORS */
		if (dolog)
		{
			const char *err;

			err = dkim_geterror(dfc->mctx_dkim);
			if (err == NULL)
				err = strerror(errno);

			syslog(LOG_ERR, "%s: %s%sresource unavailable: %s",
			       JOBID(dfc->mctx_jobid),
			       where == NULL ? "" : where,
			       where == NULL ? "" : ": ", err);
		}
		replytxt = "resource unavailable";
		break;

	  case DKIM_STAT_CANTVRFY:
		retcode = conf.cfg_badsig;
		if (dolog)
		{
			const char *err;

			err = dkim_geterror(dfc->mctx_dkim);
			if (err == NULL)
				err = "unknown cause";

			syslog(LOG_ERR, "%s: signature verification failed: %s",
			       JOBID(dfc->mctx_jobid), err);
		}
		replytxt = "DKIM signature verification failed";
		break;

	  case DKIM_STAT_KEYFAIL:
		retcode = conf.cfg_dnserr;
		if (dolog)
		{
			syslog(LOG_ERR, "%s: key retrieval failed",
			       JOBID(dfc->mctx_jobid));
		}
		replytxt = "DKIM key retrieval failed";
		break;

	  case DKIM_STAT_SYNTAX:
		retcode = conf.cfg_badsig;
		if (dolog)
		{
			const char *err;

			err = dkim_geterror(dfc->mctx_dkim);
			if (err == NULL)
				err = "unspecified";

			syslog(LOG_ERR, "%s: syntax error: %s",
			       JOBID(dfc->mctx_jobid), err);
		}
		replytxt = "DKIM signature syntax error";
		break;
	}

	switch (retcode)
	{
	  case SMFIS_REJECT:
		rcode = "550";
		xcode = "5.7.0";
		break;

	  case SMFIS_DISCARD:
		rcode = "451";
		xcode = "4.7.0";
		break;

	  default:
		break;
	}

	if (rcode != NULL && xcode != NULL && replytxt != NULL)
		(void) dkimf_setreply(ctx, rcode, xcode, replytxt);

	return retcode;
}

/*
**  DKIMF_FINDHEADER -- find a header
**
**  Parameters:
**  	dfc -- filter context
**  	hname -- name of the header of interest
**  	instance -- which instance is wanted (0 = first)
**
**  Return value:
**  	Header handle, or NULL if not found.
*/

static Header
dkimf_findheader(msgctx dfc, char *hname, int instance)
{
	Header hdr;

	assert(dfc != NULL);
	assert(hname != NULL);

	hdr = dfc->mctx_hqhead;

	while (hdr != NULL)
	{
		if (strcasecmp(hdr->hdr_hdr, hname) == 0)
		{
			if (instance == 0)
				return hdr;
			else
				instance--;
		}

		hdr = hdr->hdr_next;
	}

	return NULL;
}

/*
**  DKIMF_LOADKEYS -- load multiple keys
**
**  Parameters:
**  	file -- input file
**
**  Return value:
**  	0 on success, !0 on failure
**
**  Side effects:
**  	keyhead, keytail are updated.
*/

static int
dkimf_loadkeys(char *file)
{
	bool blank = TRUE;
	int line = 0;
	FILE *f;
	u_char *p;
	char *re;
	char *domain;
	char *path;
	char buf[BUFRSZ + 1];
	char root[MAXPATHLEN + 1];

	assert(file != NULL);

	f = fopen(file, "r");
	if (f == NULL)
	{
		fprintf(stderr, "%s: %s: fopen(): %s\n", progname,
		        file, strerror(errno));
		return -1;
	}

	memset(root, '\0', sizeof root);
	memset(buf, '\0', sizeof buf);

	while (fgets(buf, sizeof buf, f) != NULL)
	{
		/* skip comments */
		if (buf[0] == '#')
			continue;

		blank = TRUE;

		/* chomp trailing newline */
		for (p = buf; *p != '\0'; p++)
		{
			if (*p == '\n')
			{
				*p = '\0';
				break;
			}

			if (isascii(*p) && !isspace(*p))
				blank = FALSE;
		}

		/* skip blank lines */
		if (blank)
			continue;

		line++;

		if (buf[0] == '/')
		{
			sm_strlcpy(root, buf, sizeof root);
			if (root[strlen(root) - 1] != '/')
				sm_strlcat(root, "/", sizeof root);
		}

		re = NULL;
		domain = NULL;
		path = NULL;

		/*
		**  File format: <sender-glob>:<signing-domain>:<path-to-key>
		**
		**  This means if the sender matches <sender-glob>,
		**  sign with d=<signing-domain> and use the private
		**  key found in <path-to-key>, using the filename portion
		**  of the latter as the name of the selector.
		**
		**  <sender-glob> is string matching using "*" as a wildcard
		**  character.
		**
		**  #-delimited comments are allowed.  Blank lines are ignored.
		**
		**  A line starting with "/" is interpreted as a root directory
		**  for keys, meaning the <path-to-key> values after that
		**  in the file are taken relative to that path.
		*/

		re = strtok(buf, ":");
		domain = strtok(NULL, ":");
		if (domain != NULL)
			path = strtok(NULL, ":");

		if (re == NULL || domain == NULL || path == NULL)
		{
			fprintf(stderr, "%s: %s: line %d: malformed\n",
			        progname, file, line);
			fclose(f);
			return -1;
		}
		else
		{
			int status;
			char *r;
			char *end;
			size_t n;
			struct keytable *new;
			int keyfd;
			struct stat s;
			char retmp[BUFRSZ];
			char fn[MAXPATHLEN + 1];

			dkimf_mkpath(fn, sizeof fn, root, path);
			keyfd = open(fn, O_RDONLY, 0);

			if (keyfd == -1 && errno == ENOENT)
			{
				/* try appending ".pem" */
				sm_strlcat(fn, ".pem", sizeof fn);
				keyfd = open(fn, O_RDONLY, 0);
			}

			if (keyfd == -1 && errno == ENOENT)
			{
				/* try appending ".private" */
				dkimf_mkpath(fn, sizeof fn, root, path);
				sm_strlcat(fn, ".private", sizeof fn);
				keyfd = open(fn, O_RDONLY, 0);
			}

			if (keyfd == -1)
			{
				dkimf_mkpath(fn, sizeof fn, root, path);
				fprintf(stderr, "%s: %s: open(): %s\n",
				        progname, fn, strerror(errno));
				fclose(f);
				return -1;
			}

			status = fstat(keyfd, &s);
			if (status == -1)
			{
				fprintf(stderr, "%s: %s: fstat(): %s\n",
				        progname, path, strerror(errno));
				close(keyfd);
				fclose(f);
				return -1;
			}

			*p = '\0';
			end = retmp + sizeof retmp - 1;
			memset(retmp, '\0', sizeof retmp);
			retmp[0] = '^';
			r = &retmp[1];

			for (p = re; *p != '\0'; p++)
			{
				switch (*p)
				{
				  case '*':
					if (r + 3 >= end)
					{
						fprintf(stderr,
						        "%s: %s: line %d: \"%s\": expression too large\n",
						        progname, file, line,
						        re);
						close(keyfd);
						fclose(f);
						return -1;
					}

					(void) sm_strlcat(retmp, ".*",
					                  sizeof retmp);
					r += 2;
					break;

				  case '.':
				  case '$':
				  case '[':
				  case ']':
				  case '(':
				  case ')':
					if (r + 3 >= end)
					{
						fprintf(stderr,
						        "%s: %s: line %d: \"%s\": expression too large\n",
						        progname, file, line,
						        re);
						close(keyfd);
						fclose(f);
						return -1;
					}

					*r = '\\';
					r++;
					/* FALLTHROUGH */

				  default:
					*r = *p;
					r++;
					break;
				}
			}

			(void) sm_strlcat(retmp, "$", sizeof retmp);

			new = (struct keytable *) malloc(sizeof(struct keytable));
			if (new == NULL)
			{
				fprintf(stderr, "%s: malloc(): %s\n",
				        progname, strerror(errno));
				close(keyfd);
				fclose(f);
				return -1;
			}

			new->key_next = NULL;
			r = strrchr(path, '/');
			new->key_selector = strdup(r == NULL ? path : r + 1);
			if (new->key_selector == NULL)
			{
				fprintf(stderr, "%s: strdup(): %s\n",
				        progname, strerror(errno));
				close(keyfd);
				fclose(f);
				return -1;
			}

			if (domain[0] != '*')
				new->key_domain = strdup(domain);
			else
				new->key_domain = NULL;

			status = regcomp(&new->key_re, retmp,
			                 (REG_ICASE|REG_EXTENDED));
			if (status != 0)
			{
				char err[BUFRSZ];

				memset(err, '\0', sizeof err);
				(void) regerror(status, &new->key_re,
				                err, sizeof err);
				fprintf(stderr,
				        "%s: %s: line %d: regcomp(): %s\n",
				        progname, file, line, err);
				close(keyfd);
				fclose(f);
				return -1;
			}

			new->key_data = malloc(s.st_size);
			if (new->key_data == NULL)
			{
				fprintf(stderr, "%s: malloc(): %s\n",
				        progname, strerror(errno));
				close(keyfd);
				fclose(f);
				return -1;
			}
			new->key_len = s.st_size;

			n = read(keyfd, new->key_data, s.st_size);
			if (n < s.st_size)
			{
				if (n == -1)
				{
					fprintf(stderr, "%s: %s: read(): %s\n",
					        progname, path,
					        strerror(errno));
				}
				else
				{
					fprintf(stderr,
					        "%s: %s: read() truncated\n",
					        progname, path);
				}
				close(keyfd);
				fclose(f);
				return -1;
			}

			close(keyfd);

			if (keyhead == NULL)
			{
				keyhead = new;
				keytail = new;
			}
			else
			{
				keytail->key_next = new;
				keytail = new;
			}
		}
	}

	fclose(f);

	return 0;
}

/*
**  DKIMF_REPORT -- generate a report on failure if possible
**
**  Parameters:
**   	dfc -- message context
**  	result -- result to report
**  	reason -- reason to report
**
**  Return value:
**  	None.
*/

static void
dkimf_report(msgctx dfc, char *result, char *reason)
{
	int bfd = -1;
	int hfd = -1;
	int status;
	DKIM_STAT dkstatus;
	size_t inl;
	size_t outl;
	FILE *out;
	BIO *b64;
	BIO *bout;
	DKIM_SIGINFO *sig;
	struct Header *hdr;
	char buf[BUFRSZ];
	char addr[MAXADDRESS + 1];
	char hostname[DKIM_MAXHOSTNAMELEN + 1];

	assert(dfc != NULL);

	if (!send_reports)
		return;

	memset(addr, '\0', sizeof addr);
	memset(hostname, '\0', sizeof hostname);

	(void) gethostname(hostname, sizeof hostname);

	sig = dkim_getsignature(dfc->mctx_dkim);

	dkstatus = dkim_reportinfo(dfc->mctx_dkim, sig, &hfd, &bfd,
	                           addr, sizeof addr);
	if (dkstatus != DKIM_STAT_OK || addr[0] == '\0')
		return;

	pthread_mutex_lock(&popen_lock);
	out = popen(_PATH_SENDMAIL " -t" SENDMAIL_OPTIONS, "w");
	pthread_mutex_unlock(&popen_lock);

	if (out == NULL)
	{
		if (dolog)
		{
			syslog(LOG_ERR, "%s: popen(): %s", dfc->mctx_jobid,
			       strerror(errno));
		}

		return;
	}

	/* we presume sendmail will add From: and Date: ... */

	/* To: */
	fprintf(out, "To: %s\n", addr);

	/* Subject: */
	fprintf(out, "Subject: DKIM failure report for %s\n",
	        dfc->mctx_jobid);

	/* MIME stuff */
	fprintf(out, "MIME-Version: 1.0\n");
	fprintf(out,
	        "Content-Type: multipart/report; boundary=\"dkimreport/%s/%s\"",
	        hostname, dfc->mctx_jobid);

	/* ok, now then... */
	fprintf(out, "\n");

	/* first part: a text blob explaining what this is */
	fprintf(out, "--dkimreport/%s/%s\n", hostname, dfc->mctx_jobid);
	fprintf(out, "Content-Type: text/plain\n");
	fprintf(out, "\n");
	fprintf(out, "DKIM failure report for job %s on %s\n\n",
	        dfc->mctx_jobid, hostname);
	fprintf(out,
	        "The canonicalized form of the failed message is attached.\n");
	fprintf(out, "\n");

	/* second part: formatted gunk */
	fprintf(out, "--dkimreport/%s/%s\n", hostname, dfc->mctx_jobid);
	fprintf(out, "Content-Type: message/x-dkim-report\n");
	fprintf(out, "\n");
	fprintf(out, "MTA: %s\n", hostname);
	fprintf(out, "Agent: %s %s\n", DKIMF_PRODUCT, DKIMF_VERSION);
	fprintf(out, "Result: %s\n", result == NULL ? "(none)" : result);
	fprintf(out, "Reason: %s\n", reason == NULL ? "(none)" : reason);
	fprintf(out, "\n");

	/* third part: a multipart containing the interesting stuff */
	fprintf(out, "--dkimreport/%s/%s\n", hostname, dfc->mctx_jobid);
	fprintf(out, "Content-Type: multipart/mixed; boundary=\"dkimreport/%s/%s-parts\"\n",
	        hostname, dfc->mctx_jobid);
	fprintf(out, "\n");

	/* first subpart: headers */
	fprintf(out, "--dkimreport/%s/%s-parts\n", hostname, dfc->mctx_jobid);
	fprintf(out, "Content-Type: text/rfc822-headers\n");
	fprintf(out, "\n");

	for (hdr = dfc->mctx_hqhead; hdr != NULL; hdr = hdr->hdr_next)
		fprintf(out, "%s: %s\n", hdr->hdr_hdr, hdr->hdr_val);

	fprintf(out, "\n");

	/* second subpart: canonicalized form */
	fprintf(out, "--dkimreport/%s/%s-parts\n", hostname, dfc->mctx_jobid);
	fprintf(out, "Content-Type: text/plain\n");		/* ? */
	fprintf(out, "Content-Disposition: attachment; filename=\"%s.txt\"\n",
	        dfc->mctx_jobid);
	if (bfd != -1)
		fprintf(out, "Content-Description: Canonicalized headers\n");
	else
		fprintf(out, "Content-Description: Canonicalized message\n");
	fprintf(out, "Content-Transfer-Encoding: base64\n");
	fprintf(out, "\n");

	(void) lseek(hfd, SEEK_SET, 0);

	bout = BIO_new(BIO_s_file());
	BIO_set_fp(bout, out, BIO_NOCLOSE);

	b64 = BIO_new(BIO_f_base64());
	bout = BIO_push(b64, bout);

	for (;;)
	{
		inl = read(hfd, buf, sizeof buf);
		if (inl == 0)
			break;
		outl = BIO_write(bout, (char *) buf, inl);
		if (outl != inl || inl < sizeof buf)
			break;
	}

	BIO_flush(bout);
	BIO_free(bout);

	/* third subpart: canonicalized form (body) */
	if (bfd != -1)
	{
		fprintf(out,
		        "\n--dkimreport/%s/%s-parts\n", hostname,
		        dfc->mctx_jobid);
		fprintf(out, "Content-Type: text/plain\n");		/* ? */
		fprintf(out,
		        "Content-Disposition: attachment; filename=\"%s-body.txt\"\n",
		        dfc->mctx_jobid);
		fprintf(out, "Content-Description: Canonicalized body\n");
		fprintf(out, "Content-Transfer-Encoding: base64\n");
		fprintf(out, "\n");

		(void) lseek(bfd, SEEK_SET, 0);

		bout = BIO_new(BIO_s_file());
		BIO_set_fp(bout, out, BIO_NOCLOSE);

		b64 = BIO_new(BIO_f_base64());
		bout = BIO_push(b64, bout);

		for (;;)
		{
			inl = read(bfd, buf, sizeof buf);
			if (inl == 0)
				break;
			outl = BIO_write(bout, (char *) buf, inl);
			if (outl != inl || inl < sizeof buf)
				break;
		}

		BIO_flush(bout);
		BIO_free(bout);
	}

	/* end */
	fprintf(out, "\n--dkimreport/%s/%s-parts--\n", hostname,
	        dfc->mctx_jobid);
	fprintf(out, "\n--dkimreport/%s/%s--\n", hostname, dfc->mctx_jobid);

	/* send it */
	pthread_mutex_lock(&popen_lock);
	status = pclose(out);
	pthread_mutex_unlock(&popen_lock);

	if (status != 0 && dolog)
	{
		if (dolog)
		{
			syslog(LOG_ERR, "%s: pclose(): %s", dfc->mctx_jobid,
			       strerror(errno));
		}
	}
}

/*
**  END private section
**  ==================================================================
**  BEGIN milter section
*/

#if SMFI_VERSION >= 0x01000000
/*
**  MLFI_NEGOTIATE -- handler called on new SMTP connection to negotiate
**                    MTA options
**
**  Parameters:
**  	ctx -- milter context
**	f0  -- actions offered by the MTA
**	f1  -- protocol steps offered by the MTA
**	f2  -- reserved for future extensions
**	f3  -- reserved for future extensions
**	pf0 -- actions requested by the milter
**	pf1 -- protocol steps requested by the milter
**	pf2 -- reserved for future extensions
**	pf3 -- reserved for future extensions
**
**  Return value:
**  	An SMFIS_* constant.
*/

static sfsistat
mlfi_negotiate(SMFICTX *ctx,
	unsigned long f0, unsigned long f1,
	SM_UNUSED(unsigned long f2),
	SM_UNUSED(unsigned long f3),
	unsigned long *pf0, unsigned long *pf1,
	unsigned long *pf2, unsigned long *pf3)
{
	unsigned long reqactions = (SMFIF_ADDHDRS|SMFIF_CHGHDRS);
	unsigned long wantactions = (SMFIF_SETSYMLIST);
	unsigned long protosteps = (SMFIP_NOHELO|SMFIP_NORCPT|SMFIP_NOUNKNOWN|SMFIP_NODATA|SMFIP_SKIP);
	connctx cc;

	/* verify the actions we need are available */
	if (quarantine)
		reqactions |= SMFIF_QUARANTINE;
# ifdef _FFR_CAPTURE_UNKNOWN_ERRORS
	reqactions |= SMFIF_QUARANTINE;
# endif /* _FFR_CAPTURE_UNKNOWN_ERRORS */
	if ((f0 & reqactions) != reqactions)
	{
		if (dolog)
		{
			syslog(LOG_ERR,
			       "mlfi_negotiate(): required milter action(s) not available (got 0x%lx, need 0x%lx)",
			       f0, reqactions);
		}

		return SMFIS_REJECT;
	}

	/* also try to get some nice features */
	wantactions = (wantactions & f0);

	/* set the actions we want */
	*pf0 = (reqactions | wantactions);

	/* disable as many protocol steps we don't need as are available */
	*pf1 = (protosteps & f1);

# ifdef SMFIP_HDR_LEADSPC
	/* request preservation of leading spaces if possible */
	if ((f1 & SMFIP_HDR_LEADSPC) != 0)
	{
		cc = malloc(sizeof(struct connctx));
		if (cc != NULL)
		{
			memset(cc, '\0', sizeof(struct connctx));
			cc->cctx_noleadspc = TRUE;
			*pf1 |= SMFIP_HDR_LEADSPC;

			dkimf_setpriv(ctx, cc);
		}
	}
# endif /* SMFIP_HDR_LEADSPC */

	*pf2 = 0;
	*pf3 = 0;

	/* request macros if able */
	if (macros != NULL && (wantactions & SMFIF_SETSYMLIST) != 0)
	{
		int c;
		char macrolist[BUFRSZ];

		memset(macrolist, '\0', sizeof macrolist);

		sm_strlcpy(macrolist, DKIMF_EOHMACROS, sizeof macrolist);

		for (c = 0; macros[c] != NULL; c++)
		{
			if (macrolist[0] != '\0')
				sm_strlcat(macrolist, " ", sizeof macrolist);

			if (sm_strlcat(macrolist, macros[c],
			               sizeof macrolist) >= sizeof macrolist)
			{
				if (dolog)
				{
					syslog(LOG_ERR,
					       "mlfi_negotiate(): macro list overflow");
				}

				return SMFIS_REJECT;
			}

			if (smfi_setsymlist(ctx, SMFIM_EOH,
			                    macrolist) != MI_SUCCESS)
			{
				if (dolog)
				{
					syslog(LOG_ERR,
					       "smfi_setsymlist() failed");
				}

				return SMFIS_REJECT;
			}
		}
	}

	/* set "milterv2" flag if SMFIP_SKIP was available */
	if ((f1 & SMFIP_SKIP) != 0)
		milterv2 = TRUE;

	return SMFIS_CONTINUE;
}
#endif /* SMFI_VERSION >= 0x01000000 */

/*
**  MLFI_CONNECT -- connection handler
**
**  Parameters:
**  	ctx -- milter context
**  	host -- hostname
**  	ip -- address, in in_addr form
**
**  Return value:
**  	An SMFIS_* constant.
*/

sfsistat
mlfi_connect(SMFICTX *ctx, char *host, _SOCK_ADDR *ip)
{
	connctx cc;

	if (DKIM_DEBUG('t'))
	{
		pthread_mutex_lock(&count_lock);
		thread_count++;
		syslog(LOG_INFO, "thread %p connect (%d)", pthread_self(),
		       thread_count);
		pthread_mutex_unlock(&count_lock);
	}

	/* if the client is on an ignored host, then ignore it */
	if (peerlist != NULL)
	{
		/* try hostname, if available */
		if (host != NULL && host[0] != '\0' && host[0] != '[')
		{
			dkimf_lowercase(host);
			if (dkimf_checkhost(peerlist, host))
				return SMFIS_ACCEPT;
		}

		/* try IP address, if available */
		if (ip != NULL && ip->sa_family == AF_INET)
		{
			if (dkimf_checkip(peerlist, ip))
				return SMFIS_ACCEPT;
		}
	}

	/* copy hostname and IP information to a connection context */
	cc = dkimf_getpriv(ctx);
	if (cc == NULL)
	{
		cc = malloc(sizeof(struct connctx));
		if (cc == NULL)
		{
			if (dolog)
			{
				syslog(LOG_ERR, "%s malloc(): %s", host,
				       strerror(errno));
			}

			return SMFIS_TEMPFAIL;
		}

		memset(cc, '\0', sizeof(struct connctx));
	}

	sm_strlcpy(cc->cctx_host, host, sizeof cc->cctx_host);

	if (ip == NULL)
	{
		struct sockaddr_in sin;

		memset(&sin, '\0', sizeof sin);
		sin.sin_family = AF_INET;
		sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

		memcpy(&cc->cctx_ip, &sin, sizeof(cc->cctx_ip));
	}
	else
	{
		memcpy(&cc->cctx_ip, ip, sizeof(cc->cctx_ip));
	}

	cc->cctx_msg = NULL;

	dkimf_setpriv(ctx, cc);

	return SMFIS_CONTINUE;
}

/*
**  MLFI_ENVFROM -- handler for MAIL FROM command (start of message)
**
**  Parameters:
**  	ctx -- milter context
**  	envfrom -- envelope from arguments
**
**  Return value:
**  	An SMFIS_* constant.
*/

sfsistat
mlfi_envfrom(SMFICTX *ctx, char **envfrom)
{
	connctx cc;
	msgctx dfc;

	assert(ctx != NULL);
	assert(envfrom != NULL);

	cc = (connctx) dkimf_getpriv(ctx);
	assert(cc != NULL);

	if (DKIM_DEBUG('t'))
		syslog(LOG_INFO, "thread %p envfrom", pthread_self());

	/*
	**  Initialize a filter context.
	*/

	dkimf_cleanup(ctx);
	dfc = dkimf_initcontext();
	if (dfc == NULL)
	{
		if (dolog)
		{
			syslog(LOG_INFO,
			       "message requeueing (internal error)");
		}

		dkimf_cleanup(ctx);
		return SMFIS_TEMPFAIL;
	}

	/*
	**  Save it in this thread's private space.
	*/

	cc->cctx_msg = dfc;

	/*
	**  Continue processing.
	*/

	return SMFIS_CONTINUE;
}

/*
**  MLFI_HEADER -- handler for mail headers; stores the header in a vector
**                 of headers for later perusal, removing RFC822 comment
**                 substrings
**
**  Parameters:
**  	ctx -- milter context
**  	headerf -- header
**  	headerv -- value
**
**  Return value:
**  	An SMFIS_* constant.
*/

sfsistat
mlfi_header(SMFICTX *ctx, char *headerf, char *headerv)
{
	msgctx dfc;
	connctx cc;
	Header newhdr;
#ifdef _FFR_ANTICIPATE_SENDMAIL_MUNGE
	char *end;
	char *p;
#endif /* _FFR_ANTICIPATE_SENDMAIL_MUNGE */

	assert(ctx != NULL);
	assert(headerf != NULL);
	assert(headerv != NULL);

	if (DKIM_DEBUG('t'))
		syslog(LOG_INFO, "thread %p header", pthread_self());

	cc = (connctx) dkimf_getpriv(ctx);
	assert(cc != NULL);
	dfc = cc->cctx_msg;
	assert(dfc != NULL);

	/* check for too much header data */
	if (maxhdrsz > 0 &&
	    dfc->mctx_hdrbytes + strlen(headerf) + strlen(headerv) + 2 > maxhdrsz)
	{
		if (dolog)
			syslog(LOG_NOTICE, "too much header data");

		return conf.cfg_security;
	}

	newhdr = (Header) malloc(sizeof(struct Header));
	if (newhdr == NULL)
	{
		if (dolog)
			syslog(LOG_ERR, "malloc(): %s", strerror(errno));

		dkimf_cleanup(ctx);
		return SMFIS_TEMPFAIL;
	}

	(void) memset(newhdr, '\0', sizeof(struct Header));

	newhdr->hdr_hdr = strdup(headerf);

	if (dfc->mctx_tmpstr == NULL)
	{
		dfc->mctx_tmpstr = dkimf_dstring_new(BUFRSZ, 0);
		if (dfc->mctx_tmpstr == NULL)
		{
			if (dolog)
				syslog(LOG_ERR, "dkimf_dstring_new() failed");

			return SMFIS_TEMPFAIL;
		}
	}
	else
	{
		dkimf_dstring_blank(dfc->mctx_tmpstr);
	}

#ifdef _FFR_ANTICIPATE_SENDMAIL_MUNGE
	/*
	**  The sendmail MTA does some minor header rewriting on outgoing
	**  mail.  This makes things slightly prettier for the MUA, but
	**  these changes are made after this filter has already generated
	**  and added a signature.  As a result, verification of the
	**  signature will fail because what got signed isn't the same
	**  as what actually goes out.  This chunk of code attempts to
	**  compensate by arranging to feed to the canonicalization
	**  algorithms the headers exactly as the MTA will modify them, so
	**  verification should still work.  This is based on experimentation
	**  and on reading sendmail/headers.c, and may require more tweaking
	**  before it's precisely right.
	**  
	**  There are other munges the sendmail MTA makes which are not
	**  addressed by this patch.
	**
	**  This should not be used with sendmail 8.14 and later as it
	**  is not required; that version of sendmail and libmilter
	**  handles the munging correctly (by suppressing it).
	*/

	for (p = headerv; *p != '\0'; p++)
	{
		/* skip initial spaces */
		if (p == headerv && isascii(*p) && isspace(*p))
			continue;

		dkimf_dstring_cat1(dfc->mctx_tmpstr, *p);
	}

#else /* _FFR_ANTICIPATE_SENDMAIL_MUNGE */

	dkimf_dstring_copy(dfc->mctx_tmpstr, headerv);

#endif /* _FFR_ANTICIPATE_SENDMAIL_MUNGE */

#ifdef _FFR_REPLACE_RULES
	if (replist != NULL)		/* XXX -- signing mode only? */
	{
		int status;
		regmatch_t match;
		char *str;
		struct dkimf_dstring *tmphdr = NULL;
		struct replace *rep;

		tmphdr = dkimf_dstring_new(BUFRSZ, 0);
		if (tmphdr == NULL)
		{
			if (dolog)
				syslog(LOG_ERR, "dkimf_dstring_new() failed");

			return SMFIS_TEMPFAIL;
		}
	
		for (rep = replist; rep != NULL; rep = rep->repl_next)
		{
			str = dkimf_dstring_get(dfc->mctx_tmpstr);

			for (;;)
			{
				status = regexec(&rep->repl_re, str, 1,
				                 &match, 0);

				if (status == REG_NOMATCH)
				{
					break;
				}
				else if (status != 0)
				{
					if (dolog)
					{
						syslog(LOG_ERR,
						       "regexec() failed");
					}

					return SMFIS_TEMPFAIL;
				}

				dkimf_dstring_blank(tmphdr);

				dkimf_dstring_copy(tmphdr, str);
				dkimf_dstring_chop(tmphdr, match.rm_so);
				dkimf_dstring_cat(tmphdr, rep->repl_txt);
				dkimf_dstring_cat(tmphdr, str + match.rm_eo);

				dkimf_dstring_blank(dfc->mctx_tmpstr);
				str = dkimf_dstring_get(tmphdr);
				dkimf_dstring_cat(dfc->mctx_tmpstr, str);
			}
		}

		dkimf_dstring_free(tmphdr);
	}
#endif /* _FFR_REPLACE_RULES */

	newhdr->hdr_val = strdup(dkimf_dstring_get(dfc->mctx_tmpstr));

	newhdr->hdr_next = NULL;

	if (newhdr->hdr_hdr == NULL || newhdr->hdr_val == NULL)
	{
		if (dolog)
			syslog(LOG_ERR, "malloc(): %s", strerror(errno));

		TRYFREE(newhdr->hdr_hdr);
		TRYFREE(newhdr->hdr_val);
		TRYFREE(newhdr);
		dkimf_cleanup(ctx);
		return SMFIS_TEMPFAIL;
	}

	dfc->mctx_hdrbytes += strlen(newhdr->hdr_hdr) + 1;
	dfc->mctx_hdrbytes += strlen(newhdr->hdr_val) + 1;

	if (dfc->mctx_hqhead == NULL)
		dfc->mctx_hqhead = newhdr;

	if (dfc->mctx_hqtail != NULL)
		dfc->mctx_hqtail->hdr_next = newhdr;

	dfc->mctx_hqtail = newhdr;

#if _FFR_SELECT_CANONICALIZATION
	if (strcasecmp(headerf, XSELECTCANONHDR) == 0)
	{
		int c;
		char *slash;

		slash = strchr(headerv, '/');
		if (slash != NULL)
		{
			*slash = '\0';

			c = dkimf_configlookup(headerv, dkimf_canon);
			if (c != -1)
				dfc->mctx_hdrcanon = (dkim_canon_t) c;
			c = dkimf_configlookup(slash + 1, dkimf_canon);
			if (c != -1)
				dfc->mctx_bodycanon = (dkim_canon_t) c;

			*slash = '/';
		}
		else
		{
			c = dkimf_configlookup(headerv, dkimf_canon);
			if (c != -1)
				dfc->mctx_hdrcanon = (dkim_canon_t) c;
		}

		/* XXX -- eat this header? */
	}
#endif /* _FFR_SELECT_CANONICALIZATION */

#if VERIFY_DOMAINKEYS
	if (strcasecmp(headerf, DK_SIGNHEADER) == 0)
		dfc->mctx_dksigned = TRUE;
#endif /* VERIFY_DOMAINKEYS */

	return SMFIS_CONTINUE;
}

/*
**  MLFI_EOH -- handler called when there are no more headers
**
**  Parameters:
**  	ctx -- milter context
**
**  Return value:
**  	An SMFIS_* constant.
*/

sfsistat
mlfi_eoh(SMFICTX *ctx)
{
	char last;
	bool domainok;
	bool originok;
	int c;
	DKIM_STAT status;
	connctx cc;
	msgctx dfc;
	char *p;
	char *user;
#ifdef _FFR_VBR
	char *vbr_cert = NULL;
	char *vbr_type = NULL;
#endif /* _FFR_VBR */
	Header from;
	Header hdr;
	char addr[MAXADDRESS + 1];

	assert(ctx != NULL);

	if (DKIM_DEBUG('t'))
		syslog(LOG_INFO, "thread %p eoh", pthread_self());

	cc = (connctx) dkimf_getpriv(ctx);
	assert(cc != NULL);
	dfc = cc->cctx_msg;
	assert(dfc != NULL);

	/*
	**  Determine the message ID for logging.
	*/

	dfc->mctx_jobid = dkimf_getsymval(ctx, "i");
	if (dfc->mctx_jobid == NULL)
		dfc->mctx_jobid = JOBIDUNKNOWN;

#if _FFR_REQUIRED_HEADERS
	/* if requested, verify RFC2822-required headers */
	if (req_hdrs)
	{
		bool ok = TRUE;

		/* exactly one From: */
		if (dkimf_findheader(dfc, "From", 0) == NULL ||
		    dkimf_findheader(dfc, "From", 1) != NULL)
			ok = FALSE;

		/* exactly one Date: */
		if (dkimf_findheader(dfc, "Date", 0) == NULL ||
		    dkimf_findheader(dfc, "Date", 1) != NULL)
			ok = FALSE;

		if (!ok)
		{
			if (dolog)
			{
				syslog(LOG_INFO,
				       "%s RFC2822-required header(s) missing",
				       dfc->mctx_jobid);
			}

			dfc->mctx_addheader = TRUE;
			dfc->mctx_headeronly = TRUE;
			dfc->mctx_status = DKIMF_STATUS_BADFORMAT;
			return SMFIS_CONTINUE;
		}
	}
#endif /* _FFR_REQUIRED_HEADERS */

	/* find the Sender: or From: header */
	memset(addr, '\0', sizeof addr);

	for (c = 0; c < NSENDERHDRS; c++)
	{
		from = dkimf_findheader(dfc, senderhdr[c], 0);
		if (from != NULL)
			break;
	}

	if (from == NULL)
	{
		if (dolog)
		{
			syslog(LOG_INFO,
			       "%s: no sender header found; accepting",
			       dfc->mctx_jobid);
		}

		dfc->mctx_addheader = TRUE;
		dfc->mctx_headeronly = TRUE;
		dfc->mctx_status = DKIMF_STATUS_BADFORMAT;
		return SMFIS_CONTINUE;
	}

	/* extract the sender's domain */
	sm_strlcpy(addr, from->hdr_val, sizeof addr);
	status = rfc2822_mailbox_split(addr, &user, &dfc->mctx_domain);
	if (status != 0 || user == NULL || dfc->mctx_domain == NULL ||
	    user[0] == '\0' || dfc->mctx_domain[0] == '\0')
	{
		if (dolog)
		{
			syslog(LOG_INFO, "%s: can't parse From: header",
			       dfc->mctx_jobid);
		}

		dfc->mctx_addheader = TRUE;
		dfc->mctx_headeronly = TRUE;
		dfc->mctx_status = DKIMF_STATUS_BADFORMAT;
		return SMFIS_CONTINUE;
	}

	/* assume we're not signing */
	dfc->mctx_signalg = DKIM_SIGN_UNKNOWN;
	dfc->mctx_signing = FALSE;
	domainok = FALSE;
	originok = FALSE;

	/* is it a domain we sign for? */
	if (domains != NULL && dfc->mctx_domain != NULL)
	{
		int n;

		if (dompats != NULL)
		{
			for (n = 0; dompats[n] != NULL; n++)
			{
				status = regexec(dompats[n],
				                 dfc->mctx_domain,
				                 0, NULL, 0);
				if (status == 0)
				{
					domainok = TRUE;
					break;
				}
			}
		}
		else
		{
			for (n = 0; domains[n] != NULL; n++)
			{
				if (strcasecmp(dfc->mctx_domain,
				               domains[n]) == 0)
				{
					dfc->mctx_domain = domains[n];
					domainok = TRUE;
					break;
				}
			}
		}

		if (subdomains)
		{
			for (p = strchr(dfc->mctx_domain, '.');
			     p != NULL && !domainok;
			     p = strchr(p, '.'))
			{
				p++;
				if (*p == '\0')
					break;

				if (dompats != NULL)
				{
					for (n = 0; dompats[n] != NULL; n++)
					{
						status = regexec(dompats[n],
						                 dfc->mctx_domain,
						                 0, NULL, 0);
						if (status == 0)
						{
							domainok = TRUE;
							break;
						}
					}
				}
				else
				{
					for (n = 0; domains[n] != NULL; n++)
					{
						if (strcasecmp(p,
						               domains[n]) == 0)
						{
							dfc->mctx_domain = domains[n];
							domainok = TRUE;
							break;
						}
					}
				}
			}
		}
	}

#ifdef _FFR_SELECTOR_HEADER
	/* was there a header naming the selector to use? */
	if (domainok && selectorhdr != NULL)
	{
		/* find the header */
		hdr = dkimf_findheader(dfc, selectorhdr, 0);

		/* did it match a selector in the keylist? */
		if (hdr != NULL)
		{
			struct keytable *curkey;

			for (curkey = keyhead;
			     curkey != NULL;
			     curkey = curkey->key_next)
			{
				if (strcasecmp(curkey->key_selector,
				               hdr->hdr_val) == 0)
				{
					dfc->mctx_key = curkey;
					break;
				}
			}
		}
	}
#endif /* _FFR_SELECTOR_HEADER */

	/* still no key selected; check the keylist (if any) */
	if (dfc->mctx_key == NULL && keyhead != NULL)
	{
		struct keytable *curkey;
		char srchaddr[MAXADDRESS + 1];

		snprintf(srchaddr, sizeof srchaddr, "%s@%s",
		         user, dfc->mctx_domain);

		/* select the key */
		for (curkey = keyhead;
		     curkey != NULL;
		     curkey = curkey->key_next)
		{
			status = regexec(&curkey->key_re, srchaddr,
			                 0, NULL, 0);
			if (status == 0)
				break;
			if (status != REG_NOMATCH)
			{
				if (dolog)
				{
					char err[BUFRSZ];

					(void) regerror(status,
					                &curkey->key_re,
					                err, sizeof err);
					syslog(LOG_ERR,
					       "%s regexec(): %s",
					       dfc->mctx_jobid, err);
				}

				dkimf_cleanup(ctx);
				return SMFIS_TEMPFAIL;
			}
		}

		if (curkey != NULL)
		{
			dfc->mctx_key = curkey;
			domainok = TRUE;
		}
	}

	/* see if it came in on an authorized MSA/MTA connection */
	if (mtas != NULL)
	{
		int n;
		char *mtaname;

		mtaname = dkimf_getsymval(ctx, "{daemon_name}");

		if (mtaname != NULL)
		{
			for (n = 0; mtas[n] != NULL; n++)
			{
				if (strcasecmp(mtaname, mtas[n]) == 0)
				{
					originok = TRUE;
					break;
				}
			}
		}
	}

	/* see if macro tests passed */
	if (macros != NULL)
	{
		bool done = FALSE;
		int n;
		char *val;
		char *vals;
		char *last;
		char name[BUFRSZ + 1];

		if (dfc->mctx_tmpstr == NULL)
		{
			dfc->mctx_tmpstr = dkimf_dstring_new(BUFRSZ, 0);
			if (dfc->mctx_tmpstr == NULL)
			{
				if (dolog)
				{
					syslog(LOG_ERR,
					       "%s dkimf_dstring_new() failed",
					       dfc->mctx_jobid);
				}

				dkimf_cleanup(ctx);
				return SMFIS_TEMPFAIL;
			}
		}

		for (n = 0; !done && macros[n] != NULL; n++)
		{
			/* retrieve the macro */
			snprintf(name, sizeof name, "{%s}", macros[n]);
			val = dkimf_getsymval(ctx, name);

			/* short-circuit if the macro's not set */
			if (val == NULL)
				continue;

			/* macro set and we don't care about the value */
			if (val != NULL && values[n] == NULL)
			{
				originok = TRUE;
				break;
			}

			dkimf_dstring_blank(dfc->mctx_tmpstr);
			dkimf_dstring_copy(dfc->mctx_tmpstr, values[n]);
			vals = dkimf_dstring_get(dfc->mctx_tmpstr);

			for (p = strtok_r(vals, "|", &last);
			     !done && p != NULL;
			     p = strtok_r(NULL, "|", &last))
			{
				if (strcasecmp(val, p) == 0)
				{
					originok = TRUE;
					done = TRUE;
				}
			}
		}
	}

	/* see if it came from an internal or authenticated source */
	if (!originok)
	{
		char *authtype;

		authtype = dkimf_getsymval(ctx, "{auth_type}");
		if ((authtype == NULL || authtype[0] == '\0') &&
#if POPAUTH
		    !dkimf_checkpopauth(popdb, &cc->cctx_ip) &&
#endif /* POPAUTH */
		    !dkimf_checkhost(internal, cc->cctx_host) &&
		    !dkimf_checkip(internal, &cc->cctx_ip))
		{
			if (domainok && dolog &&
			    !dkimf_checkhost(exignore, cc->cctx_host) &&
			    !dkimf_checkip(exignore, &cc->cctx_ip))
			{
				syslog(LOG_NOTICE,
				       "%s external host %s attempted to send as %s",
				       dfc->mctx_jobid, cc->cctx_host,
				       dfc->mctx_domain);
			}
		}
		else
		{
			originok = TRUE;
		}
	}

	/* set signing mode if the tests passed */
	if (domainok && originok)
	{
		dfc->mctx_signalg = signalg;
		dfc->mctx_signing = TRUE;
		dfc->mctx_addheader = TRUE;
	}

	/*
	**  If we're not operating in the role matching the required operation,
	**  just accept the message and be done with it.
	*/

	if ((dfc->mctx_signing && (mode & DKIMF_MODE_SIGNER) == 0) ||
	    (!dfc->mctx_signing && (mode & DKIMF_MODE_VERIFIER) == 0))
		return SMFIS_ACCEPT;

	/* confirm that we've got a key for signing when a keylist is in use */
	if (dfc->mctx_signing && dfc->mctx_key == NULL && keyhead != NULL)
	{
		if (dolog)
		{
			syslog(LOG_NOTICE, "%s no key selected for signing",
			       dfc->mctx_jobid);
		}

		return SMFIS_TEMPFAIL;
	}

	/* grab an appropriate handle for message processing */
	if (!dfc->mctx_signing)
	{
		dfc->mctx_dkim = dkim_verify(libdkim, dfc->mctx_jobid, NULL,
		                             &status);
	}
	else if (dfc->mctx_key != NULL)
	{
		char *sdomain;

		if (dfc->mctx_key->key_domain != NULL)
			sdomain = dfc->mctx_key->key_domain;
		else
			sdomain = dfc->mctx_domain;

		dfc->mctx_dkim = dkim_sign(libdkim, dfc->mctx_jobid, NULL,
		                           (dkim_sigkey_t) dfc->mctx_key->key_data,
		                           dfc->mctx_key->key_selector,
		                           sdomain,
		                           dfc->mctx_hdrcanon,
		                           dfc->mctx_bodycanon,
		                           dfc->mctx_signalg,
		                           signbytes, &status);
	}
	else
	{
		dfc->mctx_dkim = dkim_sign(libdkim, dfc->mctx_jobid, NULL,
		                           seckey,
		                           selector,
		                           dfc->mctx_domain,
		                           dfc->mctx_hdrcanon,
		                           dfc->mctx_bodycanon,
		                           dfc->mctx_signalg,
		                           signbytes, &status);
	}

	if (dfc->mctx_dkim == NULL && status != DKIM_STAT_OK)
		return dkimf_libstatus(ctx, "dkim_new()", status);

#if _FFR_VBR
	/* establish a VBR handle */
	dfc->mctx_vbr = vbr_init(NULL, NULL, NULL);
	if (dfc->mctx_vbr == NULL)
	{
		syslog(LOG_ERR, "%s can't create VBR context",
		       dfc->mctx_jobid);
		dkimf_cleanup(ctx);
		return SMFIS_TEMPFAIL;
	}

	/* store trusted certifiers */
	if (vbr_trusted != NULL)
		vbr_trustedcerts(dfc->mctx_vbr, vbr_trusted);

	/* if signing, store the values needed to make a header */
	if (dfc->mctx_signing)
	{
		/* set the sending domain */
		vbr_setdomain(dfc->mctx_vbr, dfc->mctx_domain);

		/* VBR-Type; get value from headers or use default */
		hdr = dkimf_findheader(dfc, XVBRTYPEHEADER, 0);
		if (hdr != NULL)
			vbr_type = hdr->hdr_val;
		else
			vbr_type = vbr_deftype;

		/* X-VBR-Certifiers; get value from headers or use default */
		hdr = dkimf_findheader(dfc, XVBRCERTHEADER, 0);
		if (hdr != NULL)
			vbr_cert = hdr->hdr_val;
		else
			vbr_cert = vbr_defcert;

		/* set the message type and certifiers */
		if (vbr_type != NULL && vbr_cert != NULL)
		{
			/* set the VBR transaction type */
			(void) vbr_settype(dfc->mctx_vbr, vbr_type);
	
			/* set the VBR certifier list */
			(void) vbr_setcert(dfc->mctx_vbr, vbr_cert);
		}
	}
#endif /* _FFR_VBR */

#if VERIFY_DOMAINKEYS
	if (dfc->mctx_dksigned && !dfc->mctx_signing)
	{
		dfc->mctx_dk = dk_verify(libdk, dfc->mctx_jobid, NULL,
		                         &status);
		if (dfc->mctx_dk == NULL && status != DKIM_STAT_OK)
		{
			if (dolog)
			{
				syslog(LOG_ERR,
				       "%s: dk_verify() returned status %d",
				       dfc->mctx_jobid, status);
			}

			/* XXX -- temp-fail or continue? */
		}
	}
#endif /* VERIFY_DOMAINKEYS */

	/* run the headers */
	for (hdr = dfc->mctx_hqhead; hdr != NULL; hdr = hdr->hdr_next)
	{
		/* colon, space, CR, LF, NULL = 5 */
		c = strlen(hdr->hdr_hdr) + strlen(hdr->hdr_val) + 5;

		if (dfc->mctx_tmpstr == NULL)
		{
			dfc->mctx_tmpstr = dkimf_dstring_new(BUFRSZ, 0);
			if (dfc->mctx_tmpstr == NULL)
			{
				if (dolog)
				{
					syslog(LOG_ERR,
					       "%s: dkimf_dstring_new() failed",
					       dfc->mctx_jobid);
				}
			}
		}
		else
		{
			dkimf_dstring_blank(dfc->mctx_tmpstr);
		}

		dkimf_dstring_copy(dfc->mctx_tmpstr, hdr->hdr_hdr);
		dkimf_dstring_cat1(dfc->mctx_tmpstr, ':');
		if (!cc->cctx_noleadspc)
			dkimf_dstring_cat1(dfc->mctx_tmpstr, ' ');

		last = '\0';

		/* do milter-ized continuation conversion */
		for (p = hdr->hdr_val; *p != '\0'; p++)
		{
			if (*p == '\n' && last != '\r')
				dkimf_dstring_cat1(dfc->mctx_tmpstr, '\r');

			dkimf_dstring_cat1(dfc->mctx_tmpstr, *p);

			last = *p;
		}

		status = dkim_header(dfc->mctx_dkim,
		                     dkimf_dstring_get(dfc->mctx_tmpstr),
		                     dkimf_dstring_len(dfc->mctx_tmpstr));

		if (status != DKIM_STAT_OK)
			return dkimf_libstatus(ctx, "dkim_header()", status);

#if VERIFY_DOMAINKEYS
		if (dfc->mctx_dk != NULL)
		{
			dkimf_dstring_cat(dfc->mctx_tmpstr, CRLF);
			status = dk_header(dfc->mctx_dk,
			                   dkimf_dstring_get(dfc->mctx_tmpstr),
			                   dkimf_dstring_len(dfc->mctx_tmpstr));
			if (status != DK_STAT_OK)
			{
				if (dolog)
				{
					syslog(LOG_ERR,
					       "%s: dk_header() returned status %d",
					       dfc->mctx_jobid, status);
				}

				dk_free(dfc->mctx_dk);
				dfc->mctx_dk = NULL;
			}
		}
#endif /* VERIFY_DOMAINKEYS */
	}

#if VERIFY_DOMAINKEYS
	/* signal end of headers to libdk */
	if (dfc->mctx_dk != NULL)
	{
		status = dk_eoh(dfc->mctx_dk);
		if (status != DK_STAT_OK)
		{
			if (dolog)
			{
				syslog(LOG_ERR,
				       "%s: dk_eoh() returned status %d",
				       dfc->mctx_jobid, status);
			}

			dk_free(dfc->mctx_dk);
			dfc->mctx_dk = NULL;
		}
	}
#endif /* VERIFY_DOMAINKEYS */

	(void) dkim_set_user_context(dfc->mctx_dkim, ctx);

	/* signal end of headers to libdkim */
	status = dkim_eoh(dfc->mctx_dkim);
	switch (status)
	{
	  case DKIM_STAT_REVOKED:
		dfc->mctx_status = DKIMF_STATUS_REVOKED;
		dfc->mctx_addheader = TRUE;
		dfc->mctx_headeronly = TRUE;
		return SMFIS_CONTINUE;

	  case DKIM_STAT_BADSIG:
		dfc->mctx_status = DKIMF_STATUS_BAD;
		dfc->mctx_addheader = TRUE;
		dfc->mctx_headeronly = TRUE;
		return SMFIS_CONTINUE;

	  case DKIM_STAT_NOSIG:
		dfc->mctx_status = DKIMF_STATUS_NOSIGNATURE;
		dfc->mctx_addheader = TRUE;
		dfc->mctx_headeronly = TRUE;
		return SMFIS_CONTINUE;

	  case DKIM_STAT_NOKEY:
		dfc->mctx_status = DKIMF_STATUS_NOKEY;
		dfc->mctx_addheader = TRUE;
		dfc->mctx_headeronly = TRUE;
		return SMFIS_CONTINUE;

	  /* XXX -- other codes? */

	  case DKIM_STAT_OK:
		return SMFIS_CONTINUE;

	  default:
		return dkimf_libstatus(ctx, "dkim_eoh()", status);
	}
}

/*
**  MLFI_BODY -- handler for an arbitrary body block
**
**  Parameters:
**  	ctx -- milter context
**  	bodyp -- body block
**  	bodylen -- amount of data available at bodyp
**
**  Return value:
**  	An SMFIS_* constant.
**
**  Description:
**  	This function reads the body chunks passed by the MTA and
**  	stores them for later wrapping, if needed.
*/

sfsistat
mlfi_body(SMFICTX *ctx, u_char *bodyp, size_t bodylen)
{
	int status;
	msgctx dfc;
	connctx cc;

	assert(ctx != NULL);
	assert(bodyp != NULL);

	if (DKIM_DEBUG('t'))
		syslog(LOG_INFO, "thread %p body", pthread_self());

	cc = (connctx) dkimf_getpriv(ctx);
	assert(cc != NULL);
	dfc = cc->cctx_msg;
	assert(dfc != NULL);

#if VERIFY_DOMAINKEYS
	if (dfc->mctx_dk != NULL)
	{
		status = dk_body(dfc->mctx_dk, bodyp, bodylen);
		if (status != DK_STAT_OK)
		{
			if (dolog)
			{
				syslog(LOG_ERR,
				       "%s: dk_body() returned status %d",
				       dfc->mctx_jobid, status);
			}

			dk_free(dfc->mctx_dk);
			dfc->mctx_dk = NULL;
		}
	}
#endif /* VERIFY_DOMAINKEYS */

	/*
	**  No need to do anything if the body was empty.
	*/

	if (bodylen == 0 || dfc->mctx_headeronly)
		return SMFIS_CONTINUE;

	status = dkim_body(dfc->mctx_dkim, bodyp, bodylen);
	if (status != DKIM_STAT_OK)
		return dkimf_libstatus(ctx, "dkim_body()", status);

#ifdef SMFIS_SKIP
	if (milterv2 && dkim_minbody(dfc->mctx_dkim) == 0)
		return SMFIS_SKIP;
#endif /* SMFIS_SKIP */

	return SMFIS_CONTINUE;
}

/*
**  MLFI_EOM -- handler called at the end of the message; we can now decide
**              based on the configuration if and how to add the text
**              to this message, then release resources
**
**  Parameters:
**  	ctx -- milter context
**
**  Return value:
**  	An SMFIS_* constant.
*/

sfsistat
mlfi_eom(SMFICTX *ctx)
{
	bool testkey = FALSE;
	bool testpolicy = FALSE;
	bool susp = FALSE;
	int status = DKIM_STAT_OK;
	int c;
	sfsistat ret;
	connctx cc;
	msgctx dfc;
	char *hostname;
	Header hdr;
	unsigned char header[DKIM_MAXHEADER + 1];

	assert(ctx != NULL);

	if (DKIM_DEBUG('t'))
		syslog(LOG_INFO, "thread %p eom", pthread_self());

	cc = (connctx) dkimf_getpriv(ctx);
	assert(cc != NULL);
	dfc = cc->cctx_msg;
	assert(dfc != NULL);

	/*
	**  If necessary, try again to get the job ID in case it came down
	**  later than expected (e.g. postfix).
	*/

	if (dfc->mctx_jobid == JOBIDUNKNOWN)
	{
		dfc->mctx_jobid = dkimf_getsymval(ctx, "i");
		if (dfc->mctx_jobid == NULL)
		{
			if (no_i_whine && dolog)
			{
				syslog(LOG_WARNING,
				       "WARNING: sendmail symbol 'i' not available");
				no_i_whine = FALSE;
			}
			dfc->mctx_jobid = JOBIDUNKNOWN;
		}
	}

	/* get hostname; used in the X header and in new MIME boundaries */
	hostname = dkimf_getsymval(ctx, "j");
	if (hostname == NULL)
		hostname = HOSTUNKNOWN;

	/* remove old signatures when signing */
	if (remsigs && dfc->mctx_signing)
	{
		for (hdr = dfc->mctx_hqhead; hdr != NULL; hdr = hdr->hdr_next)
		{
			if (strcasecmp(hdr->hdr_hdr, DKIM_SIGNHEADER) == 0)
			{
				if (smfi_chgheader(ctx, hdr->hdr_hdr,
				                   0, NULL) != MI_SUCCESS)
				{
					if (dolog)
					{
						syslog(LOG_WARNING,
						       "failed to remove %s: header",
						       hdr->hdr_hdr);
					}
				}
			}
		}
	}

	/*
	**  Remove all Authentication-Results: headers as per configuration
	**  options when verifying.
	*/

	if (!dfc->mctx_signing)
	{
		struct authres *ares;

		ares = (struct authres *) malloc(sizeof(struct authres));
		if (ares == NULL)
		{
			syslog(LOG_WARNING,
			       "%s malloc(): %s", dfc->mctx_jobid,
			       strerror(errno));

			return SMFIS_TEMPFAIL;
		}

		c = 0;
		for (hdr = dfc->mctx_hqhead; hdr != NULL; hdr = hdr->hdr_next)
		{
			memset(ares, '\0', sizeof(struct authres));

			if (strcasecmp(hdr->hdr_hdr, AUTHRESULTSHDR) == 0)
			{
				bool dkimres = FALSE;
				bool hostmatch = FALSE;
				int arstat;

				/* remember index */
				c++;

				/* parse the header */
				arstat = ares_parse(hdr->hdr_val, ares);
				if (arstat == -1)
				{
					if (dolog)
					{
						syslog(LOG_WARNING,
						       "failed to parse %s: header",
						       hdr->hdr_hdr);
					}

					continue;
				}

				/* method match? */
				if (remarall)
				{
					dkimres = TRUE;
				}
				else
				{
					int d;

					for (d = 0; d < ares->ares_count; d++)
					{
						if (ares->ares_result[d].result_method == ARES_METHOD_DKIM)
							dkimres = TRUE;
					}
				}

				/* hostname match? */
				if (remar != NULL)
				{
					if (dkimf_hostlist(ares->ares_host,
					                   remar))
						hostmatch = TRUE;
				}
				else
				{
					if (strcasecmp(hostname,
					               ares->ares_host) == 0)
						hostmatch = TRUE;
				}

				/* delete if we found both */
				if (dkimres && hostmatch)
				{
					if (smfi_chgheader(ctx, hdr->hdr_hdr,
					                   c,
					                   NULL) != MI_SUCCESS)
					{
						if (dolog)
						{
							syslog(LOG_WARNING,
							       "failed to remove %s: header",
							       hdr->hdr_hdr);
						}
					}
				}
			}
		}

		free(ares);
	}

	if (!dfc->mctx_headeronly && dfc->mctx_dkim != NULL)
	{
		(void) dkim_set_user_context(dfc->mctx_dkim, ctx);

		/*
		**  Signal end-of-message to DKIM
		*/

		status = dkim_eom(dfc->mctx_dkim, &testkey);
		switch (status)
		{
		  case DKIM_STAT_OK:
			if (!dfc->mctx_signing &&
			    dkimf_findheader(dfc, DKIM_SIGNHEADER, 0) != NULL)
			{
				if (dolog_success)
				{
					syslog(LOG_INFO,
					       "%s DKIM verification successful",
					       dfc->mctx_jobid);
				}

				dfc->mctx_addheader = TRUE;
				dfc->mctx_status = DKIMF_STATUS_GOOD;
			}
			break;

		  case DKIM_STAT_CANTVRFY:
			dfc->mctx_addheader = TRUE;
			dfc->mctx_status = DKIMF_STATUS_VERIFYERR;
			break;

		  case DKIM_STAT_BADSIG:
			dfc->mctx_addheader = TRUE;
			dfc->mctx_status = DKIMF_STATUS_BAD;
			break;

		  case DKIM_STAT_NOSIG:
			/* don't know yet... */
			break;

		  case DKIM_STAT_NOKEY:
			dfc->mctx_addheader = TRUE;
			dfc->mctx_status = DKIMF_STATUS_NOKEY;
			break;

		  default:
			dkimf_log_ssl_errors(dfc->mctx_jobid);
			status = dkimf_libstatus(ctx, "dkim_eom()", status);
#ifdef _FFR_CAPTURE_UNKNOWN_ERRORS
# ifdef SMFIF_QUARANTINE
			if (dfc->mctx_capture)
			{
				if (smfi_quarantine(ctx,
				                    "capture requested") != MI_SUCCESS)
				{
					if (dolog)
					{
						syslog(LOG_ERR,
						       "%s smfi_quarantine() failed",
						       dfc->mctx_jobid);
					}
				}

				status = SMFIS_ACCEPT;
			}
# endif /* ! SMFIF_QUARANTINE */
#endif /* _FFR_CAPTURE_UNKNOWN_ERRORS */
			return status;
		}

#ifdef _FFR_STATS
		if ((status == DKIM_STAT_OK || status == DKIM_STAT_BADSIG) &&
		    !dfc->mctx_signing && statspath != NULL)
		{
			bool tmp_lengths = FALSE;
			dkim_alg_t tmp_signalg = DKIM_SIGN_UNKNOWN;
			dkim_canon_t tmp_hdrcanon = DKIM_CANON_UNKNOWN;
			dkim_canon_t tmp_bodycanon = DKIM_CANON_UNKNOWN;
			off_t signlen;
			const char *tmp_signdomain = NULL;
			DKIM_SIGINFO *sig = NULL;

			sig = dkim_getsignature(dfc->mctx_dkim);
			(void) dkim_sig_getsignalg(sig, &tmp_signalg);
			(void) dkim_sig_getcanons(sig, &tmp_hdrcanon,
			                          &tmp_bodycanon);
			(void) dkim_sig_getcanonlen(dfc->mctx_dkim, sig,
			                            NULL, NULL, &signlen);
			tmp_lengths = (signlen != (off_t) -1);
			tmp_signdomain = dkim_sig_getdomain(sig);

			dkimf_stats_record(statspath,
			                   tmp_signdomain,
			                   tmp_hdrcanon,
			                   tmp_bodycanon,
			                   tmp_signalg,
			                   (status == DKIM_STAT_OK),
			                   testkey,
			                   tmp_lengths);
		}
#endif /* _FFR_STATS */

#ifdef _FFR_ZTAGS
		if (diagdir != NULL && dfc->mctx_status == DKIMF_STATUS_BAD)
		{
			int nhdrs;
			DKIM_SIGINFO *sig;
			char *ohdrs[MAXHDRCNT];

			nhdrs = MAXHDRCNT;
			memset(ohdrs, '\0', sizeof ohdrs);

			sig = dkim_getsignature(dfc->mctx_dkim);

			status = dkim_ohdrs(dfc->mctx_dkim, sig,
			                    ohdrs, &nhdrs);
			if (status == DKIM_STAT_OK)
			{
				FILE *f;
				char dpath[MAXPATHLEN + 1];

				snprintf(dpath, sizeof dpath, "%s/%s",
				         diagdir, dfc->mctx_jobid);

				f = fopen(dpath, "w");
				if (f == NULL)
				{
					if (dolog)
					{
						syslog(LOG_ERR,
						       "%s %s: fopen(): %s",
						       dfc->mctx_jobid,
						       dpath, strerror(errno));
					}
				}
				else
				{
					int c;
# ifdef _FFR_DIFFHEADERS
					int ndiffs;
					struct dkim_hdrdiff *diffs;
# endif /* _FFR_DIFFHEADERS */
					struct Header *hdr;

					fprintf(f, "z tag headers:\n\n");

					for (c = 0; c < nhdrs; c++)
						fprintf(f, "%s\r\n", ohdrs[c]);

					fprintf(f, "--------------------\n\n");
					fprintf(f, "Received headers:\n\n");

					for (hdr = dfc->mctx_hqhead;
					     hdr != NULL;
					     hdr = hdr->hdr_next)
					{
						fprintf(f, "%s:%s%s",
						        hdr->hdr_hdr,
						        cc->cctx_noleadspc ? ""
						                           : " ",
						        hdr->hdr_val);
					}

# ifdef _FFR_DIFFHEADERS
					/* XXX -- make the "5" configurable */
					status = dkim_diffheaders(dfc->mctx_dkim,
					                          5,
					                          ohdrs,
					                          nhdrs,
					                          &diffs,
					                          &ndiffs);

					if (status == DKIM_STAT_OK &&
					    diffs != NULL && ndiffs > 0)
					{
						fprintf(f, "--------------------\n\n");
						fprintf(f, "Munging detected:\n\n");

						for (c = 0; c < ndiffs; c++)
						{
							fprintf(f,
							        "-%s\n+%s\n\n",
							        diffs[c].hd_old,
							        diffs[c].hd_new);
						}
					}
# endif /* _FFR_DIFFHEADERS */

					fclose(f);
				}
			}
		}	
#endif /* _FFR_ZTAGS */

		if (dfc->mctx_status == DKIMF_STATUS_GOOD)
		{
			if (sigmin > 0)
			{
				off_t canonlen;
				off_t bodylen;
				DKIM_SIGINFO *sig;

				sig = dkim_getsignature(dfc->mctx_dkim);
				(void) dkim_sig_getcanonlen(dfc->mctx_dkim,
				                            sig, &bodylen,
				                            &canonlen, NULL);

				if (sigmintype == SIGMIN_PERCENT)
				{
					size_t signpct;

					signpct = (100 * canonlen) / bodylen;

					if (signpct < sigmin)
						dfc->mctx_status = DKIMF_STATUS_PARTIAL;
				}
				else if (sigmintype == SIGMIN_MAXADD)
				{
					if (canonlen + sigmin < bodylen)
						dfc->mctx_status = DKIMF_STATUS_PARTIAL;
				}
				else
				{
					size_t required;

					required = MIN(sigmin, bodylen);

					if (canonlen < sigmin)
						dfc->mctx_status = DKIMF_STATUS_PARTIAL;
				}
			}
		}

		/* compute and insert the signature, if we're signing */
		if (dfc->mctx_signing)
		{
			size_t len;
			char *start;

			memset(header, '\0', sizeof header);

			len = sizeof header;
			start = header;

			if (cc->cctx_noleadspc)
			{
				len--;
				header[0] = ' ';
				start++;
			}

			status = dkim_getsighdr(dfc->mctx_dkim, start, len,
			                        DKIM_HDRMARGIN,
			                        strlen(DKIM_SIGNHEADER) + 2);
			if (status != DKIM_STAT_OK)
				return status;

			dkimf_stripcr(header);

			if (dkimf_insheader(ctx, 1, DKIM_SIGNHEADER,
			                    header) == MI_FAILURE)
			{
				if (dolog)
				{
					syslog(LOG_ERR,
					       "%s \"%s\" header add failed",
					       dfc->mctx_jobid,
					       DKIM_SIGNHEADER);
				}
			}

			if (dolog_success)
			{
				syslog(LOG_INFO, "%s \"%s\" header added",
				       dfc->mctx_jobid, DKIM_SIGNHEADER);
			}

#ifdef _FFR_VBR
			/* generate and add a VBR-Info header */
			memset(header, '\0', sizeof header);

			status = vbr_getheader(dfc->mctx_vbr, header,
			                       sizeof header);
			/* XXX -- log errors */
			if (status == DKIM_STAT_OK)
			{
				if (dkimf_insheader(ctx, 1, VBR_INFOHEADER,
				                    header) == MI_FAILURE)
				{
					if (dolog)
					{
						syslog(LOG_ERR,
						       "%s \"%s\" header add failed",
						       dfc->mctx_jobid,
						       VBR_INFOHEADER);
					}
				}
			}
#endif /* _FFR_VBR */
		}
	}

	if (dfc->mctx_dkim != NULL && !dfc->mctx_signing)
	{
		/*
		**  Evaluate sender signing policy for failed or unsigned
		**  messages.
		*/

		if (status == DKIM_STAT_OK ||
		    status == DKIM_STAT_BADSIG ||
		    status == DKIM_STAT_CANTVRFY ||
		    status == DKIM_STAT_SYNTAX ||
		    status == DKIM_STAT_REVOKED ||
		    status == DKIM_STAT_KEYFAIL ||
		    status == DKIM_STAT_NOSIG)
		{
			dkim_handling_t hcode = DKIM_HANDLING_NONE;

			status = dkim_policy(dfc->mctx_dkim, &testpolicy,
			                     &susp, NULL, &hcode, NULL);
			if (status == DKIM_STAT_OK)
			{
				if (susp)
				{
					dfc->mctx_addheader = TRUE;
					dfc->mctx_status = DKIMF_STATUS_SUSPICIOUS;
				}

				if (hcode == DKIM_HANDLING_DENY &&
				    use_ssp_deny && !testpolicy)
				{
					if (dolog)
					{
						syslog(LOG_NOTICE,
						       "%s rejected per sender domain policy",
						       dfc->mctx_jobid);
					}
					

					if (smfi_setreply(ctx,
					                  SSPDENYSMTP,
					                  SSPDENYESC,
					                  SSPDENYTEXT) != MI_SUCCESS &&
					    dolog)
					{
						syslog(LOG_NOTICE,
						       "%s smfi_setreply() failed",
						       dfc->mctx_jobid);
					}

					dkimf_cleanup(ctx);
					return SMFIS_REJECT;
				}
			}
			else if (dolog)
			{
				const char *err;

				err = dkim_geterror(dfc->mctx_dkim);
				if (err != NULL)
				{
					syslog(LOG_ERR, "%s SSP query: %s",
					       dfc->mctx_jobid, err);
				}
			}
		}
	}

	/* insert DKIM status */
	if (dfc->mctx_addheader && dfc->mctx_status != DKIMF_STATUS_UNKNOWN)
	{
		bool test;
		u_int keybits;
		char *authresult;
		char *failstatus;
		char *method;
		DKIM_SIGINFO *sig;
		char comment[BUFRSZ + 1];

		memset(comment, '\0', sizeof comment);

		test = FALSE;
		failstatus = (testkey ? "neutral" : "hardfail");

		switch (dfc->mctx_status)
		{
		  case DKIMF_STATUS_GOOD:
			authresult = "pass";
			sig = dkim_getsignature(dfc->mctx_dkim);
			assert(sig != NULL);
			(void) dkim_sig_getkeysize(sig, &keybits);
			snprintf(comment, sizeof comment, "%u-bit key",
			         keybits);
			break;

		  case DKIMF_STATUS_NOSIGNATURE:
			authresult = failstatus;
			sm_strlcpy(comment, "no signature", sizeof comment);

			if (!susp)
				dfc->mctx_addheader = FALSE;

			break;

		  case DKIMF_STATUS_BAD:
		  case DKIMF_STATUS_REVOKED:
		  case DKIMF_STATUS_PARTIAL:
		  case DKIMF_STATUS_VERIFYERR:
			authresult = failstatus;
			if (dfc->mctx_status == DKIMF_STATUS_REVOKED)
			{
				sm_strlcpy(comment, "revoked", sizeof comment);
			}
			else if (dfc->mctx_status == DKIMF_STATUS_PARTIAL)
			{
				authresult = "permerror";

				sm_strlcpy(comment, "partial verification",
				           sizeof comment);
			}
			else if (dfc->mctx_status == DKIMF_STATUS_VERIFYERR)
			{
				const char *err;

				authresult = "permerror";

				err = dkim_geterror(dfc->mctx_dkim);
				if (err != NULL)
				{
					snprintf(comment, sizeof comment,
					         "verification error: %s",
					         err);
				}
				else
				{
					sm_strlcpy(comment,
					           "verification error",
					           sizeof comment);
				}
			}
			else
			{
				sm_strlcpy(comment, "verification failed",
				           sizeof comment);
			}

			break;

		  case DKIMF_STATUS_SUSPICIOUS:
			authresult = (testpolicy ? "neutral" : "hardfail");
			sm_strlcpy(comment, "SSP", sizeof comment);
			break;

		  case DKIMF_STATUS_BADFORMAT:
			authresult = "permerror";
			sm_strlcpy(comment, "bad format", sizeof comment);
			break;

		  default:
			authresult = "neutral";
			break;
		}

#ifdef SMFIF_QUARANTINE
		/* quarantine for "bad" results if requested */
		if (quarantine &&
		    (dfc->mctx_status == DKIMF_STATUS_BAD ||
		     dfc->mctx_status == DKIMF_STATUS_REVOKED ||
		     dfc->mctx_status == DKIMF_STATUS_PARTIAL ||
		     dfc->mctx_status == DKIMF_STATUS_VERIFYERR ||
		     (dfc->mctx_status == DKIMF_STATUS_NOSIGNATURE &&
		      dfc->mctx_addheader)))
		{
			char qreason[BUFRSZ + 1];

			snprintf(qreason, sizeof qreason,
			         "%s: %s: %s", progname, failstatus, comment);
			if (smfi_quarantine(ctx, qreason) != MI_SUCCESS)
			{
				if (dolog)
				{
					syslog(LOG_ERR,
					       "%s smfi_quarantine() failed",
					       dfc->mctx_jobid);
				}
			}
		}
#endif /* SMFIF_QUARANTINE */

		if (test)
		{
			if (comment[0] == '\0')
				sm_strlcpy(comment, "testing", sizeof comment);
			else
				sm_strlcat(comment, "/testing", sizeof comment);
		}

		memset(header, '\0', sizeof header);

		method = "dkim";
		if (susp && (dfc->mctx_status == DKIMF_STATUS_BAD ||
		             dfc->mctx_status == DKIMF_STATUS_GOOD ||
		             dfc->mctx_status == DKIMF_STATUS_REVOKED ||
		             dfc->mctx_status == DKIMF_STATUS_PARTIAL ||
		             dfc->mctx_status == DKIMF_STATUS_VERIFYERR ||
		             dfc->mctx_status == DKIMF_STATUS_NOSIGNATURE))
			method = "dkim-ssp";

		if (dfc->mctx_dkim != NULL)
		{
			char val[MAXADDRESS + 1];

			memset(val, '\0', sizeof val);

			sm_strlcpy(val, "unknown", sizeof val);

			(void) dkim_sig_getidentity(dfc->mctx_dkim, NULL,
			                            val, sizeof val);

			snprintf(header, sizeof header,
			         "%s%s; %s=%s%s%s%s header.i=%s",
			         cc->cctx_noleadspc ? " " : "",
			         hostname,
			         method,
			         authresult,
			         comment[0] == '\0' ? "" : " (",
			         comment[0] == '\0' ? "" : comment,
			         comment[0] == '\0' ? "" : ")",
			         val);

			if (dfc->mctx_addheader &&
			    dkimf_insheader(ctx, 1, AUTHRESULTSHDR,
			                    header) == MI_FAILURE)
			{
				if (dolog)
				{
					syslog(LOG_ERR,
					       "%s \"%s\" header add failed",
					       dfc->mctx_jobid,
					       AUTHRESULTSHDR);
				}
			}
		}

		if (dfc->mctx_status == DKIMF_STATUS_BAD &&
		    dfc->mctx_dkim != NULL)
			dkimf_report(dfc, authresult, comment);
	}

#ifdef _FFR_VBR
	if (!dfc->mctx_signing &&
	    dkimf_findheader(dfc, VBR_INFOHEADER, 0) != NULL)
	{
		bool add_vbr_header = FALSE;
		VBR_STAT vbr_status;
		int c;
		char *vbr_result;
		char *vbr_domain;
		char *vbr_certifier;
		char *vbr_vouchers;
		char *vbr_type;
		char *p;
		char *sctx;
		char *eq;
		u_char *param;
		u_char *value;
		Header vbr_header;
		char tmp[DKIM_MAXHEADER + 1];

		for (c = 0; ; c++)
		{
			vbr_header = dkimf_findheader(dfc, VBR_INFOHEADER, c);
			if (vbr_header == NULL)
				break;

			vbr_result = NULL;
			vbr_domain = NULL;
			vbr_certifier = NULL;
			vbr_vouchers = NULL;
			vbr_type = NULL;
	
			/* break out the VBR-Info header contents */
			sm_strlcpy(tmp, vbr_header->hdr_val, sizeof tmp);
			for (p = strtok_r(tmp, ";", &sctx);
			     p != NULL;
			     p = strtok_r(NULL, ";", &sctx))
			{
				eq = strchr(p, '=');
				if (eq == NULL)
					continue;
				*eq = '\0';

				for (param = p; *param != '\0'; param++)
				{
					if (!(isascii(*param) &&
					      isspace(*param)))
						break;
				}
				dkimf_trimspaces(param);

				for (value = eq + 1; *value != '\0'; value++)
				{
					if (!(isascii(*value) &&
					      isspace(*value)))
						break;
				}
				dkimf_trimspaces(value);

				if (strcasecmp(param, "md") == 0)
				{
					vbr_domain = value;
				}
				else if (strcasecmp(param, "mc") == 0)
				{
					vbr_type = value;
				}
				else if (strcasecmp(param, "mv") == 0)
				{
					vbr_vouchers = value;
				}
			}
			
			/* use accessors to set parsed values */
			vbr_setcert(dfc->mctx_vbr, vbr_vouchers);
			vbr_settype(dfc->mctx_vbr, vbr_type);
			vbr_setdomain(dfc->mctx_vbr, vbr_domain);
		
			/* attempt the query */
			vbr_status = vbr_query(dfc->mctx_vbr, &vbr_result,
			                       &vbr_certifier);
			switch (vbr_status)
			{
			  case VBR_STAT_DNSERROR:
				if (dolog)
				{
					const char *err;

					err = vbr_geterror(dfc->mctx_vbr);

					syslog(LOG_NOTICE,
					       "%s: can't verify VBR information%s%s",
					       dfc->mctx_jobid,
					       err == NULL ? "" : ": ",
					       err == NULL ? "" : err);
				}
				vbr_result = "neutral";
				break;

			  case VBR_STAT_INVALID:
			  case VBR_STAT_NORESOURCE:
				if (dolog)
				{
					const char *err;

					err = vbr_geterror(dfc->mctx_vbr);

					syslog(LOG_NOTICE,
					       "%s: error handling VBR information%s%s",
					       dfc->mctx_jobid,
					       err == NULL ? "" : ": ",
					       err == NULL ? "" : err);
				}
				vbr_result = "neutral";
				break;

			  case DKIM_STAT_OK:
				add_vbr_header = TRUE;
				break;

			  default:
				assert(0);
			}

			if (add_vbr_header)
			{
				char hdr[DKIM_MAXHEADER + 1];

				memset(hdr, '\0', sizeof hdr);

				snprintf(hdr, sizeof hdr, "%s.md",
				         VBR_INFOHEADER);
				dkimf_lowercase(hdr);
				snprintf(header, sizeof header,
				         "%s%s %s=%s; vbr=%s%s%s%s",
				         cc->cctx_noleadspc ? " " : "",
				         hostname, hdr, vbr_domain, vbr_result,
				         vbr_certifier == NULL ? "" : " (",
				         vbr_certifier == NULL ? "" : vbr_certifier,
				         vbr_certifier == NULL ? "" : ")");
		
				if (dkimf_insheader(ctx, 1, AUTHRESULTSHDR,
				                    header) == MI_FAILURE)
				{
					if (dolog)
					{
						syslog(LOG_ERR,
						       "%s \"%s\" header add failed",
						       dfc->mctx_jobid,
						       AUTHRESULTSHDR);
					}
				}

				break;
			}
		}
	}
#endif /* _FFR_VBR */

#if VERIFY_DOMAINKEYS
	if (dfc->mctx_dk != NULL)
	{
		DK_FLAGS flags;
		char *authresult = NULL;
		char *comment = NULL;
		char hdr[DKIM_MAXHEADER + 1];
		char val[MAXADDRESS + 1];

		flags = 0;
		dfc->mctx_addheader = FALSE;

		status = dk_eom(dfc->mctx_dk, &flags);
		switch (status)
		{
		  case DK_STAT_OK:
			dfc->mctx_addheader = dfc->mctx_dksigned;
			authresult = "pass";
			break;

		  case DK_STAT_BADSIG:
			dfc->mctx_addheader = TRUE;
			authresult = "hardfail";
			break;

		  case DK_STAT_NOSIG:
			/* XXX -- extract policy */
			dfc->mctx_addheader = TRUE;
			authresult = "neutral";
			comment = "no signature";
			break;

		  case DK_STAT_NOKEY:
			/* XXX -- extract policy */
			dfc->mctx_addheader = TRUE;
			authresult = "neutral";
			comment = "no key";
			break;

		  default:
			/* XXX -- do better? */
			if (dolog)
			{
#if (DK_LIB_VERSION >= 0x00050000)
				const char *err;

				err = dk_geterror(dfc->mctx_dk);
				if (err == NULL)
					err = strerror(errno);

				syslog(LOG_INFO,
				       "%s dk_eom() returned status %d: %s",
				       dfc->mctx_jobid, status, err);
#else /* (DK_LIB_VERSION >= 0x00050000) */
				syslog(LOG_INFO,
				       "%s dk_eom() returned status %d",
				       dfc->mctx_jobid, status);
#endif /* (DK_LIB_VERSION >= 0x00050000) */
			}
			break;
		}

		if (dfc->mctx_addheader)
		{
			sm_strlcpy(hdr, "unknown", sizeof hdr);
			sm_strlcpy(val, "unknown", sizeof val);

			(void) dk_getidentity(dfc->mctx_dk, hdr, sizeof hdr,
			                      val, sizeof val);

			memset(header, '\0', sizeof header);

			snprintf(header, sizeof header,
			         "%s%s; domainkeys=%s%s%s%s%s header.%s=%s",
			         cc->cctx_noleadspc ? " " : "",
			         hostname, authresult,
			         comment == NULL ? "" : " (",
			         comment == NULL ? "" : comment,
			         comment == NULL ? "" : ")",
			         !(flags & DK_FLAG_TESTING) ? "" : " (testing)",
			         hdr, val);

			if (dkimf_insheader(ctx, 1, AUTHRESULTSHDR,
			                    header) == MI_FAILURE)
			{
				if (dolog)
				{
					syslog(LOG_ERR,
					       "%s \"%s\" header add failed",
					       dfc->mctx_jobid,
					       AUTHRESULTSHDR);
				}
			}
		}
	}
#endif /* VERIFY_DOMAINKEYS */

	/*
	**  Identify the filter, if requested.
	*/

	if (addxhdr)
	{
		char xfhdr[DKIM_MAXHEADER + 1];

		memset(xfhdr, '\0', sizeof xfhdr);

		snprintf(xfhdr, DKIM_MAXHEADER, "%s%s v%s %s %s",
		         cc->cctx_noleadspc ? " " : "",
		         DKIMF_PRODUCT, DKIMF_VERSION, hostname,
		         dfc->mctx_jobid != NULL ? dfc->mctx_jobid
		                                : JOBIDUNKNOWN);

		if (dkimf_insheader(ctx, 1, XHEADERNAME, xfhdr) != MI_SUCCESS)
		{
			if (dolog)
			{
				syslog(LOG_ERR, "%s \"%s\" header add failed",
				       dfc->mctx_jobid, XHEADERNAME);
			}

			dkimf_cleanup(ctx);
			return SMFIS_TEMPFAIL;
		}
	}

	dkimf_log_ssl_errors(dfc->mctx_jobid);

	/*
	**  If we got this far, we're ready to complete.
	*/

	ret = SMFIS_ACCEPT;

	/* translate the stored status */
	switch (dfc->mctx_status)
	{
	  case DKIMF_STATUS_GOOD:
		break;

	  case DKIMF_STATUS_BAD:
		ret = dkimf_libstatus(ctx, "mlfi_eom()", DKIM_STAT_BADSIG);
		break;

	  case DKIMF_STATUS_NOKEY:
		ret = dkimf_libstatus(ctx, "mlfi_eom()", DKIM_STAT_NOKEY);
		break;

	  case DKIMF_STATUS_REVOKED:
		ret = SMFIS_TEMPFAIL;
		break;

	  case DKIMF_STATUS_NOSIGNATURE:
		/*
		**  If no header was added, we've already decided that we
		**  don't care that there was no signature here, so don't
		**  log anything or react either.
		*/

		if (dfc->mctx_addheader)
		{
			ret = dkimf_libstatus(ctx, "mlfi_eom()",
			                      DKIM_STAT_NOSIG);
		}
		break;

	  case DKIMF_STATUS_BADFORMAT:
		ret = SMFIS_ACCEPT;
		break;

	  case DKIMF_STATUS_UNKNOWN:
		break;

	  default:
		if (status != DKIM_STAT_OK)
			ret = dkimf_libstatus(ctx, "mlfi_eom()", status);
		break;
	}

	return ret;
}

/*
**  MLFI_ABORT -- handler called if an earlier filter in the filter process
**                rejects the message
**
**  Parameters:
**  	ctx -- milter context
**
**  Return value:
**  	An SMFIS_* constant.
*/

sfsistat
mlfi_abort(SMFICTX *ctx)
{
	if (DKIM_DEBUG('t'))
		syslog(LOG_INFO, "thread %p abort", pthread_self());

	dkimf_cleanup(ctx);
	return SMFIS_CONTINUE;
}

/*
**  MLFI_CLOSE -- handler called on connection shutdown
**
**  Parameters:
**  	ctx -- milter context
**
**  Return value:
**  	An SMFIS_* constant.
*/

sfsistat
mlfi_close(SMFICTX *ctx)
{
	connctx cc;

	if (DKIM_DEBUG('t'))
	{
		pthread_mutex_lock(&count_lock);
		thread_count--;
		syslog(LOG_INFO, "thread %p close (%d)", pthread_self(),
		       thread_count);
		pthread_mutex_unlock(&count_lock);
	}

	dkimf_cleanup(ctx);

	cc = (connctx) dkimf_getpriv(ctx);
	free(cc);
	dkimf_setpriv(ctx, NULL);

#ifdef QUERY_CACHE
	if (querycache)
	{
		time_t now;

		(void) time(&now);
		if (cache_lastlog + CACHESTATSINT < now)
		{
			u_int c_hits;
			u_int c_queries;
			u_int c_expired;

			dkim_getcachestats(&c_queries, &c_hits, &c_expired);

			cache_lastlog = now;

			syslog(LOG_INFO,
			       "cache: %u quer%s, %u hit%s (%d%%), %u expired",
			       c_queries, c_queries == 1 ? "y" : "ies",
			       c_hits, c_hits == 1 ? "" : "s",
			       (c_hits * 100) / c_queries,
			       c_expired);
		}
	}
#endif /* QUERY_CACHE */

	return SMFIS_CONTINUE;
}

/*
**  smfilter -- the milter module description
*/

struct smfiDesc smfilter =
{
	DKIMF_PRODUCT,	/* filter name */
	SMFI_VERSION,	/* version code -- do not change */
	(SMFIF_ADDHDRS | SMFIF_CHGHDRS |
#ifdef SMFIF_QUARANTINE
	 SMFIF_QUARANTINE |
#endif /* SMFIF_QUARANTINE */
#ifdef SMFIF_SETSYMLIST
	 SMFIF_SETSYMLIST |
#endif /* SMFIF_SETSYMLIST */
	 0),		/* flags */
	mlfi_connect,	/* connection info filter */
	NULL,		/* SMTP HELO command filter */
	mlfi_envfrom,	/* envelope sender filter */
	NULL,		/* envelope recipient filter */
	mlfi_header,	/* header filter */
	mlfi_eoh,	/* end of header */
	mlfi_body,	/* body block filter */
	mlfi_eom,	/* end of message */
	mlfi_abort,	/* message aborted */
	mlfi_close,	/* shutdown */
#if SMFI_VERSION > 2
	NULL,		/* unrecognised command */
#endif
#if SMFI_VERSION > 3
	NULL,		/* DATA */
#endif
#if SMFI_VERSION >= 0x01000000
	mlfi_negotiate	/* negotiation callback */
#endif
};

/*
**  USAGE -- print a usage message and return the appropriate exit status
**
**  Parameters:
**  	None.
**
**  Return value:
**  	EX_USAGE.
*/

static int
usage(void)
{
	fprintf(stderr, "%s: usage: %s -p socketfile [options]\n"
	                "\t-a peerlist \tfile containing list of hosts to ignore\n"
	                "\t-A          \tauto-restart\n"
	                "\t-b modes    \tselect operating modes\n"
	                "\t-c canon    \tcanonicalization to use when signing\n"
	                "\t-C config   \tconfiguration info (see man page)\n"
	                "\t-d domlist  \tdomains to sign\n"
	                "\t-D          \talso sign subdomains\n"
	                "\t-f          \tdon't fork-and-exit\n"
	                "\t-F time     \tfixed timestamp to use when signing (test mode only)\n"
	                "\t-h          \tappend identifying header\n"
	                "\t-H hdrlist  \twhich additional headers to sign\n"
	                "\t-i ilist    \tfile containing list of internal (signing) hosts\n"
	                "\t-I elist    \tfile containing list of external domain clients\n"
	                "\t-k keyfile  \tlocation of secret key file\n"
	                "\t-K          \tload a key set instead of a single key\n"
	                "\t-l          \tlog activity to system log\n"
	                "\t-L limit    \tsignature limit requirements\n"
	                "\t-m mtalist  \tMTA daemon names for which to sign\n"
	                "\t-M macrolist\tMTA macros which enable signing\n"
			"\t-o hdrlist  \tlist of headers to omit from signing\n"
	                "\t-P pidfile  \tfile to which to write pid\n"
#if _FFR_REQUIRE_HEADERS
	                "\t-r          \trequire basic RFC2822 header compliance\n"
#endif /* _FFR_REQUIRE_HEADERS */
	                "\t-R          \tgenerate verification failure reports\n"
	                "\t-s selector \tselector to use when signing\n"
	                "\t-S signalg  \tsignature algorithm to use when signing\n"
			"\t-t testfile \tevaluate RFC2822 message in \"testfile\"\n"
			"\t-T timeout  \tDNS timeout (seconds)\n"
	                "\t-u userid   \tchange to specified userid\n"
#if POPAUTH
			"\t-U dbfile   \tuser POP AUTH database\n"
#endif /* POPAUTH */
	                "\t-v          \tincrease verbosity during testing\n"
	                "\t-V          \tprint version number and terminate\n"
	                "\t-x conffile \tread configuration from conffile\n",
	        progname, progname);
	return EX_USAGE;
}

/*
**  MAIN -- program mainline
**
**  Process command line arguments and call the milter mainline.
*/

int
main(int argc, char **argv)
{
	bool autorestart = FALSE;
	bool ztags = FALSE;
	bool blen = FALSE;
	bool gotp = FALSE;
	bool dofork = TRUE;
	bool multikey = FALSE;
	bool stricttest = FALSE;
	int c;
	int status;
	int n;
	int verbose = 0;
	int sigttl = 0;
	int clockdrift = 0;
	int maxsign;
	int filemask = -1;
	time_t fixedtime = (time_t) -1;
	unsigned long tmpl;
	const char *args = CMDLINEOPTS;
	FILE *f;
	char *be = NULL;
	char *become = NULL;
	char *domlist = NULL;
	char *mtalist = NULL;
	char *p;
	char *pidfile = NULL;
	char *keyfile = NULL;
	char *config = NULL;
	char *peerfile = NULL;
#ifdef _FFR_REPLACE_RULES
	char *repfile = NULL;
#endif /* _FFR_REPLACE_RULES */
	char *ilist = NULL;
	char *omitlist = NULL;
	char *alwayslist = NULL;
	char *siglimit = NULL;
	char *remarlist = NULL;
#if POPAUTH
	char *popdbfile = NULL;
#endif /* POPAUTH */
	char *elist = NULL;
	char *canonstr = NULL;
	char *signalgstr = NULL;
	char *macrolist = NULL;
	char *testfile = NULL;
	char *testpubkeys = NULL;
	char *signhdrs = NULL;
	unsigned char *s33krit = NULL;
	struct config *cfg = NULL;
	char *end;
	char argstr[MAXARGV];
	char confstr[BUFRSZ + 1];

	/* initialize */
	signbytes = -1L;
	milterv2 = FALSE;
	testmode = FALSE;
	addxhdr = FALSE;
	dolog = FALSE;
	dolog_success = FALSE;
	subdomains = FALSE;
	remarall = FALSE;
	remsigs = FALSE;
	use_ssp_deny = FALSE;
#ifdef QUERY_CACHE
	querycache = FALSE;
#endif /* QUERY_CACHE */
	sock = NULL;
	dompats = NULL;
	omithdrs = NULL;
	alwayshdrs = NULL;
#ifdef _FFR_SELECTOR_HEADER
	selectorhdr = NULL;
#endif /* _FFR_SELECTOR_HEADER */
#if POPAUTH
	popdb = NULL;
#endif /* POPAUTH */
	send_reports = FALSE;
	keyhead = NULL;
	keytail = NULL;
	no_i_whine = TRUE;
	selector = NULL;
#ifdef _FFR_ZTAGS
	diagdir = NULL;
#endif /* _FFR_ZTAGS */
	seckey = NULL;
	libdkim = NULL;
	domains = NULL;
	mtas = NULL;
	macros = NULL;
	values = NULL;
	peerlist = NULL;
	remar = NULL;
#ifdef _FFR_REPLACE_RULES
	replist = NULL;
#endif /* _FFR_REPLACE_RULES */
	internal = NULL;
	exignore = NULL;
#ifdef _FFR_VBR
	vbr_deftype = NULL;
	vbr_defcert = NULL;
	vbr_trusted = NULL;
#endif /* _FFR_VBR */
	quarantine = FALSE;
	hdrcanon = DKIM_CANON_DEFAULT;
	bodycanon = DKIM_CANON_DEFAULT;
	tmo = DEFTIMEOUT;
	maxhdrsz = DEFMAXHDRSZ;
	sigmin = 0;
	sigmintype = SIGMIN_BYTES;
	if (DKIM_DEBUG('t'))
	{
		thread_count = 0;
		pthread_mutex_init(&count_lock, NULL);
	}

	progname = (p = strrchr(argv[0], '/')) == NULL ? argv[0] : p + 1;

	/* process command line options */
	while ((c = getopt(argc, argv, args)) != -1)
	{
		switch (c)
		{
		  case 'a':
			if (optarg == NULL || *optarg == '\0')
				return usage();
			peerfile = optarg;
			break;

		  case 'A':
			autorestart = TRUE;
			break;

		  case 'b':
			if (optarg == NULL || *optarg == '\0')
				return usage();
			be = optarg;
			break;

		  case 'c':
			if (optarg == NULL || *optarg == '\0')
				return usage();
			canonstr = optarg;
			break;

		  case 'C':
			if (optarg == NULL || *optarg == '\0')
				return usage();
			config = optarg;
			break;

		  case 'd':
			if (optarg == NULL || *optarg == '\0')
				return usage();
			domlist = strdup(optarg);
			if (domlist == NULL)
			{
				fprintf(stderr, "%s: strdup(): %s\n", progname,
				        strerror(errno));
				return EX_SOFTWARE;
			}
			break;

		  case 'D':
			subdomains = TRUE;
			break;

		  case 'f':
			dofork = FALSE;
			break;

		  case 'F':
			if (optarg == NULL || *optarg == '\0')
				return usage();
			errno = 0;
			if (optarg[0] == '-')
			{
				errno = ERANGE;
				fixedtime = ULONG_MAX;
			}
			else
			{
				fixedtime = strtoul(optarg, &p, 10);
			}

			if (fixedtime == ULONG_MAX || errno != 0 || *p != '\0')
			{
				fprintf(stderr, "%s: invalid time value\n",
				        progname);
				return EX_USAGE;
			}
			break;

		  case 'h':
			addxhdr = TRUE;
			break;

		  case 'i':
			if (optarg == NULL || *optarg == '\0')
				return usage();
			ilist = optarg;
			break;

		  case 'I':
			if (optarg == NULL || *optarg == '\0')
				return usage();
			elist = optarg;
			break;

		  case 'k':
			if (optarg == NULL || *optarg == '\0')
				return usage();
			keyfile = optarg;
			break;

		  case 'K':
			multikey = TRUE;
			break;

		  case 'l':
			dolog = TRUE;
			break;

		  case 'L':
			if (optarg == NULL || *optarg == '\0')
				return usage();
			siglimit = optarg;
			break;

		  case 'm':
			if (optarg == NULL || *optarg == '\0')
				return usage();
			mtalist = optarg;
			break;

		  case 'M':
			if (optarg == NULL || *optarg == '\0')
				return usage();
			macrolist = optarg;
			break;

		  case 'o':
			if (optarg == NULL || *optarg == '\0')
				return usage();
			omitlist = optarg;
			break;

		  case 'p':
			if (optarg == NULL || *optarg == '\0')
				return usage();
			sock = optarg;
			(void) smfi_setconn(optarg);
			gotp = TRUE;
			break;

		  case 'P':
			if (optarg == NULL || *optarg == '\0')
				return usage();
			pidfile = optarg;
			break;

		  case 'q':
			quarantine = TRUE;
			break;

#if _FFR_REQUIRED_HEADERS
		  case 'r':
			req_hdrs = TRUE;
			break;
#endif /* _FFR_REQUIRED_HEADERS */

		  case 'R':
			send_reports = TRUE;
			break;

		  case 's':
			if (optarg == NULL || *optarg == '\0')
				return usage();
			selector = optarg;
			break;

		  case 'S':
			if (optarg == NULL || *optarg == '\0')
				return usage();
			signalgstr = optarg;
			break;

		  case 't':
			if (optarg == NULL || *optarg == '\0')
				return usage();
			testmode = TRUE;
			testfile = optarg;
			break;

		  case 'T':
			if (optarg == NULL || *optarg == '\0')
				return usage();
			errno = 0;
			if (optarg[0] == '-')
			{
				errno = ERANGE;
				tmpl = ULONG_MAX;
			}
			else
			{
				tmpl = strtoul(optarg, &p, 10);
			}

			if (tmpl > UINT_MAX || errno != 0 || *p != '\0')
			{
				fprintf(stderr, "%s: invalid value for -%c\n",
				        progname, c);
				return EX_USAGE;
			}

			tmo = (unsigned int) tmpl;

			break;

		  case 'u':
			if (optarg == NULL || *optarg == '\0')
				return usage();
			become = optarg;
			break;

#if POPAUTH
		  case 'U':
			if (optarg == NULL || *optarg == '\0')
				return usage();
			popdbfile = optarg;
			break;
#endif /* POPAUTH */

		  case 'v':
			verbose++;
			break;

		  case 'V':
			printf("%s: %s v%s\n", progname, DKIMF_PRODUCT,
			       DKIMF_VERSION);
			printf("\tCompiled with %s\n",
			       SSLeay_version(SSLEAY_VERSION));
			printf("\tSupported signing algorithms:\n");
			for (c = 0; dkimf_sign[c].str != NULL; c++)
				printf("\t\t%s\n", dkimf_sign[c].str);
			printf("\tSupported canonicalization algorithms:\n");
			for (c = 0; dkimf_canon[c].str != NULL; c++)
				printf("\t\t%s\n", dkimf_canon[c].str);
			dkimf_optlist(stdout);
			return EX_OK;

		  case 'x':
			if (optarg == NULL || *optarg == '\0')
			{
				return usage();
			}
			else
			{
				u_int line = 0;
				char *missing;
				FILE *cf;

				cf = fopen(optarg, "r");
				if (cf == NULL)
				{
					fprintf(stderr,
					        "%s: %s: fopen(): %s\n",
					        progname, optarg,
					        strerror(errno));
					return EX_UNAVAILABLE;
				}

				cfg = config_load(cf, dkimf_config, &line);

				fclose(cf);

				if (cfg == NULL)
				{
					fprintf(stderr,
					        "%s: %s: error at line %u\n",
					        progname, optarg, line);
					return EX_CONFIG;
				}

				missing = config_check(cfg, dkimf_config);
				if (missing != NULL)
				{
					fprintf(stderr,
					        "%s: %s: required parameter \"%s\" missing\n",
					        progname, optarg, missing);
					config_free(cfg);
					return EX_CONFIG;
				}
			}
			break;

		  default:
			return usage();
		}
	}

	if (optind != argc)
		return usage();

	if (dkim_ssl_version() != OPENSSL_VERSION_NUMBER)
	{
		fprintf(stderr,
		        "%s: incompatible OpenSSL versions (library = 0x%09lx, filter = %09lx)\n",
		        progname, dkim_ssl_version(), OPENSSL_VERSION_NUMBER);

		return EX_SOFTWARE;
	}

	memset(confstr, '\0', sizeof confstr);

	/* use what was found in the configuration file, if any */
	if (cfg != NULL)
	{
		char *tmp = NULL;

		dkimf_parseconfig2(cfg, "On-Default", "def", confstr,
		                   sizeof confstr);
		dkimf_parseconfig2(cfg, "On-BadSignature", "bad", confstr,
		                   sizeof confstr);
		dkimf_parseconfig2(cfg, "On-DNSError", "dns", confstr,
		                   sizeof confstr);
		dkimf_parseconfig2(cfg, "On-InternalError", "int", confstr,
		                   sizeof confstr);
		dkimf_parseconfig2(cfg, "On-NoSignature", "no", confstr,
		                   sizeof confstr);
		dkimf_parseconfig2(cfg, "On-SignatureMissing", "miss", confstr,
		                   sizeof confstr);
		dkimf_parseconfig2(cfg, "On-Security", "sec", confstr,
		                   sizeof confstr);

		if (peerfile == NULL)
		{
			(void) config_get(cfg, "PeerList", &peerfile,
			                  sizeof peerfile);
		}

		if (!autorestart)
		{
			(void) config_get(cfg, "AutoRestart", &autorestart,
			                  sizeof autorestart);
		}

		(void) config_get(cfg, "BodyLengths", &blen, sizeof blen);

		(void) config_get(cfg, "Diagnostics", &ztags, sizeof ztags);

#ifdef _FFR_ZTAGS
		(void) config_get(cfg, "DiagnosticDirectory", &diagdir,
		                  sizeof diagdir);
#endif /* _FFR_ZTAGS */

		if (be == NULL)
			(void) config_get(cfg, "Mode", &be, sizeof be);

		if (canonstr == NULL)
		{
			(void) config_get(cfg, "Canonicalization", &canonstr,
			                  sizeof canonstr);
		}

		if (domlist == NULL)
		{
			(void) config_get(cfg, "Domain", &tmp, sizeof tmp);
			if (tmp != NULL)
			{
				domlist = strdup(tmp);
				if (domlist == NULL)
				{
					fprintf(stderr,
					        "%s: strdup(): %s\n",
					        progname, strerror(errno));
					config_free(cfg);
					return EX_OSERR;
				}
			}
		}

		if (!subdomains)
		{
			(void) config_get(cfg, "SubDomains", &subdomains,
			                  sizeof subdomains);
		}

		if (dofork)
		{
			(void) config_get(cfg, "Background", &dofork,
			                  sizeof dofork);
		}

		if (!addxhdr)
		{
			(void) config_get(cfg, "X-Header", &addxhdr,
			                  sizeof addxhdr);
		}

		if (ilist == NULL)
		{
			(void) config_get(cfg, "InternalHosts", &ilist,
			                  sizeof ilist);
		}

		if (elist == NULL)
		{
			(void) config_get(cfg, "ExternalIgnoreList", &elist,
			                  sizeof elist);
		}

		if (keyfile == NULL)
		{
			(void) config_get(cfg, "KeyList", &keyfile,
			                  sizeof keyfile);
			if (keyfile != NULL)
				multikey = TRUE;
		}

		if (keyfile == NULL)
		{
			(void) config_get(cfg, "KeyFile", &keyfile,
			                  sizeof keyfile);
		}

		if (omitlist == NULL)
		{
			(void) config_get(cfg, "OmitHeaders", &omitlist,
			                  sizeof omitlist);
		}

		(void) config_get(cfg, "AlwaysSignHeaders", &alwayslist,
		                  sizeof alwayslist);

		if (!dolog)
			(void) config_get(cfg, "Syslog", &dolog, sizeof dolog);

		(void) config_get(cfg, "SyslogSuccess", &dolog_success,
		                  sizeof dolog_success);

		if (siglimit == NULL)
		{
			(void) config_get(cfg, "Minimum", &siglimit,
			                  sizeof siglimit);
		}

		if (mtalist == NULL)
		{
			(void) config_get(cfg, "MTA", &mtalist,
			                  sizeof mtalist);
		}

		if (macrolist == NULL)
		{
			(void) config_get(cfg, "MacroList", &macrolist,
			                  sizeof macrolist);
		}

		if (!gotp)
		{
			(void) config_get(cfg, "Socket", &sock, sizeof sock);
			if (sock != NULL)
			{
				gotp = TRUE;
				(void) smfi_setconn(sock);
			}
		}

		if (pidfile == NULL)
		{
			(void) config_get(cfg, "PidFile", &pidfile,
			                  sizeof pidfile);
		}

		if (!quarantine)
		{
			(void) config_get(cfg, "Quarantine", &quarantine,
			                  sizeof quarantine);
		}

#if _FFR_REQUIRED_HEADERS
		if (!req_hdrs)
		{
			(void) config_get(cfg, "RequiredHeaders", &req_hdrs,
			                  sizeof req_hdrs);
		}
#endif /* _FFR_REQUIRED_HEADERS */

		if (!send_reports)
		{
			(void) config_get(cfg, "SendReports", &send_reports,
			                  sizeof send_reports);
		}

		if (selector == NULL)
		{
			(void) config_get(cfg, "Selector", &selector,
			                  sizeof selector);
		}

		if (signalgstr == NULL)
		{
			(void) config_get(cfg, "SignatureAlgorithm",
			                  &signalgstr, sizeof signalgstr);
		}

		if (tmo == DEFTIMEOUT)
			(void) config_get(cfg, "DNSTimeout", &tmo, sizeof tmo);

		maxsign = -1;
		(void) config_get(cfg, "MaximumSignedBytes", &maxsign,
		                  sizeof maxsign);
		if (maxsign != -1)
		{
			signbytes = (long) maxsign;
			blen = TRUE;
		}

		if (become == NULL)
		{
			(void) config_get(cfg, "Userid", &become,
			                  sizeof become);
		}

#if POPAUTH
		if (popdbfile == NULL)
		{
			(void) config_get(cfg, "POPDBFile", &popdbfile,
			                  sizeof popdbfile);
		}
#endif /* POPAUTH */

		(void) config_get(cfg, "SignHeaders", &signhdrs,
		                  sizeof signhdrs);

#if _FFR_VBR
		(void) config_get(cfg, "VBR-Type", &vbr_deftype,
		                  sizeof vbr_deftype);
		(void) config_get(cfg, "VBR-Certifiers", &vbr_defcert,
		                  sizeof vbr_defcert);

		tmp = NULL;
		(void) config_get(cfg, "VBR-TrustedCertifiers", &tmp,
		                  sizeof tmp);
		if (tmp != NULL)
		{
			for (p = tmp, c = 1; *p != '\0'; p++)
			{
				if (*p == ':' || *p == ',')
					c++;
			}

			vbr_trusted = (char **) malloc(sizeof(char *) * (c + 1));
			if (vbr_trusted == NULL)
			{
				fprintf(stderr, "%s: malloc(): %s\n",
				        progname, strerror(errno));
				return EX_OSERR;
			}

			for (p = strtok(tmp, ",:"), c = 0;
			     p != NULL;
			     p = strtok(NULL, ",:"), c++)
			{
				vbr_trusted[c] = p;
				vbr_trusted[c + 1] = NULL;
			}
		}
#endif /* _FFR_VBR */

		(void) config_get(cfg, "TestPublicKeys",
		                  &testpubkeys, sizeof testpubkeys);

#ifdef _FFR_STATS
		(void) config_get(cfg, "Statistics", &statspath,
		                  sizeof statspath);
#endif /* _FFR_STATS */

		(void) config_get(cfg, "ClockDrift", &clockdrift,
		                  sizeof clockdrift);

		(void) config_get(cfg, "SignatureTTL", &sigttl, sizeof sigttl);

		(void) config_get(cfg, "RemoveARFrom", &remarlist,
		                  sizeof remarlist);

		(void) config_get(cfg, "RemoveARAll", &remarall,
		                  sizeof remarall);

		(void) config_get(cfg, "RemoveOldSignatures", &remsigs,
		                  sizeof remsigs);

		(void) config_get(cfg, "UseSSPDeny", &use_ssp_deny,
		                  sizeof use_ssp_deny);

		(void) config_get(cfg, "StrictTestMode", &stricttest,
		                  sizeof stricttest);

		(void) config_get(cfg, "UMask", &filemask, sizeof filemask);

#ifdef _FFR_SELECTOR_HEADER
		(void) config_get(cfg, "SelectorHeader", &selectorhdr,
		                  sizeof selectorhdr);
#endif /* _FFR_SELECTOR_HEADER */

#ifdef _FFR_REPLACE_RULES
		(void) config_get(cfg, "ReplaceRules", &repfile,
		                  sizeof repfile);
#endif /* _FFR_REPLACE_RULES */

#ifdef QUERY_CACHE
		(void) config_get(cfg, "QueryCache", &querycache,
		                  sizeof querycache);
#endif /* QUERY_CACHE */

		(void) config_get(cfg, "MaximumHeaders", &maxhdrsz,
		                  sizeof maxhdrsz);
	}

#ifndef SMFIF_QUARANTINE
	if (quarantine)
	{
		fprintf(stderr, "%s: quarantine service not available\n",
		        progname);
		return EX_SOFTWARE;
	}
#endif /* ! SMFIF_QUARANTINE */

	if (config == NULL && confstr[0] != '\0')
		config = confstr;
	if (!dkimf_parseconfig(config))
		return EX_USAGE;

	if (!gotp && !testmode)
	{
		fprintf(stderr, "%s: milter socket must be specified\n",
		        progname);
		if (argc == 1)
			fprintf(stderr, "\t(use \"-?\" for help)\n");
		return EX_CONFIG;
	}

	if (siglimit != NULL)
	{
		errno = 0;

		if (siglimit[0] == '-')
		{
			tmpl = ULONG_MAX;
			errno = ERANGE;
		}

		tmpl = strtoul(siglimit, &p, 10);
		if (tmpl > UINT_MAX || errno != 0)
		{
			fprintf(stderr, "%s: invalid value for -L: `%s'\n",
			        progname, siglimit);
			return EX_USAGE;
		}

		sigmin = (unsigned int) tmpl;

		if (*p == '%')
		{
			if (sigmin > 100)
			{
				fprintf(stderr,
				        "%s: invalid value for -L: `%s'\n",
				        progname, siglimit);
				return EX_USAGE;
			}

			sigmintype = SIGMIN_PERCENT;
		}
		else if (*p == '+')
		{
			sigmintype = SIGMIN_MAXADD;
		}
		else if (*p != '\0')
		{
			fprintf(stderr, "%s: invalid value for -L: `%s'\n",
			        progname, siglimit);
			return EX_USAGE;
		}
	}

	if (be == NULL)
	{
		mode = (testmode ? DKIMF_MODE_VERIFIER : DKIMF_MODE_DEFAULT);
	}
	else
	{
		mode = 0;

		for (p = be; *p != '\0'; p++)
		{
			switch (*p)
			{
			  case 's':
				mode |= DKIMF_MODE_SIGNER;
				break;

			  case 'v':
				mode |= DKIMF_MODE_VERIFIER;
				break;

			  default:
				fprintf(stderr, "%s: unknown role flag '%c'\n",
				        progname, *p);
				return EX_USAGE;
			}
		}
	}

	if ((mode & DKIMF_MODE_SIGNER) != 0 &&
	    ((multikey && keyfile == NULL) ||
	     (!multikey && (keyfile == NULL || selector == NULL))))
	{
		fprintf(stderr,
		        "%s: at least one selector and key required for signing mode\n",
		        progname);
		return EX_USAGE;
	}

	if (canonstr != NULL)
	{
		p = strchr(canonstr, '/');
		if (p == NULL)
		{
			hdrcanon = dkimf_configlookup(canonstr, dkimf_canon);
			if (hdrcanon == -1)
			{
				fprintf(stderr,
				        "%s: unknown canonicalization \"%s\"\n",
				        progname, canonstr);
				return EX_USAGE;
			}

			bodycanon = DKIM_CANON_DEFAULT;
		}
		else
		{
			*p = '\0';

			hdrcanon = dkimf_configlookup(canonstr, dkimf_canon);
			if (hdrcanon == -1)
			{
				fprintf(stderr,
				        "%s: unknown canonicalization \"%s\"\n",
				        progname, canonstr);
				return EX_USAGE;
			}

			bodycanon = dkimf_configlookup(p + 1, dkimf_canon);
			if (bodycanon == -1)
			{
				fprintf(stderr,
				        "%s: unknown canonicalization \"%s\"\n",
				        progname, p + 1);
				return EX_USAGE;
			}

			*p = '/';
		}
	}

	if (signalgstr != NULL)
	{
		signalg = dkimf_configlookup(signalgstr, dkimf_sign);
		if (signalg == -1)
		{
			fprintf(stderr,
			        "%s: unknown signature algorithm \"%s\"\n",
			        progname, signalgstr);
			return EX_USAGE;
		}
	}
	else
	{
		signalg = DKIM_SIGN_DEFAULT;
	}

	if (domlist != NULL)
	{
		int n;

		bool makepats = FALSE;

		if (domlist[0] == '/' && access(domlist, F_OK) == 0)
		{
			int nalloc = 0;
			FILE *f;
			char line[BUFRSZ + 1];

			n = 0;

			f = fopen(domlist, "r");
			if (f == NULL)
			{
				fprintf(stderr, "%s: %s: fopen(): %s\n",
				        progname, domlist, strerror(errno));
				return EX_UNAVAILABLE;
			}

			memset(line, '\0', sizeof line);
			while (fgets(line, BUFRSZ, f) != NULL)
			{
				for (p = line; *p != '\0'; p++)
				{
					if (*p == '\n' || *p == '#')
					{
						*p = '\0';
						break;
					}
				}

				dkimf_trimspaces(line);
				if (strlen(line) == 0)
					continue;

				if (nalloc <= n)
				{
					if (nalloc == 0)
					{
						domains = (char **) malloc(2 * sizeof(char *));
						nalloc = 1;
					}
					else
					{
						domains = (char **) realloc(domains,
						                            (nalloc * 2 + 1) * sizeof(char *));
						nalloc = nalloc * 2;
					}

					if (domains == NULL)
					{
						fprintf(stderr,
						        "%s: malloc(): %s\n",
						        progname,
						        strerror(errno));
						return EX_UNAVAILABLE;
					}
				}

				domains[n] = strdup(line);
				if (domains[n] == NULL)
				{
					fprintf(stderr, "%s: strdup(): %s\n",
					        progname, strerror(errno));
					return EX_UNAVAILABLE;
				}

				if (strchr(domains[n], '*') != NULL)
					makepats = TRUE;

				n++;
			}

			if (domains != NULL)
				domains[n] = NULL;

			fclose(f);
		}
		else
		{
			n = 1;

			for (p = domlist; *p != '\0'; p++)
			{
				if (*p == ',')
					n++;
			}

			domains = (char **) malloc((n + 1) * sizeof(char *));
			if (domains == NULL)
			{
				fprintf(stderr, "%s: malloc(): %s\n",
				        progname, strerror(errno));
				return EX_UNAVAILABLE;
			}

			n = 0;

			for (p = strtok(domlist, ",");
			     p != NULL;
			     p = strtok(NULL, ","))
			{
				domains[n] = p;

				if (strchr(domains[n], '*') != NULL)
					makepats = TRUE;

				n++;
			}

			domains[n] = NULL;
		}

		if (makepats)
		{
			char *end;
			char *q;
			char patbuf[BUFRSZ + 1];

			dompats = (regex_t **) malloc ((n + 1) * sizeof (regex_t *));
			if (dompats == NULL)
			{
				fprintf(stderr, "%s: malloc(): %s\n",
				        progname, strerror(errno));
				return EX_UNAVAILABLE;
			}
			memset(dompats, '\0', (n + 1) * sizeof (regex_t *));

			for (c = 0; c < n; c++)
			{
				memset(patbuf, '\0', sizeof patbuf);
				end = patbuf + sizeof patbuf;
				patbuf[0] = '^';

				for (p = domains[c], q = patbuf + 1;
				     *p != '\0' && q < end;
				     p++)
				{
					switch (*p)
					{
					  case '*':
						*q = '.';
						q++;
						*q = '*';
						q++;
						break;

					  case '.':
						*q = '\\';
						q++;
						*q = '.';
						q++;
						break;

					  default:
						*q = *p;
						q++;
						break;
					}
				}

				*q++ = '$';
				if (q >= end)
				{
					fprintf(stderr,
					        "%s: regular expression for \"%s\" too large\n",
					        progname, domains[c]);
					return EX_UNAVAILABLE;
				}

				dompats[c] = (regex_t *) malloc(sizeof(regex_t));
				if (dompats[c] == NULL)
				{
					fprintf(stderr, "%s: malloc(): %s\n",
					        progname, strerror(errno));
					return EX_UNAVAILABLE;
				}

				status = regcomp(dompats[c], patbuf,
				                 (REG_EXTENDED|REG_ICASE));
				if (status != 0)
				{
					(void) regerror(status, dompats[c],
					                patbuf, sizeof patbuf);
					fprintf(stderr, "%s: regcomp(): %s\n",
					        progname, patbuf);
					return EX_UNAVAILABLE;
				}
			}
		}
	}

	if (omitlist != NULL)
	{
		int n = 1;

		for (p = omitlist; *p != '\0'; p++)
		{
			if (*p == ',')
				n++;
		}

		omithdrs = (char **) malloc((n + 1) * sizeof(char *));
		if (omithdrs == NULL)
		{
			fprintf(stderr, "%s: malloc(): %s\n",
				progname, strerror(errno));
			return EX_UNAVAILABLE;
		}
		
		n = 0;

		for (p = strtok(omitlist, ",");
		     p != NULL;
		     p = strtok(NULL, ","))
			   omithdrs[n++] = p;

		omithdrs[n] = NULL;
	}

	if (alwayslist != NULL)
	{
		int n = 1;

		for (p = alwayslist; *p != '\0'; p++)
		{
			if (*p == ',')
				n++;
		}

		alwayshdrs = (char **) malloc((n + 1) * sizeof(char *));
		if (alwayshdrs == NULL)
		{
			fprintf(stderr, "%s: malloc(): %s\n",
				progname, strerror(errno));
			return EX_UNAVAILABLE;
		}
		
		n = 0;

		for (p = strtok(alwayslist, ",");
		     p != NULL;
		     p = strtok(NULL, ","))
			   alwayshdrs[n++] = p;

		alwayshdrs[n] = NULL;
	}

	if (macrolist != NULL)
	{
		int n = 1;
		char *macrocopy;

		for (p = macrolist; *p != '\0'; p++)
		{
			if (*p == ',')
				n++;
		}

		macros = (char **) malloc((n + 1) * sizeof(char *));
		values = (char **) malloc((n + 1) * sizeof(char *));
		macrocopy = strdup(macrolist);

		if (macros == NULL || values == NULL || macrocopy == NULL)
		{
			fprintf(stderr, "%s: malloc(): %s\n",
			        progname, strerror(errno));
			return EX_UNAVAILABLE;
		}

		n = 0;
		for (p = strtok(macrocopy, ",");
		     p != NULL;
		     p = strtok(NULL, ","))
		{
			macros[n] = p;
			values[n] = strchr(p, '=');
			if (values[n] != NULL)
			{
				*(values[n]) = '\0';
				values[n] += 1;
			}
			n++;
		}
		macros[n] = NULL;
		values[n] = NULL;
	}

	if (mtalist != NULL)
	{
		int n = 1;

		for (p = mtalist; *p != '\0'; p++)
		{
			if (*p == ',')
				n++;
		}

		mtas = (char **) malloc((n + 1) * sizeof(char *));
		if (mtas == NULL)
		{
			fprintf(stderr, "%s: malloc(): %s\n",
			        progname, strerror(errno));
			return EX_UNAVAILABLE;
		}

		n = 0;

		for (p = strtok(mtalist, ",");
		     p != NULL;
		     p = strtok(NULL, ","))
			mtas[n++] = p;

		mtas[n] = NULL;
	}

	if (remarlist != NULL)
	{
		int n = 1;

		for (p = remarlist; *p != '\0'; p++)
		{
			if (*p == ',')
				n++;
		}

		remar = (char **) malloc((n + 1) * sizeof(char *));
		if (remar == NULL)
		{
			fprintf(stderr, "%s: malloc(): %s\n",
			        progname, strerror(errno));
			return EX_UNAVAILABLE;
		}

		n = 0;

		for (p = strtok(remarlist, ",");
		     p != NULL;
		     p = strtok(NULL, ","))
			remar[n++] = p;

		remar[n] = NULL;
	}

	/* peer list */
	if (peerfile != NULL && !testmode)
	{
		FILE *f;

		f = fopen(peerfile, "r");
		if (f == NULL)
		{
			fprintf(stderr, "%s: %s: fopen(): %s\n", progname,
			        peerfile, strerror(errno));
			return EX_UNAVAILABLE;
		}

		if (!dkimf_load_list(f, NULL, &peerlist))
		{
			fclose(f);
			return EX_UNAVAILABLE;
		}

		fclose(f);
	}

	/* internal list */
	if (ilist != NULL && !testmode)
	{
		FILE *f;

		f = fopen(ilist, "r");
		if (f == NULL)
		{
			fprintf(stderr, "%s: %s: fopen(): %s\n", progname,
			        ilist, strerror(errno));
			return EX_UNAVAILABLE;
		}

		if (!dkimf_load_list(f, NULL, &internal))
		{
			fclose(f);
			return EX_UNAVAILABLE;
		}

		fclose(f);
	}
	else
	{
		if (!dkimf_load_list(NULL, defilist, &internal))
			return EX_UNAVAILABLE;
	}

	/* external ignore list */
	if (elist != NULL && !testmode)
	{
		FILE *f;

		f = fopen(elist, "r");
		if (f == NULL)
		{
			fprintf(stderr, "%s: %s: fopen(): %s\n", progname,
			        elist, strerror(errno));
			return EX_UNAVAILABLE;
		}

		if (!dkimf_load_list(f, NULL, &exignore))
		{
			fclose(f);
			return EX_UNAVAILABLE;
		}

		fclose(f);
	}

	/* suppress a bunch of things if we're in test mode */
	if (testmode)
	{
		dolog = FALSE;
		autorestart = FALSE;
		dofork = FALSE;
		become = NULL;
		pidfile = NULL;
	}

#ifdef _FFR_REPLACE_RULES
	/* replacement list */
	if (repfile != NULL)
	{
		FILE *f;

		f = fopen(repfile, "r");
		if (f == NULL)
		{
			fprintf(stderr, "%s: %s: fopen(): %s\n", progname,
			        repfile, strerror(errno));
			return EX_UNAVAILABLE;
		}

		if (!dkimf_load_replist(f, &replist))
		{
			fclose(f);
			return EX_UNAVAILABLE;
		}

		fclose(f);
	}
#endif /* _FFR_REPLACE_RULES */

	/* activate logging */
	if (dolog)
	{
#ifdef LOG_MAIL
		openlog(progname, LOG_PID, LOG_MAIL);
#else /* LOG_MAIL */
		openlog(progname, LOG_PID);
#endif /* LOG_MAIL */
	}

	dkimf_setmaxfd();

	/* change user if appropriate */
	if (become != NULL)
	{
		gid_t gid;
		char *colon;
		struct passwd *pw;
		struct group *gr = NULL;

		/* see if there was a group specified; if so, validate */
		colon = strchr(become, ':');
		if (colon != NULL)
		{
			*colon = '\0';

			gr = getgrnam(colon + 1);
			if (gr == NULL)
			{
				char *q;

				gid = (gid_t) strtol(colon + 1, &q, 10);
				if (*q == '\0')
					gr = getgrgid(gid);

				if (gr == NULL)
				{
					if (dolog)
					{
						syslog(LOG_ERR,
						       "no such group or gid `%s'",
						       colon + 1);
					}

					fprintf(stderr,
					        "%s: no such group `%s'\n",
					        progname, colon + 1);

					return EX_DATAERR;
				}
			}
		}

		/* validate the user */
		pw = getpwnam(become);
		if (pw == NULL)
		{
			char *q;
			uid_t uid;

			uid = (uid_t) strtol(become, &q, 10);
			if (*q == '\0')
				pw = getpwuid(uid);

			if (pw == NULL)
			{
				if (dolog)
				{
					syslog(LOG_ERR,
					       "no such user or uid `%s'",
					       become);
				}

				fprintf(stderr, "%s: no such user `%s'\n",
				        progname, become);

				return EX_DATAERR;
			}
		}

		if (gr == NULL)
			gid = pw->pw_gid;

		/* make all the process changes */
		if (getuid() != pw->pw_uid)
		{
			if (initgroups(pw->pw_name, gid) != 0)
			{
				if (dolog)
				{
					syslog(LOG_ERR, "initgroups(): %s",
					       strerror(errno));
				}

				fprintf(stderr, "%s: initgroups(): %s\n",
				        progname, strerror(errno));

				return EX_NOPERM;
			}
			else if (setgid(gid) != 0)
			{
				if (dolog)
				{
					syslog(LOG_ERR, "setgid(): %s",
					       strerror(errno));
				}

				fprintf(stderr, "%s: setgid(): %s\n", progname,
				        strerror(errno));

				return EX_NOPERM;
			}
			else if (setuid(pw->pw_uid) != 0)
			{
				if (dolog)
				{
					syslog(LOG_ERR, "setuid(): %s",
					       strerror(errno));
				}

				fprintf(stderr, "%s: setuid(): %s\n", progname,
				        strerror(errno));

				return EX_NOPERM;
			}
		}

		(void) endpwent();
	}

	/* load the secret key, if one was specified */
	if (keyfile != NULL && multikey)
	{
		status = dkimf_loadkeys(keyfile);

		if (status != 0)
			return EX_UNAVAILABLE;
	}
	else if (keyfile != NULL)
	{
		int fd;
		size_t rlen;
		struct stat s;

		status = stat(keyfile, &s);
		if (status != 0)
		{
			if (dolog)
			{
				int saveerrno;

				saveerrno = errno;

				syslog(LOG_ERR, "%s: stat(): %s", keyfile,
				       strerror(errno));

				errno = saveerrno;
			}

			fprintf(stderr, "%s: %s: stat(): %s\n", progname,
			        keyfile, strerror(errno));

			return EX_UNAVAILABLE;
		}

		s33krit = malloc(s.st_size + 1);
		if (s33krit == NULL)
		{
			if (dolog)
			{
				int saveerrno;

				saveerrno = errno;

				syslog(LOG_ERR, "malloc(): %s", 
				       strerror(errno));

				errno = saveerrno;
			}

			fprintf(stderr, "%s: malloc(): %s\n", progname,
			        strerror(errno));

			return EX_UNAVAILABLE;
		}
		keylen = s.st_size + 1;

		fd = open(keyfile, O_RDONLY, 0);
		if (fd < 0)
		{
			if (dolog)
			{
				int saveerrno;

				saveerrno = errno;

				syslog(LOG_ERR, "%s: open(): %s", keyfile,
				       strerror(errno));

				errno = saveerrno;
			}

			fprintf(stderr, "%s: %s: open(): %s\n", progname,
			        keyfile, strerror(errno));

			free(s33krit);
			return EX_UNAVAILABLE;
		}

		rlen = read(fd, s33krit, s.st_size + 1);
		if (rlen == -1)
		{
			if (dolog)
			{
				int saveerrno;

				saveerrno = errno;

				syslog(LOG_ERR, "%s: read(): %s", keyfile,
				       strerror(errno));

				errno = saveerrno;
			}

			fprintf(stderr, "%s: %s: read(): %s\n", progname,
			        keyfile, strerror(errno));

			close(fd);
			free(s33krit);
			return EX_UNAVAILABLE;
		}
		else if (rlen != s.st_size)
		{
			if (dolog)
			{
				syslog(LOG_ERR, "%s: read() wrong size (%u)",
				       keyfile, rlen);
			}

			fprintf(stderr, "%s: %s: read() wrong size (%u)\n",
			        progname, keyfile, rlen);

			close(fd);
			free(s33krit);
			return EX_UNAVAILABLE;
		}

		close(fd);
		s33krit[s.st_size] = '\0';
		seckey = s33krit;
	}

	die = FALSE;

	if (autorestart)
	{
		bool quitloop = FALSE;
		int status;
		pid_t pid;
		pid_t wpid;
		struct sigaction sa;

		if (dofork)
		{
			pid = fork();
			switch (pid)
			{
			  case -1:
				if (dolog)
				{
					int saveerrno;

					saveerrno = errno;

					syslog(LOG_ERR, "fork(): %s",
					       strerror(errno));

					errno = saveerrno;
				}

				fprintf(stderr, "%s: fork(): %s\n",
				        progname, strerror(errno));

				dkimf_zapkey();
				return EX_OSERR;

			  case 0:
				dkimf_stdio();
				break;

			  default:
				dkimf_zapkey();
				return EX_OK;
			}
		}

		if (pidfile != NULL)
		{
			f = fopen(pidfile, "w");
			if (f != NULL)
			{
				fprintf(f, "%ld\n", (long) getpid());
				(void) fclose(f);
			}
			else
			{
				if (dolog)
				{
					syslog(LOG_ERR,
					       "can't write pid to %s: %s",
					       pidfile, strerror(errno));
				}
			}
		}

		sa.sa_handler = dkimf_sighandler;
		/* XXX -- HAHAHAH => sa.sa_sigaction = NULL; */
		sigemptyset(&sa.sa_mask);
		sigaddset(&sa.sa_mask, SIGHUP);
		sigaddset(&sa.sa_mask, SIGINT);
		sigaddset(&sa.sa_mask, SIGTERM);
		sa.sa_flags = 0;

		if (sigaction(SIGHUP, &sa, NULL) != 0 ||
		    sigaction(SIGINT, &sa, NULL) != 0 ||
		    sigaction(SIGTERM, &sa, NULL) != 0)
		{
			if (dolog)
			{
				syslog(LOG_ERR, "[parent] sigaction(): %s",
				       strerror(errno));
			}
		}

		while (!quitloop)
		{
			if (dkimf_socket_cleanup(sock) != 0)
			{
				syslog(LOG_ERR,
				       "[parent] socket cleanup failed: %s",
				       strerror(errno));
				return EX_UNAVAILABLE;
			}

			pid = fork();
			switch (pid)
			{
			  case -1:
				if (dolog)
				{
					syslog(LOG_ERR, "fork(): %s",
					       strerror(errno));
				}

				dkimf_zapkey();
				return EX_OSERR;

			  case 0:
				sa.sa_handler = SIG_DFL;

				if (sigaction(SIGHUP, &sa, NULL) != 0 ||
				    sigaction(SIGINT, &sa, NULL) != 0 ||
				    sigaction(SIGTERM, &sa, NULL) != 0)
				{
					if (dolog)
					{
						syslog(LOG_ERR,
						       "[child] sigaction(): %s",
						       strerror(errno));
					}
				}

				quitloop = TRUE;
				break;

			  default:
				for (;;)
				{
					wpid = wait(&status);

					if (wpid == -1 && errno == EINTR)
					{
						if (die)
						{
							dkimf_killchild(pid,
							              diesig);
							dkimf_zapkey();

							if (pidfile != NULL)
								(void) unlink(pidfile);

							exit(EX_OK);
						}
					}

					if (pid != wpid)
						continue;

					if (wpid != -1 && dolog)
					{
						if (WIFSIGNALED(status))
						{
							syslog(LOG_NOTICE,
							       "terminated with signal %d, restarting",
							       WTERMSIG(status));
						}
						else if (WIFEXITED(status))
						{
							syslog(LOG_NOTICE,
							       "exited with status %d, restarting",
							       WEXITSTATUS(status));
						}
					}

					break;
				}
				break;
			}
		}
	}

	if (filemask != -1)
		(void) umask((mode_t) filemask);

	if (!testmode)
	{
		/* register with the milter interface */
		if (smfi_register(smfilter) == MI_FAILURE)
		{
			if (dolog)
				syslog(LOG_ERR, "smfi_register() failed");

			fprintf(stderr, "%s: smfi_register() failed\n",
			        progname);

			dkimf_zapkey();

			if (!autorestart && pidfile != NULL)
				(void) unlink(pidfile);

			return EX_UNAVAILABLE;
		}

		/* try to establish the milter socket */
		if (smfi_opensocket(FALSE) == MI_FAILURE)
		{
			if (dolog)
				syslog(LOG_ERR, "smfi_opensocket() failed");

			fprintf(stderr, "%s: smfi_opensocket() failed\n",
			        progname);

			dkimf_zapkey();

			return EX_UNAVAILABLE;
		}
	}

	if (!autorestart && dofork)
	{
		pid_t pid;

		pid = fork();
		switch (pid)
		{
		  case -1:
			if (dolog)
			{
				int saveerrno;

				saveerrno = errno;

				syslog(LOG_ERR, "fork(): %s", strerror(errno));

				errno = saveerrno;
			}

			fprintf(stderr, "%s: fork(): %s\n", progname,
			        strerror(errno));

			dkimf_zapkey();

			return EX_OSERR;

		  case 0:
			dkimf_stdio();
			break;

		  default:
			dkimf_zapkey();
			return EX_OK;
		}
	}

	/* write out the pid */
	if (!autorestart && pidfile != NULL)
	{
		f = fopen(pidfile, "w");
		if (f != NULL)
		{
			fprintf(f, "%ld\n", (long) getpid());
			(void) fclose(f);
		}
		else
		{
			if (dolog)
			{
				syslog(LOG_ERR, "can't write pid to %s: %s",
				       pidfile, strerror(errno));
			}
		}
	}

	ERR_load_crypto_strings();

	/* initialize the DKIM package */
	libdkim = dkim_init(NULL, NULL);
	if (libdkim == NULL)
	{
		if (dolog)
			syslog(LOG_ERR, "can't initialize DKIM library");

		dkimf_zapkey();

		if (!autorestart && pidfile != NULL)
			(void) unlink(pidfile);

		return EX_UNAVAILABLE;
	}
	else
	{
		u_int opts;

		(void) dkim_options(libdkim, DKIM_OP_GETOPT, DKIM_OPTS_FLAGS,
		                    &opts, sizeof opts);
		opts |= DKIM_LIBFLAGS_ACCEPTV05;
#ifdef QUERY_CACHE
		if (querycache)
		{
			opts |= DKIM_LIBFLAGS_CACHE;
			(void) time(&cache_lastlog);
		}
#endif /* QUERY_CACHE */
		(void) dkim_options(libdkim, DKIM_OP_SETOPT, DKIM_OPTS_FLAGS,
		                    &opts, sizeof opts);
	}

#ifdef USE_ARLIB
	/* set the DNS callback */
	(void) dkim_set_dns_callback(dfc->mctx_dkim, dkimf_sendprogress,
	                             CBINTERVAL);
#endif /* USE_ARLIB */

	(void) dkim_options(libdkim, DKIM_OP_SETOPT, DKIM_OPTS_SKIPHDRS,
	                    (void *) should_not_signhdrs, sizeof (u_char **));

	(void) dkim_options(libdkim, DKIM_OP_SETOPT, DKIM_OPTS_TIMEOUT,
	                    &tmo, sizeof tmo);

	if (sigttl != 0)
	{
		time_t sigtime = (time_t) sigttl;

		(void) dkim_options(libdkim, DKIM_OP_SETOPT,
		                    DKIM_OPTS_SIGNATURETTL, &sigtime,
		                    sizeof sigtime);
	}

	if (clockdrift != 0)
	{
		time_t drift = (time_t) clockdrift;

		(void) dkim_options(libdkim, DKIM_OP_SETOPT,
		                    DKIM_OPTS_CLOCKDRIFT, &drift,
		                    sizeof drift);
	}

	if (send_reports || DKIM_DEBUG('c') || blen || ztags)
	{
		u_int opts;

		(void) dkim_options(libdkim, DKIM_OP_GETOPT, DKIM_OPTS_FLAGS,
		                    &opts, sizeof opts);
		opts |= DKIM_LIBFLAGS_TMPFILES;
		if (DKIM_DEBUG('c'))
			opts |= DKIM_LIBFLAGS_KEEPFILES;
		if (blen)
			opts |= DKIM_LIBFLAGS_SIGNLEN;
		if (ztags)
			opts |= DKIM_LIBFLAGS_ZTAGS;
		(void) dkim_options(libdkim, DKIM_OP_SETOPT, DKIM_OPTS_FLAGS,
		                    &opts, sizeof opts);
	}

	if (omithdrs != NULL)
	{
		status = dkim_options(libdkim, DKIM_OP_SETOPT,
		                      DKIM_OPTS_SKIPHDRS,
		                      omithdrs, sizeof omithdrs);
		if (status != DKIM_STAT_OK)
		{
			fprintf(stderr,
			        "%s: dkim_options(DKIM_OPTS_SKIPHDRS) returned %d\n",
			        progname, status);

			if (!autorestart && pidfile != NULL)
				(void) unlink(pidfile);

			return EX_SOFTWARE;
		}
	}

	if (alwayshdrs != NULL)
	{
		status = dkim_options(libdkim, DKIM_OP_SETOPT,
		                      DKIM_OPTS_ALWAYSHDRS,
		                      alwayshdrs, sizeof alwayshdrs);
		if (status != DKIM_STAT_OK)
		{
			fprintf(stderr,
			        "%s: dkim_options(DKIM_OPTS_ALWAYSHDRS) returned %d\n",
			        progname, status);

			if (!autorestart && pidfile != NULL)
				(void) unlink(pidfile);

			return EX_SOFTWARE;
		}
	}

	if (signhdrs != NULL)
	{
		size_t len;
		char **signhdrlist;

		c = 1;
		for (p = signhdrs; p != NULL; p = strchr(p + 1, ','))
			c++;

		len = (c + 1) * sizeof(char *);
		signhdrlist = malloc(len);
		if (signhdrlist == NULL)
		{
			fprintf(stderr, "%s: malloc(): %s\n",
			        progname, strerror(errno));

			if (!autorestart && pidfile != NULL)
				(void) unlink(pidfile);

			return EX_OSERR;
		}

		memset(signhdrlist, '\0', len);
		for (c = 0, p = strtok(signhdrs, ",");
		     p != NULL;
		     c++, p = strtok(NULL, ","))
			signhdrlist[c] = p;

		status = dkim_options(libdkim, DKIM_OP_SETOPT,
		                      DKIM_OPTS_SIGNHDRS, signhdrlist,
		                      sizeof signhdrlist);
	}

	if (testpubkeys != NULL)
	{
		dkim_query_t qtype = DKIM_QUERY_FILE;

		(void) dkim_options(libdkim, DKIM_OP_SETOPT,
		                    DKIM_OPTS_QUERYMETHOD,
		                    &qtype, sizeof qtype);
		(void) dkim_options(libdkim, DKIM_OP_SETOPT,
		                    DKIM_OPTS_QUERYINFO,
		                    testpubkeys, strlen(testpubkeys));
	}

#if VERIFY_DOMAINKEYS
	libdk = dk_init(NULL, NULL);
	if (libdk == NULL)
	{
		if (dolog)
			syslog(LOG_ERR, "can't initialize DK library");

		if (!autorestart && pidfile != NULL)
			(void) unlink(pidfile);

		return EX_UNAVAILABLE;
	}
#endif /* VERIFY_DOMAINKEYS */

	/* perform test mode */
	if (testfile != NULL)
	{
		status = dkimf_testfile(libdkim, testfile, fixedtime,
		                        stricttest, verbose);
		dkim_close(libdkim);
		return status;
	}

	pthread_mutex_init(&popen_lock, NULL);

	memset(argstr, '\0', sizeof argstr);
	end = &argstr[sizeof argstr - 1];
	n = sizeof argstr;
	for (c = 1, p = argstr; c < argc && p < end; c++)
	{
		if (strchr(argv[c], ' ') != NULL)
		{
			status = snprintf(p, n, "%s \"%s\"",
			                  c == 1 ? "args:" : "",
			                  argv[c]);
		}
		else
		{
			status = snprintf(p, n, "%s %s",
			                  c == 1 ? "args:" : "",
			                  argv[c]);
		}

		p += status;
		n -= status;
	}

#if POPAUTH
	if (popdbfile != NULL)
	{
		status = dkimf_initpopauth();
		if (status != 0)
		{
			fprintf(stderr, "%s: can't initialize mutex: %s\n",
			        progname, strerror(status));
			syslog(LOG_ERR, "can't initialize mutex: %s",
			       popdbfile);
		}

		status = 0;

# if DB_VERSION_MAJOR > 2
		status = db_create(&popdb, NULL, 0);
		if (status == 0)
		{
#  if DB_VERSION_MAJOR > 3
			status = popdb->open(popdb, NULL, popdbfile, NULL,
			                     DB_UNKNOWN, (DB_RDONLY|DB_THREAD),
			                     0);
#  else /* DB_VERSION_MAJOR > 3 */
			status = popdb->open(popdb, popdbfile, NULL, DB_UNKNOWN,
			                     (DB_RDONLY|DB_THREAD), 0);
#  endif /* DB_VERSION_MAJOR > 3 */
		}
# elif DB_VERSION_MAJOR == 2
		status = db_open(popdbfile, DB_HASH, DB_RDONLY, DB_MODE, NULL,
		                 NULL, &popdb);
# else /* DB_VERSION_MAJOR < 2 */
		popdb = dbopen(popdbfile, O_RDONLY, DB_HASH, NULL);
		if (popdb == NULL)
			status = errno;
# endif /* DB_VERSION_MAJOR */
		if (status != 0)
		{
			fprintf(stderr, "%s: can't open database %s: %s\n",
			        progname, popdbfile, db_strerror(errno));
			syslog(LOG_ERR, "can't open database %s",
			       popdbfile);

			dkimf_zapkey();

			if (!autorestart && pidfile != NULL)
				(void) unlink(pidfile);

			return EX_UNAVAILABLE;
		}
	}
#endif /* POPAUTH */

#ifdef _FFR_STATS
	dkimf_stats_init();
#endif /* _FFR_STATS */

	if (dolog)
	{
		syslog(LOG_INFO, "%s v%s starting (%s)", DKIMF_PRODUCT,
		       DKIMF_VERSION, argstr);
	}

	/* call the milter mainline */
	errno = 0;
	status = smfi_main();

	if (dolog)
	{
		syslog(LOG_INFO,
		       "%s v%s terminating with status %d, errno = %d",
		       DKIMF_PRODUCT, DKIMF_VERSION, status, errno);
	}

#if POPAUTH
	if (popdb != NULL)
	{
# if DB_VERSION_MAJOR < 2
		(void) popdb->close(popdb);
# else /* DB_VERSION_MAJOR < 2 */
		(void) popdb->close(popdb, 0);
# endif /* DB_VERSION_MAJOR */
	}
#endif /* POPAUTH */

	dkim_close(libdkim);

	dkimf_zapkey();

	if (!autorestart && pidfile != NULL)
		(void) unlink(pidfile);

	return status;
}


syntax highlighted by Code2HTML, v. 0.9.1