/*
**  Copyright (c) 2004-2008 Sendmail, Inc. and its suppliers.
**	All rights reserved.
**
**  $Id: dk-filter.c,v 1.183 2008/09/04 18:32:36 msk Exp $
*/

#ifndef lint
static char dk_filter_c_id[] = "@(#)$Id: dk-filter.c,v 1.183 2008/09/04 18:32:36 msk Exp $";
#endif /* !lint */

/* system includes */
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/socket.h>
#if DEBUG
# include <netinet/in.h>
# include <arpa/inet.h>
#endif /* DEBUG */
#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 <assert.h>
#include <ctype.h>
#include <fcntl.h>
#include <pthread.h>
#include <netdb.h>
#include <signal.h>
#include <regex.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 */

/* openssl includes */
#include <openssl/err.h>
#include <openssl/bio.h>
#include <openssl/evp.h>

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

/* libmilter includes */
#ifndef DEBUG
#include "libmilter/mfapi.h"
#endif /* !DEBUG */

/* libdk includes */
#include <dk.h>

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

/* dk-filter includes */
#include "dk-filter.h"
#include "dk-crypto.h"
#include "util.h"

#ifdef DEBUG
/* DEBUGGING STUFF */
# define MI_SUCCESS	1
# define MI_FAILURE	(-1)
# define SMFIS_CONTINUE	0
# define SMFIS_ACCEPT	1
# define SMFIS_REJECT	2
# define SMFIS_DISCARD	3
# define SMFIS_TEMPFAIL	4
# define sfsistat	int
# define SMFICTX	void
# define _SOCK_ADDR	struct sockaddr

int smfi_addheader __P((void *, char *, char *));
int smfi_chgheader __P((void *, char *, int, char *));
void *smfi_getpriv __P((void *));
char *smfi_getsymval __P((void *, char *));
int smfi_insheader __P((void *, int, char *, char *));
int smfi_replacebody __P((void *, char *, size_t));
void smfi_setconn __P((char *));
void smfi_setpriv __P((void *, void *));
void smfi_setreply __P((void *, char *, char *, char *));

char *smfis_ret[] = {
	"SMFIS_CONTINUE",
	"SMFIS_ACCEPT",
	"SMFIS_REJECT",
	"SMFIS_DISCARD",
	"SMFIS_TEMPFAIL",
};
#endif /* DEBUG */

/*
**  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 */
	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 */
	int		mctx_status;		/* status to report back */
	dk_canon_t	mctx_canonalg;		/* canonicalization algorithm */
	dk_alg_t	mctx_signalg;		/* signature algorithm */
	int		mctx_queryalg;		/* query algorithm */
	char *		mctx_jobid;		/* job ID */
	char *		mctx_domain;		/* domain doing the signing */
	DK *		mctx_dk;		/* DomainKeys handle */
	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[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 */
};

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

/*
**  LOOKUP -- lookup table
*/

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

#define	CFG_NOSIGNATURE		1
#define	CFG_BADSIGNATURE	2
#define	CFG_SIGMISSING		3
#define	CFG_DNSERROR		4
#define	CFG_INTERNAL		5

#define	DKF_MODE_SIGNER		0x01
#define	DKF_MODE_VERIFIER	0x02
#define	DKF_MODE_DEFAULT	(DKF_MODE_SIGNER|DKF_MODE_VERIFIER)

#define	DKF_STATUS_GOOD		0
#define	DKF_STATUS_BAD		1
#define	DKF_STATUS_NOKEY	2
#define	DKF_STATUS_REVOKED	3
#define	DKF_STATUS_NOSIGNATURE	4
#define	DKF_STATUS_BADFORMAT	6
#define	DKF_STATUS_NONPART	7
#define	DKF_STATUS_UNKNOWN	8

struct lookup dkf_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 },
	{ NULL,			-1 },
};

struct lookup dkf_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 dkf_status[] =
{
	{ "unknown",		DKF_STATUS_UNKNOWN },
	{ "good",		DKF_STATUS_GOOD },
	{ "bad",		DKF_STATUS_BAD },
	{ "no key",		DKF_STATUS_NOKEY },
	{ "revoked",		DKF_STATUS_REVOKED },
	{ "no signature",	DKF_STATUS_NOSIGNATURE },
	{ "bad format",		DKF_STATUS_BADFORMAT },
	{ "non-participant",	DKF_STATUS_NONPART },
	{ NULL,			-1 },
};

struct lookup dkf_canon[] =
{
	{ "nofws",		DK_CANON_NOFWS },
	{ "simple",		DK_CANON_SIMPLE },
	{ NULL,			-1 },
};

/* PROTOTYPES */
sfsistat mlfi_abort __P((SMFICTX *));
sfsistat mlfi_body __P((SMFICTX *, u_char *, size_t));
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 dkf_cleanup __P((SMFICTX *));

/* GLOBALS */
bool addxhdr;					/* add identifying header? */
bool hdrlist;					/* signature includes hdrlist */
bool dolog;					/* syslog */
bool quarantine;				/* quarantine failures? */
bool no_i_whine;				/* noted ${i} is undefined */
bool send_reports;				/* send failure reports */
#if _FFR_FLUSH_HEADERS
bool flushheaders;				/* flush old DK headers */
#endif /* _FFR_FLUSH_HEADERS */
#if _FFR_REQUIRED_HEADERS
bool req_hdrs;					/* required header checks */
#endif /* _FFR_REQUIRED_HEADERS */
bool subdomains;				/* sign subdomains */
bool die;					/* global "die" flag */
unsigned int tmo;				/* DNS timeout */
unsigned int mode;				/* operating mode */
int diesig;					/* signal to distribute */
size_t keylen;					/* size of secret key */
dk_canon_t canon;				/* canon. method for signing */
char *progname;					/* program name */
unsigned char *seckey;				/* secret key data */
char *selector;					/* key selector */
struct Config config;				/* configuration */
DK_LIB *libdk;					/* libdk handle */
struct keytable *keyhead;			/* key list */
struct keytable *keytail;			/* key list */
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 **macros;					/* macros/values to check */
char **values;					/* macros/values to check */
#if POPAUTH
DB *popdb;					/* POP auth DB */
#endif /* POPAUTH */
pthread_mutex_t count_lock;			/* counter lock */
int thread_count;				/* thread count */
pthread_mutex_t popen_lock;			/* popen() lock */

