/*
**  Copyright (c) 2004-2009 Sendmail, Inc. and its suppliers.
**    All rights reserved.
**
**  $Id: dk.c,v 1.192 2009/02/04 17:52:47 msk Exp $
*/

#ifndef lint
static char dk_c_id[] = "@(#)$Id: dk.c,v 1.192 2009/02/04 17:52:47 msk Exp $";
#endif /* !lint */

/* system includes */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <ctype.h>
#include <syslog.h>
#include <pthread.h>

/* OpenSSL includes */
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/bio.h>
#include <openssl/err.h>

/* libdk includes */
#include <dk.h>
#include <dk-private.h>
#include <util.h>

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

/* libar includes */
#if USE_ARLIB
# include "ar.h"
#endif /* USE_ARLIB */

/* libdk includes */
#include "rfc2822.h"

/* useful stuff */
#ifndef NULL
# define NULL	0
#endif /* ! NULL */
#ifndef FALSE
# define FALSE	0
#endif /* ! FALSE */
#ifndef TRUE
# define TRUE	1
#endif /* ! TRUE */
#ifndef MAXHOSTNAMELEN
# define MAXHOSTNAMELEN		256
#endif /* ! MAXHOSTNAMELEN */

#ifndef __P
# ifdef __STDC__
#  define __P(x)		x
# else /* __STDC__ */
#  define __P(x)		()
# endif /* __STDC__ */
#endif /* ! __P */

#define	CRLF			"\r\n"	/* CRLF */

/* limits, macros, etc. */
#define BUFRSZ			1024	/* block I/O buffer size */
#define	DEFERRLEN		128	/* default error buffer size */
#define	DEFTIMEOUT		5	/* default DNS timeout */
#define MAXPARAMETER		256	/* biggest parameter we accept */

#ifdef _FFR_HASH_BUFFERING
# define  DK_HASHBUFSIZE	4096	/* hash buffer size */
#endif /* _FFR_HASH_BUFFERING */

/* defaults */
#define DK_DEFAULT_SELECTOR	"main"	/* default selector (filename only) */
#define	DK_TMPDIR		"/var/tmp"
					/* default dir. for temp files */

/* states */
#define	DK_STATE_INIT		0	/* initialized */
#define	DK_STATE_HEADER		1	/* receiving headers */
#define	DK_STATE_EOH		2	/* at or past EOH */
#define	DK_STATE_BODY		3	/* receiving body */
#define	DK_STATE_EOM		4	/* at EOM */


/* files */
#define	DK_PRIVATEKEY		"/var/db/domainkeys/%s.key.pem"
					/* template filename for private keys */

/* prototypes */
static void dk_error(DK *dk, const char *format, ...);
static void dk_canon __P((DK *, u_char *, size_t));
#ifdef _FFR_HASH_BUFFERING
static void dk_canonbuffer __P((DK *, u_char *, size_t));
#endif /* _FFR_HASH_BUFFERING */

#ifdef _FFR_HASH_BUFFERING
# define DK_CANON(x,y,z)	dk_canonbuffer(x,y,z)
#else /* _FFR_HASH_BUFFERING */
# define DK_CANON(x,y,z)	dk_canon(x,y,z)
#endif /* _FFR_HASH_BUFFERING */


/* parameter lookup table */
struct lookup
{
	char *str;
	int code;
};

struct lookup params[] = {
	{ "a",	DK_PARAM_SIGNALG },
	{ "b",	DK_PARAM_SIGNATURE },
	{ "c",	DK_PARAM_CANONALG },
	{ "d",	DK_PARAM_DOMAIN },
	{ "h",	DK_PARAM_HDRLIST },
	{ "q",	DK_PARAM_QUERYMETHOD },
	{ "s",	DK_PARAM_SELECTOR },
	{ NULL,	-1 }
};

/* local definitions needed for DNS queries */
#define	MAXPACKET		8192
#if defined(__RES) && (__RES >= 19940415)
# define RES_UNC_T		char *
#else /* __RES && __RES >= 19940415 */
# define RES_UNC_T		unsigned char *
#endif /* __RES && __RES >= 19940415 */