#ifdef DEBUG
static void *fakepriv;				/* fake private space */
#endif /* DEBUG */

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

#define	DK_DEFAULT_SELECTOR	"main"		/* default selector name */

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

/* MACROS */
#define	DK_DEBUG(x)	(getenv("DKDEBUG") != NULL && \
			 strchr(getenv("DKDEBUG"), (x)) != NULL)
#define	JOBID(x)	((x) == NULL ? JOBIDUNKNOWN : (x))
#define	TRYFREE(x)	do { \
				if ((x) != NULL) \
				{ \
					free(x); \
					(x) = NULL; \
				} \
			} while (0)

#define	DKF_EOHMACROS	"i {daemon_name} {auth_type}"



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

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

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

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

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

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

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

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

		for (key = keyhead; key != NULL; key = key->key_next)
		{
			memset(key->key_data, '\0', key->key_len);
			free(key->key_data);
		}
	}
}

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

static void
dkf_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);
		(void) close(devnull);
	}

	(void) setsid();
}

/*
**  DKF_SIGNALG -- return text version of the signing algorithm
**
**  Parameters:
**  	alg -- A dk_alg_t
**
**  Return value:
**  	Pointer to a string which is the text version of the provided code.
*/

static char *
dkf_signalg(dk_alg_t alg)
{
	switch (alg)
	{
	  case DK_SIGN_RSASHA1:
		return "rsa-sha1";

	  case DK_SIGN_UNKNOWN:
		return "unknown";

	  default:
		return NULL;
	}
}

/*
**  DKF_CANONALG -- return text version of the canonicalization algorithm
**
**  Parameters:
**  	alg -- A dk_canon_t
**
**  Return value:
**  	Pointer to a string which is the text version of the provided code.
*/

static char *
dkf_canonalg(dk_canon_t alg)
{
	switch (alg)
	{
	  case DK_CANON_NOFWS:
		return "nofws";

	  case DK_CANON_SIMPLE:
		return "simple";

	  case DK_CANON_UNKNOWN:
		return "unknown";

	  default:
		return NULL;
	}
}

/*
**  DKF_QUERYALG -- return text version of the query algorithm
**
**  Parameters:
**  	alg -- integer representing the query algorithm used
**
**  Return value:
**  	Pointer to a string which is the text version of the provided code.
*/

static char *
dkf_queryalg(int alg)
{
	switch (alg)
	{
	  case DK_QUERY_DNS:
		return "dns";

	  case DK_QUERY_UNKNOWN:
		return "unknown";

	  default:
		return NULL;
	}
}

/*
**  DKF_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
dkf_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 = DKF_STATUS_UNKNOWN;
	ctx->mctx_canonalg = canon;
	ctx->mctx_signalg = DK_SIGN_DEFAULT;
	ctx->mctx_queryalg = DK_QUERY_DEFAULT;

	return ctx;
}

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

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

#ifndef DEBUG
	assert(ctx != NULL);
#endif /* !DEBUG */

	cc = (connctx) smfi_getpriv(ctx);

	if (cc == NULL)
		return;

	dfc = cc->cctx_msg;

	/* release memory */
	if (dfc != NULL)
	{
		TRYFREE(dfc->mctx_domain);
		/* TRYFREE(dfc->mctx_jobid); */

		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_dk != NULL)
			dk_free(dfc->mctx_dk);

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

/*
**  DKF_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
dkf_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;
	}
}

/*
**  DKF_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
dkf_parseconfig(char *confstr)
{
	int vs;
	char *p;
	char *v;
	char *tmp;

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

	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)
			return FALSE;
		*v = '\0';
		v++;

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

		/* apply what's been found */
		switch (dkf_configlookup(p, dkf_params))
		{
		  case CFG_NOSIGNATURE:
			config.cfg_nosig = vs;
			break;

		  case CFG_BADSIGNATURE:
			config.cfg_badsig = vs;
			break;

		  case CFG_SIGMISSING:
			config.cfg_sigmissing = vs;
			break;

		  case CFG_DNSERROR:
			config.cfg_dnserr = vs;
			break;

		  case CFG_INTERNAL:
			config.cfg_internal = vs;
			break;

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

	return TRUE;
}

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

static sfsistat
dkf_libstatus(SMFICTX *ctx, char *where, int status)
{
	int retcode = SMFIS_CONTINUE;
	msgctx dfc;
	connctx cc;

#ifndef DEBUG
	assert(ctx != NULL);
#endif /* ! DEBUG */

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

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

	  case DK_STAT_INTERNAL:
		retcode = config.cfg_internal;
		if (dolog)
		{
			const char *err;

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

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

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

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

	  case DK_STAT_NORESOURCE:
		retcode = config.cfg_internal;
		if (dolog)
		{
			const char *err;

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

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

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

			err = dk_geterror(dfc->mctx_dk);
			if (err == NULL)
				err = "unknown cause";

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

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

			err = dk_geterror(dfc->mctx_dk);
			if (err == NULL)
				err = "unspecified";

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

	if (ERR_peek_error() != 0 && dolog)
	{
		int n;
		unsigned long e;
		char errbuf[BUFRSZ + 1];
		char tmp[BUFRSZ + 1];

		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(dfc->mctx_jobid), errbuf);
	}

	if (status != DK_STAT_OK)
		dkf_cleanup(ctx);

	return retcode;
}

/*
**  DKF_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.
*/

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

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

static int
dkf_loadkeys(char *file)
{
	bool blank = TRUE;
	int line = 0;
	FILE *f;
	char *p;
	char buf[BUFRSZ + 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(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++;
		p = strchr(buf, ':');
		if (p == NULL)
		{
			fprintf(stderr, "%s: %s: line %d: malformed\n",
			        progname, file, line);
			fclose(f);
			return -1;
		}
		else
		{
			int status;
			char *q;
			char *r;
			char *end;
			size_t n;
			struct keytable *newkey;
			int keyfd;
			struct stat s;
			char retmp[BUFRSZ];

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

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

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

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

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

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

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

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

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

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

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

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

			close(keyfd);

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

	fclose(f);

	return 0;
}

/*
**  DKF_REPORT -- generate a report on failure if possible
**
**  Parameters:
**   	dk -- DK handle
**  	reason -- reason to report
**
**  Return value:
**  	None.
*/

void
dkf_report(msgctx dfc, char *result, char *reason)
{
	int cfd;
	int status;
	DK_STAT dkstatus;
	size_t inl;
	size_t outl;
	FILE *out;
	BIO *b64;
	BIO *bout;
	struct Header *hdr;
	char buf[BUFRSZ];
	char addr[MAXADDRESS + 1];
	char hostname[MAXHOSTNAMELEN + 1];

	assert(dfc != NULL);

	if (!send_reports)
		return;

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

	(void) gethostname(hostname, sizeof hostname);

	dkstatus = dk_reportinfo(dfc->mctx_dk, &cfd, addr, sizeof addr);
	if (dkstatus != DK_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: DomainKeys failure report for %s\n",
	        dfc->mctx_jobid);

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

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

	/* first part: a text blob explaining what this is */
	fprintf(out, "--dkreport/%s/%s\n", hostname, dfc->mctx_jobid);
	fprintf(out, "Content-Type: text/plain\n");
	fprintf(out, "\n");
	fprintf(out, "DomainKeys 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, "--dkreport/%s/%s\n", hostname, dfc->mctx_jobid);
	fprintf(out, "Content-Type: message/x-dk-report\n");
	fprintf(out, "\n");
	fprintf(out, "MTA: %s\n", hostname);
	fprintf(out, "Agent: %s %s\n", DKF_PRODUCT, DKF_VERSION);
	fprintf(out, "Result: %s\n", result == NULL ? "(none)" : result);
	fprintf(out, "Reason: %s\n", reason == NULL ? "(none)" : reason);
	fprintf(out, "\n");

	/* third part: headers */
	fprintf(out, "--dkreport/%s/%s\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");

	/* fourth part: canonicalized form */
	fprintf(out, "--dkreport/%s/%s\n", hostname, dfc->mctx_jobid);
	fprintf(out, "Content-Type: text/plain\n");		/* ? */
	fprintf(out, "Content-Disposition: attachment; filename=\"%s.txt\"\n",
	        dfc->mctx_jobid);
	fprintf(out, "Content-Transfer-Encoding: base64\n");
	fprintf(out, "\n");

	(void) lseek(cfd, 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(cfd, buf, sizeof buf);
		if (inl == 0)
			break;
		outl = BIO_write(bout, (char *) buf, inl);
		if (outl != inl || inl < sizeof buf)
			break;
	}

	BIO_free(bout);

	/* end */
	fprintf(out, "\n--dkreport/%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);
	connctx cc;

	/* initialize connection context */
	cc = malloc(sizeof(struct connctx));
	if (cc != NULL)
	{
		memset(cc, '\0', sizeof(struct connctx));
		smfi_setpriv(ctx, cc);
	}

	/* verify the actions we need are available */
	if (quarantine)
		reqactions |= SMFIF_QUARANTINE;
	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)
	{
		if (cc != NULL)
		{
			cc->cctx_noleadspc = TRUE;
			*pf1 |= SMFIP_HDR_LEADSPC;
		}
	}
# 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, DKF_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;
		}
	}

	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 (DK_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] != '[')
		{
			dkf_lowercase(host);
			if (dkf_checkhost(peerlist, host))
				return SMFIS_ACCEPT;
		}

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

	/* copy hostname and IP information to a connection context */
	cc = smfi_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;

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

#ifndef DEBUG
	assert(ctx != NULL);
	assert(envfrom != NULL);
#endif /* !DEBUG */

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

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

	/*
	**  Initialize a filter context.
	*/

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

		dkf_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;
	char *q;
	char hv[MAXHEADER];
#endif /* _FFR_ANTICIPATE_SENDMAIL_MUNGE */

#ifndef DEBUG
	assert(ctx != NULL);
#endif /* !DEBUG */
	assert(headerf != NULL);
	assert(headerv != NULL);

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

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

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

		dkf_cleanup(ctx);
		return SMFIS_TEMPFAIL;
	}

	(void) memset(newhdr, '\0', sizeof(struct Header));
	newhdr->hdr_hdr = strdup(headerf);

#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.
	*/

	(void) memset(hv, '\0', sizeof hv);
	
	end = hv + sizeof hv;

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

		*q = *p;
		q++;
	}

	newhdr->hdr_val = strdup(hv);
#else /* _FFR_ANTICIPATE_SENDMAIL_MUNGE */
	newhdr->hdr_val = strdup(headerv);
#endif /* _FFR_ANTICIPATE_SENDMAIL_MUNGE */

	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);
		dkf_cleanup(ctx);
		return SMFIS_TEMPFAIL;
	}

	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;

		c = dkf_configlookup(headerv, dkf_canon);
		if (c != -1)
			dfc->mctx_canonalg = (dk_canon_t) c;
	}
#endif /* _FFR_SELECT_CANONICALIZATION */

	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)
{
	bool msgsigned;
	bool domainok;
	bool originok;
	bool skip;
	int status;
	connctx cc;
	msgctx dfc;
	char *p;
	char *domain = NULL;
	Header from;
	Header hdr;
	char addr[MAXADDRESS + 1];
	char hdrbuf[MAXHEADER + 1];

#ifndef DEBUG
	assert(ctx != NULL);
#endif /* !DEBUG */

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

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

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

	dfc->mctx_jobid = smfi_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 (dkf_findheader(dfc, "From", 0) == NULL ||
		    dkf_findheader(dfc, "From", 1) != NULL)
			ok = FALSE;

		/* exactly one Date: */
		if (dkf_findheader(dfc, "Date", 0) == NULL ||
		    dkf_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 = DKF_STATUS_BADFORMAT;
			return SMFIS_CONTINUE;
		}
	}