/* base64 alphabet */
static unsigned char alphabet[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

/* base64 decode stuff */
static int decoder[256] =
{
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0,
	0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
	14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0,
	0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

struct dk_lib
{
	int		dkl_flags;
	int		dkl_querymethod;
	void *		(*dkl_malloc) (void *closure, size_t nbytes);
	void		(*dkl_free) (void *closure, void *p);
#if USE_ARLIB
	AR_LIB		dkl_arlib;
#endif /* USE_ARLIB */
	char 		dkl_tmpdir[MAXPATHLEN + 1];
	u_char		dkl_queryinfo[MAXPATHLEN + 1];
};

/* other stuff */
#ifndef MIN
# define MIN(x,y)		((x) < (y) ? (x) : (y))
#endif /* ! MIN */

#define	DK_DEBUG(x)		(getenv("DKDEBUG") != NULL && \
				 strchr(getenv("DKDEBUG"), (x)) != NULL)

#define	DK_DECODE_SIGNATURE	0
#define	DK_DECODE_KEY		1

#define	DK_CRLF_UNKNOWN		(-1)
#define	DK_CRLF_CRLF		0
#define	DK_CRLF_LF		1

#define	DK_MODE_UNKNOWN		(-1)
#define	DK_MODE_SIGN		0
#define	DK_MODE_VERIFY		1

#define	DK_FLAG_FREESIGNATURE	0001

/*
**  ===== PRIVATE SECTION
*/

/*
**  DK_MALLOC -- allocate some memory
**
**  Parameters:
**  	libhandle -- DK_LIB handle
**  	closure -- closure to use
**  	nbytes -- how many bytes we want
**
**  Return value:
**   	Pointer to new memory, or NULL if it failed.
*/

static void *
dk_malloc(DK_LIB *libhandle, void *closure, size_t nbytes)
{
	assert(libhandle != NULL);

	if (libhandle->dkl_malloc == NULL)
		return malloc(nbytes);
	else
		return libhandle->dkl_malloc(closure, nbytes);
}

/*
**  DK_MFREE -- free some memory
**
**  Parameters:
**  	libhandle -- DK_LIB handle
**  	closure -- closure to use
**  	p -- pointer referring to memory to release
**
**  Return value:
**   	None.
*/

static void
dk_mfree(DK_LIB *libhandle, void *closure, void *p)
{
	assert(libhandle != NULL);

	if (libhandle->dkl_free == NULL)
		free(p);
	else
		libhandle->dkl_free(closure, p);
}

/*
**  DK_STRDUP -- duplicate a string
**
**  Parameters:
**  	libhandle -- DK_LIB handle
**  	closure -- closure to use
**  	str -- string to clone
**
**  Return value:
**  	Pointer to copy of "str", or NULL on failure.
*/

static char *
dk_strdup(DK_LIB *libhandle, void *closure, char *str)
{
	char *newc;
	size_t len;

	assert(libhandle != NULL);
	assert(str != NULL);

	len = strlen(str) + 1;
	newc = dk_malloc(libhandle, closure, len);
	if (newc != NULL)
		sm_strlcpy(newc, str, len);
	return newc;

}

/*
**  DK_ISFWS -- return TRUE iff the specified character is folding whitespace
**
**  Parameters:
**  	c -- character
**
**  Return value:
**  	TRUE iff the specified character is folding whitespace.
*/

static bool
dk_isfws(int c)
{
	return (c == 0x09 || c == 0x0a || c == 0x0d || c == 0x20);
}

/*
**  DK_DECODE -- decode a base64-encoded key or signature
**
**  Parameters:
**  	dk -- DK handle
**  	what -- what to decode (DK_DECODE_KEY or DK_DECODE_SIGNATURE)
**
**  Return value:
**  	TRUE on success, FALSE otherwise (malloc() failure).
*/

static bool
dk_decode(DK *dk, int what)
{
	int n;
	int bits = 0;
	int char_count = 0;
	size_t keyidx;
	unsigned char *c;
	unsigned char *x;

	assert(dk != NULL);
	assert(what == DK_DECODE_KEY || what == DK_DECODE_SIGNATURE);

	if (what == DK_DECODE_KEY)
	{
		dk->dk_key = dk_malloc(dk->dk_libhandle, dk->dk_closure,
		                       dk->dk_b64len);
		if (dk->dk_key == NULL)
		{
			dk_error(dk, "unable to allocate %d byte(s)",
			         dk->dk_b64len);
			return FALSE;
		}
		memset(dk->dk_key, '\0', dk->dk_b64len);
		x = dk->dk_key;
	}
	else
	{
		dk->dk_signature = dk_malloc(dk->dk_libhandle, dk->dk_closure,
		                             dk->dk_b64len);
		if (dk->dk_signature == NULL)
		{
			dk_error(dk, "unable to allocate %d byte(s)",
			         dk->dk_b64len);
			return FALSE;
		}
		memset(dk->dk_signature, '\0', dk->dk_b64len);
		x = dk->dk_signature;
		dk->dk_flags |= DK_FLAG_FREESIGNATURE;
	}

	keyidx = 0;

	for (c = dk->dk_b64, n = 0;
	     n < dk->dk_b64len;
	     c++, n++)
	{
		/* end padding */
		if (*c == '=')
			break;

		/* skip stuff not part of the base64 alphabet (RFC2045) */
		if (!((*c >= 'A' && *c <= 'Z') ||
		      (*c >= 'a' && *c <= 'z') ||
		      (*c >= '0' && *c <= '9') ||
		      (*c == '+') ||
		      (*c == '/')))
			continue;

		/* everything else gets decoded */
		bits += decoder[(int) *c];
		char_count++;
		if (char_count == 4)
		{
			x[keyidx++] = (bits >> 16);
			x[keyidx++] = ((bits >> 8) & 0xff);
			x[keyidx++] = (bits & 0xff);
			bits = 0;
			char_count = 0;
		}
		else
		{
			bits <<= 6;
		}
	}

	/* XXX -- don't bother checking for proper termination (for now) */

	/* process trailing data, if any */
	switch (char_count)
	{
	  case 0:
		break;

	  case 1:
		/* base64 decoding incomplete; at least two bits missing */
		break;

	  case 2:
		x[keyidx++] = (bits >> 10);
		break;

	  case 3:
		x[keyidx++] = (bits >> 16);
		x[keyidx++] = ((bits >> 8) & 0xff);
		break;
	}

	/* store the length of what was decoded */
	if (what == DK_DECODE_KEY)
	{
		dk->dk_keylen = keyidx;
	}
	else
	{
		dk->dk_signlen = keyidx;
	}

	return TRUE;
}

/*
**  DK_IN_USE -- see if a domain seems to be using DomainKeys
**
**  Parameters:
**  	dk -- DK handle
**
**  Return value:
**  	-1 -- error
**  	0 -- domain does not appear to use DomainKeys
**  	1 -- domain appears to use DomainKeys
*/

static int
dk_in_use(DK *dk)
{
	int qdcount;
	int ancount;
	int status;
	int n;
	int c;
	int type = -1;
	int class = -1;
	int state;
#if USE_ARLIB
	int error;
#endif /* USE_ARLIB */
	size_t plen = 0;
	size_t anslen;
#if USE_ARLIB
	AR_QUERY q;
#endif /* USE_ARLIB */
	char *ssel;
	unsigned char *p;
	unsigned char *cp;
	unsigned char *eom;
	unsigned char ansbuf[MAXPACKET];
	unsigned char buf[BUFRSZ + 1];
	char qname[MAXHOSTNAMELEN + 1];
	char tag[MAXPARAMETER + 1];
	char value[MAXPARAMETER + 1];
#if USE_ARLIB
	struct timeval timeout;
#endif /* USE_ARLIB */
	HEADER hdr;

	assert(dk != NULL);

	ssel = dk_sterilize(dk->dk_domain);
	if (ssel == NULL)
		return -1;

	/* send the NS query */
	memset(qname, '\0', sizeof qname);
	snprintf(qname, sizeof qname - 1, "%s.%s", DK_DNSNAME,
	         dk_sterilize(dk->dk_domain));

#if USE_ARLIB
	timeout.tv_sec = dk->dk_timeout;
	timeout.tv_usec = 0;
	if (DK_DEBUG('r'))
	{
		syslog(LOG_INFO, "thread %p ar_addquery(`%s')", pthread_self(),
		       qname);
	}
	q = ar_addquery(dk->dk_libhandle->dkl_arlib, qname, C_IN, T_TXT,
	                MAXCNAMEDEPTH, ansbuf, sizeof ansbuf, &error,
	                dk->dk_timeout == 0 ? NULL : &timeout);
	if (DK_DEBUG('r'))
	{
		syslog(LOG_INFO, "thread %p ar_addquery(`%s') done",
		       pthread_self(), qname);
	}
	if (q == NULL)
		return -1;
	status = ar_waitreply(dk->dk_libhandle->dkl_arlib, q, NULL, NULL);
	(void) ar_cancelquery(dk->dk_libhandle->dkl_arlib, q);
	if (DK_DEBUG('r'))
	{
		syslog(LOG_INFO, "thread %p ar_cancelquery(`%s')",
		       pthread_self(), qname);
	}
#else /* USE_ARLIB */
	status = res_query(qname, C_IN, T_TXT, ansbuf, sizeof ansbuf);
#endif /* USE_ARLIB */

#if USE_ARLIB
	if (status == AR_STAT_ERROR || status == AR_STAT_EXPIRED)
	{
		if (DK_DEBUG('d') && dk->dk_id != NULL)
		{
			syslog(LOG_NOTICE, "%s: ar_waitreply(): %s", dk->dk_id,
			       ar_strerror(error));
		}
		return -1;
	}
#else /* USE_ARLIB */
	/*
	**  A -1 return from res_query could mean a bunch of things,
	**  not just NXDOMAIN.  You can use h_errno to determine what
	**  -1 means.  This is poorly documented.
	*/

	if (status == -1)
	{
		switch (h_errno)
		{
		  case HOST_NOT_FOUND:
		  case NO_DATA:
			return 0;

		  case TRY_AGAIN:
		  case NO_RECOVERY:
		  default:
			return -1;
		}
	}
#endif /* USE_ARLIB */

	/* set up pointers */
	anslen = sizeof ansbuf;
	memcpy(&hdr, ansbuf, sizeof hdr);
	cp = (u_char *) &ansbuf + HFIXEDSZ;
	eom = (u_char *) &ansbuf + anslen;

	/* skip over the name at the front of the answer */
	for (qdcount = ntohs((unsigned short) hdr.qdcount);
	     qdcount > 0;
	     qdcount--)
	{
		/* copy it first */
		(void) dn_expand((unsigned char *) &ansbuf, eom, cp, qname,
		                 sizeof qname);

		if ((n = dn_skipname(cp, eom)) < 0)
			return DK_STAT_INTERNAL;
		cp += n;

		/* extract the type and class */
		if (cp + INT16SZ + INT16SZ > eom)
			return -1;

		GETSHORT(type, cp);
		GETSHORT(class, cp);
	}

	if (type != T_TXT || class != C_IN)
		return -1;

	/* if NXDOMAIN, return DK_STAT_CANTVRFY */
	if (hdr.rcode == NXDOMAIN)
		return 0;

	/* get the answer count */
	ancount = ntohs((unsigned short) hdr.ancount);
	if (ancount == 0)
		return 0;

	/* if truncated, we can't do it */
	if (hdr.tc)
		return -1;

	/* grab the label, even though we know what we asked... */
	if ((n = dn_expand((unsigned char *) &ansbuf, eom, cp,
	                   (RES_UNC_T) qname, sizeof qname)) < 0)
		return DK_STAT_INTERNAL;
	/* ...and move past it */
	cp += n;

	/* extract the type and class */
	if (cp + INT16SZ + INT16SZ > eom)
		return -1;
	GETSHORT(type, cp);
	GETSHORT(class, cp);

	/* reject anything that's not valid (stupid wildcards) */
	if (type != T_TXT || class != C_IN)
		return -1;

	/* skip the TTL */
	cp += INT32SZ;

	/* get payload length */
	if (cp + INT16SZ > eom)
		return -1;
	GETSHORT(n, cp);

	/* XXX -- maybe deal with a partial reply rather than require it all */
	if (cp + n > eom)
		return -1;

	/* extract the payload */
	memset(buf, '\0', sizeof buf);
	p = buf;
	while (n > 0)
	{
		c = *cp++;
		n--;
		while (c > 0)
		{
			*p++ = *cp++;
			c--;
			n--;
		}
	}

	state = 0;
	for (p = buf; ; p++)
	{
		if (*p == '\0')
		{
			switch (state)
			{
			  case 0:
			  case 4:
				return 1;

			  case 1:
			  case 2:
				/* malformed */
				return -1;

			  case 3:
				state = 4;
				break;
			}
		}

		switch (state)
		{
		  case 0:				/* before tag */
			if (isascii(*p) && isspace(*p))
				continue;
			memset(tag, '\0', sizeof tag);
			plen = 0;
			state = 1;
			/* FALLTHROUGH */

		  case 1:				/* in tag */
			if (*p == '=')
			{
				state = 2;
				continue;
			}
			else if (isascii(*p) && isspace(*p))
			{
				continue;
			}
			else if (plen < sizeof tag)
			{
				tag[plen++] = *p;
				continue;
			}
			/* FALLTHROUGH */

		  case 2:				/* before value */
			if (isascii(*p) && isspace(*p))
				continue;
			memset(value, '\0', sizeof value);
			plen = 0;
			state = 3;
			/* FALLTHROUGH */

		  case 3:				/* in value */
			if (*p == ';')
			{
				state = 4;
				/* FALLTHROUGH */
			}
			else if (isascii(*p) && isspace(*p))
			{
				continue;
			}
			else if (plen < sizeof tag)
			{
				value[plen++] = *p;
				continue;
			}
			else
			{
				continue;
			}
			/* FALLTHROUGH */

		  case 4:				/* after value */
			if (strcasecmp(tag, "t") == 0)
			{
				if (value[0] == 'y')
					dk->dk_testing = TRUE;
			}
			else if (strcasecmp(tag, "o") == 0)
			{
				if (value[0] == '-')
					dk->dk_signall = TRUE;
			}
			else if (strcasecmp(tag, "n") == 0)
			{
				/* XXX -- other info?  or something */
				/* XXX -- yahoo-inc.com uses it, so tolerate */
				/* XXX -- (i.e. ignore) it for now */
			}
			else if (strcasecmp(tag, "r") == 0)
			{
				sm_strlcpy(dk->dk_reportaddr, value,
				           sizeof dk->dk_reportaddr);
			}
			else
			{
				return -1;
			}

			state = 0;
			break;
		}

		if (*p == '\0')
			break;
	}

	return 1;
}


/*
**  DK_GET_KEY_FILE -- retrieve a DK key from a text file (for testing)
**
**  Parameters:
**  	dk -- DK handle
**  	selector -- selector to retrieve
**  	domain -- domain from which to retrieve
**  	buf -- buffer into which to write the result
**  	buflen -- bytes available at "buf"
**
**  Return value:
**  	A DK_STAT_* constant.
**
**  Notes:
**  	The file opened is defined by the library option DK_OPTS_QUERYINFO
**  	and must be set prior to use of this function.  Failing to do
**  	so will cause this function to return DK_STAT_CANTVRFY every time.
**  	The file should contain lines of the form:
** 
**  		<selector>._domainkey.<domain> <space> key-data
**
**  	Case matching on the left is case-sensitive, but libdkim already
**  	wraps the domain name to lowercase.
*/

static DK_STAT
dk_get_key_file(DK *dk, char *selector, char *domain, u_char *buf,
                size_t buflen)
{
	FILE *f;
	u_char *p;
	u_char *p2;
	char *path;
	char name[BUFRSZ + 1];

	assert(dk != NULL);

	path = dk->dk_libhandle->dkl_queryinfo;
	if (path[0] == '\0')
	{
		dk_error(dk, "query file not defined");
		return DK_STAT_CANTVRFY;
	}

	f = fopen(path, "r");
	if (f == NULL)
	{
		dk_error(dk, "%s: fopen(): %s", path, strerror(errno));
		return DK_STAT_CANTVRFY;
	}

	snprintf(name, sizeof name, "%s.%s.%s", selector, DK_DNSNAME, domain);

	memset(buf, '\0', sizeof buf);
	while (fgets(buf, BUFRSZ, f) != NULL)
	{
		p2 = NULL;

		for (p = buf; *p != '\0'; p++)
		{
			if (*p == '\n')
			{
				*p = '\0';
				break;
			}
			else if (isascii(*p) && isspace(*p))
			{
				*p = '\0';
				p2 = p + 1;
			}
			else if (p2 != NULL)
			{
				break;
			}
		}

		if (strcasecmp(name, buf) == 0)
		{
			sm_strlcpy(buf, p2, buflen);
			fclose(f);
			return DK_STAT_OK;
		}
	}

	fclose(f);

	return DK_STAT_NOKEY;
}

/*
**  DK_GET_KEY_DNS -- acquire from DNS the key that will be used to either
**                    generate or verify a signature
**
**  Parameters:
**  	dk -- DK handle
**  	selector -- selector to retrieve
**  	domain -- domain from which to retrieve
**  	buf -- buffer into which to write the result
**  	buflen -- bytes available at "buf"
**
**  Return value:
**  	A DK_STAT.
*/

static DK_STAT
dk_get_key_dns(DK *dk, char *selector, char *domain, u_char *buf,
               size_t buflen)
{
	bool key = FALSE;
	int status;
	int state;
	int qdcount;
	int ancount;
#if USE_ARLIB
	int error;
#endif /* USE_ARLIB */
	int n = 0;
	int l;
	int type = -1;
	int class = -1;
	size_t anslen;
#if USE_ARLIB
	AR_QUERY q;
#endif /* USE_ARLIB */
	unsigned char *p;
	unsigned char *r;
	unsigned char *cp;
	unsigned char *eob;
	unsigned char *eom;
	char qname[MAXHOSTNAMELEN + 1];
	unsigned char ansbuf[MAXPACKET];
#if USE_ARLIB
	struct timeval timeout;
#endif /* USE_ARLIB */
	HEADER hdr;

	assert(dk != NULL);

	snprintf(qname, sizeof qname - 1, "%s.%s.%s", dk->dk_selector,
	         DK_DNSNAME, dk->dk_domain);

#if USE_ARLIB
	timeout.tv_sec = dk->dk_timeout;
	timeout.tv_usec = 0;
	if (DK_DEBUG('r'))
	{
		syslog(LOG_INFO, "thread %p ar_addquery(`%s')", pthread_self(),
		       qname);
	}
	q = ar_addquery(dk->dk_libhandle->dkl_arlib, qname, C_IN, T_TXT,
	                MAXCNAMEDEPTH, ansbuf, sizeof ansbuf, &error,
	                dk->dk_timeout == 0 ? NULL : &timeout);
	if (q == NULL)
		return DK_STAT_INTERNAL;
	status = ar_waitreply(dk->dk_libhandle->dkl_arlib, q, NULL, NULL);
	(void) ar_cancelquery(dk->dk_libhandle->dkl_arlib, q);
	if (DK_DEBUG('r'))
	{
		syslog(LOG_INFO, "thread %p ar_cancelquery(`%s')",
		       pthread_self(), qname);
	}
#else /* USE_ARLIB */
	status = res_query(qname, C_IN, T_TXT, ansbuf, sizeof ansbuf);
#endif /* USE_ARLIB */

#if USE_ARLIB
	if (status == AR_STAT_ERROR || status == AR_STAT_EXPIRED)
	{
		if (DK_DEBUG('d') && dk->dk_id != NULL)
		{
			syslog(LOG_NOTICE, "%s: ar_waitreply(): %s", dk->dk_id,
			       ar_strerror(error));
		}

		dk_error(dk, "DNS error or timeout for `%s'", qname);
		return DK_STAT_CANTVRFY;
	}
#else /* USE_ARLIB */
	/*
	**  A -1 return from res_query could mean a bunch of things,
	**  not just NXDOMAIN.  You can use h_errno to determine what
	**  -1 means.  This is poorly documented.
	*/

	if (status == -1)
	{
		switch (h_errno)
		{
		  case HOST_NOT_FOUND:
		  case NO_DATA:
			dk_error(dk, "`%s' not found", qname);
			return DK_STAT_NOKEY;

		  case TRY_AGAIN:
		  case NO_RECOVERY:
		  default:
			dk_error(dk, "DNS error or timeout for `%s'", qname);
			return DK_STAT_CANTVRFY;
		}
	}
#endif /* USE_ARLIB */

	/* set up pointers */
	anslen = sizeof ansbuf;
	memcpy(&hdr, ansbuf, sizeof hdr);
	cp = (u_char *) &ansbuf + HFIXEDSZ;
	eom = (u_char *) &ansbuf + anslen;

	/* skip over the name at the front of the answer */
	for (qdcount = ntohs((unsigned short) hdr.qdcount);
	     qdcount > 0;
	     qdcount--)
	{
		/* copy it first */
		(void) dn_expand((unsigned char *) &ansbuf, eom, cp, qname,
		                 sizeof qname);

		if ((n = dn_skipname(cp, eom)) < 0)
		{
			dk_error(dk, "key record corrupted");
			return DK_STAT_INTERNAL;
		}
		cp += n;

		/* extract the type and class */
		if (cp + INT16SZ + INT16SZ > eom)
			return DK_STAT_INTERNAL;
		GETSHORT(type, cp);
		GETSHORT(class, cp);
	}

	if (type != T_TXT || class != C_IN)
	{
		dk_error(dk, "unexpected key reply");
		return DK_STAT_INTERNAL;
	}

	/* if NXDOMAIN, return DK_STAT_NOKEY */
	if (hdr.rcode == NXDOMAIN)
	{
		dk_error(dk, "`%s' not found", qname);
		return DK_STAT_NOKEY;
	}

	/* get the answer count */
	ancount = ntohs((unsigned short) hdr.ancount);
	if (ancount == 0)
	{
		dk_error(dk, "DNS error or timeout for `%s'", qname);
		return DK_STAT_CANTVRFY;
	}

	/* if truncated, we can't do it */
	if (hdr.tc)
	{
		dk_error(dk, "DNS reply for `%s' truncated", qname);
		return DK_STAT_INTERNAL;
	}

	/*
	**  Extract the data from the first answer.
	*/

	/* grab the label, even though we know what we asked... */
	if ((n = dn_expand((unsigned char *) &ansbuf, eom, cp,
	                   (RES_UNC_T) qname, sizeof qname)) < 0)
	{
		dk_error(dk, "key record corrupted");
		return DK_STAT_INTERNAL;
	}
	/* ...and move past it */
	cp += n;

	/* extract the type and class */
	if (cp + INT16SZ + INT16SZ > eom)
	{
		dk_error(dk, "key record corrupted");
		return DK_STAT_INTERNAL;
	}
	GETSHORT(type, cp);
	GETSHORT(class, cp);

	/* skip the TTL */
	cp += INT32SZ;

	/* get payload length */
	if (cp + INT16SZ > eom)
	{
		dk_error(dk, "key record corrupted");
		return DK_STAT_INTERNAL;
	}
	GETSHORT(n, cp);

	/* XXX -- maybe deal with a partial reply rather than require it all */
	if (cp + n > eom)
	{
		dk_error(dk, "key record corrupted");
		return DK_STAT_INTERNAL;
	}

	/* decode the TXT record */
	eob = buf + buflen - 1;
	memset(buf, '\0', buflen);

	for (p = cp, l = 0, r = buf;
	     p <= eom && n >= 0 && r <= eob;
	     p++, n--, l--)
	{
		/* if appropriate, consume a length byte */
		if (p < eom && n > 0 && l == 0)
		{
			l = *p + 1;
		}
		else
		{
			*r = *p;
			r++;
		}
	}

	return DK_STAT_OK;
}

/*
**  DK_GET_KEY -- acquire the key that will be used to either
**                generate or verify a signature
**
**  Parameters:
**  	dk -- DK handle
**
**  Return value:
**  	A DK_STAT.
*/

static DK_STAT
dk_get_key(DK *dk)
{
	bool key = FALSE;
	DK_STAT status;
	int state;
	size_t keyidx;
	int plen = 0;
	char *ssel;
	unsigned char *p;
	unsigned char *eom;
	unsigned char buf[BUFRSZ + 1];
	char tag[MAXPARAMETER + 1];
	char qname[MAXHOSTNAMELEN + 1];
	char value[MAXPARAMETER + 1];

	assert(dk != NULL);

	if (dk->dk_mode != DK_MODE_VERIFY)
	{
		FILE *f;
		size_t n;
		struct stat s;
		char fn[MAXPATHLEN + 1];

		/* already got one? */
		if (dk->dk_key != NULL)
			return DK_STAT_OK;

		ssel = dk_sterilize(dk->dk_selector);
		if (ssel == NULL)
		{
			dk_error(dk, "unsafe selector data");
			return DK_STAT_SYNTAX;
		}

		memset(fn, '\0', sizeof fn);
		snprintf(fn, sizeof fn - 1, DK_PRIVATEKEY,
		         dk->dk_selector == NULL ? DK_DEFAULT_SELECTOR : ssel);
		f = fopen(fn, "r");
		if (f == NULL)
		{
			dk_error(dk, "%s: fopen(): %s", fn, strerror(errno));
			return DK_STAT_INTERNAL;
		}

		if (fstat(fileno(f), &s) != 0)
		{
			dk_error(dk, "%s: fstat(): %s", fn, strerror(errno));
			return DK_STAT_INTERNAL;
		}

		dk->dk_key = dk_malloc(dk->dk_libhandle, dk->dk_closure,
		                       (size_t) s.st_size);
		if (dk->dk_key == NULL)
		{
			dk_error(dk, "unable to allocate %d byte(s)",
			         s.st_size);
			return DK_STAT_NORESOURCE;
		}
		dk->dk_keylen = s.st_size;

		n = fread(dk->dk_key, (unsigned int) s.st_size, 1, f);
		if (n != 1)
		{
			fclose(f);
			dk_error(dk, "%s: fread(): %s", fn, strerror(errno));
			return DK_STAT_INTERNAL;
		}

		fclose(f);
		return DK_STAT_OK;
	}

	switch (dk->dk_querymethod)
	{
	  case DK_QUERY_DNS:
		status = dk_get_key_dns(dk, dk->dk_selector,
		                        dk->dk_domain, buf, sizeof buf);
		if (status != DK_STAT_OK)
			return status;

		break;

	  case DK_QUERY_FILE:
		status = dk_get_key_file(dk, dk->dk_selector,
		                         dk->dk_domain, buf, sizeof buf);
		if (status != DK_STAT_OK)
			return status;

		break;

	  default:
		assert(0);
	}

	snprintf(qname, sizeof qname - 1, "%s.%s.%s", dk->dk_selector,
	         DK_DNSNAME, dk->dk_domain);

	/* decode the tags, allocate memory, and copy */
	eom = buf + strlen(buf);
	state = 0;
	keyidx = 0;
	for (p = buf; p <= eom; p++)
	{
		switch (state)
		{
		  case 0:				/* before tag */
			if (isascii(*p) && isspace(*p))
				continue;
			memset(tag, '\0', sizeof tag);
			plen = 0;
			state = 1;
			/* FALLTHROUGH */

		  case 1:				/* in tag */
			if (*p == '=')
			{
				state = 2;
				continue;
			}
			else if (isascii(*p) && isspace(*p))
			{
				continue;
			}
			else if (plen < sizeof tag)
			{
				tag[plen++] = *p;
				continue;
			}
			/* FALLTHROUGH */

		  case 2:				/* before value */
			if (isascii(*p) && isspace(*p))
				continue;
			memset(value, '\0', sizeof value);
			plen = 0;
			state = 3;
			key = (strcasecmp(tag, "p") == 0);
			if (dk->dk_b64 == NULL && key)
			{
				dk->dk_b64len = strlen(p + 1);
				dk->dk_b64 = dk_malloc(dk->dk_libhandle,
				                       dk->dk_closure,
				                       dk->dk_b64len);
				if (dk->dk_b64 == NULL)
				{
					dk_error(dk,
					         "unable to allocate %d byte(s)",
					         dk->dk_b64len);

					return DK_STAT_NORESOURCE;
				}
			}
			/* FALLTHROUGH */

		  case 3:				/* in value */
			if (*p == ';' || *p == '\0')
			{
				state = 4;
				/* FALLTHROUGH */
			}
			else if (isascii(*p) && isspace(*p))
			{
				continue;
			}
			else if (key && keyidx < dk->dk_b64len)
			{
				dk->dk_b64[keyidx] = *p;
				keyidx++;
				continue;
			}
			else if (!key && plen < sizeof value)
			{
				value[plen++] = *p;
				continue;
			}
			else
			{
				continue;
			}
			/* FALLTHROUGH */

		  case 4:				/* after value */
			if (strcasecmp(tag, "g") == 0)
			{
				dk->dk_gran = dk_strdup(dk->dk_libhandle,
				                        dk->dk_closure,
				                        value);
			}
			else if (strcasecmp(tag, "k") == 0)
			{
				/* key type */
				if (strcasecmp(value, "rsa") != 0)
				{
					dk_error(dk,
					         "unknown key type `%s' at `%s'",
					         value, qname);
					return DK_STAT_CANTVRFY;
				}
			}
			else if (strcasecmp(tag, "t") == 0)
			{
				if (value[0] == 'y')
					dk->dk_testing = TRUE;
			}
			else if (strcasecmp(tag, "p") == 0)
			{
				/* signature; handled earlier */
				dk->dk_b64len = keyidx;

				/* empty == revoked */
				if (dk->dk_b64[0] == '\0')
				{
					dk->dk_revoked = TRUE;
				}
				else if (!dk_decode(dk, DK_DECODE_KEY))
				{
					dk_error(dk,
					         "can't decode key at `%s'",
					         qname);
					return DK_STAT_CANTVRFY;
				}
			}
			else if (strcasecmp(tag, "n") == 0)
			{
				/* XXX -- comment */
			}
			else
			{
				/* XXX -- unknown; ignore */
			}

			state = 0;
			break;
		}
	}

	/* XXX -- verify valid and required values here instead of dk_eoh()? */

	return DK_STAT_OK;
}

/*
**  DK_PARAM_LOOKUP -- lookup the parameter's code
**
**  Parameters:
**  	str -- string to evaluate
**
**  Return value:
**  	Mnemonic code, or -1 on error.
*/

static int
dk_param_lookup(str)
	char *str;
{
	int c;

	assert(str != NULL);

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

	return -1;
}

/*
**  DK_PROCESS_VALUE -- process the value that was found
**
**  Parameters:
**  	dk -- DomainKeys handle
**  	pcode -- parameter whose value is being provided
**  	value -- the value
**
**  Return value:
**  	1 -- valid
**  	0 -- invalid
**  	-1 -- error (malloc() failure in dk_decode())
*/

static int
dk_process_value(DK *dk, int pcode, char *value)
{
	char *p;
	char *ctx;

	assert(dk != NULL);
	assert(value != NULL);

	switch (pcode)
	{
	  case DK_PARAM_SIGNALG:
		if (value[0] == '\0')
		{
			dk->dk_signalg = DK_SIGN_DEFAULT;
			return 1;
		}
		else if (strcasecmp(value, "rsa-sha1") == 0)
		{
			dk->dk_signalg = DK_SIGN_RSASHA1;
			return 1;
		}

		return 0;

	  case DK_PARAM_CANONALG:
		if (value[0] == '\0')
		{
			dk->dk_canonalg = DK_CANON_DEFAULT;
			return 1;
		}
		else if (strcasecmp(value, "simple") == 0)
		{
			dk->dk_canonalg = DK_CANON_SIMPLE;
			return 1;
		}
		else if (strcasecmp(value, "nofws") == 0)
		{
			dk->dk_canonalg = DK_CANON_NOFWS;
			return 1;
		}

		return 0;

	  case DK_PARAM_QUERYMETHOD:
		if (value[0] == '\0')
		{
			/* we'll be generous and allow a default here */
			dk->dk_querymethod = DK_QUERY_DEFAULT;
			return 1;
		}
		else if (strcasecmp(value, "dns") == 0)
		{
			dk->dk_querymethod = DK_QUERY_DNS;
			return 1;
		}

		return 0;

	  case DK_PARAM_DOMAIN:
		if (strlen(value) > 0)
		{
			dk->dk_domain = dk_strdup(dk->dk_libhandle,
			                          dk->dk_closure, value);
			return 1;
		}
		else
		{
			/* domain has no default */
			return 0;
		}

	  case DK_PARAM_SELECTOR:
		if (strlen(value) > 0)
		{
			dk->dk_selector = dk_strdup(dk->dk_libhandle,
			                            dk->dk_closure, value);
			return 1;
		}
		else
		{
			/* selector has no default */
			return 0;
		}

	  case DK_PARAM_SIGNATURE:
		/* base64-decode the signature into dk_signature */
		dk->dk_b64len = strlen(dk->dk_b64);
		if (!dk_decode(dk, DK_DECODE_SIGNATURE))
			return -1;
		dk_mfree(dk->dk_libhandle, dk->dk_closure, dk->dk_b64);
		dk->dk_b64 = NULL;
		return 1;

	  case DK_PARAM_HDRLIST:
		dk->dk_shdrlist = strdup(value);
		if (dk->dk_shdrlist == NULL)
			return -1;
		for (p = strtok_r(dk->dk_shdrlist, ":", &ctx);
		     p != NULL && dk->dk_hdrlidx < MAXHDRCNT;
		     p = strtok_r(NULL, ":", &ctx))
		{
			while (isascii(*p) && isspace(*p))
				p++;
			dk->dk_hdrlist[dk->dk_hdrlidx] = p;
			dk->dk_hdrlidx++;
		}
		return 1;

	  default:
		return 0;
	}
}

/*
**  DK_PROCESS_DKHEADER -- process a DomainKeys: header
**
**  Parameters:
**  	dk -- DomainKeys handle
**  	hdr -- header contents
**
**  Return value:
**  	-1 -- system error; check errno
**  	0  -- success
**  	1  -- parse error
*/

static int
dk_process_dkheader(DK *dk, const char *hdr)
{
	bool comment = FALSE;
	bool escaped = FALSE;
	bool quoting = FALSE;
	int state = 0;
	int status;
	int plen = 0;
	int pcode = 0;
	size_t hlen;
	const char *p;
	char param[MAXPARAMETER + 1];
	char value[MAXPARAMETER + 1];

	assert(dk != NULL);
	assert(hdr != NULL);

	hlen = strlen(hdr);

	for (p = hdr; *p != '\0'; p++)
	{
		if (!escaped)
		{
			if (*p == '\\')
			{
				escaped = TRUE;
				continue;
			}

			if (*p == '(')
			{
				comment = TRUE;
				continue;
			}
			else if (*p == ')')
			{
				comment = FALSE;
				continue;
			}

			if (*p == '"')
				quoting = !quoting;
		}

		if (comment)
			continue;

		if (escaped)
			escaped = FALSE;

		switch (state)
		{
		  case 0:			/* before parameter */
			if (isascii(*p) && isspace(*p))
				continue;
			state = 1;
			memset(param, '\0', sizeof param);
			plen = 0;
			/* FALLTHROUGH */

		  case 1:			/* in parameter */
			if (*p == '=')
			{
				state = 3;
				continue;
			}
			else if (!quoting && isascii(*p) && isspace(*p))
			{
				state = 2;
			}
			else if (plen < MAXPARAMETER)
			{
				param[plen] = *p;
				plen++;
				continue;
			}

			/* FALLTHROUGH */

		  case 2:			/* after parameter */
			if (isascii(*p) && isspace(*p))
				continue;
			else if (*p == '=')
				state = 3;
			else
				return 1;
			break;

		  case 3:			/* before value */
			pcode = dk_param_lookup(param);
			if (pcode == -1)
				return 1;
			if (isascii(*p) && isspace(*p))
				continue;
			state = 4;
			memset(value, '\0', sizeof value);
			plen = 0;
			if (pcode == DK_PARAM_SIGNATURE)
			{
				dk->dk_b64 = dk_malloc(dk->dk_libhandle,
				                       dk->dk_closure, hlen);
				if (dk->dk_b64 == NULL)
					return -1;
				memset(dk->dk_b64, '\0', hlen);
			}

			/* FALLTHROUGH */

		  case 4:			/* in value */
			if (!quoting && !escaped && *p == ';')
			{
				status = dk_process_value(dk, pcode, value);
				if (status == 0)
					return 1;
				else if (status == -1)
					return -1;

				if (*p == ';')
					state = 0;
				else
					state = 5;
				continue;
			}

			switch (pcode)
			{
			  case DK_PARAM_SIGNALG:
			  case DK_PARAM_DOMAIN:
			  case DK_PARAM_CANONALG:
			  case DK_PARAM_QUERYMETHOD:
			  case DK_PARAM_SELECTOR:
			  case DK_PARAM_HDRLIST:
				if (plen < MAXPARAMETER &&
				    (quoting || escaped ||
				     !(isascii(*p) && isspace(*p))))
				{
					value[plen] = *p;
					plen++;
				}
				break;

			  case DK_PARAM_SIGNATURE:
				dk->dk_b64[plen] = *p;
				plen++;
				break;

			  default:
				return 1;
			}
			break;

		  case 5:			/* after value */
			if (isascii(*p) && isspace(*p))
				continue;

			switch (pcode)
			{
			  case DK_PARAM_SIGNATURE:
				dk->dk_b64len = plen;
				break;

			  default:
				break;
			}

			status = dk_process_value(dk, pcode, value);
			if (status == 0)
				return 1;
			else if (status == -1)
				return -1;

			if (*p == ';')
				state = 0;
			else
				return 1;
			break;

		  default:			/* shouldn't happen */
			assert(0);
		}
	}

	if (state == 4)
	{
		status = dk_process_value(dk, pcode, value);
		if (status == 0)
			return 1;
		else if (status == -1)
			return -1;
	}

	/* XXX -- verify valid and required values here instead of dk_eoh()? */

	return 0;
}

/*
**  DK_HDRSIGNED -- see if this header is in the list of signed headers
**
**  Parameters:
**  	dk -- DomainKeys handle
**  	hdr -- header being considered
**  	mark -- mark this header ("found")
**
**  Return value:
**  	TRUE iff the header under evaluation was included in the signature
*/

static bool
dk_hdrsigned(DK *dk, u_char *hdr, bool mark)
{
	unsigned int c;
	unsigned char *colon;

	assert(dk != NULL);
	assert(hdr != NULL);

	colon = strchr(hdr, ':');
	assert(colon != NULL);

	/* if verifying and no header list given, all headers were signed */
	if (dk->dk_mode == DK_MODE_VERIFY && dk->dk_hdrlidx == 0)
		return TRUE;

	for (c = 0; c < dk->dk_hdrlidx; c++)
	{
		if (strncasecmp(dk->dk_hdrlist[c], hdr, colon - hdr) == 0 &&
		    strlen(dk->dk_hdrlist[c]) == colon - hdr)
		{
			if (mark)
				dk->dk_hdrmark[c] = TRUE;
			return TRUE;
		}
	}

	return FALSE;
}

/*
**  DK_NEW -- allocate a new DomainKeys handle
*/

static DK *
dk_new(DK_LIB *libhandle, const char *id, void *memclosure,
       dk_canon_t canon_alg, dk_alg_t sign_alg, DK_STAT *statp)
{
	DK *newdk;

	/* allocate the handle */
	newdk = dk_malloc(libhandle, memclosure, sizeof(struct dk));
	if (newdk == NULL)
	{
		*statp = DK_STAT_NORESOURCE;
		return NULL;
	}

	/* populate defaults */
	memset(newdk, '\0', sizeof(struct dk));
	newdk->dk_id = id;
	newdk->dk_signalg = (sign_alg == -1 ? DK_SIGN_DEFAULT
	                                  : sign_alg);
	newdk->dk_canonalg = (canon_alg == -1 ? DK_CANON_DEFAULT
	                                    : canon_alg);
	newdk->dk_querymethod = DK_QUERY_UNKNOWN;
	newdk->dk_crlf = DK_CRLF_UNKNOWN;
	newdk->dk_mode = DK_MODE_UNKNOWN;
	newdk->dk_state = DK_STATE_INIT;
	newdk->dk_closure = memclosure;
	newdk->dk_libhandle = libhandle;
	newdk->dk_tmpdir = libhandle->dkl_tmpdir;
	newdk->dk_timeout = DEFTIMEOUT;
	if ((libhandle->dkl_flags & DK_LIBFLAGS_HDRLIST) != 0)
		newdk->dk_flags |= DK_FLAG_HDRLIST;

	*statp = DK_STAT_OK;
	return newdk;
}

/*
**  DK_CANONINIT -- initialize canonicalization
**
**  Parameters:
**  	dk -- DK handle
*/

static DK_STAT
dk_canoninit(DK *dk)
{
	struct dk_sha1 *sha1;
	int saveerr;

	assert(dk != NULL);

#ifdef _FFR_HASH_BUFFERING
	dk->dk_hashbuf = dk_malloc(dk->dk_libhandle, dk->dk_closure,
	                           DK_HASHBUFSIZE);
	if (dk->dk_hashbuf == NULL)
	{
		dk_error(dk, "unable to allocate %d byte(s)",
		           DK_HASHBUFSIZE);
		return DK_STAT_NORESOURCE;
	}
	dk->dk_hashbufsize = DK_HASHBUFSIZE;
	dk->dk_hashbuflen = 0;
#endif /* _FFR_HASH_BUFFERING */

	sha1 = dk_malloc(dk->dk_libhandle, dk->dk_closure,
							sizeof(struct dk_sha1));

	if (sha1 == NULL)
	{
		dk_error(dk, "unable to allocate %d byte(s)",
		         sizeof(struct dk_sha1));
		return DK_STAT_NORESOURCE;
	}
	memset(sha1, '\0', sizeof(struct dk_sha1));
	sha1->sha1_tmpfd = -1;
	dk->dk_signinfo = (void *) sha1;

	SHA1_Init(&sha1->sha1_sha1);

	if (DK_DEBUG('c') ||
	    (dk->dk_libhandle->dkl_flags & DK_LIBFLAGS_TMPFILES) != 0)
	{
		/* cache the stored stuff for later processing */
		snprintf(sha1->sha1_tmppath, sizeof sha1->sha1_tmppath - 1,
		         "%s/dk.%d.XXXXXX", dk->dk_tmpdir, getpid());
		sha1->sha1_tmpfd = mkstemp(sha1->sha1_tmppath);
		saveerr = errno;
		if (sha1->sha1_tmpfd == -1)
		{
			/* looks like this is a bug on OpenBSD */
			(void) unlink(sha1->sha1_tmppath);

			errno = saveerr;

			dk_error(dk, "unable to create temporary file at %s",
			         sha1->sha1_tmppath);
			return DK_STAT_NORESOURCE;
		}
		sha1->sha1_tmpbio = BIO_new_fd(sha1->sha1_tmpfd, 1);

		if (!DK_DEBUG('c') &&
	    	    (dk->dk_libhandle->dkl_flags & DK_LIBFLAGS_KEEPFILES) == 0)
			(void) unlink(sha1->sha1_tmppath);
	}

	return DK_STAT_OK;
}

#ifdef _FFR_HASH_BUFFERING
/*
**  DK_CANONBUFFER -- buffer for dk_canon()
**
**  Parameters:
**  	dk -- DK handle
**  	buf -- buffer containing canonicalized data
**  	buflen -- number of bytes to consume
**
**  Return value:
**  	None.
*/

static void
dk_canonbuffer(DK *dk, u_char *buf, size_t buflen)
{
	assert(dk != NULL);

	/* NULL buffer or 0 length means flush */
	if ((buf == NULL || buflen == 0) && dk->dk_hashbuflen > 0)
	{
		dk_canon(dk, dk->dk_hashbuf, dk->dk_hashbuflen);
		dk->dk_hashbuflen = 0;
		return;
	}

	/* not enough buffer space; write the buffer out */
	if (dk->dk_hashbuflen + buflen > dk->dk_hashbufsize)
	{
		dk_canon(dk, dk->dk_hashbuf, dk->dk_hashbuflen);
		dk->dk_hashbuflen = 0;
	}

	/*
	**  Now, if the input is bigger than the buffer, write it too;
	**  otherwise cache it.
	*/

	if (buflen >= dk->dk_hashbufsize)
	{
		dk_canon(dk, buf, buflen);
	}
	else
	{
		memcpy(&dk->dk_hashbuf[dk->dk_hashbuflen], buf, buflen);
		dk->dk_hashbuflen += buflen;
	}
}
#endif /* _FFR_HASH_BUFFERING */

/*
**  DK_CANON -- canonicalize some data
**
**  Parameters:
**  	dk -- DK handle
**  	buf -- buffer containing data
**  	buflen -- number of bytes to consume
**
**  Return value:
**  	None.
*/

static void
dk_canon(DK *dk, unsigned char *buf, size_t buflen)
{
	struct dk_sha1 *sha1;

	assert(dk != NULL);

	sha1 = (struct dk_sha1 *) dk->dk_signinfo;

	if (buf == NULL || buflen == 0)
		return;

	if (dk->dk_writesep)
	{
		if (sha1->sha1_tmpfd != -1)
			BIO_write(sha1->sha1_tmpbio, CRLF, 2);

		SHA1_Update(&sha1->sha1_sha1, CRLF, 2);

		dk->dk_writesep = FALSE;
	}

	if (sha1->sha1_tmpfd != -1)
		BIO_write(sha1->sha1_tmpbio, buf, buflen);

	SHA1_Update(&sha1->sha1_sha1, buf, buflen);
}

/*
**  DK_ERROR -- log an error into a DK handle
**
**  Parameters:
**  	dk -- DK context in which this is performed
**  	format -- format to apply
**  	... -- arguments
**
**  Return value:
**  	None.
*/

void
dk_error(DK *dk, const char *format, ...)
{
	int flen;
	int saverr;
	char *newdk;
	va_list va;

	assert(dk != NULL);
	assert(format != NULL);

	saverr = errno;

	if (dk->dk_error == NULL)
	{
		dk->dk_error = dk_malloc(dk->dk_libhandle, dk->dk_closure,
		                         DEFERRLEN);
		if (dk->dk_error == NULL)
		{
			errno = saverr;
			return;
		}
		dk->dk_errlen = DEFERRLEN;
	}

	for (;;)
	{
		va_start(va, format);
		flen = vsnprintf(dk->dk_error, dk->dk_errlen, format, va);
		va_end(va);

		/* compensate for broken vsnprintf() implementations */
		if (flen == -1)
			flen = dk->dk_errlen * 2;

		if (flen >= dk->dk_errlen)
		{
			newdk = dk_malloc(dk->dk_libhandle, dk->dk_closure,
			                flen + 1);
			if (newdk == NULL)
			{
				errno = saverr;
				return;
			}

			dk_mfree(dk->dk_libhandle, dk->dk_closure,
			         dk->dk_error);
			dk->dk_error = newdk;
			dk->dk_errlen = flen + 1;
		}
		else
		{
			break;
		}
	}

	errno = saverr;
}

/*
**  ===== PUBLIC SECTION
*/

/*
**  DK_INIT -- initialize the DomainKeys system
*/

DK_LIB *
dk_init(void *(*caller_mallocf)(void *closure, size_t nbytes),
        void (*caller_freef)(void *closure, void *p))
{
	DK_LIB *libhandle;
	char *path;

	/* initialize the resolver */
	(void) res_init();

	/* initialize OpenSSL algorithms */
#if 0
	OpenSSL_add_all_algorithms();
#endif /* 0 */

	/* copy the parameters */
	libhandle = (DK_LIB *) malloc(sizeof(struct dk_lib));
	if (libhandle == NULL)
		return NULL;

	memset(libhandle, '\0', sizeof(struct dk_lib));

	libhandle->dkl_malloc = caller_mallocf;
	libhandle->dkl_free = caller_freef;
	libhandle->dkl_flags = DK_LIBFLAGS_DEFAULT;
	libhandle->dkl_querymethod = DK_QUERY_UNKNOWN;

	path = getenv("DK_TMPDIR");
	if (path == NULL)
		path = DK_TMPDIR;
	sm_strlcpy(libhandle->dkl_tmpdir, path, sizeof libhandle->dkl_tmpdir);

#if USE_ARLIB
	libhandle->dkl_arlib = ar_init(NULL, NULL, NULL, 0);
	if (libhandle->dkl_arlib == NULL)
	{
		free(libhandle);
		return NULL;
	}
#else /* USE_ARLIB */
	(void) res_init();
#endif /* USE_ARLIB */

	return libhandle;
}

/*
**  DK_CLOSE -- shut down a DK library package
**
**  Parameters:
**  	lib -- library handle to shut down
**
**  Return value:
**  	None.
*/

void
dk_close(DK_LIB *lib)
{
	assert(lib != NULL);

#ifdef USE_ARLIB
	if (lib->dkl_arlib != NULL)
		(void) ar_shutdown(lib->dkl_arlib);
#endif /* USE_ARLIB */

	free((void *) lib);

	EVP_cleanup();
}

/*
**  DK_SIGN -- allocate a handle for use in a signature operation
*/

DK *
dk_sign(DK_LIB *dklib, const char *id, void *memclosure,
        const dk_sigkey_t *secretkey, dk_canon_t canonalg, dk_alg_t signalg,
        DK_STAT *statp)
{
	DK *newdk;

	assert(dklib != NULL);
	assert(canonalg == DK_CANON_SIMPLE || canonalg == DK_CANON_NOFWS);
	assert(signalg == DK_SIGN_RSASHA1);

	newdk = dk_new(dklib, id, memclosure, canonalg, signalg, statp);

	if (newdk != NULL)
	{
		newdk->dk_mode = DK_MODE_SIGN;

		if (secretkey != NULL)
		{
			newdk->dk_keylen = strlen((const char *) secretkey);
			newdk->dk_key = (unsigned char *) dk_malloc(dklib,
			                                          memclosure,
		                                                  newdk->dk_keylen + 1);
			if (newdk->dk_key == NULL)
			{
				dk_free(newdk);
				return NULL;
			}

			memcpy(newdk->dk_key, secretkey, newdk->dk_keylen + 1);
		}
	}

	return newdk;
}

/*
**  DK_VERIFY -- allocate a handle for use in a verification operation
*/

DK *
dk_verify(DK_LIB *dklib, const char *id, void *memclosure, DK_STAT *statp)
{
	DK *newdk;
	assert(dklib != NULL);

	newdk = dk_new(dklib, id, memclosure, DK_CANON_UNKNOWN,
	             DK_SIGN_UNKNOWN, statp);
	if (newdk != NULL)
		newdk->dk_mode = DK_MODE_VERIFY;
	return newdk;
}

/*
**  DK_GETERROR -- return any stored error string from within the DK
**                 context handle
*/

const char *
dk_geterror(DK *dk)
{
	assert(dk != NULL);

	return dk->dk_error;
}

/*
**  DK_HEADER -- process a header
*/

DK_STAT
dk_header(DK *dk, u_char *hdr, size_t len)
{
	assert(dk != NULL);
	assert(hdr != NULL);
	assert(len > 0);

	/* verify state */
	if (dk->dk_state > DK_STATE_HEADER)
		return DK_STAT_INTERNAL;
	dk->dk_state = DK_STATE_HEADER;

	/*
	**  If we're signing, or we found the DomainKeys: header before,
	**  this is a header of interest.  Canonicalize and process.
	*/

	if (dk->dk_mode != DK_MODE_VERIFY || dk->dk_processing)
	{
		bool prevcr;
		bool skipit = FALSE;
		unsigned char *p;
		unsigned char *pend;
		char *q;
		char *qend;

		pend = hdr + len;
		qend = &dk->dk_hdrbuf[sizeof(dk->dk_hdrbuf)];
		prevcr = FALSE;

		if (dk->dk_mode == DK_MODE_VERIFY)
		{
			/* is this header signed? */
			if (!dk_hdrsigned(dk, hdr, TRUE))
				skipit = TRUE;
		}
		else
		{
			/* XXX -- omit Received: ? */

			if (!dk_hdrsigned(dk, hdr, FALSE) &&
			    dk->dk_hdrlidx < MAXHDRCNT)
			{
				dk->dk_hdrlist[dk->dk_hdrlidx] = &dk->dk_hdrbuf[dk->dk_hdrlen];
				dk->dk_hdrlidx++;
			}
		}

		if (dk->dk_hdrsidx < MAXHDRCNT)
		{
			dk->dk_hdrset[dk->dk_hdrsidx] = &dk->dk_hdrbuf[dk->dk_hdrlen];
			dk->dk_hdrsidx++;
		}


		for (p = hdr, q = &dk->dk_hdrbuf[dk->dk_hdrlen];
		     !skipit && p < pend && q < qend;
		     p++)
		{
			if (*p == '\n' && dk->dk_canonalg != DK_CANON_NOFWS)
			{
				if (prevcr)
				{
					prevcr = FALSE;
					*q = '\n';
				}
				else
				{
					*q = '\r';
					if (q < qend)
					{
						q++;
						*q = '\n';
					}
				}
			}
			else if (dk->dk_canonalg == DK_CANON_NOFWS)
			{
				if (*p == 0x20 ||
				    *p == 0x09 ||
				    *p == 0x0a ||
				    *p == 0x0D)
					continue;

				*q = *p;
			}
			else
			{
				*q = *p;
			}

			if (*p == '\r')
				prevcr = TRUE;

			q++;
		}

		if (!skipit && dk->dk_canonalg == DK_CANON_NOFWS)
		{
			if (q < qend)
			{
				*q = '\r';
				q++;
			}
			if (q < qend)
			{
				*q = '\n';
				q++;
			}
		}

		*q = '\0';
		q++;
		dk->dk_hdrlen = q - dk->dk_hdrbuf;
	}

	/*
	**  Store the From: or Sender: value regardless of its position
	**  in case no DomainKey-Signature: header is found.
	*/

	if ((strncasecmp(hdr, "from:", 5) == 0 && dk->dk_uhdrn[0] == '\0') ||
	    strncasecmp(hdr, "sender:", 7) == 0)
	{
		int status;
		char *user;
		char *domain;
		u_char *colon;
		u_char tmphdr[MAXHEADER + 1];

		sm_strlcpy(tmphdr, hdr, sizeof tmphdr);

		colon = (u_char *) strchr(tmphdr, ':');

		status = rfc2822_mailbox_split(colon + 1, &user, &domain);
		if (status != 0 || user == NULL || domain == NULL)
		{
			dk_error(dk, "unable to parse sender `%s'", colon + 1);
			return DK_STAT_SYNTAX;
		}
		sm_strlcpy(dk->dk_uhdrn, tmphdr,
		        MIN(sizeof dk->dk_uhdrn, (u_int) (colon - tmphdr) + 1));
		dk_lowercase(dk->dk_uhdrn);
		snprintf(dk->dk_uhdrv, sizeof dk->dk_uhdrv, "%s@%s", user,
		         domain);
	}

	/*
	**  If this is a DomainKey-Signature: header, cache it and note that
	**  the rest is of interest.
	*/

	if (strncasecmp(hdr, DK_SIGNHEADER, strlen(DK_SIGNHEADER)) == 0 &&
	    hdr[strlen(DK_SIGNHEADER)] == ':')
	{
		/* first DomainKey-Signature: header? */
		if (!dk->dk_processing)
		{
			u_char *hval;

			hval = hdr + strlen(DK_SIGNHEADER) + 1;

			if (dk_process_dkheader(dk, hval) != 0)
			{
				dk_error(dk, "syntax error in signature");
				return DK_STAT_SYNTAX;
			}

			dk->dk_processing = TRUE;
		}

		/*  XXX -- OPTIMIZATION
		**  For signed messages, at this point we know under which
		**  domain and selector the message was signed.  We could
		**  start the appropriate TXT query here.
		*/
	}

	return DK_STAT_OK;
}

/*
**  DK_EOH -- note end of headers
*/

DK_STAT
dk_eoh(DK *dk)
{
	int saveerr;
	unsigned int c;
	unsigned int d;
	size_t hlen;
	char *colon;
	struct dk_sha1 *sha1;
	char *sender;
	char *from;
	char *hdr;
	unsigned char *p;
	int status;

	assert(dk != NULL);

	/* verify state */
	if (dk->dk_state > DK_STATE_EOH)
		return DK_STAT_INTERNAL;
	dk->dk_state = DK_STATE_EOH;

	sender = NULL;
	from = NULL;
	for (c = 0; c < dk->dk_hdrsidx; c++)
	{
		if (strncasecmp(dk->dk_hdrset[c], "from:", 5) == 0)
		{
			if (from != NULL)
			{
				dk_error(dk, "multiple From: headers found");
				return DK_STAT_SYNTAX;
			}

			from = dk->dk_hdrset[c];
		}

		if (strncasecmp(dk->dk_hdrset[c], "sender:", 7) == 0)
		{
			if (sender != NULL)
			{
				dk_error(dk, "multiple Sender: headers found");
				return DK_STAT_SYNTAX;
			}

			sender = dk->dk_hdrset[c];
		}
	}

	hdr = NULL;

	if (sender == NULL)
		hdr = from;
	else
		hdr = sender;

	/* if verifying and no from/sender header was found, short-circuit */
	if (hdr == NULL && dk->dk_mode == DK_MODE_VERIFY)
	{
		char *at;

		at = strchr(dk->dk_uhdrv, '@');
		if (at == NULL || *(at + 1) == '\0')
		{
			dk_error(dk, "missing or empty domain name in sender");
			return DK_STAT_SYNTAX;
		}

		/* clean up if dk->dk_domain already allocated */
		if (dk->dk_domain == NULL)
		{
			dk_mfree(dk->dk_libhandle, dk->dk_closure,
			         dk->dk_domain);
		}

		dk->dk_domain = dk_malloc(dk->dk_libhandle,
		                          dk->dk_closure,
		                          strlen(at + 1) + 1);
		if (dk->dk_domain == NULL)
		{
			dk_error(dk, "unable to allocate %d byte(s)",
			         strlen(at + 1) + 1);
			return DK_STAT_NORESOURCE;
		}
		sm_strlcpy(dk->dk_domain, at + 1, strlen(at + 1) + 1);

		dk->dk_skipbody = TRUE;

		(void) dk_in_use(dk);
		if (dk->dk_signall || DK_DEBUG('s'))
			return DK_STAT_NOSIG;
		else
			return DK_STAT_OK;
	}

	/* if we found a signature but the sender doesn't match it, say so */
	if (hdr != NULL)
	{
		size_t len;
		char *user;
		char *domain;

		colon = strchr(hdr, ':');
		len = strlen(colon + 1) + 1;
		dk->dk_sender = dk_malloc(dk->dk_libhandle,
		                          dk->dk_closure, len);
		if (dk->dk_sender == NULL)
			return DK_STAT_INTERNAL;
		sm_strlcpy(dk->dk_frombuf, colon + 1, sizeof dk->dk_frombuf);
		status = rfc2822_mailbox_split(dk->dk_frombuf, &user, &domain);
		if (status != 0 || user == NULL || domain == NULL)
		{
			dk_error(dk, "unable to parse sender `%s'", colon + 1);
			return DK_STAT_SYNTAX;
		}
		snprintf(dk->dk_sender, len, "%s@%s", user, domain);

		/* verify domain/subdomain match */
		if (dk->dk_mode == DK_MODE_VERIFY)
		{
			bool ok = FALSE;

			/* incorrect DomainKey-Signature: header? */
			if (dk->dk_domain == NULL)
			{
				dk_error(dk, "can't determine signing domain");
				return DK_STAT_CANTVRFY;
			}

			if (strcasecmp(domain, dk->dk_domain) != 0)
			{
				for (p = strchr(domain, '.');
				     p != NULL;
				     p = strchr(p + 1, '.'))
				{
					if (strcasecmp(p + 1,
					               dk->dk_domain) == 0)
					{
						ok = TRUE;
						break;
					}
				}
			}
			else
			{
				ok = TRUE;
			}

			if (!ok)
			{
				dk_error(dk,
				         "signing domain and sending domain mismatch");
				return DK_STAT_CANTVRFY;
			}
		}

		dk->dk_user = dk_strdup(dk->dk_libhandle, dk->dk_closure,
		                        user);
		if (dk->dk_domain == NULL)
		{
			dk->dk_domain = dk_strdup(dk->dk_libhandle,
			                          dk->dk_closure,
			                          domain);
		}
	}
	else
	{
		dk_error(dk, "no sender header found");
		return DK_STAT_SYNTAX;
	}

	/* verify granularity */
	if (dk->dk_gran != NULL && dk->dk_gran[0] != '\0')
	{
		if (strcmp(dk->dk_user, dk->dk_gran) != 0)
		{
			dk_error(dk, "key granularity mismatch");
			return DK_STAT_CANTVRFY;
		}
	}

	status = dk_canoninit(dk);
	if (status != DK_STAT_OK)
		return status;

	/* dk_hdrlist -- set of headers to include */
	/* dk_hdrset -- set of headers on input */

	/*
	**  I would normally be embarrassed to use an O(N^2)
	**  algorithm, but this is a small data set.
	*/

	if (dk->dk_hdrlidx > 0 && (dk->dk_flags & DK_FLAG_HDRLIST) != 0)
	{
		/* reset sender/from buffer to use what was actually signed */
		memset(dk->dk_uhdrn, '\0', sizeof dk->dk_uhdrn);
		memset(dk->dk_uhdrv, '\0', sizeof dk->dk_uhdrv);

		for (c = 0; c < dk->dk_hdrlidx; c++)
		{
			colon = strchr(dk->dk_hdrlist[c], ':');
			if (colon == NULL)
				hlen = strlen(dk->dk_hdrlist[c]);
			else
				hlen = colon - dk->dk_hdrlist[c];

			for (d = 0; d < dk->dk_hdrsidx; d++)
			{
				if (strncasecmp(dk->dk_hdrset[d],
				                dk->dk_hdrlist[c],
				                hlen) == 0 &&
				    dk->dk_hdrset[d][hlen] == ':')
				{
					dk_canon(dk, dk->dk_hdrset[d],
					         strlen(dk->dk_hdrset[d]));

					colon = strchr(dk->dk_hdrset[d], ':');

					/* extract correct sender/from data */
					if ((strncasecmp(dk->dk_hdrlist[c],
					                 "from",
					                  hlen) == 0 &&
					     dk->dk_uhdrn[0] == '\0') ||
					    strncasecmp(dk->dk_hdrlist[c],
					    "sender", hlen) == 0)
					{
						strncpy(dk->dk_uhdrn,
						        dk->dk_hdrlist[c],
						        hlen);
						if (colon != NULL)
						{
							sm_strlcpy(dk->dk_uhdrv,
							           colon + 1,
							           sizeof dk->dk_uhdrv);
						}
					}

				}
			}
		}

		if (dk->dk_uhdrv[0] != '\0')
		{
			int status;
			char *user;
			char *domain;
			u_char *at;
			char addr[MAXADDRESS + 1];

			sm_strlcpy(addr, dk->dk_uhdrv, sizeof addr);
			status = rfc2822_mailbox_split(addr, &user, &domain);
			if (status != 0 || user == NULL || domain == NULL)
			{
				dk_error(dk, "unable to parse sender `%s'",
				         addr);
				return DK_STAT_SYNTAX;
			}
			snprintf(dk->dk_uhdrv, sizeof dk->dk_uhdrv, "%s@%s",
			         user, domain);
			at = strchr(dk->dk_uhdrv, '@');
			dk_lowercase(at + 1);
		}
	}
	else
	{
		unsigned int c;

		for (c = 0; c < dk->dk_hdrsidx; c++)
		{
			dk_canon(dk, dk->dk_hdrset[c],
			         strlen(dk->dk_hdrset[c]));
		}
	}

	/*
	**  The next call to dk_canon() that actually writes something
	**  should write an extra CRLF to separate the headers from the
	**  body.
	*/

	dk->dk_writesep = TRUE;

	/*
	**  If no DomainKeys: header was found, determine whether or not
	**  there should be one, and return something appropriate.
	*/

	if (!dk->dk_processing)
	{
		int status;

		if (dk->dk_domain == NULL)
		{
			char *at;

			at = strchr(dk->dk_sender, '@');
			if (at == NULL || *(at + 1) == '\0')
			{
				dk_error(dk, "can't find sender domain in `%s'",
				         dk->dk_sender);
				return DK_STAT_SYNTAX;
			}

			dk->dk_domain = dk_malloc(dk->dk_libhandle,
			                          dk->dk_closure,
			                          strlen(at + 1) + 1);
			if (dk->dk_domain == NULL)
			{
				dk_error(dk, "unable to allocate %d byte(s)",
				         strlen(at + 1) + 1);
				return DK_STAT_NORESOURCE;
			}
			sm_strlcpy(dk->dk_domain, at + 1, strlen(at + 1) + 1);
		}

		if (dk->dk_selector == NULL && dk->dk_mode == DK_MODE_VERIFY)
		{
			(void) dk_in_use(dk);
			if (dk->dk_signall || DK_DEBUG('s'))
				return DK_STAT_NOSIG;
			else
				return DK_STAT_OK;
		}

		status = dk_get_key(dk);
		if (status == DK_STAT_INTERNAL)
		{
			return status;
		}
		else if (status != DK_STAT_OK)
		{
			if (dk->dk_mode == DK_MODE_VERIFY)
			{
				if (dk->dk_key != NULL || dk->dk_signall ||
				    DK_DEBUG('s'))
					return DK_STAT_NOSIG;
				else
					return DK_STAT_NOKEY;
			}
			else
			{
				if (dk->dk_key == NULL)
					return DK_STAT_NOKEY;
			}
		}
	}

	/*
	**  Apply signature defaults.
	*/

	if (dk->dk_processing)
	{
		if (dk->dk_libhandle->dkl_querymethod != DK_QUERY_UNKNOWN)
			dk->dk_querymethod = dk->dk_libhandle->dkl_querymethod;
		else if (dk->dk_querymethod == DK_QUERY_UNKNOWN)
			dk->dk_querymethod = DK_QUERY_DEFAULT;
	}

	/*
	**  If some of the supposedly-signed headers were absent,
	**  return an error.
	*/

	if (dk->dk_processing)
	{
		unsigned int c;

		for (c = 0; c < dk->dk_hdrlidx; c++)
		{
			if (!dk->dk_hdrmark[c])
				return DK_STAT_BADSIG;
		}
	}

	/*
	**  Return DK_STAT_REVOKED if the key was revoked.
	*/

	if (dk->dk_revoked)
		return DK_STAT_REVOKED;

	/*
	**  Success!
	*/

	return DK_STAT_OK;
}

/*
**  DK_BODY -- process a body chunk
*/

DK_STAT
dk_body(DK *dk, u_char *buf, size_t len)
{
	size_t lidx;
	size_t wlen;
	u_char *p;
	u_char *eob;
	u_char *wrote;
	unsigned char lbuf[BUFRSZ];

	assert(dk != NULL);
	assert(buf != NULL);
	assert(len > 0);

	/* verify state */
	if (dk->dk_state > DK_STATE_BODY)
		return DK_STAT_INTERNAL;
	dk->dk_state = DK_STATE_BODY;

	if (dk->dk_skipbody)
		return DK_STAT_OK;

	eob = buf + len - 1;
	wrote = buf;
	wlen = 0;
	lidx = 0;

	/* run it through the canonicalizing stuff */
	switch (dk->dk_canonalg)
	{
	  case DK_CANON_SIMPLE:
		for (p = buf; p <= eob; p++)
		{
			if (*p == '\n')
			{
				int c;

				/* adaptive */
				if (dk->dk_crlf == DK_CRLF_UNKNOWN)
				{
					if (dk->dk_lastchar == '\r')
						dk->dk_crlf = DK_CRLF_CRLF;
					else
						dk->dk_crlf = DK_CRLF_LF;
				}

				if (dk->dk_crlf == DK_CRLF_CRLF &&
				    dk->dk_lastchar == '\r')
				{
					if (wlen == 1)
					{
						dk->dk_blanks++;
						wrote = p + 1;
						wlen = 0;
					}
					else
					{
						for (c = 0;
						     c < dk->dk_blanks;
						     c++)
							DK_CANON(dk, CRLF, 2);

						dk->dk_blanks = 0;

						DK_CANON(dk, wrote, wlen + 1);

						wlen = 0;
						wrote = p + 1;
					}
					dk->dk_lastchar = *p;
					continue;
				}
				else if (dk->dk_crlf == DK_CRLF_LF)
				{
					if (wlen == 0)
					{
						dk->dk_blanks++;
						wrote = p + 1;
					}
					else
					{
						DK_CANON(dk, wrote, wlen);
						DK_CANON(dk, CRLF, 2);

						wlen = 0;
						wrote = p + 1;
					}

					dk->dk_lastchar = *p;
					continue;
				}
			}

			dk->dk_lastchar = *p;
			wlen++;
		}

		/* write what's left */
		if (wlen > 0)
		{
			if (dk->dk_blanks > 0)
			{
				int c;

				for (c = 0;
				     c < dk->dk_blanks;
				     c++)
					DK_CANON(dk, CRLF, 2);

				dk->dk_blanks = 0;
			}

			DK_CANON(dk, wrote, wlen);
		}

		break;

	  case DK_CANON_NOFWS:
		for (p = buf; p <= eob; p++)
		{
			if (*p == '\n')
			{
				int c;

				/* adaptive */
				if (dk->dk_crlf == DK_CRLF_UNKNOWN)
				{
					if (dk->dk_lastchar == '\r')
						dk->dk_crlf = DK_CRLF_CRLF;
					else
						dk->dk_crlf = DK_CRLF_LF;
				}

				if ((dk->dk_lastchar == '\r' &&
				     dk->dk_crlf == DK_CRLF_CRLF) ||
				    dk->dk_crlf == DK_CRLF_LF)
				{
					if (lidx == 0)
					{
						dk->dk_blanks++;
					}
					else
					{
						for (c = 0;
						     c < dk->dk_blanks;
						     c++)
							DK_CANON(dk, CRLF, 2);

						dk->dk_blanks = 0;

						DK_CANON(dk, lbuf, lidx);
						DK_CANON(dk, CRLF, 2);

						lidx = 0;
					}
				}
			}
			else if (!dk_isfws(*p))
			{
				if (lidx == sizeof lbuf)
				{
					if (dk->dk_blanks > 0)
					{
						int c;

						for (c = 0;
						     c < dk->dk_blanks;
						     c++)
							DK_CANON(dk, CRLF, 2);

						dk->dk_blanks = 0;
					}

					DK_CANON(dk, lbuf, lidx);
					lidx = 0;
				}

				lbuf[lidx] = *p;
				lidx++;
			}

			dk->dk_lastchar = *p;
		}

		if (lidx > 0)
		{
			if (dk->dk_blanks > 0)
			{
				int c;

				for (c = 0;
				     c < dk->dk_blanks;
				     c++)
					DK_CANON(dk, CRLF, 2);

				dk->dk_blanks = 0;
			}

			DK_CANON(dk, lbuf, lidx);
		}

		break;

	  default:
		DK_CANON(dk, buf, len);
		break;
	}

	return DK_STAT_OK;
}

/*
**  DK_EOM -- note end of message
*/

DK_STAT
dk_eom(DK *dk, DK_FLAGS *dkf)
{
	int ret;
	BIO *key = NULL;
	struct dk_sha1 *sha1;
	unsigned char md[SHA_DIGEST_LENGTH];

	assert(dk != NULL);

	/* verify state */
	if (dk->dk_state >= DK_STATE_EOM)
		return DK_STAT_INTERNAL;
	dk->dk_state = DK_STATE_EOM;

	sha1 = (struct dk_sha1 *) dk->dk_signinfo;

	ret = DK_STAT_OK;

	/* if we're verifying, do the verification */
	if (dk->dk_mode == DK_MODE_VERIFY)
	{
		int status;

#ifdef _FFR_HASH_BUFFERING
		dk_canonbuffer(dk, NULL, 0);
#endif /* _FFR_HASH_BUFFERING */

		/* no sender header was found below the signature */
		if (dk->dk_skipbody && dk->dk_processing)
		{
			dk_error(dk, "no sender header found below signature");
			return DK_STAT_SYNTAX;
		}

		if (dk->dk_key == NULL)
		{
			if (dk->dk_selector == NULL)
			{
				(void) dk_in_use(dk);
				if (dk->dk_signall || DK_DEBUG('s'))
					return DK_STAT_NOSIG;
				else
					return DK_STAT_OK;
			}

			status = dk_get_key(dk);
			if (status != DK_STAT_OK)
				return status;
		}

		/* load the public key */
		key = BIO_new_mem_buf(dk->dk_key, dk->dk_keylen);
		if (key == NULL)
		{
			dk_error(dk, "BIO_new_mem_buf() failed");
			return DK_STAT_NORESOURCE;
		}
		sha1->sha1_pkey = d2i_PUBKEY_bio(key, NULL);
		if (sha1->sha1_pkey == NULL)
		{
			dk_error(dk, "d2i_PUBKEY_bio() failed");
			BIO_free(key);
			return DK_STAT_NORESOURCE;
		}

		/* set up the RSA object */
		sha1->sha1_rsa = EVP_PKEY_get1_RSA(sha1->sha1_pkey);
		if (sha1->sha1_rsa == NULL)
		{
			dk_error(dk, "EVP_PKEY_get1_RSA() failed");
			BIO_free(key);
			return DK_STAT_NORESOURCE;
		}

		sha1->sha1_keysize = RSA_size(sha1->sha1_rsa);
		sha1->sha1_pad = RSA_PKCS1_PADDING;

		/* compute the SHA1 hash */
		SHA1_Final(md, &sha1->sha1_sha1);

		/* verify the signature */
		sha1->sha1_rsain = dk->dk_signature;
		sha1->sha1_rsainlen = dk->dk_signlen;

		status = RSA_verify(NID_sha1, md, SHA_DIGEST_LENGTH,
		                    sha1->sha1_rsain, sha1->sha1_rsainlen,
		                    sha1->sha1_rsa);

		if (status == 0)
			ret = DK_STAT_BADSIG;
	}

	/* return flags */
	(void) dk_in_use(dk);
	if (dkf != NULL)
	{
		if (dk->dk_testing)
			*dkf |= DK_FLAG_TESTING;
		if (dk->dk_signall)
			*dkf |= DK_FLAG_SIGNSALL;
	}

	if (key != NULL)
		BIO_free(key);

	return ret;
}

/*
**  DK_GETSIG -- compute the signature for a message
*/

DK_STAT
dk_getsig(DK *dk, u_char *buf, size_t len)
{
	int bits;
	int c;
	int char_count;
	int status;
	u_int l;
	size_t b64idx;
	size_t b64len;
	unsigned char *b64;
	BIO *key;
	struct dk_sha1 *sha1;
	unsigned char md[SHA_DIGEST_LENGTH];

	assert(dk != NULL);
	assert(buf != NULL);
	assert(len > 0);

	/* verify state */
	if (dk->dk_state != DK_STATE_EOM)
	{
		dk_error(dk, "dk_getsig() called out of sequence");
		return DK_STAT_INTERNAL;
	}

	sha1 = (struct dk_sha1 *) dk->dk_signinfo;

	/* must be called only with a signing handle */
	if (dk->dk_mode == DK_MODE_VERIFY)
	{
		dk_error(dk, "dk_getsig() called with verifying handle");
		return DK_STAT_INTERNAL;
	}

	/* try to get the signature if it wasn't given */
	if (dk->dk_key == NULL)
	{
		int status;

		status = dk_get_key(dk);
		if (status != DK_STAT_OK)
			return status;
	}

#ifdef _FFR_HASH_BUFFERING
	dk_canonbuffer(dk, NULL, 0);
#endif /* _FFR_HASH_BUFFERING */

	/* load the private key */
	key = BIO_new_mem_buf(dk->dk_key, dk->dk_keylen);
	if (key == NULL)
	{
		dk_error(dk, "BIO_new_mem_buf() failed");
		return DK_STAT_NORESOURCE;
	}
	sha1->sha1_pkey = PEM_read_bio_PrivateKey(key, NULL, NULL, NULL);
	if (sha1->sha1_pkey == NULL)
	{
		dk_error(dk, "PEM_read_bio_PrivateKey() failed");
		BIO_free(key);
		return DK_STAT_NORESOURCE;
	}

	/* set up the RSA object */
	sha1->sha1_rsa = EVP_PKEY_get1_RSA(sha1->sha1_pkey);
	if (sha1->sha1_rsa == NULL)
	{
		dk_error(dk, "EVP_PKEY_get1_RSA() failed");
		BIO_free(key);
		return DK_STAT_NORESOURCE;
	}

	sha1->sha1_keysize = RSA_size(sha1->sha1_rsa);
	sha1->sha1_pad = RSA_PKCS1_PADDING;
	sha1->sha1_rsaout = dk_malloc(dk->dk_libhandle, dk->dk_closure,
	                              sha1->sha1_keysize);
	if (sha1->sha1_rsaout == NULL)
	{
		dk_error(dk, "unable to allocate %d byte(s)",
		         sha1->sha1_keysize);
		BIO_free(key);
		return DK_STAT_NORESOURCE;
	}

	BIO_reset(sha1->sha1_tmpbio);

	/* compute the sha1 hash of the message */
	SHA1_Final(md, &sha1->sha1_sha1);

	/* compute the signature on the hash */
	sha1->sha1_rsainlen = SHA_DIGEST_LENGTH;
	sha1->sha1_rsain = md;

	status = RSA_sign(NID_sha1, sha1->sha1_rsain,
	                  sha1->sha1_rsainlen, sha1->sha1_rsaout,
	                  &l, sha1->sha1_rsa);

	if (status == 0 || l == 0)
	{
		BIO_free(key);
		dk_error(dk, "RSA_sign() returned %d, length %d", status, l);
		return DK_STAT_INTERNAL;
	}

	sha1->sha1_rsaoutlen = l;

	/* store the signature */
	dk->dk_signature = sha1->sha1_rsaout;
	dk->dk_signlen = sha1->sha1_rsaoutlen;

	/* base64-encode the signature for validation */
	b64len = sha1->sha1_rsaoutlen * 3 + 5;
	b64len += (b64len / 60);
	b64 = dk_malloc(dk->dk_libhandle, dk->dk_closure, b64len);
	if (b64 == NULL)
	{
		dk_error(dk, "unable to allocate %d byte(s)", b64len);
		BIO_free(key);
		return DK_STAT_NORESOURCE;
	}
	memset(b64, '\0', b64len);

	bits = 0;
	char_count = 0;
	b64idx = 0;
	for (c = 0; c < sha1->sha1_rsaoutlen; c++)
	{
		bits += sha1->sha1_rsaout[c];
		char_count++;
		if (char_count == 3)
		{
			if (b64idx > b64len - 4)
			{
				BIO_free(key);
				dk_error(dk, "base64 encoding failed");
				return DK_STAT_INTERNAL;
			}

			b64[b64idx++] = alphabet[bits >> 18];
			b64[b64idx++] = alphabet[(bits >> 12) & 0x3f];
			b64[b64idx++] = alphabet[(bits >> 6) & 0x3f];
			b64[b64idx++] = alphabet[bits & 0x3f];
			bits = 0;
			char_count = 0;
		}
		else
		{
			bits <<= 8;
		}
	}

	if (char_count != 0)
	{
		if (b64idx > b64len - 4)
		{
			BIO_free(key);
			dk_error(dk, "base64 encoding failed");
			return DK_STAT_INTERNAL;
		}

		bits <<= 16 - (8 * char_count);
		b64[b64idx++] = alphabet[bits >> 18];
		b64[b64idx++] = alphabet[(bits >> 12) & 0x3f];
		if (char_count == 1)
		{
			b64[b64idx++] = '=';
			b64[b64idx++] = '=';
		}
		else
		{
			b64[b64idx++] = alphabet[(bits >> 6) & 0x3f];
			b64[b64idx++] = '=';
		}
	}

	dk->dk_b64len = b64idx;
	dk->dk_b64 = b64;

	/* extract the signature */
	strncpy(buf, dk->dk_b64, MIN(len, dk->dk_b64len));

	BIO_free(key);
	return DK_STAT_OK;
}

/*
**  DK_GETHDRS -- report which headers were included in calculation of
**                the signature
*/

DK_STAT
dk_gethdrs(DK *dk, int *hcnt, u_char *buf, size_t buflen)
{
	int cnt = 0;
	unsigned int c;
	char *colon;
	char *end;

	assert(dk != NULL);
	assert(buf != NULL);
	assert(buflen > 0);

	/* verify state */
	if (dk->dk_state != DK_STATE_EOM)
		return DK_STAT_INTERNAL;

	end = buf + strlen(buf);

	for (c = 0; c < dk->dk_hdrlidx; c++)
	{
		if (c != 0)
		{
			if (sm_strlcat(buf, ":", buflen) >= buflen)
				return DK_STAT_INTERNAL;
			end++;
		}

		if (sm_strlcat(buf, dk->dk_hdrlist[c], buflen) >= buflen)
			return DK_STAT_INTERNAL;

		colon = strchr(end, ':');
		if (colon == NULL)
			return DK_STAT_INTERNAL;

		cnt++;

		*colon = '\0';

		for (; *end != '\0'; end++)
		if (isascii(*end) && isupper(*end))
			*end = tolower(*end);
	}

	*hcnt = cnt;

	return DK_STAT_OK;
}

/*
**  DK_OPTIONS -- get or set a library option
**
**  Parameters:
**  	lib -- DK library handle
**  	op -- operation to perform
**  	opt -- option to get/set
**  	ptr -- pointer to its old/new value
**  	len -- memory available at "ptr"
**
**  Return value:
**  	A DK_STAT constant.
*/

DK_STAT
dk_options(DK_LIB *lib, int op, dk_opts_t opt, void *ptr, size_t len)
{
	assert(lib != NULL);
	assert(op == DK_OP_SETOPT || op == DK_OP_GETOPT);
	assert(len != 0);

	switch (opt)
	{
	  case DK_OPTS_TMPDIR:
		if (ptr == NULL)
			return DK_STAT_INVALID;

		if (op == DK_OP_GETOPT)
		{
			sm_strlcpy((u_char *) ptr,
			           lib->dkl_tmpdir, len);
		}
		else
		{
			sm_strlcpy(lib->dkl_tmpdir, (u_char *) ptr,
			           sizeof lib->dkl_tmpdir);
		}
		return DK_STAT_OK;

	  case DK_OPTS_FLAGS:
		if (ptr == NULL)
			return DK_STAT_INVALID;

		if (len != sizeof lib->dkl_flags)
			return DK_STAT_INVALID;

		if (op == DK_OP_GETOPT)
		{
			memcpy(ptr, &lib->dkl_flags, len);
		}
		else
		{
			memcpy(&lib->dkl_flags, ptr, len);
		}
		return DK_STAT_OK;

	  case DK_OPTS_QUERYMETHOD:
		if (ptr == NULL)
			return DK_STAT_INVALID;

		if (len != sizeof lib->dkl_querymethod)
			return DK_STAT_INVALID;

		if (op == DK_OP_GETOPT)
		{
			memcpy(ptr, &lib->dkl_querymethod, len);
		}
		else
		{
			memcpy(&lib->dkl_querymethod, ptr, len);
		}
		return DK_STAT_OK;

	  case DK_OPTS_QUERYINFO:
		if (ptr == NULL)
			return DK_STAT_INVALID;

		if (op == DK_OP_GETOPT)
		{
			sm_strlcpy(ptr, lib->dkl_queryinfo, len);
		}
		else
		{
			sm_strlcpy(lib->dkl_queryinfo, ptr,
			           sizeof lib->dkl_queryinfo);
		}
		return DK_STAT_OK;

	  default:
		return DK_STAT_INVALID;
	}

	/* to silence -Wall */
	return DK_STAT_INTERNAL;
}

/*
**  DK_REPORTINFO -- get report information
*/

DK_STAT
dk_reportinfo(DK *dk, int *fd, char *addr, size_t alen)
{
	struct dk_sha1 *sha1;

	assert(dk != NULL);

	/* verify state */
	if (dk->dk_state != DK_STATE_EOM)
		return DK_STAT_INTERNAL;

	sha1 = (struct dk_sha1 *) dk->dk_signinfo;

	if (fd != NULL)
	{
		if (sha1 == NULL)
			*fd = -1;
		else
			*fd = sha1->sha1_tmpfd;
	}

	if (addr != NULL)
		sm_strlcpy(addr, dk->dk_reportaddr, alen);

	return DK_STAT_OK;
}

/*
**  DK_GETIDENTITY -- retrieve apparent signer's identity
*/

DK_STAT
dk_getidentity(DK *dk, char *hname, size_t hnamelen,
               char *hval, size_t hvallen)
{
	assert(dk != NULL);

	if (dk->dk_state < DK_STATE_EOH)
		return DK_STAT_INTERNAL;

	if (hname != 0 && hnamelen > 0)
		sm_strlcpy(hname, dk->dk_uhdrn, hnamelen);

	if (hval != 0 && hvallen > 0)
		sm_strlcpy(hval, dk->dk_uhdrv, hvallen);

	return DK_STAT_OK;
}

/*
**  DK_TIMEOUT -- set/get the current DNS timeout
*/

DK_STAT
dk_timeout(DK *dk, int newt, int *old)
{
	assert(dk != NULL);

	if (old != NULL)
		*old = dk->dk_timeout;

	if (newt != -1)
		dk->dk_timeout = newt;

	return DK_STAT_OK;
}

/*
**  DK_FREE -- release resources associated with a DK handle
*/

DK_STAT
dk_free(DK *dk)
{
	struct dk_sha1 *sha1;

	assert(dk != NULL);

	sha1 = (struct dk_sha1 *) dk->dk_signinfo;

#define	CLOBBER(x)	if ((x) != NULL) \
			{ \
				dk_mfree(dk->dk_libhandle, dk->dk_closure, (x)); \
				(x) = NULL; \
			}

#define	BIO_CLOBBER(x)	if ((x) != NULL) \
			{ \
				BIO_free((x)); \
				(x) = NULL; \
			}

#define	RSA_CLOBBER(x)	if ((x) != NULL) \
			{ \
				RSA_free((x)); \
				(x) = NULL; \
			}

#define	EVP_CLOBBER(x)	if ((x) != NULL) \
			{ \
				EVP_PKEY_free((x)); \
				(x) = NULL; \
			}

	if (sha1 != NULL)
	{
		BIO_CLOBBER(sha1->sha1_tmpbio);
		EVP_CLOBBER(sha1->sha1_pkey);
		RSA_CLOBBER(sha1->sha1_rsa);
		CLOBBER(sha1->sha1_rsaout);
		CLOBBER(dk->dk_signinfo);
	}

	CLOBBER(dk->dk_sender);
	CLOBBER(dk->dk_domain);
	CLOBBER(dk->dk_user);
	CLOBBER(dk->dk_selector);
	if (dk->dk_flags & DK_FLAG_FREESIGNATURE)
		CLOBBER(dk->dk_signature);
	CLOBBER(dk->dk_b64);
	CLOBBER(dk->dk_key);
	CLOBBER(dk->dk_gran);

#ifdef _FFR_HASH_BUFFERING
	CLOBBER(dk->dk_hashbuf);
#endif /* _FFR_HASH_BUFFERING */

	dk_mfree(dk->dk_libhandle, dk->dk_closure, dk);

	return DK_STAT_OK;
}