#endif /* _FFR_REQUIRED_HEADERS */

	msgsigned = (dkf_findheader(dfc, DK_SIGNHEADER, 0) != NULL);

	/* find the Sender: or From: header */
	memset(addr, '\0', sizeof addr);
	from = dkf_findheader(dfc, "Sender", 0);
	if (from == NULL)
		from = dkf_findheader(dfc, "From", 0);
	if (from == NULL)
	{
		if (dolog)
		{
			syslog(LOG_INFO,
			       "%s: no From: or Sender: header; accepting",
			       dfc->mctx_jobid);
		}

		if (msgsigned)
			dfc->mctx_addheader = TRUE;
		dfc->mctx_headeronly = TRUE;
		dfc->mctx_status = DKF_STATUS_BADFORMAT;
		return SMFIS_CONTINUE;
	}

	/* extract the sender's domain */
	sm_strlcpy(addr, from->hdr_val, sizeof addr);
	status = rfc2822_mailbox_split(addr, &p, &domain);
	if (status != 0 || p == NULL || domain == NULL)
	{
		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 = DKF_STATUS_BADFORMAT;
		return SMFIS_CONTINUE;
	}

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

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

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

		if (!domainok && subdomains)
		{
			for (p = strchr(domain, '.');
			     p != NULL && !domainok;
			     p = strchr(p + 1, '.'))
			{
				p++;
				if (*p == '\0')
					break;

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

		dfc->mctx_domain = strdup(domain);
	}

	/* is it a domain we sign for?  (take two) */
	if (keyhead != NULL)
	{
		struct keytable *curkey;
		char srchaddr[MAXADDRESS + 1];

		snprintf(srchaddr, sizeof srchaddr, "%s@%s",
		         p, 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);
				}

				dkf_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 = smfi_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)
	{
		int n;
		char *val;
		char name[BUFRSZ + 1];

		for (n = 0; macros[n] != NULL; n++)
		{
			snprintf(name, sizeof name, "{%s}", macros[n]);
			val = smfi_getsymval(ctx, name);
			if (val == NULL)
				continue;
			if (values[n] == NULL ||
			    (values[n] != NULL &&
			     strcasecmp(values[n], val) == 0))
			{
				originok = TRUE;
				break;
			}
		}
	}

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

		authtype = smfi_getsymval(ctx, "{auth_type}");
		if ((authtype == NULL || authtype[0] == '\0') &&
#if POPAUTH
		    !dkf_checkpopauth(popdb, &cc->cctx_ip) &&
#endif /* POPAUTH */
		    !dkf_checkhost(internal, cc->cctx_host) &&
		    !dkf_checkip(internal, &cc->cctx_ip))
		{
			if (domainok && dolog &&
			    !dkf_checkhost(exignore, cc->cctx_host) &&
			    !dkf_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 = DK_SIGN_RSASHA1;
		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 & DKF_MODE_SIGNER) == 0) ||
	    (!dfc->mctx_signing && (mode & DKF_MODE_VERIFIER) == 0))
		return SMFIS_ACCEPT;

	/* grab an appropriate handle for message processing */
	if (!dfc->mctx_signing)
	{
		dfc->mctx_dk = dk_verify(libdk, dfc->mctx_jobid, NULL,
		                         &status);
	}
	else
	if (dfc->mctx_key != NULL)
	{
		dfc->mctx_dk = dk_sign(libdk, dfc->mctx_jobid, NULL,
		                      (dk_sigkey_t *) dfc->mctx_key->key_data,
		                      dfc->mctx_canonalg, dfc->mctx_signalg,
		                      &status);
	}
	else
	{
		dfc->mctx_dk = dk_sign(libdk, dfc->mctx_jobid, NULL,
		                      (dk_sigkey_t *) seckey,
		                      dfc->mctx_canonalg, dfc->mctx_signalg,
		                      &status);
	}
	if (dfc->mctx_dk == NULL && status != DK_STAT_OK)
		return dkf_libstatus(ctx, "dk_new()", status);

	(void) dk_timeout(dfc->mctx_dk, tmo, NULL);

	/* run the headers */
	memset(hdrbuf, '\0', sizeof hdrbuf);
	for (hdr = dfc->mctx_hqhead; hdr != NULL; hdr = hdr->hdr_next)
	{
		skip = FALSE;

		if (omithdrs != NULL && dfc->mctx_signing)
		{
			int c;

			for (c = 0; omithdrs[c] != NULL; c++)
			{
				if (strcasecmp(hdr->hdr_hdr, omithdrs[c]) == 0)
				{
					skip = TRUE;
					break;
				}
			}

			if (skip)
				continue;
		}

		snprintf(hdrbuf, MAXHEADER, "%s:%s%s%s", hdr->hdr_hdr,
		         cc->cctx_noleadspc ? "" : " ",
		         hdr->hdr_val, CRLF);
		status = dk_header(dfc->mctx_dk, hdrbuf, strlen(hdrbuf));
		if (status != DK_STAT_OK)
			return dkf_libstatus(ctx, "dk_header()", status);
	}

	/* signal end of headers */
	status = dk_eoh(dfc->mctx_dk);
	switch (status)
	{
	  case DK_STAT_REVOKED:
		dfc->mctx_status = DKF_STATUS_REVOKED;
		dfc->mctx_addheader = TRUE;
		dfc->mctx_headeronly = TRUE;
		return SMFIS_CONTINUE;

	  case DK_STAT_BADSIG:
		dfc->mctx_status = DKF_STATUS_BAD;
		dfc->mctx_addheader = TRUE;
		dfc->mctx_headeronly = TRUE;
		return SMFIS_CONTINUE;

	  case DK_STAT_NOSIG:
		dfc->mctx_status = DKF_STATUS_NOSIGNATURE;
		dfc->mctx_addheader = TRUE;
		dfc->mctx_headeronly = TRUE;
		return SMFIS_CONTINUE;

	  case DK_STAT_NOKEY:
		dfc->mctx_status = DKF_STATUS_NOKEY;
		dfc->mctx_addheader = TRUE;
		dfc->mctx_headeronly = TRUE;
		return SMFIS_CONTINUE;

	  /* XXX -- other codes? */

	  case DK_STAT_OK:
		return SMFIS_CONTINUE;

	  default:
		return dkf_libstatus(ctx, "dk_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;

#ifndef DEBUG
	assert(ctx != NULL);
#endif /* !DEBUG */
	assert(bodyp != NULL);

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

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

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

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

	status = dk_body(dfc->mctx_dk, bodyp, bodylen);
	if (status != DK_STAT_OK)
		return dkf_libstatus(ctx, "dk_body()", status);

	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)
{
	int status = DK_STAT_OK;
	int c;
	int n;
	int w;
	int s;
	int dkf;
	sfsistat ret;
	connctx cc;
	msgctx dfc;
	char *hostname;
#if _FFR_FLUSH_HEADERS
	Header hdr;
#endif /* _FFR_FLUSH_HEADERS */
	unsigned char sig[MAXSIGNATURE];
	unsigned char header[MAXHEADER + 1];

#ifndef DEBUG
	assert(ctx != NULL);
#endif /* !DEBUG */

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

	cc = (connctx) smfi_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 = smfi_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 = smfi_getsymval(ctx, "j");
	if (hostname == NULL)
		hostname = HOSTUNKNOWN;

#if _FFR_FLUSH_HEADERS
	if (flushheaders)
	{
		for (hdr = dfc->mctx_hqhead; hdr != NULL; hdr = hdr->hdr_next)
		{
			if ((dfc->mctx_signing &&
			     strcasecmp(hdr->hdr_hdr, DK_SIGNHEADER) == 0) ||
			    strcasecmp(hdr->hdr_hdr, AUTHRESULTSHDR) == 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);
					}
				}
			}
		}
	}
#endif /* _FFR_FLUSH_HEADERS */

	if (!dfc->mctx_headeronly)
	{
		/*
		**  Signal end-of-message to DomainKeys
		*/

		dkf = 0;
		status = dk_eom(dfc->mctx_dk, &dkf);
		switch (status)
		{
		  case DK_STAT_OK:
			if (!dfc->mctx_signing &&
			    dkf_findheader(dfc, DK_SIGNHEADER, 0) != NULL)
			{
				dfc->mctx_addheader = TRUE;
				dfc->mctx_status = DKF_STATUS_GOOD;
			}
			break;

		  case DK_STAT_BADSIG:
			dfc->mctx_addheader = TRUE;
			dfc->mctx_status = DKF_STATUS_BAD;
			break;

		  case DK_STAT_NOSIG:
			dfc->mctx_addheader = TRUE;
			dfc->mctx_status = DKF_STATUS_NOSIGNATURE;
			break;

		  case DK_STAT_NOKEY:
			dfc->mctx_addheader = TRUE;
			dfc->mctx_status = DKF_STATUS_NOKEY;
			break;

		  default:
			return dkf_libstatus(ctx, "dk_eom()", status);
		}

#ifdef SMFIF_QUARANTINE
		/* quarantine for "bad" results if requested */
		if (quarantine &&
		    (status == DK_STAT_BADSIG ||
		     (status == DK_STAT_NOSIG && dfc->mctx_addheader)))
		{
			char *failstatus;
			char qreason[BUFRSZ + 1];

			failstatus = "bad signature";
			if (status == DK_STAT_NOSIG)
				failstatus = "no signature";

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

		/* compute and insert the signature, if we're signing */
		if (dfc->mctx_signing)
		{
			int hcnt = 0;
			char *sel;

			sel = selector;
			if (dfc->mctx_key != NULL)
				sel = dfc->mctx_key->key_selector;

			memset(sig, '\0', sizeof sig);
			status = dk_getsig(dfc->mctx_dk, sig, sizeof sig);
			if (status != DK_STAT_OK)
			{
				return dkf_libstatus(ctx, "dk_getsig()",
				                     status);
			}

			memset(dfc->mctx_hlist, '\0', sizeof dfc->mctx_hlist);
			if (hdrlist)
			{
				sm_strlcpy(dfc->mctx_hlist, "h=",
				           sizeof dfc->mctx_hlist);
				status = dk_gethdrs(dfc->mctx_dk, &hcnt,
				                    dfc->mctx_hlist + 2,
				                    sizeof dfc->mctx_hlist - 4);
				if (status != DK_STAT_OK)
				{
					return dkf_libstatus(ctx,
					                     "dk_gethdrs()",
					                     status);
				}

				if (sm_strlcat(dfc->mctx_hlist, ";\n\t",
				               sizeof dfc->mctx_hlist) >= sizeof dfc->mctx_hlist)
				{
					if (dolog)
					{
						syslog(LOG_ERR,
						       "%s header list too long",
						       dfc->mctx_jobid);
					}

					dkf_cleanup(ctx);
					return SMFIS_TEMPFAIL;
				}

				if (hcnt == 0)
				{
					dfc->mctx_hlist[0] = '\0';
				}
				else
				{
					dkf_splithdrs(dfc->mctx_hlist,
				                      sizeof(dfc->mctx_hlist));
				}
			}

			memset(header, '\0', sizeof header);
			snprintf(header, sizeof header,
			         "%sa=%s; s=%s; d=%s; c=%s; q=%s;\n\t%sb=",
			         cc->cctx_noleadspc ? " " : "",
			         dkf_signalg(dfc->mctx_signalg),
			         sel == NULL ? DK_DEFAULT_SELECTOR : sel,
			         dfc->mctx_domain,
			         dkf_canonalg(dfc->mctx_canonalg),
			         dkf_queryalg(dfc->mctx_queryalg),
			         dfc->mctx_hlist);
			n = strlen(header);
			s = strlen(sig);
			w = 10;
			for (c = 0; c < s && n < sizeof header; c++)
			{
				header[n] = sig[c];
				n++;
				w++;

				if (w >= HEADERMARGIN && (c < s - 1))
				{
					sm_strlcat(header, "\n\t",
					           sizeof header);
					n += 2;
					w = 8;
				}
			}

			if (smfi_insheader(ctx, 1, DK_SIGNHEADER,
			                   header) == MI_FAILURE)
			{
				if (dolog)
				{
					syslog(LOG_ERR,
					       "%s smfi_insheader() failed",
					       dfc->mctx_jobid);
				}
			}
		}
	}

	/* insert DomainKeys status */
	if (dfc->mctx_addheader && dfc->mctx_status != DKF_STATUS_UNKNOWN)
	{
		char *authresult;
		char *comment = NULL;

		switch (dfc->mctx_status)
		{
		  case DKF_STATUS_GOOD:
			authresult = "pass";
			break;

		  case DKF_STATUS_BAD:
		  case DKF_STATUS_REVOKED:
		  case DKF_STATUS_NOSIGNATURE:
			authresult = "fail";
			if (dfc->mctx_status == DKF_STATUS_REVOKED)
				comment = "revoked";
			else if (dfc->mctx_status == DKF_STATUS_NOSIGNATURE)
				comment = "no signature";
			break;

		  case DKF_STATUS_BADFORMAT:
			authresult = "permerror";
			comment = "bad format";
			break;

		  default:
			authresult = "neutral";
			break;
		}

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

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

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

			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 ? "" : ")",
			         !(dkf & DK_FLAG_TESTING) ? "" : " (testing)",
			         hdr, val);
		}
		else
		{
			snprintf(header, sizeof header,
			         "%s%s; domainkeys=%s%s%s%s%s",
			         cc->cctx_noleadspc ? " " : "",
			         hostname, authresult,
			         comment == NULL ? "" : " (",
			         comment == NULL ? "" : comment,
			         comment == NULL ? "" : ")",
                                 !(dkf & DK_FLAG_TESTING) ? "" : " (testing)");
		}

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

		if (dfc->mctx_status == DKF_STATUS_BAD &&
		    dfc->mctx_dk != NULL)
			dkf_report(dfc, authresult, comment);
	}

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

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

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

		snprintf(xfhdr, sizeof xfhdr, "%s%s v%s %s %s",
		         cc->cctx_noleadspc ? " " : "",
		         DKF_PRODUCT, DKF_VERSION, hostname,
		         dfc->mctx_jobid != NULL ? dfc->mctx_jobid
		                                : JOBIDUNKNOWN);

		if (smfi_insheader(ctx, 1, XHEADERNAME, xfhdr) != MI_SUCCESS)
		{
			if (dolog)
				syslog(LOG_ERR, "smfi_insheader() failed");

			dkf_cleanup(ctx);
			return SMFIS_TEMPFAIL;
		}
	}

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

	ret = SMFIS_ACCEPT;

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

	  case DKF_STATUS_BAD:
		ret = dkf_libstatus(ctx, "mlfi_eom()", DK_STAT_BADSIG);
		break;

	  case DKF_STATUS_NOKEY:
		ret = dkf_libstatus(ctx, "mlfi_eom()", DK_STAT_NOKEY);
		break;

	  case DKF_STATUS_REVOKED:
		ret = SMFIS_TEMPFAIL;
		break;

	  case DKF_STATUS_NOSIGNATURE:
		ret = dkf_libstatus(ctx, "mlfi_eom()", DK_STAT_NOSIG);
		break;

	  case DKF_STATUS_BADFORMAT:
	  case DKF_STATUS_NONPART:
		ret = SMFIS_ACCEPT;
		break;

	  case DKF_STATUS_UNKNOWN:
		break;

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

        if ((dkf & DK_FLAG_TESTING) != 0)
                ret = SMFIS_ACCEPT;

	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 (DK_DEBUG('t'))
		syslog(LOG_INFO, "thread %p abort", pthread_self());

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

#ifndef DEBUG
	assert(ctx != NULL);
#endif /* !DEBUG */

	if (DK_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);
	}

	dkf_cleanup(ctx);

	cc = (connctx) smfi_getpriv(ctx);
	free(cc);
	smfi_setpriv(ctx, NULL);

	return SMFIS_CONTINUE;
}

#ifndef DEBUG
/*
**  smfilter -- the milter module description
*/

struct smfiDesc smfilter =
{
	DKF_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
};
#endif /* !DEBUG */

#ifdef DEBUG
int
smfi_chgheader(void *ctx, char *hdr, int idx, char *val)
{
	printf("smfi_chgheader(<ctx>, `%s', `%d', `%s')\n", hdr, idx,
	       val == NULL ? "(null)" : val);
	return MI_SUCCESS;
}

int
smfi_replacebody(void *ctx, char *p, size_t len)
{
	printf("smfi_replacebody(<ctx>, `%.20s%s', `%d')\n", p,
	       strlen(p) > 20 ? "..." : "", len);
	return MI_SUCCESS;
}

int
smfi_addheader(void *ctx, char *hdr, char *val)
{
	printf("smfi_addheader(<ctx>, `%s', `%s')\n", hdr, val);
	return MI_SUCCESS;
}

int
smfi_insheader(void *ctx, int idx, char *hdr, char *val)
{
	printf("smfi_insheader(<ctx>, %d, `%s', `%s')\n", idx, hdr, val);
	return MI_SUCCESS;
}

void
smfi_setconn(char *file)
{
	printf("smfi_setconn(`%s')\n", file);
}

void
smfi_setpriv(void *ctx, void *priv)
{
	fakepriv = priv;
}

void *
smfi_getpriv(void *ctx)
{
	return fakepriv;
}

void
smfi_setreply(void *ctx, char *sc, char *esc, char *reply)
{
	printf("smfi_setreply(<ctx>, `%s', `%s', `%s')\n",
	       sc, esc, reply);
}

char *
smfi_getsymval(void *ctx, char *sym)
{
	char *ret;
	size_t l;
	connctx cc;

	l = strlen(sym) + 6 + 1;
	cc = fakepriv;

	printf("smfi_getsymval(<ctx>, `%s')\n", sym);
	ret = malloc(l);
	snprintf(ret, l, "DEBUG-%s", sym);
	return ret;
}

/*
**  DKF_DEBUG -- debugging code; simulates libmilter calls
**
**  Parameters:
**  	None.
**
**  Return value:
**  	None.
*/

int
dkf_debug(void)
{
	bool done;
	int status;
	size_t len;
	time_t now;
	char *p;
	char *env[2];
	char tmphdr[4096];
	char data[513];
	char block[4096];
	struct sockaddr_in sin;

	time(&now);
	srandom(now);

	memset(data, '\0', sizeof data);
	memset(tmphdr, '\0', sizeof tmphdr);

	memset(&sin, '\0', sizeof sin);
	sin.sin_family = AF_INET;
	sin.sin_port = time(NULL) % 65536;
	sin.sin_addr.s_addr = inet_addr("127.0.0.1");
	status = mlfi_connect(NULL, "localhost", (_SOCK_ADDR *) &sin);
	printf("mlfi_connect(NULL, `%s', <sockaddr>) returns %s\n",
	       "localhost", smfis_ret[status]);

	for (;;)
	{
		if (fgets(data, 512, stdin) == NULL)
			return 1;

		for (p = data; *p != '\0'; p++)
			if (*p == '\r' || *p == '\n')
			{
				*p = '\0';
				break;
			}

		if (strcmp(data, ".") == 0)
			break;

		env[0] = &data[1];
		env[1] = NULL;

		if (data[0] == 'F')
		{
			status = mlfi_envfrom(NULL, env);
			printf("mlfi_envfrom(NULL, `%s') returns %s\n", env[0],
	       		       smfis_ret[status]);
		}
/*
		else if (data[0] == 'T')
		{
			status = mlfi_envrcpt(NULL, env);
			printf("mlfi_envrcpt(NULL, `%s') returns %s\n", env[0],
	       		       smfis_ret[status]);
		}
*/
		else
		{
			return 1;
		}

		if (status != SMFIS_CONTINUE)
			return 0;
	}

	for (;;)
	{
		memset(data, '\0', 513);
		if (fgets(data, 512, stdin) == NULL)
			return 1;

		for (p = data; *p != '\0'; p++)
		{
			if (*p == '\r' || *p == '\n')
			{
				*p = '\0';
				break;
			}
		}

		if (strlen(data) > 0 && isascii(data[0]) && isspace(data[0]))
		{
			sm_strlcat(tmphdr, "\r\n", sizeof tmphdr);
			sm_strlcat(tmphdr, data, sizeof tmphdr);
			continue;
		}

		if (strlen(tmphdr) != 0)
		{
			char *q;

			p = strchr(tmphdr, ':');
			*p = '\0';
			for (q = p + 1; isspace(*q); q++)
				continue;
			status = mlfi_header(NULL, tmphdr, q);
			printf("mlfi_header(NULL, `%s', `%s') returns %s\n",
			       tmphdr, q, smfis_ret[status]);
			if (status != SMFIS_CONTINUE)
				return 0;
			memset(tmphdr, '\0', sizeof tmphdr);
		}

		if (strlen(data) == 0)
			break;

		sm_strlcat(tmphdr, data, sizeof tmphdr);
	}

	status = mlfi_eoh(NULL);
	printf("mlfi_eoh(NULL) returns %s\n", smfis_ret[status]);
	if (status != SMFIS_CONTINUE)
		return 0;

	done = FALSE;
	while (!done)
	{
		len = fread(block, 1, 4096, stdin);
		status = mlfi_body(NULL, block, len);
		printf("mlfi_body(NULL, <body>, %d) returns %s\n",
		       len, smfis_ret[status]);
		if (status != SMFIS_CONTINUE)
			return 0;
		if (len < 4096)
			done = TRUE;
	}

	status = mlfi_eom(NULL);
	printf("mlfi_eom(NULL) returns %s\n", smfis_ret[status]);

	status = mlfi_close(NULL);
	printf("mlfi_close(NULL) returns %s\n", smfis_ret[status]);

	return 0;
}
#endif /* DEBUG */

/*
**  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"
	                "-a peerlist \tfile containing list of hosts to ignore\n"
	                "-A          \tauto-restart\n"
	                "-b modes    \tselect operating modes\n"
	                "-c canon    \tcanonicalization to use when signing\n"
	                "-C config   \tconfiguration info (see man page)\n"
	                "-d domlist  \tdomains to sign\n"
	                "-D          \talso sign subdomains\n"
	                "-f          \tdon't fork-and-exit\n"
#if _FFR_FLUSH_HEADERS
	                "-F          \tflush special headers before signing\n"
#endif /* _FFR_FLUSH_HEADERS */
	                "-h          \tappend identifying header\n"
	                "-H          \tsign with explicit header lists\n"
	                "-i ilist    \tfile containing list of internal (signing) hosts\n"
	                "-I elist    \tfile containing list of external domain clients\n"
	                "-k          \tload a key set instead of a single key\n"
	                "-l          \tlog activity to system log\n"
	                "-m mtalist  \tMTA daemon names for which to sign\n"
	                "-M macrolist\tMTA macros which enable signing\n"
	                "-o hdrlist  \tlist of headers to omit from signing\n"
	                "-P pidfile  \tfile to which to write pid\n"
#if _FFR_REQUIRED_HEADERS
	                "-r          \trequire basic RFC2822 header compliance\n"
#endif /* _FFR_REQUIRED_HEADERS */
	                "-R          \tgenerate verification failure reports\n"
	                "-s keyfile  \tlocation of secret key file\n"
	                "-S selector \tselector to use when signing\n"
	                "-u userid   \tchange to specified userid\n"
#if POPAUTH
			"-U dbfile   \tuser POP AUTH database\n"
#endif /* POPAUTH */
	                "-V          \tprint version number and terminate\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 gotp = FALSE;
	bool dofork = TRUE;
	bool multikey = FALSE;
	int c;
	int status;
#ifndef DEBUG
	int n;
#endif /* ! DEBUG */
	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 *confstr = NULL;
	char *peerfile = NULL;
	char *ilist = NULL;
	char *omitlist = NULL;
#if POPAUTH
	char *popdbfile = NULL;
#endif /* POPAUTH */
	char *elist = NULL;
	char *canonstr = NULL;
	char *macrolist = NULL;
	unsigned char *s33krit = NULL;
#ifndef DEBUG
	char *end;
	char argstr[MAXARGV];
#endif /* ! DEBUG */

	/* initialize */
	addxhdr = FALSE;
	dolog = FALSE;
	subdomains = FALSE;
	hdrlist = FALSE;
	dompats = NULL;
	omithdrs = NULL;
#if POPAUTH
	popdb = NULL;
#endif /* POPAUTH */
#if _FFR_FLUSH_HEADERS
	flushheaders = FALSE;
#endif /* _FFR_FLUSH_HEADERS */
	send_reports = FALSE;
	keyhead = NULL;
	keytail = NULL;
	no_i_whine = TRUE;
	selector = NULL;
	seckey = NULL;
	libdk = NULL;
	domains = NULL;
	mtas = NULL;
	macros = NULL;
	values = NULL;
	peerlist = NULL;
	internal = NULL;
	exignore = NULL;
	quarantine = FALSE;
	canon = DK_CANON_SIMPLE;
	tmo = DEFTIMEOUT;
	if (DK_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();
			confstr = 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;

#if _FFR_FLUSH_HEADERS
		  case 'F':
			flushheaders = TRUE;
			break;
#endif /* _FFR_FLUSH_HEADERS */

		  case 'h':
			addxhdr = TRUE;
			break;

		  case 'H':
			hdrlist = 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':
			multikey = TRUE;
			break;

		  case 'l':
#ifndef DEBUG
			dolog = TRUE;
#endif /* !DEBUG */
			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();
			(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();
			keyfile = optarg;
			break;

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

		  case 'T':
			if (optarg == NULL || *optarg == '\0')
				return usage();
			tmo = strtoul(optarg, &p, 10);
			if (*p != '\0')
			{
				fprintf(stderr, "%s: invalid value for -%c\n",
				        progname, c);
				return EX_USAGE;
			}
			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':
			printf("%s: %s v%s\n", progname, DKF_PRODUCT,
			       DKF_VERSION);
			dkf_optlist(stdout);
			return EX_OK;

		  default:
			return usage();
		}
	}

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

	if (!dkf_parseconfig(confstr))
		return EX_USAGE;

	if (!gotp)
		return usage();

	if (be == NULL)
	{
		mode = DKF_MODE_DEFAULT;
	}
	else
	{
		mode = 0;

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

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

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

	if (canonstr != NULL)
	{
		canon = dkf_configlookup(canonstr, dkf_canon);
		if (canon == -1)
		{
			fprintf(stderr, "%s: unknown canonicalization \"%s\"\n",
			        progname, canonstr);
			return EX_USAGE;
		}
	}

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

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

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

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

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

		fclose(f);
	}

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

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

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

		fclose(f);
	}
	else
	{
		Peer newpeer;

		newpeer = (struct Peer *) malloc(sizeof(struct Peer));
		if (newpeer == NULL)
		{
			fprintf(stderr, "%s: malloc(): %s\n", progname,
			        strerror(errno));
			return EX_UNAVAILABLE;
		}

		newpeer->peer_next = NULL;
		newpeer->peer_info = LOCALHOST;

		internal = newpeer;
	}

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

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

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

		fclose(f);
	}

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

	dkf_setmaxfd();

	/* change user if appropriate */
	if (become != NULL)
	{
		struct passwd *pw;

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

		(void) endpwent();

		if (setgid(pw->pw_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;
		}
	}

	/* load the secret key, if one was specified */
	if (keyfile != NULL && multikey)
	{
		status = dkf_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));

				dkf_zapkey();
				return EX_OSERR;

			  case 0:
				dkf_stdio();
				break;

			  default:
				dkf_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 = dkf_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)
		{
			pid = fork();
			switch (pid)
			{
			  case -1:
				if (dolog)
				{
					syslog(LOG_ERR, "fork(): %s",
					       strerror(errno));
				}

				dkf_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)
						{
							dkf_killchild(pid,
							              diesig);
							dkf_zapkey();
							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;
			}
		}
	}

#ifndef DEBUG
	/* 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);

		dkf_zapkey();
		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);

		dkf_zapkey();
		return EX_UNAVAILABLE;
	}
#endif /* !DEBUG */

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

			dkf_zapkey();
			return EX_OSERR;

		  case 0:
			dkf_stdio();
			break;

		  default:
			dkf_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));
			}
		}
	}

	status = dkf_crypto_init();
	if (status != 0)
	{
		if (dolog)
			syslog(LOG_ERR, "can't initialize OpenSSL library");

		dkf_zapkey();
		return EX_UNAVAILABLE;
	}

	/* initialize the DomainKeys package */
	libdk = dk_init(NULL, NULL);
	if (libdk == NULL)
	{
		if (dolog)
			syslog(LOG_ERR, "can't initialize DomainKeys library");

		dkf_zapkey();
		return EX_UNAVAILABLE;
	}

	if (hdrlist)
	{
		int opts;

		opts = DK_LIBFLAGS_HDRLIST;

		(void) dk_options(libdk, DK_OP_SETOPT, DK_OPTS_FLAGS, &opts,
		                  sizeof opts);
	}

	if (send_reports)
	{
		int opts;

		(void) dk_options(libdk, DK_OP_GETOPT, DK_OPTS_FLAGS, &opts,
		                  sizeof opts);

		opts |= DK_LIBFLAGS_TMPFILES;

		(void) dk_options(libdk, DK_OP_SETOPT, DK_OPTS_FLAGS, &opts,
		                  sizeof opts);
	}

	pthread_mutex_init(&popen_lock, NULL);

#ifdef DEBUG
	return dkf_debug();
#else /* DEBUG */
	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 = dkf_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(dbfile, 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\n",
			        progname, db_strerror(errno));
			syslog(LOG_ERR, "can't open database %s",
			       popdbfile);

			dkf_zapkey();
			dkf_crypto_free();
			return EX_UNAVAILABLE;
		}
	}
#endif /* POPAUTH */

	if (dolog)
	{
		syslog(LOG_INFO, "%s v%s starting (%s)", DKF_PRODUCT,
		       DKF_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",
		       DKF_PRODUCT, DKF_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 */

	dkf_crypto_free();

	dkf_zapkey();

	dk_close(libdk);

	return status;
#endif /* DEBUG */
}
