/*
**  Copyright (c) 2005-2009 Sendmail, Inc. and its suppliers.
**    All rights reserved.
**
**  Copyright (c) 2009-2015, The Trusted Domain Project.  All rights reserved.
*
*/

#include "build-config.h"

/* for Solaris */
#ifndef _REENTRANT
# define _REENTRANT
#endif /* ! REENTRANT */

/* system includes */
#include <sys/param.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
# include <stdbool.h>
#include <inttypes.h>
#include <netdb.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <limits.h>
#include <unistd.h>
#ifndef USE_GNUTLS
# include <pthread.h>
#endif /* ! USE_GNUTLS */
#include <resolv.h>
#ifdef USE_TRE
# ifdef TRE_PRE_080
#  include <tre/regex.h>
#  define tre_regcomp	regcomp
#  define tre_regexec	regexec
#  define tre_regaexec	regaexec
#  define tre_regfree	regfree
#  define tre_regerror	regerror
# else /* TRE_PRE_080 */
#  include <tre/tre.h>
#  ifndef TRE_USE_SYSTEM_REGEX_H
#   define regcomp	tre_regcomp
#   define regexec	tre_regexec
#   define regfree	tre_regfree
#   define regerror	tre_regerror
#  endif /* TRE_USE_SYSTEM_REGEX_H */
# endif /* TRE_PRE_080 */
#else /* USE_TRE */
# include <regex.h>
#endif /* USE_TRE */

#ifdef __STDC__
# include <stdarg.h>
#else /* __STDC__ */
# include <varargs.h>
#endif /* _STDC_ */

#ifdef USE_GNUTLS
/* GnuTLS includes */
# include <gnutls/gnutls.h>
# include <gnutls/crypto.h>
# include <gnutls/abstract.h>
# include <gnutls/x509.h>
#else /* USE_GNUTLS */
/* OpenSSL includes */
# include <openssl/opensslv.h>
# include <openssl/pem.h>
# include <openssl/rsa.h>
# include <openssl/bio.h>
# include <openssl/err.h>
# include <openssl/sha.h>
#endif /* USE_GNUTLS */

/* libopendkim includes */
#include "dkim-internal.h"
#include "dkim-types.h"
#include "dkim-tables.h"
#include "dkim-keys.h"
#include "dkim-report.h"
#include "dkim-util.h"
#include "dkim-canon.h"
#include "dkim-dns.h"
#ifdef QUERY_CACHE
# include "dkim-cache.h"
#endif /* QUERY_CACHE */
#include "util.h"
#include "base64.h"
#include "dkim-mailparse.h"
#include "dkim-arparse.h"

/* libbsd if found */
#ifdef USE_BSD_H
# include <bsd/string.h>
#endif /* USE_BSD_H */

/* libstrl if needed */
#ifdef USE_STRL_H
# include <strl.h>
#endif /* USE_STRL_H */

/* prototypes */
void dkim_error __P((DKIM *, const char *, ...));

/* macros */
#define	DKIM_STATE_INIT		0
#define	DKIM_STATE_HEADER	1
#define	DKIM_STATE_EOH1		2
#define	DKIM_STATE_EOH2		3
#define	DKIM_STATE_BODY		4
#define	DKIM_STATE_EOM1		5
#define	DKIM_STATE_EOM2		6
#define	DKIM_STATE_UNUSABLE	99

#define	DKIM_CHUNKSTATE_INIT	0
#define	DKIM_CHUNKSTATE_HEADER	1
#define	DKIM_CHUNKSTATE_BODY	2
#define	DKIM_CHUNKSTATE_DONE	3

#define	DKIM_CRLF_UNKNOWN	(-1)
#define	DKIM_CRLF_LF		0
#define	DKIM_CRLF_CRLF		1

#define	DKIM_PHASH(x)		((x) - 32)

#ifdef _FFR_DIFFHEADERS
# define COST_INSERT		1
# define COST_DELETE		1
# define COST_SUBST		2
#endif /* _FFR_DIFFHEADERS */

#define	BUFRSZ			1024
#define	CRLF			"\r\n"
#define	SP			" "

#define	DEFCLOCKDRIFT		300
#define	DEFMINKEYBITS		1024
#define	DEFTIMEOUT		10
#define	MINSIGLEN		8

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

#ifndef T_AAAA
# define T_AAAA			28
#endif /* ! T_AAAA */

/* need fast strtoul() and strtoull()? */
#ifdef NEED_FAST_STRTOUL
# define strtoul(x,y,z)		dkim_strtoul((x), (y), (z))
# define strtoull(x,y,z)	dkim_strtoull((x), (y), (z))
#endif /* NEED_FAST_STRTOUL */

#define	CLOBBER(x)	if ((x) != NULL) \
			{ \
				dkim_mfree(dkim->dkim_libhandle, dkim->dkim_closure, (x)); \
				(x) = NULL; \
			}

#define	HCLOBBER(x)	if ((x) != NULL) \
			{ \
				free((x)); \
				(x) = NULL; \
			}

# define DSTRING_CLOBBER(x) if ((x) != NULL) \
			{ \
				dkim_dstring_free((x)); \
				(x) = NULL; \
			}

#ifdef USE_GNUTLS
# define KEY_CLOBBER(x)	if ((x) != NULL) \
			{ \
				gnutls_x509_privkey_deinit((x)); \
				(x) = NULL; \
			}

# define PUBKEY_CLOBBER(x)	if ((x) != NULL) \
			{ \
				gnutls_pubkey_deinit((x)); \
				(x) = NULL; \
			}

# define PRIVKEY_CLOBBER(x)	if ((x) != NULL) \
			{ \
				gnutls_privkey_deinit((x)); \
				(x) = NULL; \
			}

#else /* USE_GNUTLS */
# 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; \
			}
#endif /* ! USE_GNUTLS */

/* macros */
#define DKIM_ISLWSP(x)  ((x) == 011 || (x) == 013 || (x) == 014 || (x) == 040)

/* recommended list of headers to sign, from RFC6376 Section 5.4 */
/* const */ char *dkim_should_signhdrs[] =
{
	"from",
	"reply-to",
	"subject",
	"date",
	"to",
	"cc",
	"resent-date",
	"resent-from",
	"resent-sender",
	"resent-to",
	"resent-cc",
	"in-reply-to",
	"references",
	"list-id",
	"list-help",
	"list-unsubscribe",
	"list-subscribe",
	"list-post",
	"list-owner",
	"list-archive",
	"list-unsubscribe-post", // after RFC8058
	"cfbl-address", // after RFC9477
	"cfbl-feedback-id",
	NULL
};

/* recommended list of headers not to sign, from RFC6376 Section 5.4 */
/* const */  char *dkim_should_not_signhdrs[] =
{
	"return-path",
	"received",
	"comments",
	"keywords",
	NULL
};

/* required list of headers to sign */
/* const */  char *dkim_required_signhdrs[] =
{
	"from",
	NULL
};

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


/*
* Cast functions for signed/unsigned conversion required by crypto lib.
*/
static inline unsigned char * u_char_ptr(char *cast)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpointer-sign"
	return cast;
#pragma GCC diagnostic pop
}

static inline char * char_ptr(unsigned char *cast)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpointer-sign"
	return cast;
#pragma GCC diagnostic pop
}

/*
**  DKIM_SET_FREE -- destroy a DKIM_SET 
**
**  Parameters:
**  	dkim -- DKIM context
**  	set  -- the set to destroy
**
**  Return value:
**  	None
*/

static void
dkim_set_free(DKIM *dkim, DKIM_SET *set)
{
	int c;
	DKIM_PLIST *plist;
	DKIM_PLIST *pnext;

	assert(set != NULL);

	for (c = 0; c < NPRINTABLE; c++)
	{
		for (plist = set->set_plist[c]; plist != NULL; plist = pnext)
		{
			pnext = plist->plist_next;

			CLOBBER(plist);
		}
	}

	CLOBBER(set->set_data);
	CLOBBER(set);
}

/*
**  DKIM_SET_FIRST -- return first set in a context
**
**  Parameters:
**  	dkim -- DKIM context
**  	type -- type to find, or DKIM_SETTYPE_ANY
**
**  Return value:
**  	Pointer to the first DKIM_SET in the context, or NULL if none.
*/

static DKIM_SET *
dkim_set_first(DKIM *dkim, dkim_set_t type)
{
	DKIM_SET *set;

	assert(dkim != NULL);

	if (type == DKIM_SETTYPE_ANY)
		return dkim->dkim_sethead;

	for (set = dkim->dkim_sethead; set != NULL; set = set->set_next)
	{
		if (set->set_type == type)
			return set;
	}

	return NULL;
}

/*
**  DKIM_SET_NEXT -- return next set in a context
**
**  Parameters:
**  	set -- last set reported (i.e. starting point for this search)
**  	type -- type to find, or DKIM_SETTYPE_ANY
**
**  Return value:
**  	Pointer to the next DKIM_SET in the context, or NULL if none.
*/

static DKIM_SET *
dkim_set_next(DKIM_SET *cur, dkim_set_t type)
{
	DKIM_SET *set;

	assert(cur != NULL);

	if (type == DKIM_SETTYPE_ANY)
		return cur->set_next;

	for (set = cur->set_next; set != NULL; set = set->set_next)
	{
		if (set->set_type == type)
			return set;
	}

	return NULL;
}

/*
**  DKIM_PARAM_GET -- get a parameter from a set
**
**  Parameters:
**  	set -- set to search
**  	param -- parameter to find
**
**  Return value:
**  	Pointer to the parameter requested, or NULL if it's not in the set.
*/

static char *
dkim_param_get(DKIM_SET *set, char *param)
{
	DKIM_PLIST *plist;

	assert(set != NULL);
	assert(param != NULL);

	for (plist = set->set_plist[DKIM_PHASH(*(unsigned char*)param)];
	     plist != NULL;
	     plist = plist->plist_next)
	{
		if (strcmp(plist->plist_param, param) == 0)
			return plist->plist_value;
	}

	return NULL;
}

/*
**  DKIM_ADD_PLIST -- add an entry to a parameter-value set
**
**  Parameters:
**  	dkim -- DKIM context in which this is performed
**  	set -- set to modify
**   	param -- parameter
**  	value -- value
**  	force -- override existing value, if any
**
**  Return value:
**  	0 on success, -1 on failure.
**
**  Notes:
**  	Data is not copied; a reference to it is stored.
*/

static int
dkim_add_plist(DKIM *dkim, DKIM_SET *set, char *param, char *value,
               _Bool force)
{
	DKIM_PLIST *plist;

	assert(dkim != NULL);
	assert(set != NULL);
	assert(param != NULL);
	assert(value != NULL);

	if (!isprint(*(unsigned char*)param))
	{
		dkim_error(dkim, "invalid parameter '%s'", param);
		return -1;
	}

	/* see if we have one already */
	for (plist = set->set_plist[DKIM_PHASH(*(unsigned char*)param)];
	     plist != NULL;
	     plist = plist->plist_next)
	{
		if (strcasecmp(plist->plist_param, param) == 0)
			break;
	}

	/* nope; make one and connect it */
	if (plist == NULL)
	{
		int n;

		plist = (DKIM_PLIST *) DKIM_MALLOC(dkim, sizeof(DKIM_PLIST));
		if (plist == NULL)
		{
			return -1;
		}
		force = TRUE;
		n = DKIM_PHASH(*(unsigned char*)param);
		plist->plist_next = set->set_plist[n];
		set->set_plist[n] = plist;
		plist->plist_param = param;
	}

	/* set the value if "force" was set (or this was a new entry) */
	if (force)
		plist->plist_value = value;

	return 0;
}


/*
**  ARC_CHECK_UINT -- check a parameter for a valid unsigned integer
**
**  Parameters:
**  	value -- value to check
**  	allow_zero -- if true, allow zero
**
**  Return value:
**  	TRUE iff the input value looks like a properly formed unsigned integer
** 	that is not zero.
*/

static _Bool
arc_check_uint(char const *value)
{
	uint64_t tmp = 0;
	char *end;

	assert(value != NULL);

	errno = 0;

	if (value[0] == '-')
	{
		errno = ERANGE;
		tmp = -1;
	}
	else if (value[0] == '\0')
	{
		errno = EINVAL;
		tmp = -1;
	}
	else
	{
		tmp = strtoll(value, &end, 10);
	}

	return !(tmp <= 0 || errno != 0 || *end != '\0');
}

/*
**  DKIM_PROCESS_SET -- process a parameter set, i.e. a string of the form
**                      param=value[; param=value]*
**
**  Parameters:
**  	dkim -- DKIM context in which this is performed
**  	type -- a DKIM_SETTYPE constant
**  	str -- string to be scanned
**  	len -- number of bytes available at "str"
**  	udata -- header data
**  	syntax -- only check syntax and don't add 'set' to dkim handle set
**  	          list if TRUE
**  	name -- an optional "name" for this set
**
**  Return value:
**  	A set.
*/

DKIM_SET*
dkim_process_set(DKIM *dkim, dkim_set_t type, char *str, size_t len,
                 struct dkim_header *udata, _Bool syntax, const char *name,
                 const char *settype)
{
	_Bool spaced;
	int state;
	int status;
	char *p;
	char *param;
	char *value;
	char *hcopy;
	DKIM_SET *set;

	assert(dkim != NULL);
	assert(str != NULL);
	assert(type == DKIM_SETTYPE_SIGNATURE ||
	       type == DKIM_SETTYPE_SIGREPORT ||
	       type == DKIM_SETTYPE_KEY       ||
	       type == DKIM_SETTYPE_ARC_AS    ||
	       type == DKIM_SETTYPE_ARC_AMS   ||
	       type ==  DKIM_SETTYPE_ARC_AAR);

	param = NULL;
	value = NULL;
	state = 0;
	spaced = FALSE;

	hcopy = DKIM_MALLOC(dkim, len + 1);
	if (hcopy == NULL)
		return NULL;

	strlcpy(hcopy, str, len + 1);

	set = (DKIM_SET *) DKIM_MALLOC(dkim, sizeof(DKIM_SET));
	if (set == NULL)
	{
		DKIM_FREE(dkim, hcopy);
		return NULL;
	}

	set->set_type = type;
	if (settype == NULL)
		settype = dkim_code_to_name(settypes, type);
	set->set_name = name;

	if (!syntax)
	{
		if (dkim->dkim_sethead == NULL)
			dkim->dkim_sethead = set;
		else
			dkim->dkim_settail->set_next = set;

		dkim->dkim_settail = set;
	}

	set->set_next = NULL;
	memset(&set->set_plist, '\0', sizeof set->set_plist);
	set->set_data = hcopy;
	set->set_udata = udata;
	set->set_bad = FALSE;

	for (p = hcopy; *p != 0; p++)
	{
#if defined EAI_INCOMPATIBLE
		if (!isascii(*(unsigned char*)p) || (!isprint(*(unsigned char*)p) && !isspace(*(unsigned char*)p)))
		{
			dkim_error(dkim,
			           "invalid character (ASCII 0x%02x at offset %d) in %s data",
			           *(unsigned char*)p, p - hcopy, settype);
			set->set_bad = TRUE;
			return set;
		}
#endif
		switch (state)
		{
		  case 0:				/* before param */
			if (isspace(*(unsigned char*)p))
			{
				continue;
			}
			else if (isalnum(*(unsigned char*)p))
			{
				param = p;
				state = 1;
			}
			else
			{
				dkim_error(dkim,
				           "syntax error in %s data (ASCII 0x%02x at offset %d)",
				           settype, *(unsigned char*)p, p - hcopy);
				set->set_bad = TRUE;
				return set;
			}
			break;

		  case 1:				/* in param */
			if (isspace(*(unsigned char*)p))
			{
				spaced = TRUE;
			}
			else if (*p == '=')
			{
				*p = 0;
				state = 2;
				spaced = FALSE;
			}
			else if (*p == ';' || spaced)
			{
				dkim_error(dkim,
				           "syntax error in %s data (ASCII 0x%02x at offset %d)",
				           settype, *(unsigned char*)p, p - hcopy);
				set->set_bad = TRUE;
				return set;
			}
			break;

		  case 2:				/* before value */
			if (isspace(*(unsigned char*)p))
			{
				continue;
			}
			else if (*(unsigned char*)p == ';')		/* empty value */
			{
				*p = 0;
				value = p;

				/* collapse the parameter */
				dkim_collapse(param);

				/* create the DKIM_PLIST entry */
				status = dkim_add_plist(dkim, set, param, value, TRUE);
				if (status == -1)
				{
					set->set_bad = TRUE;
					return set;
				}

				/* reset */
				param = NULL;
				value = NULL;
				state = 0;
			}
			else
			{
				value = p;
				state = 3;
			}
			break;

		  case 3:				/* in value */
			if (*p == ';')
			{
				*p = 0;

				/* collapse the parameter and value */
				dkim_collapse(param);
				dkim_collapse(value);

				/* create the DKIM_PLIST entry */
				status = dkim_add_plist(dkim, set, param, value, TRUE);
				if (status == -1)
				{
					set->set_bad = TRUE;
					return set;
				}

				/* reset */
				param = NULL;
				value = NULL;
				state = 0;
			}
			break;

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

	switch (state)
	{
	  case 0:					/* before param */
	  case 3:					/* in value */
		/* parse the data found, if any */
		if (value != NULL)
		{
			/* collapse the parameter and value */
			dkim_collapse(param);
			dkim_collapse(value);

			/* create the DKIM_PLIST entry */
			status = dkim_add_plist(dkim, set, param, value, TRUE);
			if (status == -1)
			{
				set->set_bad = TRUE;
				return set;
			}
		}
		break;

	  case 2:					/* before value */
		/* create an empty DKIM_PLIST entry */
		status = dkim_add_plist(dkim, set, param, "", TRUE);
		if (status == -1)
		{
			set->set_bad = TRUE;
			return set;
		}
		break;

	  case 1:					/* after param */
		if (type == DKIM_SETTYPE_ARC_AAR) // authserv-id
			break;

		dkim_error(dkim, "tag without value at end of %s data",
		           settype);
		set->set_bad = TRUE;
		return set;

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

	/* load up defaults, assert requirements */
	switch (set->set_type)
	{
	  case DKIM_SETTYPE_SIGREPORT:
		/* check validity of "rp" */
		value = dkim_param_get(set, "rp");
		if (value != NULL)
		{
			unsigned int tmp = 0;

			tmp = (unsigned int) strtoul(value, &p, 10);
			if (tmp > 100 || *p != 0)
			{
				dkim_error(dkim,
				           "invalid parameter(s) in %s data",
				           settype);
				set->set_bad = TRUE;
				return set;
			}
		}
		break;
		
	  case DKIM_SETTYPE_SIGNATURE:
	  {
		/* make sure required stuff is here */
		char *miss_param;
		if (dkim_param_get(set, miss_param = "s") == NULL ||
			dkim_param_get(set, miss_param = "h") == NULL ||
			dkim_param_get(set, miss_param = "d") == NULL ||
			dkim_param_get(set, miss_param = "b") == NULL ||
			dkim_param_get(set, miss_param = "v") == NULL ||
			dkim_param_get(set, miss_param = "a") == NULL)
		{
			dkim_error(dkim, "missing parameter \"%s\" in %s data",
				miss_param, settype);
			set->set_bad = TRUE;
			return set;
		}

		/* test validity of "t" and "x" */
		value = dkim_param_get(set, "t");
		if (value != NULL)
		{
			uint64_t tmp = 0;
			char *end;

			errno = 0;

			if (value[0] == '-')
			{
				errno = ERANGE;
				tmp = (uint64_t) -1;
			}
			else if (value[0] == '\0')
			{
				errno = EINVAL;
				tmp = (uint64_t) -1;
			}
			else
			{
				tmp = strtoull((char *) value, &end, 10);
			}

			if (tmp == (uint64_t) -1 || errno != 0 ||
			    *end != '\0')
			{
				dkim_error(dkim,
				           "invalid \"t\" value in %s data",
				           settype);
				set->set_bad = TRUE;
				return set;
			}
		}

		value = dkim_param_get(set, "x");
		if (value != NULL)
		{
			uint64_t tmp = 0;
			char *end;

			errno = 0;

			if (value[0] == '-')
			{
				errno = ERANGE;
				tmp = (uint64_t) -1;
			}
			else if (value[0] == '\0')
			{
				errno = EINVAL;
				tmp = (uint64_t) -1;
			}
			else
			{
				tmp = strtoull((char *) value, &end, 10);
			}

			if (tmp == (uint64_t) -1 || errno != 0 ||
			    *end != '\0')
			{
				dkim_error(dkim,
				           "invalid \"x\" value in %s data",
				           settype);
				set->set_bad = TRUE;
				return set;
			}
		}

		if (syntax)
		{
			// dkim_set_free(dkim, set);
			return set;
		}

		/* default for "c" */
		status = dkim_add_plist(dkim, set, "c", "simple/simple", FALSE);
		if (status == -1)
		{
			set->set_bad = TRUE;
			return set;
		}

		/* default for "q" */
		status = dkim_add_plist(dkim, set, "q", "dns/txt", FALSE);
		if (status == -1)
		{
			set->set_bad = TRUE;
			return set;
		}

		break;
	  }

	  case DKIM_SETTYPE_KEY:
		if (syntax)
		{
			return set;
		}

		/* default for "k" */
		status = dkim_add_plist(dkim, set, "k", "rsa", FALSE);
		if (status == -1)
		{
			set->set_bad = TRUE;
			return set;
		}

		break;

	  default:
	  {
		assert(set->set_type >= DKIM_SETTYPE_ARC_ANY);

		p = dkim_param_get(set, "i");
		if (p == NULL || !arc_check_uint(p) ||
			(p[1] != 0 && p[2] != 0 && p[3] != 0))
		{
			dkim_error(dkim,
					  "invalid \"i\" value in %s data",
					  settype);
			set->set_bad = TRUE;
			dkim->arc_state = ARC_CHAIN_FAIL;
			return set;
		}
		unsigned int const inst = atoi(p);
		if (inst > dkim->dkim_arcmax)
			dkim->dkim_arcmax = inst;

		switch (set->set_type)
		{
			case DKIM_SETTYPE_ARC_AS:
			{
				/* make sure required stuff is here */
				if (dkim_param_get(set, "b") == NULL ||
					dkim_param_get(set, "s") == NULL ||
					dkim_param_get(set, "d") == NULL ||
					dkim_param_get(set, "a") == NULL)
				/*	dkim_param_get(set, "t") == NULL: No:
				*   "t" is supported but not required
				*   (it is RECOMMENDED in DKIM).
				*/
				{
					dkim_error(dkim, "missing parameter(s) in %s data",
							  settype);
					set->set_bad = TRUE;
					dkim->arc_state = ARC_CHAIN_FAIL;
					return set;
				}

				/* test validity of "cv" */
				p = dkim_param_get(set, "cv");
				if (p == NULL || strcasecmp(p, inst==1? "none": "pass") != 0)
				{
					if (p && strcasecmp(p, "fail") == 0)
					{
						dkim->arc_state = ARC_CHAIN_FAIL_CV;
					}
					else
					{
						dkim_error(dkim,
								  "invalid \"cv\" value in %s data",
								  settype);
						set->set_bad = TRUE;
						dkim->arc_state = ARC_CHAIN_FAIL;
						return set;
					}
				}
				break;
			}
			case DKIM_SETTYPE_ARC_AMS:
			{
				/* make sure required stuff is here */
				if (dkim_param_get(set, "s") == NULL ||
					dkim_param_get(set, "h") == NULL ||
					dkim_param_get(set, "d") == NULL ||
					dkim_param_get(set, "b") == NULL ||
					dkim_param_get(set, "bh") == NULL ||
					dkim_param_get(set, "c") == NULL ||
					dkim_param_get(set, "a") == NULL)
				{
					dkim_error(dkim, "missing parameter(s) in %s data",
							  settype);
					set->set_bad = TRUE;
					dkim->arc_state = ARC_CHAIN_FAIL;
					return set;
				}

				/* make sure nothing got signed that shouldn't be */
				p = dkim_param_get(set, "h");
				char *hcopy = strdup(p);
				if (hcopy == NULL)
				{
					len = strlen(p);
					set->set_bad = TRUE;
					return set;
				}

				char *ctx = NULL;
				for (p = strtok_r(hcopy, ":", &ctx);
					 p != NULL;
					 p = strtok_r(NULL, ":", &ctx))
				{
					if (strcasecmp(p, "ARC-Seal") == 0)
					{
						dkim_error(dkim, "ARC-Message-Signature signs %s",
								  p);
						set->set_bad = TRUE;
						dkim->arc_state = ARC_CHAIN_FAIL;
						return set;
					}
				}
				free(hcopy);

				/* test validity of "t", "x", and "i" */
				p = dkim_param_get(set, "t");
				if (p != NULL && !arc_check_uint(p))
				{
					dkim_error(dkim,
							  "invalid \"t\" value in %s data",
							  settype);
					set->set_bad = TRUE;
					dkim->arc_state = ARC_CHAIN_FAIL;
					return set;
				}

				p = dkim_param_get(set, "x");
				if (p != NULL && !arc_check_uint(p))
				{
					dkim_error(dkim,
							  "invalid \"x\" value in %s data",
							  settype);
					set->set_bad = TRUE;
					dkim->arc_state = ARC_CHAIN_FAIL;
					return set;
				}

				/* default for "q" */
				status = dkim_add_plist(dkim, set, "q", "dns/txt", FALSE);
				if (status == -1)
				{
					set->set_bad = TRUE;
					return set;
				}
				break;
			}
			case DKIM_SETTYPE_ARC_AAR:
			{
				break;
			}
			default:
				assert(0);
		}
		set->set_type += inst;
	  }
	}

	return set;
}

/*
**  DKIM_LOAD_SSL_ERRORS -- suck out any OpenSSL errors queued in the thread
**                          and attach them to the DKIM handle
**
**  Parameters:
**  	dkim -- DKIM handle to update
**  	status -- status code (not used for OpenSSL)
**
**  Return value:
**  	None.
*/

static void
dkim_load_ssl_errors(DKIM *dkim, int status)
{
	assert(dkim != NULL);

	if (dkim->dkim_sslerrbuf == NULL)
	{
		dkim->dkim_sslerrbuf = dkim_dstring_new(dkim, BUFRSZ,
		                                        MAXBUFRSZ);
	}

#ifdef USE_GNUTLS

	if (dkim->dkim_sslerrbuf != NULL)
	{
		if (dkim_dstring_len(dkim->dkim_sslerrbuf) > 0)
			dkim_dstring_cat(dkim->dkim_sslerrbuf, "; ");

		dkim_dstring_cat(dkim->dkim_sslerrbuf,
		                 (char *) gnutls_strerror(status));
	}

#else /* USE_GNUTLS */
	(void)status; // compiler happy

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

		saveerr = errno;

		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)
			{
				dkim_dstring_catn(dkim->dkim_sslerrbuf, "; ", 2);
			}
			dkim_dstring_cat(dkim->dkim_sslerrbuf, tmp);
		}

		errno = saveerr;
	}
#endif /* USE_GNUTLS */
}

/*
**  DKIM_SIG_LOAD_SSL_ERRORS -- suck out any OpenSSL errors queued in the thread
**                              and attach them to the signature
**
**  Parameters:
**  	dkim -- DKIM handle in which to allocate storage
**  	sig -- signature to update
**  	status -- status code (not used for OpenSSL)
**
**  Return value:
**  	None.
*/

static void
dkim_sig_load_ssl_errors(DKIM *dkim, DKIM_SIGINFO *sig, int status)
{
	assert(dkim != NULL);
	assert(sig != NULL);

	if (sig->sig_sslerrbuf == NULL)
		sig->sig_sslerrbuf = dkim_dstring_new(dkim, BUFRSZ, MAXBUFRSZ);

#ifdef USE_GNUTLS

	if (sig->sig_sslerrbuf != NULL)
	{
		if (dkim_dstring_len(sig->sig_sslerrbuf) > 0)
			dkim_dstring_cat(sig->sig_sslerrbuf, "; ");

		dkim_dstring_cat(sig->sig_sslerrbuf,
		                 (char *) gnutls_strerror(status));
	}

#else /* USE_GNUTLS */
	(void) status; // not used

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

		saveerr = errno;

		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)
			{
				dkim_dstring_catn(sig->sig_sslerrbuf, "; ", 2);
			}
			dkim_dstring_cat(sig->sig_sslerrbuf, tmp);
		}

		errno = saveerr;
	}
#endif /* ! USE_GNUTLS */
}

/*
**  DKIM_PRIVKEY_LOAD -- attempt to load a private key for later use
**
**  Parameters:
**  	dkim -- DKIM handle
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_privkey_load(DKIM *dkim)
{
	assert(dkim != NULL);

	if (dkim->dkim_mode != DKIM_MODE_SIGN)
		return DKIM_STAT_INVALID;

	if (dkim->dkim_signalg != DKIM_SIGN_DEFAULT &&
		dkim->dkim_signalg != DKIM_SIGN_RSASHA1 &&
		dkim->dkim_signalg != DKIM_SIGN_RSASHA256 &&
		dkim->dkim_signalg != DKIM_SIGN_ED25519SHA256)
			return DKIM_STAT_INVALID;

	struct dkim_crypto *crypto = dkim->dkim_keydata;
	int crypto_is_new = 0;
	if (crypto == NULL)
	{
		crypto = DKIM_MALLOC(dkim, sizeof(struct dkim_crypto));
		if (crypto == NULL)
		{
			return DKIM_STAT_NORESOURCE;
		}
		crypto_is_new = 1;
		memset(crypto, '\0', sizeof(struct dkim_crypto));
	}

	dkim->dkim_keydata = crypto;

	// some have "-----BEGIN PRIVATE KEY-----\n", some "-----BEGIN RSA PRIVATE KEY-----\n"
	int is_pem = strstr(dkim->dkim_key, "-----BEGIN ") != NULL;

#ifdef USE_GNUTLS 
	crypto->crypto_keydata.data = u_char_ptr(dkim->dkim_key);
	crypto->crypto_keydata.size = dkim->dkim_keylen;
	int status = gnutls_x509_privkey_init(&crypto->crypto_key);
	if (status != GNUTLS_E_SUCCESS)
	{
		dkim_load_ssl_errors(dkim, status);
		dkim_error(dkim, "gnutls_x509_privkey_init() failed");
		goto return_noresource_and_free;
	}

	status = gnutls_x509_privkey_import(crypto->crypto_key,
		&crypto->crypto_keydata,
		is_pem? GNUTLS_X509_FMT_PEM: GNUTLS_X509_FMT_DER);

	if (status != GNUTLS_E_SUCCESS)
	{
		dkim_load_ssl_errors(dkim, status);
		dkim_error(dkim, "gnutls_x509_privkey_import() failed");
		gnutls_x509_privkey_deinit(crypto->crypto_key);
		goto return_noresource_and_free;
	}

	status = gnutls_privkey_init(&crypto->crypto_privkey);
	if (status != GNUTLS_E_SUCCESS)
	{
		dkim_load_ssl_errors(dkim, status);
		dkim_error(dkim, "gnutls_privkey_init() failed");
		goto return_noresource_and_free;
	}

	status = gnutls_privkey_import_x509(crypto->crypto_privkey, crypto->crypto_key, 0);
	if (status != GNUTLS_E_SUCCESS)
	{
		dkim_load_ssl_errors(dkim, status);
		dkim_error(dkim, "gnutls_privkey_import_x509() failed");
		(void) gnutls_privkey_deinit(crypto->crypto_privkey);
		goto return_noresource_and_free;
	}

	int type = gnutls_privkey_get_pk_algorithm(
		crypto->crypto_privkey, &crypto->crypto_keysize);
	if (dkim->dkim_signalg == DKIM_SIGN_DEFAULT)
	{
		if (type == GNUTLS_PK_RSA)
			dkim->dkim_signalg = DKIM_SIGN_RSASHA256;
		else if (type == GNUTLS_PK_EDDSA_ED25519)
			dkim->dkim_signalg = DKIM_SIGN_ED25519SHA256;
		else
		{
			dkim_error(dkim, "unknown algorithm %d for key", (int) type);
			return DKIM_STAT_INTERNAL;
		}
	}

#else /* USE_GNUTLS */

	if (crypto->crypto_keydata == NULL)
	{
		crypto->crypto_keydata = BIO_new_mem_buf(dkim->dkim_key,
		                                   dkim->dkim_keylen);
		if (crypto->crypto_keydata == NULL)
		{
			dkim_error(dkim, "BIO_new_mem_buf() failed");
			goto return_noresource_and_free;
		}
	}

#if OPENSSL_VERSION_NUMBER >= 0x30000000L

	EVP_PKEY *pkey = NULL;
	const char *keytype = NULL;
	if (dkim->dkim_signalg == DKIM_SIGN_ED25519SHA256)
		keytype ="ED25519";
	else if (dkim->dkim_signalg != DKIM_SIGN_DEFAULT)
		keytype = "RSA";
	OSSL_DECODER_CTX *dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey,
		is_pem? "PEM": "DER",
		NULL, keytype, OSSL_KEYMGMT_SELECT_PRIVATE_KEY, NULL, NULL);
	if (dctx == NULL)
	{
		dkim_load_ssl_errors(dkim, 0);
		dkim_error(dkim, "OSSL_DECODER_CTX_new_for_pkey() failed");
		BIO_CLOBBER(crypto->crypto_keydata);
		goto return_noresource_and_free;
	}

	int ret = OSSL_DECODER_from_bio(dctx, crypto->crypto_keydata);
	OSSL_DECODER_CTX_free(dctx);
	if (!ret)
	{
		dkim_load_ssl_errors(dkim, 0);
		dkim_error(dkim, "OSSL_DECODER_from_bio() failed");
		BIO_CLOBBER(crypto->crypto_keydata);
		goto return_noresource_and_free;
	}

	crypto->crypto_pkey = pkey;
	crypto->crypto_keysize = EVP_PKEY_get_bits(pkey);
	crypto->crypto_outlen = EVP_PKEY_size(pkey);
	crypto->crypto_out = DKIM_MALLOC(dkim, crypto->crypto_outlen);
	if (crypto->crypto_out == NULL)
	{
		EVP_PKEY_free(pkey);
		crypto->crypto_pkey = NULL;
		BIO_CLOBBER(crypto->crypto_keydata);
		goto return_noresource_and_free;
	}
	if (dkim->dkim_signalg == DKIM_SIGN_DEFAULT)
	{
		if (EVP_PKEY_is_a(pkey, "RSA"))
			dkim->dkim_signalg = DKIM_SIGN_RSASHA256;
		else
			dkim->dkim_signalg = DKIM_SIGN_ED25519SHA256;
	}

#else /* OPENSSL_VERSION_NUMBER >= 0x30000000L */

	if (is_pem)
	{						/* PEM */
		crypto->crypto_pkey =
			PEM_read_bio_PrivateKey(crypto->crypto_keydata,
				NULL, NULL, NULL);

		if (crypto->crypto_pkey == NULL)
		{
			dkim_load_ssl_errors(dkim, 0);
			dkim_error(dkim, "PEM_read_bio_PrivateKey() failed");
			BIO_CLOBBER(crypto->crypto_keydata);
			goto return_noresource_and_free;
		}
	}
	else
	{						/* DER */
		crypto->crypto_pkey =
			d2i_PrivateKey_bio(crypto->crypto_keydata, NULL);

		if (crypto->crypto_pkey == NULL)
		{
			dkim_load_ssl_errors(dkim, 0);
			dkim_error(dkim, "d2i_PrivateKey_bio() failed");
			BIO_CLOBBER(crypto->crypto_keydata);
			goto return_noresource_and_free;
		}
	}

	int key_type = EVP_PKEY_type(EVP_PKEY_base_id(crypto->crypto_pkey));
	if (key_type == EVP_PKEY_RSA)
	{
		if (dkim->dkim_signalg == DKIM_SIGN_DEFAULT)
			dkim->dkim_signalg = DKIM_SIGN_RSASHA256;
		crypto->crypto_key = EVP_PKEY_get1_RSA(crypto->crypto_pkey);
		if (crypto->crypto_key == NULL)
		{
			dkim_load_ssl_errors(dkim, 0);
			dkim_error(dkim, "EVP_PKEY_get1_RSA() failed");
			BIO_CLOBBER(crypto->crypto_keydata);
			goto return_noresource_and_free;
		}

		crypto->crypto_keysize = RSA_size(crypto->crypto_key) * 8;
		crypto->crypto_pad = RSA_PKCS1_PADDING;
	}
	else
	/*
	* EVP_PKEY_get1_* is not implemented for ed25519.  Need v.3.0.0/
	*/
	{
		if (dkim->dkim_signalg == DKIM_SIGN_DEFAULT)
			dkim->dkim_signalg = DKIM_SIGN_ED25519SHA256;
		crypto->crypto_keysize = EVP_PKEY_size(crypto->crypto_pkey) * 8;
		crypto->crypto_key = NULL;
	}

	crypto->crypto_outlen = crypto->crypto_keysize / 8;
	crypto->crypto_out = DKIM_MALLOC(dkim, crypto->crypto_outlen);
	if (crypto->crypto_out == NULL)
	{
		if (crypto->crypto_key)
		{
			RSA_free(crypto->crypto_key);
			crypto->crypto_key = NULL;
		}
		BIO_CLOBBER(crypto->crypto_keydata);
		goto return_noresource_and_free;
	}
#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */
#endif /* USE_GNUTLS */

	return DKIM_STAT_OK;

return_noresource_and_free:
	if (crypto_is_new)
	{
		free(crypto);
		dkim->dkim_keydata = NULL;
	}
	return DKIM_STAT_NORESOURCE;
}

/*
**  DKIM_CHECK_REQUIREDHDRS -- see if all requried headers are present
**
**  Parameters:
**  	dkim -- DKIM handle
**
**  Return value:
**  	Pointer to the name of a header that's absent, or NULL if all
**  	are present.
*/

static char *
dkim_check_requiredhdrs(DKIM *dkim)
{
	_Bool found;
	int c;
	size_t len;
	struct dkim_header *hdr;
	char **required_signhdrs;

	assert(dkim != NULL);

	required_signhdrs = dkim->dkim_libhandle->dkiml_requiredhdrs;
	for (c = 0; required_signhdrs[c] != NULL; c++)
	{
		found = FALSE;
		len = strlen(required_signhdrs[c]);

		for (hdr = dkim->dkim_hhead; hdr != NULL; hdr = hdr->hdr_next)
		{
			if (hdr->hdr_namelen == len &&
			    strncasecmp((char *) hdr->hdr_text,
			                required_signhdrs[c],
			                len) == 0)
			{
				found = TRUE;
				break;
			}
		}

		if (!found)
			return required_signhdrs[c];
	}

	return NULL;
}

/*
**  DKIM_SET_GETUDATA -- retrieve user data associated with a set
**
**  Parameters:
**  	set -- a DKIM_SET handle
**
**  Return value:
**  	Stored opaque handle, if any; NULL otherwise.
*/

static struct dkim_header *
dkim_set_getudata(DKIM_SET *set)
{
	assert(set != NULL);

	return set->set_udata;
}

/*
**  DKIM_GET_HEADER -- find a header in a queue of headers
**
**  Parameters:
**  	dkim -- DKIM handle
**  	name -- name of the header to find
**  	namelen -- length of the header name at "name" (or 0)
**  	inst -- instance to find (0 == first/any)
**
**  Return value:
**  	Pointer to a (struct dkim_header), or NULL if not found.
*/

static struct dkim_header *
dkim_get_header(DKIM *dkim, char const *name, size_t namelen, int inst)
{
	size_t len;
	struct dkim_header *hdr;

	assert(dkim != NULL);
	assert(name != NULL);

	if (namelen == 0)
		len = strlen(name);
	else
		len = namelen;

	for (hdr = dkim->dkim_hhead; hdr != NULL; hdr = hdr->hdr_next)
	{
		if (hdr->hdr_namelen == len &&
		    strncasecmp(hdr->hdr_text, name, len) == 0)
		{
			if (inst == 0)
				return hdr;
			else
				inst--;
		}
	}

	return NULL;
}

/*
**  DKIM_GET_PROCESSED_HEADER -- get a header that was passed to dkim_header()
**
**  Parameters:
**      dkim -- DKIM handle from which to get SSL error
**      name -- header name
**      inst -- occurrence (0 based)
**
**  Return value:
**      Pointer to the string, if found, or NULL otherwise.
*/
char const *dkim_get_processed_header(DKIM *dkim, char const *name, int inst)
{
	size_t namelen = strlen(name);
	struct dkim_header *hdr = dkim_get_header(dkim, name, namelen, inst);
	if (hdr)
		return hdr->hdr_text;

	return NULL;
}

/*
**  DKIM_KEY_SMTP -- return TRUE iff a parameter set defines an SMTP key
**
**  Parameters:
**  	set -- set to be checked
**
**  Return value:
**  	TRUE iff "set" contains an "s" parameter whose value is either
**  	"email" or "*".
*/

static _Bool
dkim_key_smtp(DKIM_SET *set)
{
	char *val;
	char *last;
	char *p;
	char buf[BUFRSZ + 1];

	assert(set != NULL);
	assert(set->set_type == DKIM_SETTYPE_KEY);

	val = dkim_param_get(set, "s");

	if (val == NULL)
		return TRUE;

	strlcpy(buf, val, sizeof buf);

	for (p = strtok_r(buf, ":", &last);
		p != NULL;
		p = strtok_r(NULL, ":", &last))
	{
		if (strcmp(p, "*") == 0 ||
			strcasecmp(p, "email") == 0)
				return TRUE;
	}

	return FALSE;
}

/*
**  DKIM_KEY_HASHOK -- return TRUE iff a signature's hash is in the approved
**                     list of hashes for a given key
**
**  Parameters:
**  	sig -- DKIM_SIGINFO handle
**  	hashlist -- colon-separated approved hash list
**
**  Return value:
**  	TRUE iff a particular hash is in the approved list of hashes.
*/

static _Bool
dkim_key_hashok(DKIM_SIGINFO *sig, char *hashlist)
{
	u_int hashalg;
	char *x, *y;
	char tmp[BUFRSZ + 1];

	assert(sig != NULL);

	if (hashlist == NULL)
		return TRUE;

	x = NULL;
	memset(tmp, '\0', sizeof tmp);

	y = hashlist;
	for (;;)
	{
		if (*y == ':' || *y == '\0')
		{
			if (x != NULL)
			{
				strlcpy(tmp, x, sizeof tmp);
				tmp[y - x] = '\0';
				hashalg = dkim_name_to_code(hashes, tmp);
				if (hashalg == sig->sig_hashtype)
					return TRUE;
			}

			x = NULL;
		}
		else if (x == NULL)
		{
			x = y;
		}

		if (*y == '\0')
			return FALSE;
		y++;
	}

	/* NOTREACHED */
}

/*
**  DKIM_KEY_HASHESOK -- return TRUE iff this key supports at least one
**                       hash method we know about (or doesn't specify)
**
**  Parameters:
**  	hashlist -- colon-separated list of hashes (or NULL)
**
**  Return value:
**  	TRUE iff this key supports at least one hash method we know about
**  	(or doesn't specify)
*/

static _Bool
dkim_key_hashesok(DKIM_LIB *lib, char *hashlist)
{
	char *x, *y;
	char tmp[BUFRSZ + 1];

	assert(lib != NULL);

	if (hashlist == NULL)
		return TRUE;

	x = NULL;
	memset(tmp, '\0', sizeof tmp);

	y = hashlist;
	for (;;)
	{
		if (*y == ':' || *y == '\0')
		{
			if (x != NULL)
			{
				int hashcode;

				strlcpy(tmp, x, sizeof tmp);
				tmp[y - x] = '\0';

				hashcode = dkim_name_to_code(hashes, tmp);

				if (hashcode != -1 &&
				    (hashcode != DKIM_HASHTYPE_SHA256 ||
				     dkim_libfeature(lib, DKIM_FEATURE_SHA256)))
					return TRUE;
			}

			x = NULL;
		}
		else if (x == NULL)
		{
			x = y;
		}

		if (*y == '\0')
			return FALSE;
		y++;
	}

	/* NOTREACHED */
}

/*
**  DKIM_SIG_HDRLISTOK -- return TRUE iff a header list contained at least
**                        all of those headers which MUST be signed
**
**  Parameters:
**  	dkim -- DKIM handle
**  	hdrlist -- header list to be checked
**
**  Return value:
**  	1 if the header list meets spec requirements,
**  	0 if not,
**  	-1 on error
*/

static _Bool
dkim_sig_hdrlistok(DKIM *dkim, char *hdrlist)
{
	_Bool in = FALSE;
	_Bool found;
	int c;
	int d;
	int nh;
	char *p;
	char **ptrs;
	char **required_signhdrs;;
	char tmp[DKIM_MAXHEADER + 1];

	assert(dkim != NULL);
	assert(hdrlist != NULL);

	strlcpy(tmp, hdrlist, sizeof tmp);

	/* figure out how many headers were named */
	c = 0;
	for (p = tmp; *p != 0; p++)
	{
		int ch = *(unsigned char*)p;
		if (ch == ':')
		{
			if (!in)
				return 0;
			in = FALSE;
		}
	/*
		Spaces are eliminated by dkim_collapse(), in dkim_process_set()
		so it is not necessary to test them here.

		else if (isspace(ch)) // isprint is true in [32, 126]
			continue;
	*/
		else if (isprint(ch))
		{
			assert(!isspace(ch));
			if (!in)
			{
				c++;
				in = TRUE;
			}
		}
		else
			return 0;
	}

	if (c == 0 || !in) // no fields or trailing colon
		return 0;

	nh = c;

	/* allocate an array of pointers to them */
	ptrs = DKIM_MALLOC(dkim, sizeof(char *) * nh);
	if (ptrs == NULL)
	{
		return -1;
	}

	/* set the pointers */
	c = 0;
	in = FALSE;
	for (p = tmp; *p != 0; p++)
	{
		int ch = *(unsigned char*)p;
		if (ch == ':')
		{
			*p = '\0';
			in = FALSE;
		}
		else if (!isspace(ch) && !in)
		{
			ptrs[c++] = p;
			in = TRUE;
		}
	}

	/* verify that each required header was represented */
	required_signhdrs = dkim->dkim_libhandle->dkiml_requiredhdrs;
	for (d = 0; ; d++)
	{
		if (required_signhdrs[d] == NULL)
			break;

		found = FALSE;

		for (c = 0; c < nh; c++)
		{
			if (strcasecmp(required_signhdrs[d], ptrs[c]) == 0)
			{
				found = TRUE;
				break;
			}
		}

		if (!found)
		{
			DKIM_FREE(dkim, ptrs);

			return 0;
		}
	}

	DKIM_FREE(dkim, ptrs);

	return 1;
}

/*
**  DKIM_SIG_DOMAINOK -- return TRUE iff a signature appears to have valid
**                       domain correlation; that is, "i" must be the same
**                       domain as or a subdomain of "d"
**
**  Parameters:
**  	dkim -- DKIM handle
**  	set -- signature set to be checked
**
**  Return value:
**  	TRUE iff the "i" parameter and the "d" parameter match up.
*/

static _Bool
dkim_sig_domainok(DKIM *dkim, DKIM_SET *set)
{
	char *at;
	char *dot;
	char *i;
	char *d;
	char addr[MAXADDRESS + 1];

	assert(dkim != NULL);
	assert(set != NULL);
	assert(set->set_type == DKIM_SETTYPE_SIGNATURE);

	i = dkim_param_get(set, "i");
	d = dkim_param_get(set, "d");

	assert(d != NULL);

	memset(addr, 0, sizeof addr);

	if (i == NULL)
		snprintf(addr, sizeof addr, "@%s", d);
	else
		dkim_qp_decode(i, addr, sizeof addr - 1);

	at = strrchr(addr, '@');
	if (at == NULL)
		return FALSE;

	if (strcasecmp(at + 1, d) == 0)
		return TRUE;

	for (dot = strchr(at, '.'); dot != NULL; dot = strchr(dot + 1, '.'))
	{
		if (strcasecmp(dot + 1, d) == 0)
		{
			dkim->dkim_subdomain = TRUE;
			return TRUE;
		}
	}

	return FALSE;
}

/*
**  DKIM_SIG_EXPIRED -- return TRUE iff a signature appears to have expired
**
**  Parameters:
**  	set -- signature set to be checked
**  	drift -- seconds of drift allowed
**
**  Return value:
**  	TRUE iff "set" contains an "x=" parameter which indicates a time
**  	which has passed.
**
**  Notes:
**  	Syntax is not checked here.  It's checked in dkim_process_set().
*/

static _Bool
dkim_sig_expired(DKIM_SET *set, uint64_t drift)
{
	assert(set != NULL);
	assert(set->set_type == DKIM_SETTYPE_SIGNATURE ||
		set->set_type > DKIM_SETTYPE_ARC_AMS);

	char *val = dkim_param_get(set, "x");
	if (val == NULL)
		return FALSE;

	static_assert(sizeof(uint64_t) == sizeof(unsigned long long));
	unsigned long long expire = strtoull(val, NULL, 10);

	time_t now;
	time(&now);
	unsigned long long nowl = now;

	return nowl >= expire + drift;
}

/*
**  DKIM_SIG_TIMESTAMPSOK -- return TRUE iff a signature appears to have
**                           both a timestamp and an expiration date and they
**                           are properly ordered
**
**  Parameters:
**  	set -- signature set to be checked
**
**  Return value:
**  	TRUE: - "set" contains both a "t=" parameter and an "x=" parameter
**  	        and the latter is greater than the former
**  	      - "set" is missing either "t=" or "x=" (or both)
**  	FALSE: otherwise
**
**  Notes:
**  	Syntax is not checked here.  It's checked in dkim_process_set().
*/

static _Bool
dkim_sig_timestampsok(DKIM_SET *set)
{
	uint64_t signtime;
	uint64_t expire;
	char *val;

	assert(set != NULL);
	assert(set->set_type == DKIM_SETTYPE_SIGNATURE ||
		set->set_type > DKIM_SETTYPE_ARC_ANY);

	val = dkim_param_get(set, "t");
	if (val == NULL)
		return TRUE;
	static_assert(sizeof(uint64_t) == sizeof(unsigned long long));
	signtime = strtoull(val, NULL, 10);

	val = dkim_param_get(set, "x");
	if (val == NULL)
		return TRUE;

	expire = strtoull(val, NULL, 10);

	return signtime < expire;
}

/*
**  DKIM_SIG_FUTURE -- return TRUE iff a signature appears to have been
**                     generated in the future
**
**  Parameters:
**  	set -- signature set to be checked
**  	drift -- seconds of drift allowed
**
**  Return value:
**  	TRUE iff "set" contains a "t=" parameter which indicates a time
**  	in the future.
**
**  Notes:
**  	Syntax is not checked here.  It's checked in dkim_process_set().
*/

static _Bool
dkim_sig_future(DKIM_SET *set, uint64_t drift)
{
	uint64_t signtime;
	uint64_t nowl;
	time_t now;
	char *val;

	assert(set != NULL);
	assert(set->set_type == DKIM_SETTYPE_SIGNATURE ||
		set->set_type > DKIM_SETTYPE_ARC_ANY);

	val = dkim_param_get(set, "t");
	if (val == NULL)
		return FALSE;

	static_assert(sizeof(uint64_t) == sizeof(unsigned long long));
	signtime = strtoull(val, NULL, 10);

	(void) time(&now);
	nowl = (uint64_t) now;

	return (nowl < signtime - drift);
}

/*
**  DKIM_SIG_VERSIONOK -- return TRUE iff a signature appears to have a version
**                        we can accept
**
**  Parameters:
**  	dkim -- DKIM handle
**  	set -- signature set to be checked
**
**  Return value:
**  	TRUE iff "set" appears to be based on a version of DKIM that is
**  	supported by this API.
*/

static _Bool
dkim_sig_versionok(DKIM *dkim, DKIM_SET *set)
{
	char *v;

	assert(set != NULL);
	assert(set->set_type == DKIM_SETTYPE_SIGNATURE);

	v = (char *) dkim_param_get(set, "v");

	assert(v != NULL);

	/* check for DKIM_VERSION_SIG */
	if (strcmp(v, DKIM_VERSION_SIG) == 0)
		return TRUE;

	/* check for DKIM_VERSION_SIGOLD if allowed */
	if ((dkim->dkim_libhandle->dkiml_flags & DKIM_LIBFLAGS_ACCEPTV05) &&
	    strcmp(v, DKIM_VERSION_SIGOLD) == 0)
		return TRUE;

	return FALSE;
}

/*
**  DKIM_SIG_FROMSET -- fill a signature with data from the set
**
**  Parameters:
**  	dkim -- DKIM handle
**  	sig -- signature to be filled
**  	set -- signature data
**  	type -- kind of signature, according to sigtype
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

static DKIM_STAT
dkim_sig_fromset(DKIM *dkim, DKIM_SIGINFO *sig, DKIM_SET *set, sigtype type)
{
	sig->sig_error = DKIM_SIGERROR_UNKNOWN;
	sig->sig_dnssec_key = DKIM_DNSSEC_UNKNOWN;

	/* store the set */
	sig->sig_taglist = set;

	DKIM_LIB *lib = dkim->dkim_libhandle;
	
	/* override query method? */
	if (lib->dkiml_querymethod != DKIM_QUERY_UNKNOWN)
		sig->sig_query = lib->dkiml_querymethod;

	/* critical stuff: signing domain */
	char *param = dkim_param_get(set, "d");
	if (param == NULL)
	{
		sig->sig_error = DKIM_SIGERROR_MISSING_D;
		return DKIM_STAT_OK;
	}
	else if (param[0] == '\0')
	{
		sig->sig_error = DKIM_SIGERROR_EMPTY_D;
		return DKIM_STAT_OK;
	}
	sig->sig_domain = param;

	/* critical stuff: selector */
	param = dkim_param_get(set, "s");
	if (param == NULL)
	{
		sig->sig_error = DKIM_SIGERROR_MISSING_S;
		return DKIM_STAT_OK;
	}
	else if (param[0] == '\0')
	{
		sig->sig_error = DKIM_SIGERROR_EMPTY_S;
		return DKIM_STAT_OK;
	}
	sig->sig_selector = param;

	/* some basic checks first */
	if (type == sigtype_dkim)
	{
		param = dkim_param_get(set, "v");
		if (param == NULL)
		{
			sig->sig_error = DKIM_SIGERROR_MISSING_V;
			return DKIM_STAT_OK;
		}
		if (param[0] == '\0')
		{
			sig->sig_error = DKIM_SIGERROR_EMPTY_V;
			return DKIM_STAT_OK;
		}
		if (!dkim_sig_versionok(dkim, set))
		{
			sig->sig_error = DKIM_SIGERROR_VERSION;
			return DKIM_STAT_OK;
		}
		if (!dkim_sig_domainok(dkim, set))
		{
			sig->sig_error = DKIM_SIGERROR_DOMAIN;
			return DKIM_STAT_OK;
		}
	}

	if (type == sigtype_dkim || type == sigtype_ams)
	{
		if (dkim_sig_expired(set, lib->dkiml_clockdrift))
		{
			sig->sig_error = DKIM_SIGERROR_EXPIRED;
			return DKIM_STAT_OK;
		}
		if (!dkim_sig_timestampsok(set))
		{
			sig->sig_error = DKIM_SIGERROR_TIMESTAMPS;
			return DKIM_STAT_OK;
		}
	}
	if (dkim_sig_future(set, lib->dkiml_clockdrift))
	{
		sig->sig_error = DKIM_SIGERROR_FUTURE;
		return DKIM_STAT_OK;
	}

	/* determine canonicalizations */
	dkim_canon_t bodycanon;
	dkim_canon_t hdrcanon;
	if (type == sigtype_as)
	{
		hdrcanon = DKIM_CANON_RELAXED;
		bodycanon = DKIM_CANON_UNKNOWN;
	}
	else
	{
		param = dkim_param_get(set, "c");
		if (param == NULL)
		{
			hdrcanon = DKIM_CANON_SIMPLE;
			bodycanon = DKIM_CANON_SIMPLE;
		}
		else
		{
			char *q;
			char value[BUFRSZ + 1];

			strlcpy(value, param, sizeof value);

			q = strchr(value, '/');
			if (q != NULL)
				*q = '\0';

			hdrcanon = dkim_name_to_code(canonicalizations, value);
			if (hdrcanon == -1)
			{
				sig->sig_error = DKIM_SIGERROR_INVALID_HC;
				return DKIM_STAT_OK;
			}

			if (q == NULL)
			{
				bodycanon = DKIM_CANON_SIMPLE;
			}
			else
			{
				bodycanon = dkim_name_to_code(canonicalizations,
											  q + 1);

				if (bodycanon == -1)
				{
					sig->sig_error = DKIM_SIGERROR_INVALID_BC;
					return DKIM_STAT_OK;
				}
			}
		}
	}

	/* determine hash type */
	dkim_alg_t signalg;
	int hashtype;
	param = dkim_param_get(set, "a");
	if (param == NULL)
	{
		sig->sig_error = DKIM_SIGERROR_MISSING_A;
		return DKIM_STAT_OK;
	}
	else
	{
		signalg = dkim_name_to_code(algorithms, param);

		if (signalg == -1)
		{
			sig->sig_error = DKIM_SIGERROR_INVALID_A;
			return DKIM_STAT_OK;
		}

		switch (signalg)
		{
		  case DKIM_SIGN_RSASHA1:
			hashtype = DKIM_HASHTYPE_SHA1;
			break;

		  case DKIM_SIGN_RSASHA256:
			if (dkim_libfeature(lib, DKIM_FEATURE_SHA256))
			{
				hashtype = DKIM_HASHTYPE_SHA256;
			}
			else
			{
				sig->sig_error = DKIM_SIGERROR_INVALID_A;
				return DKIM_STAT_OK;
			}
			break;

		  case DKIM_SIGN_ED25519SHA256:
			if (dkim_libfeature(lib, DKIM_FEATURE_ED25519))
			{
				hashtype = DKIM_HASHTYPE_SHA256;
			}
			else
			{
				sig->sig_error = DKIM_SIGERROR_INVALID_A;
				return DKIM_STAT_OK;
			}
			break;

		  default:
			assert(0);
			/* NOTREACHED */
		}

		sig->sig_signalg = signalg;
		sig->sig_hashtype = hashtype;
	}

	/* determine header list */
	char *hdrlist = NULL;
	param = dkim_param_get(set, "h");
	if (type == sigtype_as)
	{
		/*
		*  Note especially that the DKIM "h" tag is NOT allowed
		* and, if found, MUST result in a cv status of "fail"
		*/
		if (param != NULL)
		{
			sig->sig_error = DKIM_SIGERROR_MISSING_H;
			return DKIM_STAT_OK;
		}
	}
	else if (type == sigtype_dkim || type == sigtype_ams)
	{
		if (param == NULL)
		{
			sig->sig_error = DKIM_SIGERROR_MISSING_H;
			return DKIM_STAT_OK;
		}
		else if (param[0] == '\0')
		{
			sig->sig_error = DKIM_SIGERROR_EMPTY_H;
			return DKIM_STAT_OK;
		}

		int hstatus = dkim_sig_hdrlistok(dkim, param);
		if (hstatus == 0)
		{
			sig->sig_error = DKIM_SIGERROR_INVALID_H;
			return DKIM_STAT_OK;
		}
		else if (hstatus == -1)
		{
			return DKIM_STAT_NORESOURCE;
		}

		hdrlist = param;
	}

	/* determine signing length, (should be ssize_t) */
	size_t signlen;
	if (type == sigtype_as)
	{
		signlen = 0;
	}
	else if (type == sigtype_dkim || type == sigtype_ams)
	{
		signlen = (size_t) -1;
		param = dkim_param_get(set, "l");
		if (param != NULL)
		{
			char *q;

			errno = 0;
			if (param[0] == '-')
			{
				errno = ERANGE;
				signlen = ULONG_MAX;
			}
			else
			{
				signlen = (ssize_t) strtoul(param, &q, 10);
			}

			if (signlen == ULONG_MAX || errno != 0 || *q != '\0')
			{
				sig->sig_error = DKIM_SIGERROR_INVALID_L;
				return DKIM_STAT_OK;
			}
		}
	}

	/* query method */
	if (type == sigtype_dkim || type == sigtype_ams)
	{
		param = dkim_param_get(set, "q");
		if (param != NULL)
		{
			_Bool bad_qo = FALSE;
			dkim_query_t q = (dkim_query_t) -1;
			char *p;
			char *last;
			char *opts;
			char tmp[BUFRSZ + 1];
			char qtype[BUFRSZ + 1];

			strlcpy(qtype, param, sizeof qtype);

			for (p = strtok_r(qtype, ":", &last);
				 p != NULL;
				 p = strtok_r(NULL, ":", &last))
			{
				opts = strchr(p, '/');
				if (opts != NULL)
				{
					strlcpy(tmp, p, sizeof tmp);
					p = tmp;
					opts = strchr((char *) tmp, '/');
					if (opts != NULL)
					{
						*opts = '\0';
						opts++;
					}
				}

				/* unknown type */
				q = dkim_name_to_code(querytypes, p);
				if (q == (dkim_query_t) -1)
					continue;

				if (q == DKIM_QUERY_DNS)
				{
					/* "txt" option required */
					if (opts == NULL ||
						strcmp((char *) opts, "txt") != 0)
					{
						bad_qo = TRUE;
						continue;
					}
				}

				break;
			}

			if (lib->dkiml_key_lookup == NULL)
			{
				if (q == (dkim_query_t) -1)
				{
					sig->sig_error = DKIM_SIGERROR_INVALID_Q;
					return DKIM_STAT_OK;
				}
				else if (bad_qo)
				{
					sig->sig_error = DKIM_SIGERROR_INVALID_QO;
					return DKIM_STAT_OK;
				}
			}

			sig->sig_query = q;
		}
	}

	/* override query method? */
	if (lib->dkiml_querymethod != DKIM_QUERY_UNKNOWN)
		sig->sig_query = lib->dkiml_querymethod;

	/* timestamp */
	param = dkim_param_get(set, "t");
	if (param == NULL)
	{
		sig->sig_timestamp = 0;
	}
	else
	{
		static_assert(sizeof(uint64_t) == sizeof(unsigned long long));
		sig->sig_timestamp = strtoull(param, NULL, 10);
	}

	/* body hash */
	if (type == sigtype_dkim || type == sigtype_ams)
	{
		param = dkim_param_get(set, "bh");
		if (param == NULL)
		{
			sig->sig_error = DKIM_SIGERROR_MISSING_BH;
			return DKIM_STAT_OK;
		}
		else if (param[0] == '\0')
		{
			sig->sig_error = DKIM_SIGERROR_EMPTY_BH;
			return DKIM_STAT_OK;
		}
	}

	/* signature */
	param = dkim_param_get(set, "b");
	if (param == NULL)
	{
		sig->sig_error = DKIM_SIGERROR_MISSING_B;
		return DKIM_STAT_OK;
	}
	else if (param[0] == '\0')
	{
		sig->sig_error = DKIM_SIGERROR_EMPTY_B;
		return DKIM_STAT_OK;
	}

	size_t b64siglen = strlen(param);
	sig->sig_sig = DKIM_MALLOC(dkim, b64siglen);
	if (sig->sig_sig == NULL)
	{
		return DKIM_STAT_NORESOURCE;
	}

	int blen = dkim_base64_decode(param,
								sig->sig_sig,
								b64siglen);
	if (blen < 0)
	{
		sig->sig_error = DKIM_SIGERROR_CORRUPT_B;
		return DKIM_STAT_OK;
	}
	else
	{
		sig->sig_siglen = blen;
	}

	/* canonicalization handle for the headers */
	DKIM_CANON *hc;
	DKIM_STAT status = dkim_add_canon(dkim, TRUE, hdrcanon, hashtype,
							hdrlist, dkim_set_getudata(set), 0,
							&hc);
	if (status != DKIM_STAT_OK)
		return status;

	sig->sig_hdrcanon = hc;
	sig->sig_hdrcanonalg = hdrcanon;

	/* canonicalization handle for the body */
	if (type == sigtype_dkim || type == sigtype_ams)
	{
		DKIM_CANON *bc;
		status = dkim_add_canon(dkim, FALSE, bodycanon, hashtype,
								NULL, NULL, signlen,
								&bc);
		if (status != DKIM_STAT_OK)
			return status;

		sig->sig_bodycanon = bc;
		sig->sig_bodycanonalg = bodycanon;
	}

	/* the rest */
	sig->sig_bh = DKIM_SIGBH_UNTESTED;
	sig->sig_flags = 0;

#if 0  // options commented out in struct dkim_lib (dkim-types.h)
       // t-test41 and t-test45 commented out in Makefile.am
	/* allow the user to generate its handle */
	if (lib->dkiml_sig_handle != NULL)
		sig->sig_context = lib->dkiml_sig_handle(dkim->dkim_closure);

	/* populate the user handle */
	if (lib->dkiml_sig_tagvalues != NULL)
	{
		u_int n;
		dkim_param_t pcode;
		struct dkim_plist *plist;
		void *user;

		user = sig->sig_context;

		for (n = 0; n < NPRINTABLE; n++)
		{
			for (plist = set->set_plist[n];
				 plist != NULL;
				 plist = plist->plist_next)
			{
				pcode = dkim_name_to_code(sigparams, plist->plist_param);

				(void) lib->dkiml_sig_tagvalues(user,
												pcode,
												plist->plist_param,
												plist->plist_value);
			}
		}
	}
#endif
	return DKIM_STAT_OK;
}

/*
**  DKIM_SIGLIST_SETUP -- create a signature list and load the elements
**
**  Parameters:
**  	dkim -- DKIM handle
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_siglist_setup(DKIM *dkim)
{
	_Bool bsh;
	unsigned int c;
	size_t len;
	DKIM_SET *set;
	DKIM_LIB *lib;

	assert(dkim != NULL);

	lib = dkim->dkim_libhandle;

	bsh = ((lib->dkiml_flags & DKIM_LIBFLAGS_BADSIGHANDLES) != 0);

	len = dkim->dkim_sigcount * sizeof(DKIM_SIGINFO *);
	dkim->dkim_siglist = DKIM_MALLOC(dkim, len);
	if (dkim->dkim_siglist == NULL)
	{
		return DKIM_STAT_NORESOURCE;
	}

	/* allocate the siginfo elements */
	for (c = 0; c < dkim->dkim_sigcount; c++)
	{
		dkim->dkim_siglist[c] = DKIM_MALLOC(dkim,
		                                    sizeof(DKIM_SIGINFO));
		if (dkim->dkim_siglist[c] == NULL)
		{
			unsigned int n;

			for (n = 0; n < c; n++)
				DKIM_FREE(dkim, dkim->dkim_siglist[n]);
			return DKIM_STAT_NORESOURCE;
		}

		memset(dkim->dkim_siglist[c], '\0', sizeof(DKIM_SIGINFO));
	}

	/* populate the elements */
	for (set = dkim_set_first(dkim, DKIM_SETTYPE_SIGNATURE), c = 0;
	     set != NULL && c < dkim->dkim_sigcount;
	     set = dkim_set_next(set, DKIM_SETTYPE_SIGNATURE), c++)
	{
		/* cope with bad ones */
		if (set->set_bad && !bsh)
		{
			c--;
			continue;
		}

		DKIM_STAT status =
			dkim_sig_fromset(dkim, dkim->dkim_siglist[c], set, sigtype_dkim);
		if (status != DKIM_STAT_OK)
			return status;
	}

	return DKIM_STAT_OK;
}

/*
**  DKIM_SIG_CLEAR -- free signature allocations
**
**  Parameters:
**  	dkim -- DKIM handle
**  	sig -- DKIM_SIGINFO handle
**
**  Return value:
**  	None.
*/

static void dkim_sig_clear(DKIM *dkim, DKIM_SIGINFO *sig)
{

#if 0 // options commentd out from struct dkim_lib (dkim-types.h)
	if (sig->sig_context != NULL &&
		dkim->dkim_libhandle->dkiml_sig_handle_free != NULL)
	{
		dkim->dkim_libhandle->dkiml_sig_handle_free(dkim->dkim_closure,
													sig->sig_context);
	}
#endif
	if (sig->sig_sslerrbuf != NULL)
		dkim_dstring_free(sig->sig_sslerrbuf);

	CLOBBER(sig->sig_key);
	CLOBBER(sig->sig_sig);
	//if (sig->sig_keytype == DKIM_KEYTYPE_RSA)
	{
		struct dkim_crypto *crypto;

		crypto = sig->sig_signature;
		if (crypto != NULL)
		{
#ifdef USE_GNUTLS
			KEY_CLOBBER(crypto->crypto_key);
			PUBKEY_CLOBBER(crypto->crypto_pubkey);
			PRIVKEY_CLOBBER(crypto->crypto_privkey);
			HCLOBBER(crypto->crypto_out.data);
#else /* USE_GNUTLS */
			BIO_CLOBBER(crypto->crypto_keydata);
			EVP_CLOBBER(crypto->crypto_pkey);
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
			CLOBBER(crypto->crypto_out);
#else /* OPENSSL_VERSION_NUMBER >= 0x30000000L */
			RSA_CLOBBER(crypto->crypto_key);
			CLOBBER(crypto->crypto_out);
#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */
#endif /* USE_GNUTLS */
		}
	}
	CLOBBER(sig->sig_signature);
	CLOBBER(sig);
}

/*
**  DKIM_GENSIGHDR -- generate a signature header
**
**  Parameters:
**  	dkim -- DKIM handle
**  	sig -- DKIM_SIGINFO handle
**  	dstr -- dstring to which to write
**  	delim -- delimiter
**
**  Return value:
**  	Number of bytes written to "dstr", or <= 0 on error.
*/

static size_t
dkim_gensighdr(DKIM *dkim, sigtype type, DKIM_SIGINFO *sig,
	struct dkim_dstring *dstr, char *delim)
{
	_Bool firsthdr;
	_Bool nosigner = FALSE;
	int n;
	int status;
	int delimlen;
	size_t hashlen;
	char *hash;
	struct dkim_header *hdr;
	char tmp[DKIM_MAXHEADER + 1];
	char b64hash[DKIM_MAXHEADER + 1];

	assert(dkim != NULL);
	assert(sig != NULL);
	assert(dstr != NULL);
	assert(delim != NULL);

	delimlen = strlen(delim);

	/* bail if we were asked to generate an invalid signature */
	if (type == sigtype_dkim && dkim->dkim_signer != NULL)
	{
		_Bool match = FALSE;
		char *sd;

		sd = strchr(dkim->dkim_signer, '@');
		if (sd == NULL)
		{
			dkim_error(dkim, "syntax error in signer value");
			return 0;
		}

		if (strcasecmp(sd + 1, sig->sig_domain) == 0)
		{
			match = TRUE;
		}
		else
		{
			for (sd = strchr(sd + 1, '.');
			     sd != NULL && !match;
			     sd = strchr(sd + 1, '.'))
			{
				if (strcasecmp(sd + 1, sig->sig_domain) == 0)
					match = TRUE;
			}
		}

		if (!match)
		{
			if ((dkim->dkim_libhandle->dkiml_flags & DKIM_LIBFLAGS_DROPSIGNER) == 0)
			{
				dkim_error(dkim,
				           "d=/i= mismatch on signature generation");
				return 0;
			}
			else
			{
				nosigner = TRUE;
			}
		}
	}

	/*
	**  We need to generate a signature: header template
	**  and include it in the canonicalization.
	*/

	/* basic required stuff */
	if (type == sigtype_dkim)
		dkim_dstring_printf(dstr, "v=%s;%s", DKIM_VERSION_SIG, delim);
	else
		dkim_dstring_printf(dstr, "i=%u;%s", dkim->dkim_arcmax + 1, delim);

	dkim_dstring_printf(dstr, "a=%s;%s",
		dkim_code_to_name(algorithms, sig->sig_signalg), delim);

	if (type == sigtype_as)
		dkim_dstring_printf(dstr, "cv=%s;%s",
			dkim_code_to_name(chainstatus, dkim->arc_state),
			delim);
	else
		dkim_dstring_printf(dstr, "c=%s/%s;%s",
			dkim_code_to_name(canonicalizations, sig->sig_hdrcanonalg),
			dkim_code_to_name(canonicalizations, sig->sig_bodycanonalg),
			delim);

	dkim_dstring_printf(dstr, "d=%s;%ss=%s;%st=%" PRIu64,
		sig->sig_domain, delim,
		sig->sig_selector, delim,
		sig->sig_timestamp);

	if (dkim->dkim_querymethods != NULL)
	{
		_Bool firstq = TRUE;
		struct dkim_qmethod *q;

		for (q = dkim->dkim_querymethods; q != NULL; q = q->qm_next)
		{
			if (firstq)
			{
				dkim_dstring_printf(dstr, ";%sq=%s", delim,
						    q->qm_type);
			}
			else
			{
				dkim_dstring_printf(dstr, ":%s", q->qm_type);
			}

			if (q->qm_options)
			{
				dkim_dstring_printf(dstr, "/%s",
				                    q->qm_options);
			}

			firstq = FALSE;
		}
	}

	if (dkim->dkim_libhandle->dkiml_sigttl != 0)
	{
		uint64_t expire;

		expire = sig->sig_timestamp + dkim->dkim_libhandle->dkiml_sigttl;
		dkim_dstring_printf(dstr, ";%sx=%" PRIu64, delim, expire);
	}

	if (type == sigtype_dkim && dkim->dkim_signer != NULL && !nosigner)
		dkim_dstring_printf(dstr, ";%si=%s", delim, dkim->dkim_signer);

	if (dkim->dkim_xtags != NULL)
	{
		struct dkim_xtag *x;

		for (x = dkim->dkim_xtags; x != NULL; x = x->xt_next)
		{
			dkim_dstring_printf(dstr, ";%s%s=%s", delim,
			                    x->xt_tag, x->xt_value);
		}
	}

	if (type != sigtype_as)
	{
		memset(b64hash, '\0', sizeof b64hash);

		status = dkim_canon_closebody(dkim);
		if (status != DKIM_STAT_OK)
			return 0;

		status = dkim_canon_getfinal(sig->sig_bodycanon, &hash, &hashlen);
		if (status != DKIM_STAT_OK)
		{
			dkim_error(dkim, "dkim_canon_getfinal() failed");
			return (size_t) -1;
		}

		status = dkim_base64_encode(hash, hashlen,
									b64hash, sizeof b64hash);

		dkim_dstring_printf(dstr, ";%sbh=%s", delim, b64hash);

		/* l= */
		if (dkim->dkim_partial)
		{
			dkim_dstring_printf(dstr, ";%sl=%lu", delim,
								(u_long) sig->sig_bodycanon->canon_wrote);
		}

		/* h= */
		firsthdr = TRUE;
		for (hdr = dkim->dkim_hhead; hdr != NULL; hdr = hdr->hdr_next)
		{
			if ((hdr->hdr_flags & DKIM_HDR_TO_BE_SIGNED) == 0)
				continue;

			if (!firsthdr)
			{
				dkim_dstring_cat1(dstr, ':');
			}
			else
			{
				dkim_dstring_cat1(dstr, ';');
				dkim_dstring_catn(dstr, delim, delimlen);
				dkim_dstring_catn(dstr, "h=", 2);
			}

			firsthdr = FALSE;

			dkim_dstring_catn(dstr, hdr->hdr_text, hdr->hdr_namelen);
		}

		if (dkim->dkim_libhandle->dkiml_oversignhdrs != NULL &&
			dkim->dkim_libhandle->dkiml_oversignhdrs[0] != NULL)
		{
			_Bool wrote = FALSE;

			if (firsthdr)
			{
				dkim_dstring_cat1(dstr, ';');
				dkim_dstring_catn(dstr, delim, delimlen);
				dkim_dstring_catn(dstr, "h=", 2);
			}
			else
			{
				dkim_dstring_cat1(dstr, ':');
			}

			for (n = 0;
				 dkim->dkim_libhandle->dkiml_oversignhdrs[n] != NULL;
				 n++)
			{
				if (dkim->dkim_libhandle->dkiml_oversignhdrs[n][0] == '\0')
					continue;

				if (wrote)
					dkim_dstring_cat1(dstr, ':');

				dkim_dstring_cat(dstr,
								 dkim->dkim_libhandle->dkiml_oversignhdrs[n]);

				wrote = TRUE;
			}
		}
	}

	/* if reports were requested, stick that in too */
	if (type == sigtype_dkim)
	{
		if ((dkim->dkim_libhandle->dkiml_flags & DKIM_LIBFLAGS_REQUESTREPORTS) != 0)
			dkim_dstring_printf(dstr, ";%sr=y", delim);

		/* if diagnostic headers were requested, include 'em */
		if (dkim->dkim_libhandle->dkiml_flags & DKIM_LIBFLAGS_ZTAGS)
		{
			_Bool first;
			int status;
			int len;
			char *hend;
			char *colon;
			char name[DKIM_MAXHEADER + 1];

			dkim_dstring_cat1(dstr, ';');
			dkim_dstring_catn(dstr, delim, delimlen);
			dkim_dstring_catn(dstr, "z=", 2);

			first = TRUE;

			for (hdr = dkim->dkim_hhead; hdr != NULL; hdr = hdr->hdr_next)
			{
				/* apply "skip" header and "sign" header lists */
				hend = hdr->hdr_text + hdr->hdr_textlen;
				colon = memchr(hdr->hdr_text, ':', hdr->hdr_textlen);
				if (colon != NULL)
				{
					hend = colon;

					while (hend > hdr->hdr_text &&
						   isascii(*(unsigned char*)(hend - 1)) &&
						   isspace(*(unsigned char*)(hend - 1)))
						hend--;
				}

				strlcpy(name, hdr->hdr_text, sizeof name);
				if (hend != NULL)
					name[hend - hdr->hdr_text] = '\0';

				if (dkim->dkim_libhandle->dkiml_skipre)
				{
					status = regexec(&dkim->dkim_libhandle->dkiml_skiphdrre,
									 name, 0, NULL, 0);

					if (status == 0)
						continue;
					else
						assert(status == REG_NOMATCH);
				}

				if (dkim->dkim_libhandle->dkiml_signre)
				{
					status = regexec(&dkim->dkim_libhandle->dkiml_hdrre,
									 name, 0, NULL, 0);

					if (status == REG_NOMATCH)
						continue;
					else
						assert(status == 0);
				}

				if (!first)
				{
					dkim_dstring_cat1(dstr, '|');
				}

				first = FALSE;

				len = dkim_qp_encode(hdr->hdr_text, tmp, sizeof tmp);

				if (len > 0)
					dkim_dstring_catn(dstr, tmp, (size_t) len);
			}
		}
	}

	/* and finally, an empty b= */
	dkim_dstring_cat1(dstr, ';');
	dkim_dstring_catn(dstr, delim, delimlen);
	dkim_dstring_catn(dstr, "b=", 2);

	return dkim_dstring_len(dstr);
}


/*
**  DKIM_TOBESIGNED -- tell if a header field is going to be signed.
**
**  Parameters:
**  	dkim -- DKIM handle
**  	name -- header field name
**
**  Return value:
**  	0 won't be signed, 1 will be signed, -1 error.
*/

int dkim_tobesigned(DKIM_LIB *lib, char const* name)
{
	if (lib->dkiml_skipre)
	{
		int status = regexec(&lib->dkiml_skiphdrre, name, 0, NULL, 0);
		if (status == 0)
			return 0;
		else if (status != REG_NOMATCH)
			return -1;
	}

	if (lib->dkiml_signre)
	{
		int status = regexec(&lib->dkiml_hdrre, name, 0, NULL, 0);
		if (status == 0)
			return 1;
		else if (status == REG_NOMATCH)
			return 0;
		else
			return -1;
	}

	return 1;
}


/*
**  DKIM_GETSENDER -- determine sender and store it in the handle
**
**  Parameters:
**  	dkim -- DKIM handle
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

static DKIM_STAT
dkim_getsender(DKIM *dkim)
{
	DKIM_STAT status;
	char *domain;
	char *user;
	struct dkim_header *sender = NULL;
	struct dkim_header *cur;

	assert(dkim != NULL);

	if (dkim->dkim_sender != NULL)
		return DKIM_STAT_OK;

	for (cur = dkim->dkim_hhead; cur != NULL; cur = cur->hdr_next)
	{
		if (cur->hdr_namelen == 4 &&
		    strncasecmp("from", (char *) cur->hdr_text, 4) == 0)
		{
			sender = cur;
			break;
		}
	}

	if (sender == NULL)
	{
		dkim_error(dkim, "no from header field detected");
		return DKIM_STAT_SYNTAX;
	}
	dkim->dkim_senderhdr = sender;

	if (sender->hdr_colon == NULL)
	{
		dkim_error(dkim, "syntax error in headers");
		return DKIM_STAT_SYNTAX;
	}

	dkim->dkim_sender = dkim_strdup(dkim, sender->hdr_colon + 1, 0);
	if (dkim->dkim_sender == NULL)
		return DKIM_STAT_NORESOURCE;

	status = my_mail_parse(dkim->dkim_sender, &user, &domain);
	if (status != 0 || domain == NULL || user == NULL ||
	    domain[0] == '\0' || user[0] == '\0')
	{
		dkim_error(dkim, "can't determine sender address");
		return DKIM_STAT_SYNTAX;
	}

	if (dkim->dkim_domain == NULL)
	{
		dkim->dkim_domain = dkim_strdup(dkim, domain, 0);
		if (dkim->dkim_domain == NULL)
			return DKIM_STAT_NORESOURCE;
	}

	dkim->dkim_user = dkim_strdup(dkim, user, 0);
	if (dkim->dkim_user == NULL)
		return DKIM_STAT_NORESOURCE;

	return DKIM_STAT_OK;
}

static inline DKIM_SIGINFO*
check_sigmatch(DKIM_SIGINFO *sig, DKIM_SIGINFO *osig)
/*
* Check if sig matches an old sig
*/
{
	/* don't self-search */
	if (sig == osig)
		return NULL;

	/* skip unprocessed signatures */
	if ((osig->sig_flags & DKIM_SIGFLAG_PROCESSED) == 0)
		return NULL;

	/* skip unless selector and domain match */
	if (strcmp(osig->sig_domain, sig->sig_domain) != 0 ||
		strcmp(osig->sig_selector, sig->sig_selector) != 0)
			return NULL;

	return osig;
}

/*
**  DKIM_GET_KEY -- acquire a public key used for verification
**
**  Parameters:
**  	dkim -- DKIM handle
**  	sig -- DKIM_SIGINFO handle
**  	test -- skip signature-specific validity checks
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_get_key(DKIM *dkim, DKIM_SIGINFO *sig, _Bool test)
{
	_Bool gotkey = FALSE;			/* key stored */
	_Bool gotset = FALSE;			/* set parsed */
	_Bool gotreply = FALSE;			/* reply received */
	int status;
	unsigned int c;
	DKIM_SIGINFO *osig = NULL;
	struct dkim_set *set = NULL;
	char *p;
	char buf[BUFRSZ + 1];

	assert(dkim != NULL);
	assert(sig != NULL);
	assert(sig->sig_selector != NULL);
	assert(sig->sig_domain != NULL);

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

	char *sig_b64key = NULL;
	size_t sig_b64keylen = 0;


	/* see if one of the other signatures already has the key we need */

	for (c = 0; c < dkim->dkim_sigcount; c++)
	{
		osig = check_sigmatch(sig, dkim->dkim_siglist[c]);
		if (osig)
			break;
	}

	if (osig == NULL && dkim->arc_sets != NULL)
		for (c = 0; c < dkim->dkim_arccount; ++c)
		{
			struct arc_set *arcset = &dkim->arc_sets[c];
			osig = check_sigmatch(sig, arcset->arcsig_as);
			if (osig)
				break;

			osig = check_sigmatch(sig, arcset->arcsig_ams);
			if (osig)
				break;
		}

	if (osig) /* we got a match!  copy the key data (if any)... */
	{
		if (osig->sig_key != NULL)
		{
			sig->sig_key = DKIM_MALLOC(dkim, osig->sig_keylen);
			if (sig->sig_key == NULL)
			{
				return DKIM_STAT_NORESOURCE;
			}

			memcpy(sig->sig_key, osig->sig_key, osig->sig_keylen);
			sig->sig_keylen = osig->sig_keylen;

			gotkey = TRUE;
		}

		/* ...and the key tag list (if any) */
		if (osig->sig_keytaglist != NULL)
		{
			sig->sig_keytaglist = osig->sig_keytaglist;
			set = sig->sig_keytaglist;

			gotset = TRUE;
			gotreply = TRUE;
		}
	}

	/* try a local function if there was one defined */
	if (!gotkey && dkim->dkim_libhandle->dkiml_key_lookup != NULL)
	{
		DKIM_CBSTAT cbstatus;

		cbstatus = dkim->dkim_libhandle->dkiml_key_lookup(dkim,
		                                                  sig,
		                                                  buf,
		                                                  sizeof buf);
		switch (cbstatus)
		{
		  case DKIM_CBSTAT_CONTINUE:
			gotreply = TRUE;
			break;

		  case DKIM_CBSTAT_REJECT:
			return DKIM_STAT_CBREJECT;

		  case DKIM_CBSTAT_TRYAGAIN:
			return DKIM_STAT_CBTRYAGAIN;

		  case DKIM_CBSTAT_NOTFOUND:
			return DKIM_STAT_NOKEY;

		  case DKIM_CBSTAT_ERROR:
			return DKIM_STAT_CBERROR;

		  case DKIM_CBSTAT_DEFAULT:
			break;

		  default:
			return DKIM_STAT_CBINVALID;
		}
	}

	/* if no local function or it returned no result, make the query */
	if (!gotreply)
	{
		/* use appropriate get method */
		switch (sig->sig_query)
		{
		  case DKIM_QUERY_DNS:
			status = (int) dkim_get_key_dns(dkim, sig, buf,
			                                sizeof buf);
			if (status != (int) DKIM_STAT_OK)
				return (DKIM_STAT) status;
			break;

		  case DKIM_QUERY_FILE:
			status = (int) dkim_get_key_file(dkim, sig, buf,
			                                 sizeof buf);
			if (status != (int) DKIM_STAT_OK)
				return (DKIM_STAT) status;
			break;

		  default:
			assert(0);
		}

		// signal that this was loaded
		sig->sig_flags |= DKIM_SIGFLAG_KEYLOADED;
	}

	/* decode the payload */
	if (!gotset)
	{
		if (buf[0] == '\0')
		{
			dkim_error(dkim, "empty key record");
			return DKIM_STAT_SYNTAX;
		}

		set = dkim_process_set(dkim, DKIM_SETTYPE_KEY, buf,
		                          strlen(buf), NULL, FALSE,
		                          NULL, NULL);
		if (set == NULL)
			return DKIM_STAT_NORESOURCE;

		if (set->set_bad)
			return DKIM_STAT_SYNTAX;

		sig->sig_keytaglist = set;
	}

	/* verify key version first */
	p = dkim_param_get(set, "v");
	if (p != NULL && strcmp(p, DKIM_VERSION_KEY) != 0)
	{
		dkim_error(dkim, "invalid key version '%s'", p);
		sig->sig_error = DKIM_SIGERROR_KEYVERSION;
		return DKIM_STAT_SYNTAX;
	}

	/* then make sure the hash type is something we can handle */
	p = dkim_param_get(set, "h");
	if (!dkim_key_hashesok(dkim->dkim_libhandle, p))
	{
		dkim_error(dkim, "unknown hash '%s'", p);
		sig->sig_error = DKIM_SIGERROR_KEYUNKNOWNHASH;
		return DKIM_STAT_SYNTAX;
	}
	/* ...and that this key is approved for this signature's hash */
	else if (!test && !dkim_key_hashok(sig, p))
	{
		dkim_error(dkim, "signature-key hash mismatch");
		sig->sig_error = DKIM_SIGERROR_KEYHASHMISMATCH;
		return DKIM_STAT_CANTVRFY;
	}

	/* make sure it's a key designated for e-mail */
	if (!dkim_key_smtp(set))
	{
		dkim_error(dkim, "key type mismatch");
		sig->sig_error = DKIM_SIGERROR_NOTEMAILKEY;
		return DKIM_STAT_CANTVRFY;
	}

	/* then key type */
	p = dkim_param_get(set, "k");
	int key_type;
	if (p == NULL)
	{
		dkim_error(dkim, "key type missing");
		sig->sig_error = DKIM_SIGERROR_KEYTYPEMISSING;
		return DKIM_STAT_SYNTAX;
	}
	else if ((key_type = dkim_name_to_code(keytypes, p)) == -1)
	{
		dkim_error(dkim, "unknown key type '%s'", p);
		sig->sig_error = DKIM_SIGERROR_KEYTYPEUNKNOWN;
		return DKIM_STAT_SYNTAX;
	}

	if (!gotkey)
	{
		/* decode the key */
		sig_b64key = dkim_param_get(set, "p");
		if (sig_b64key == NULL)
		{
			dkim_error(dkim, "key missing");
			return DKIM_STAT_SYNTAX;
		}
		else if (sig_b64key[0] == '\0')
		{
			return DKIM_STAT_REVOKED;
		}
		sig_b64keylen = strlen(sig_b64key);

		sig->sig_key = DKIM_MALLOC(dkim, sig_b64keylen);
		if (sig->sig_key == NULL)
		{
			return DKIM_STAT_NORESOURCE;
		}

		/*
		* Ed25519 public keys have only the 32 byte value.  Add
		* a constant prefix in that case.
		*/
		if (key_type == DKIM_KEYTYPE_ED25519 && sig_b64keylen == 44)
		{
			static const char prefix[] =
			{
				0x30, 0x2a, 0x30, 0x05, 0x06, 0x03,
				0x2b, 0x65, 0x70, 0x03, 0x21, 0x00
			};
			memcpy(sig->sig_key, prefix, sizeof prefix);
			status = dkim_base64_decode(
				sig_b64key, sig->sig_key + sizeof prefix,
					sig_b64keylen - sizeof prefix);
			if (status > 0)
				status += sizeof prefix;
		}
		else
			status = dkim_base64_decode(
				sig_b64key, sig->sig_key, sig_b64keylen);
		if (status < 0)
		{
			dkim_error(dkim, "key missing");
			return DKIM_STAT_SYNTAX;
		}

		sig->sig_keylen = status;
	}

	/* store key flags */
	p = dkim_param_get(set, "t");
	if (p != NULL)
	{
		u_int flag;
		char *t;
		char *last;
		char tmp[BUFRSZ + 1];

		strlcpy(tmp, p, sizeof tmp);

		for (t = strtok_r(tmp, ":", &last);
		     t != NULL;
		     t = strtok_r(NULL, ":", &last))
		{
			flag = (u_int) dkim_name_to_code(keyflags, t);
			if (flag != (u_int) -1)
				sig->sig_flags |= flag;
		}
	}

	return DKIM_STAT_OK;
}

/*
**  DKIM_HEADERCHECK -- check header validity
**
**  Parameters:
**  	dkim -- DKIM handle
**
**  Return value:
**  	TRUE iff the header meets sanity checks.
*/

static _Bool
dkim_headercheck(DKIM *dkim)
{
	struct dkim_header *hdr;

	assert(dkim != NULL);

	if ((dkim->dkim_libhandle->dkiml_flags & DKIM_LIBFLAGS_STRICTHDRS) != 0)
	{
		int status;
		char *user;
		char *domain;
		char *tmp;

		/* Date (must be exactly one) */
		hdr = dkim_get_header(dkim, "Date", 4, 0);
		if (hdr == NULL)
		{
			dkim_error(dkim, "Date: header field absent");
			return FALSE;
		}

		hdr = dkim_get_header(dkim, "Date", 4, 1);
		if (hdr != NULL)
		{
			dkim_error(dkim,
			           "multiple Date: header fields present");
			return FALSE;
		}

		/* From (must be exactly one) */
		hdr = dkim_get_header(dkim, "From", 4, 1);
		if (hdr != NULL)
		{
			dkim_error(dkim,
			           "multiple From: header fields present");
			return FALSE;
		}

		hdr = dkim_get_header(dkim, "From", 4, 0);
		if (hdr == NULL)
		{
			dkim_error(dkim, "From: header field absent");
			return FALSE;
		}

		/* confirm it's parsable */
		tmp = strdup(hdr->hdr_colon + 1);
		if (tmp != NULL)
		{
			status = my_mail_parse(tmp, &user, &domain);
			if (status != 0 ||
			    user == NULL || user[0] == '\0' ||
			    domain == NULL || domain[0] == '\0')
			{
				dkim_error(dkim, "From: header field cannot be parsed");
				return FALSE;
			}

			free(tmp);
		}

		/* Sender (no more than one) */
		hdr = dkim_get_header(dkim, "Sender", 6, 1);
		if (hdr != NULL)
		{
			dkim_error(dkim,
			           "multiple Sender: header fields present");
			return FALSE;
		}

		/* Reply-To (no more than one) */
		hdr = dkim_get_header(dkim, "Reply-To", 8, 1);
		if (hdr != NULL)
		{
			dkim_error(dkim,
			           "multiple Reply-To: header fields present");
			return FALSE;
		}

		/* To (no more than one) */
		hdr = dkim_get_header(dkim, "To", 2, 1);
		if (hdr != NULL)
		{
			dkim_error(dkim,
			           "multiple To: header fields present");
			return FALSE;
		}

		/* Cc (no more than one) */
		hdr = dkim_get_header(dkim, "Cc", 2, 1);
		if (hdr != NULL)
		{
			dkim_error(dkim,
			           "multiple Cc: header fields present");
			return FALSE;
		}

		/* Bcc (should we even bother?) */
		hdr = dkim_get_header(dkim, "Bcc", 3, 1);
		if (hdr != NULL)
		{
			dkim_error(dkim,
			           "multiple Bcc: header fields present");
			return FALSE;
		}

		/* Message-ID (no more than one) */
		hdr = dkim_get_header(dkim, "Message-ID", 10, 1);
		if (hdr != NULL)
		{
			dkim_error(dkim,
			           "multiple Message-ID: header fields present");
			return FALSE;
		}

		/* In-Reply-To (no more than one) */
		hdr = dkim_get_header(dkim, "In-Reply-To", 11, 1);
		if (hdr != NULL)
		{
			dkim_error(dkim,
			           "multiple In-Reply-To: header fields present");
			return FALSE;
		}

		/* References (no more than one) */
		hdr = dkim_get_header(dkim, "References", 10, 1);
		if (hdr != NULL)
		{
			dkim_error(dkim,
			           "multiple References: header fields present");
			return FALSE;
		}

		/* Subject (no more than one) */
		hdr = dkim_get_header(dkim, "Subject", 7, 1);
		if (hdr != NULL)
		{
			dkim_error(dkim,
			           "multiple Subject: header fields present");
			return FALSE;
		}
	}

	return TRUE;
}

/*
**  DKIM_EOH_SIGN -- declare end-of-headers; prepare for signing
** 
**  Parameters:
**  	dkim -- DKIM handle
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

static DKIM_STAT
dkim_eoh_sign(DKIM *dkim)
{
	_Bool keep;
	_Bool tmp;
	char *hn = NULL;
	DKIM_STAT status;
	int hashtype = DKIM_HASHTYPE_UNKNOWN;
	DKIM_CANON *bc;
	DKIM_CANON *hc;
	DKIM_LIB *lib;

	assert(dkim != NULL);

#ifdef _FFR_RESIGN
	if (dkim->dkim_resign != NULL && dkim->dkim_hdrbind)
		return DKIM_STAT_INVALID;
#endif /* _FFR_RESIGN */

	if (dkim->dkim_state >= DKIM_STATE_EOH2)
		return DKIM_STAT_INVALID;
	if (dkim->dkim_state < DKIM_STATE_EOH2)
		dkim->dkim_state = DKIM_STATE_EOH2;

	lib = dkim->dkim_libhandle;
	assert(lib != NULL);

	tmp = ((lib->dkiml_flags & DKIM_LIBFLAGS_TMPFILES) != 0);
	keep = ((lib->dkiml_flags & DKIM_LIBFLAGS_KEEPFILES) != 0);

	dkim->dkim_version = lib->dkiml_version;

	/* check for header validity */
	if (!dkim_headercheck(dkim))
	{
		dkim->dkim_state = DKIM_STATE_UNUSABLE;
		return DKIM_STAT_SYNTAX;
	}

	/*
	**  Verify that all the required headers are present and
	**  marked for signing.
	*/

	hn = dkim_check_requiredhdrs(dkim);
	if (hn != NULL)
	{
		dkim_error(dkim, "required header \"%s\" not found", hn);
		dkim->dkim_state = DKIM_STATE_UNUSABLE;
		return DKIM_STAT_SYNTAX;
	}

	/* determine hash type */
	switch (dkim->dkim_signalg)
	{
	  case DKIM_SIGN_RSASHA1:
		hashtype = DKIM_HASHTYPE_SHA1;
		break;

	  case DKIM_SIGN_RSASHA256:
	  case DKIM_SIGN_ED25519SHA256:
		hashtype = DKIM_HASHTYPE_SHA256;
		break;

	  default:
		assert(0);
		/* NOTREACHED */
	}

	if (dkim->dkim_siglist == NULL)
	{
		/* initialize signature and canonicalization for signing */
		dkim->dkim_siglist = DKIM_MALLOC(dkim, sizeof(DKIM_SIGINFO **));
		if (dkim->dkim_siglist == NULL)
		{
			dkim_error(dkim, "failed to allocate %d byte(s)",
			           sizeof(DKIM_SIGINFO *));
			return DKIM_STAT_NORESOURCE;
		}

		dkim->dkim_siglist[0] = DKIM_MALLOC(dkim,
		                                    sizeof(struct dkim_siginfo));
		if (dkim->dkim_siglist[0] == NULL)
		{
			dkim_error(dkim, "failed to allocate %d byte(s)",
			           sizeof(struct dkim_siginfo));
			return DKIM_STAT_NORESOURCE;
		}
		dkim->dkim_sigcount = 1;
		memset(dkim->dkim_siglist[0], '\0',
		       sizeof(struct dkim_siginfo));
		dkim->dkim_siglist[0]->sig_domain = dkim->dkim_domain;
		dkim->dkim_siglist[0]->sig_selector = dkim->dkim_selector;
		dkim->dkim_siglist[0]->sig_hashtype = hashtype;
		dkim->dkim_siglist[0]->sig_signalg = dkim->dkim_signalg;

		status = dkim_add_canon(dkim, TRUE, dkim->dkim_hdrcanonalg,
		                        hashtype, NULL, NULL, 0, &hc);
		if (status != DKIM_STAT_OK)
			return status;

		status = dkim_add_canon(dkim, FALSE, dkim->dkim_bodycanonalg,
		                        hashtype, NULL, NULL,
		                        dkim->dkim_signlen, &bc);
		if (status != DKIM_STAT_OK)
			return status;

		dkim->dkim_siglist[0]->sig_hdrcanon = hc;
		dkim->dkim_siglist[0]->sig_hdrcanonalg = dkim->dkim_hdrcanonalg;
		dkim->dkim_siglist[0]->sig_bodycanon = bc;
		dkim->dkim_siglist[0]->sig_bodycanonalg = dkim->dkim_bodycanonalg;

		if (dkim->dkim_libhandle->dkiml_fixedtime != 0)
		{
			dkim->dkim_siglist[0]->sig_timestamp = dkim->dkim_libhandle->dkiml_fixedtime;
		}
		else
		{
			time_t now;

			(void) time(&now);

			dkim->dkim_siglist[0]->sig_timestamp = (uint64_t) now;
		}
	}

	/* initialize all canonicalizations */
	status = dkim_canon_init(dkim, tmp, keep);
	if (status != DKIM_STAT_OK)
		return status;

	/* run the headers */
	status = dkim_canon_runheaders(dkim);
	if (status != DKIM_STAT_OK)
		return status;

	return DKIM_STAT_OK;
}

/*
**  DKIM_EOH_VERIFY -- declare end-of-headers; set up verification
** 
**  Parameters:
**  	dkim -- DKIM handle
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

static DKIM_STAT
dkim_eoh_verify(DKIM *dkim)
{
	_Bool keep;
	_Bool tmp;
	_Bool bsh;
	DKIM_STAT status;
	unsigned int c;
	DKIM_LIB *lib;
	DKIM_SET *set;

	assert(dkim != NULL);

	if (dkim->dkim_state >= DKIM_STATE_EOH2)
		return DKIM_STAT_INVALID;
	if (dkim->dkim_state < DKIM_STATE_EOH1)
		dkim->dkim_state = DKIM_STATE_EOH1;

	lib = dkim->dkim_libhandle;
	assert(lib != NULL);

	bsh = ((lib->dkiml_flags & DKIM_LIBFLAGS_BADSIGHANDLES) != 0);
	tmp = ((lib->dkiml_flags & DKIM_LIBFLAGS_TMPFILES) != 0);
	keep = ((lib->dkiml_flags & DKIM_LIBFLAGS_KEEPFILES) != 0);

	/* populate some stuff like dkim_sender, dkim_domain, dkim_user */
	status = dkim_getsender(dkim);
	if (status != DKIM_STAT_OK && !bsh)
	{
		dkim->dkim_state = DKIM_STATE_UNUSABLE;
		return status;
	}

	/* check for header validity */
	if (!dkim_headercheck(dkim))
	{
		dkim->dkim_state = DKIM_STATE_UNUSABLE;
		return DKIM_STAT_SYNTAX;
	}

	/* allocate the siginfo array if not already done */
	if (dkim->dkim_siglist == NULL)
	{
		/* count the signatures */
		for (set = dkim_set_first(dkim, DKIM_SETTYPE_SIGNATURE);
		     set != NULL;
		     set = dkim_set_next(set, DKIM_SETTYPE_SIGNATURE))
		{
			if (!set->set_bad || bsh)
				dkim->dkim_sigcount++;
		}

		/* if no signatures, return such */
		if (dkim->dkim_sigcount == 0)
		{
			if (dkim->dkim_arccount == 0)
			{
				dkim->dkim_skipbody = TRUE;
				dkim->dkim_state = DKIM_STATE_EOH2;
				return DKIM_STAT_NOSIG;
			}
		}
		else
		{
			status = dkim_siglist_setup(dkim);
			if (status != DKIM_STAT_OK)
				return status;
		}

		/* initialize all discovered canonicalizations */
		status = dkim_canon_init(dkim, tmp, keep);
		if (status != DKIM_STAT_OK)
			return status;
	}

	if (dkim->dkim_sigcount != 0)
	{
		/* call the prescreen callback, if defined */
		if (lib->dkiml_prescreen != NULL && !dkim->dkim_eoh_reentry)
		{
			status = lib->dkiml_prescreen(dkim,
										  dkim->dkim_siglist,
										  dkim->dkim_sigcount);
			switch (status)
			{
			  case DKIM_CBSTAT_CONTINUE:
			  case DKIM_CBSTAT_DEFAULT:
				break;

			  case DKIM_CBSTAT_REJECT:
				return DKIM_STAT_CBREJECT;

			  case DKIM_CBSTAT_TRYAGAIN:
				return DKIM_STAT_CBTRYAGAIN;

			  case DKIM_CBSTAT_ERROR:
				return DKIM_STAT_CBERROR;

			  default:
				return DKIM_STAT_CBINVALID;
			}
		}

		/* if set to ignore everything, treat message as unsigned */
		set = NULL;
		for (c = 0; c < dkim->dkim_sigcount; c++)
		{
			if (!(dkim->dkim_siglist[c]->sig_flags & DKIM_SIGFLAG_IGNORE))
			{
				set = dkim->dkim_siglist[c]->sig_taglist;
				break;
			}
		}

		if (set == NULL)
		{
			dkim->dkim_skipbody = TRUE;
			dkim->dkim_state = DKIM_STATE_EOH2;
			return DKIM_STAT_NOSIG;
		}
	}

	/* run the headers */
	if (!dkim->dkim_eoh_reentry)
	{
		status = dkim_canon_runheaders(dkim);
		if (status != DKIM_STAT_OK)
			return status;
	}

	if (dkim->dkim_sigcount == 0)
	{
		dkim->dkim_state = DKIM_STATE_EOH2;
		return DKIM_STAT_OK;
	}

	/* do public key verification of all still-enabled signatures here */
	if ((lib->dkiml_flags & DKIM_LIBFLAGS_DELAYSIGPROC) == 0)
	{
		for (c = 0; c < dkim->dkim_sigcount; c++)
		{
			if (!(dkim->dkim_siglist[c]->sig_flags & DKIM_SIGFLAG_PROCESSED) &&
			    !(dkim->dkim_siglist[c]->sig_flags & DKIM_SIGFLAG_IGNORE) &&
			    dkim->dkim_siglist[c]->sig_error == DKIM_SIGERROR_UNKNOWN)
			{
				status = dkim_sig_process(dkim,
				                          dkim->dkim_siglist[c]);
				// dkim_sig_process may call key lookup function
				if (status != DKIM_STAT_OK)
				{
					if (status == DKIM_STAT_CBTRYAGAIN)
						dkim->dkim_eoh_reentry = TRUE;

					return status;
				}
			}
		}
	}

	/* no re-entries beyond this point */
	dkim->dkim_state = DKIM_STATE_EOH2;

	/*
	**  Possible short-circuit here if all signatures are:
	**  - marked to be ignored
	**  - definitely invalid
	**  - verification attempted but failed
	*/

	if ((lib->dkiml_flags & DKIM_LIBFLAGS_EOHCHECK) != 0)
	{
		_Bool good = FALSE;
		DKIM_SIGINFO *sig;

		for (c = 0; c < dkim->dkim_sigcount; c++)
		{
			sig = dkim->dkim_siglist[c];

			/* ignored? */
			if ((sig->sig_flags & DKIM_SIGFLAG_IGNORE) != 0)
				continue;

			/* had a processing error? */
			if (sig->sig_error != DKIM_SIGERROR_UNKNOWN &&
			    sig->sig_error != DKIM_SIGERROR_OK)
				continue;

			/* processed but didn't pass? */
			if ((sig->sig_flags & DKIM_SIGFLAG_PROCESSED) != 0 &&
			    (sig->sig_flags & DKIM_SIGFLAG_PASSED) == 0)
				continue;

			/* OK we had a good one */
			good = TRUE;
			break;
		}

		/* no good ones */
		if (!good)
		{
			/* report error on the first one */
			sig = dkim->dkim_siglist[0];
			if (sig->sig_error != DKIM_SIGERROR_UNKNOWN &&
			    sig->sig_error != DKIM_SIGERROR_OK)
			{
				dkim_error(dkim,
				           dkim_code_to_name(sigerrors,
				                             sig->sig_error));
			}

			return DKIM_STAT_CANTVRFY;
		}
	}

	return DKIM_STAT_OK;
}

/*
**  -ale: DKIM_BORROW_BODY_VERIFY -- borrow body from a previous run.
**  Set up verification as if the body was read and matched any
**  signature that matched in the previous run.
** 
**  Parameters:
**  	dkim -- DKIM handle
**  	dkim1 -- DKIM handle 
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_borrow_body_verify(DKIM *dkim, DKIM const *dkim1)
{
	assert(dkim != NULL);
	assert(dkim1 != NULL);

	DKIM_STAT status = dkim_body(dkim, "\r\n", 2);

	unsigned int c;
	for (c = 0; c < dkim->dkim_sigcount; ++c)
	{
		DKIM_SIGINFO *s = dkim->dkim_siglist[c];
		DKIM_SIGINFO *s1 = dkim_get_samesig(dkim1, s, c);
		if (s1)
		{
			s->sig_bh = s1->sig_bh;

			if (s->sig_bh == DKIM_SIGBH_MATCH && s->sig_bodycanon)
			/*
			* Remove any l= tag that might have been set, as it would
			* cause DKIM_SIGERROR_TOOLARGE_L on our faked body.
			*/
				s->sig_bodycanon->canon_length = (size_t) -1;
		}
	}

	return status;
}


/*
**  -ale: DKIM_BORROW_HEADER_VERIFY -- borrow headers from a previous run.
**  Set up verification as if the header was read and eoh_verify()
**  called.  Any header read is not considered.
** 
**  Parameters:
**  	dkim -- DKIM handle
**  	dkim1 -- DKIM handle 
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_borrow_header_verify(DKIM *dkim, DKIM const *dkim1)
{
	_Bool keep;
	_Bool tmp;
	DKIM_STAT status;
	DKIM_LIB *lib;
	DKIM_SET *set;
	size_t len;

	assert(dkim != NULL);
	assert(dkim1 != NULL);

	if (dkim->dkim_state >= DKIM_STATE_EOH2 ||
		dkim1->dkim_state < DKIM_STATE_EOH2)
		return DKIM_STAT_INVALID;
	if (dkim->dkim_state < DKIM_STATE_EOH1)
		dkim->dkim_state = DKIM_STATE_EOH1;

	lib = dkim->dkim_libhandle;
	assert(lib != NULL);

	tmp = ((lib->dkiml_flags & DKIM_LIBFLAGS_TMPFILES) != 0);
	keep = ((lib->dkiml_flags & DKIM_LIBFLAGS_KEEPFILES) != 0);

	dkim->dkim_sender = dkim_strdup(dkim, dkim1->dkim_sender, 0);
	if (dkim->dkim_sender == NULL)
		return DKIM_STAT_NORESOURCE;

	dkim->dkim_user = dkim_strdup(dkim, dkim1->dkim_user, 0);
	if (dkim->dkim_user == NULL)
		return DKIM_STAT_NORESOURCE;


	/* skip any ARC processing */
	dkim->dkim_arccount = 0;
	dkim->arc_state = ARC_CHAIN_NONE;

	/* allocate the siginfo array if not already done */
	if (dkim->dkim_siglist == NULL)
	{
		/* if no signatures, return such */
		if ((dkim->dkim_sigcount = dkim1->dkim_sigcount) == 0)
		{
			dkim->dkim_skipbody = TRUE;
			return DKIM_STAT_NOSIG;
		}

		len = dkim->dkim_sigcount * sizeof(DKIM_SIGINFO *);
		dkim->dkim_siglist = DKIM_MALLOC(dkim, len);
		if (dkim->dkim_siglist == NULL)
		{
			return DKIM_STAT_NORESOURCE;
		}

		unsigned int c = 0, c1;

		for (c1 = 0; c1 < dkim->dkim_sigcount; c1++)
		{
			DKIM_SIGINFO *sig1 = dkim1->dkim_siglist[c1];
			if ((sig1->sig_flags & DKIM_SIGFLAG_PASSED) == 0)
				continue;  // borrow only good signatures

			DKIM_SIGINFO *sig = dkim->dkim_siglist[c] =
				DKIM_MALLOC(dkim, sizeof(DKIM_SIGINFO));
			if (sig == NULL)
			{
				unsigned int n;
				for (n = 0; n < c; n++)
					DKIM_FREE(dkim, dkim->dkim_siglist[n]);
				return DKIM_STAT_NORESOURCE;
			}

			memcpy(sig, sig1, sizeof(DKIM_SIGINFO));

			sig->sig_key = DKIM_MALLOC(dkim, sig->sig_keylen);
			if (sig->sig_key == NULL)
			{
				unsigned int n;
				for (n = 0; n < c; n++)
					DKIM_FREE(dkim, dkim->dkim_siglist[n]);
				return DKIM_STAT_NORESOURCE;
			}
			memcpy(sig->sig_key, sig1->sig_key, sig->sig_keylen);

			sig->sig_sig = DKIM_MALLOC(dkim, sig->sig_siglen);
			if (sig->sig_sig == NULL)
			{
				unsigned int n;
				for (n = 0; n < c; n++)
					DKIM_FREE(dkim, dkim->dkim_siglist[n]);
				return DKIM_STAT_NORESOURCE;
			}
			memcpy(sig->sig_sig, sig1->sig_sig, sig->sig_siglen);

#if 0
			/* canonicalization handle for the headers */
			/* We don't compute header hashes any more, so skip this */
			status = dkim_add_canon(dkim, TRUE,
									sig->sig_hdrcanonalg,
									sig->sig_hashtype,
									NULL /* ?? dkim_param_get(set, "h"); */,
									NULL /* ?? dkim_set_getudata(set)*/,
									0, &sig->sig_hdrcanon);
			if (status != DKIM_STAT_OK)
				return status;
#endif


			/* canonicalization handle for the body */
			status = dkim_add_canon(dkim, FALSE,
				sig->sig_bodycanonalg,
				sig->sig_hashtype,
				NULL, NULL, -1 /* signlen, a.k.a. l= */,
				&sig->sig_bodycanon);
			if (status != DKIM_STAT_OK)
				return status;

			sig->sig_bh = DKIM_SIGBH_UNTESTED;
			sig->sig_error = DKIM_SIGERROR_UNKNOWN;
			sig->sig_signature = NULL;
			sig->sig_sslerrbuf = NULL;
			++c;
		}

		dkim->dkim_sigcount = c;

		/* initialize all discovered canonicalizations */
		status = dkim_canon_init(dkim, tmp, keep);
		if (status != DKIM_STAT_OK)
			return status;
	}

	/* call the prescreen callback, if defined */
	if (lib->dkiml_prescreen != NULL && !dkim->dkim_eoh_reentry)
	{
		status = lib->dkiml_prescreen(dkim,
		                              dkim->dkim_siglist,
		                              dkim->dkim_sigcount);
		switch (status)
		{
		  case DKIM_CBSTAT_CONTINUE:
		  case DKIM_CBSTAT_DEFAULT:
			break;

		  case DKIM_CBSTAT_REJECT:
			return DKIM_STAT_CBREJECT;

		  case DKIM_CBSTAT_TRYAGAIN:
			return DKIM_STAT_CBTRYAGAIN;

		  case DKIM_CBSTAT_ERROR:
			return DKIM_STAT_CBERROR;

		  default:
			return DKIM_STAT_CBINVALID;
		}
	}

	/* if set to ignore everything, treat message as unsigned */
	set = NULL;
	for (unsigned int c = 0; c < dkim->dkim_sigcount; c++)
	{
		if (!(dkim->dkim_siglist[c]->sig_flags & DKIM_SIGFLAG_IGNORE))
		{
			set = dkim->dkim_siglist[c]->sig_taglist;
			break;
		}
	}

	if (set == NULL)
	{
		dkim->dkim_skipbody = TRUE;
		dkim->dkim_state = DKIM_STATE_EOH2;
		return DKIM_STAT_NOSIG;
	}

	/* run the headers */
	if (!dkim->dkim_eoh_reentry)
	{
		status = dkim_canon_runheaders(dkim);
		if (status != DKIM_STAT_OK)
			return status;
	}

	/* do public key verification of all still-enabled signatures here */
	if ((lib->dkiml_flags & DKIM_LIBFLAGS_DELAYSIGPROC) == 0)
	{
		for (unsigned int c = 0; c < dkim->dkim_sigcount; c++)
		{
			if (!(dkim->dkim_siglist[c]->sig_flags & DKIM_SIGFLAG_PROCESSED) &&
			    !(dkim->dkim_siglist[c]->sig_flags & DKIM_SIGFLAG_IGNORE) &&
			    dkim->dkim_siglist[c]->sig_error == DKIM_SIGERROR_UNKNOWN)
			{
				status = dkim_sig_process(dkim,
				                          dkim->dkim_siglist[c]);
				// dkim_sig_process may call key lookup function
				if (status != DKIM_STAT_OK)
				{
					if (status == DKIM_STAT_CBTRYAGAIN)
						dkim->dkim_eoh_reentry = TRUE;

					return status;
				}
			}
		}
	}

	/* no re-entries beyond this point */
	dkim->dkim_state = DKIM_STATE_EOH2;

	/*
	**  Possible short-circuit here if all signatures are:
	**  - marked to be ignored
	**  - definitely invalid
	**  - verification attempted but failed
	*/

	if ((lib->dkiml_flags & DKIM_LIBFLAGS_EOHCHECK) != 0)
	{
		_Bool good = FALSE;
		DKIM_SIGINFO *sig;

		for (unsigned int c = 0; c < dkim->dkim_sigcount; c++)
		{
			sig = dkim->dkim_siglist[c];

			/* ignored? */
			if ((sig->sig_flags & DKIM_SIGFLAG_IGNORE) != 0)
				continue;

			/* had a processing error? */
			if (sig->sig_error != DKIM_SIGERROR_UNKNOWN &&
			    sig->sig_error != DKIM_SIGERROR_OK)
				continue;

			/* processed but didn't pass? */
			if ((sig->sig_flags & DKIM_SIGFLAG_PROCESSED) != 0 &&
			    (sig->sig_flags & DKIM_SIGFLAG_PASSED) == 0)
				continue;

			/* OK we had a good one */
			good = TRUE;
			break;
		}

		/* no good ones */
		if (!good)
		{
			/* report error on the first one */
			sig = dkim->dkim_siglist[0];
			if (sig->sig_error != DKIM_SIGERROR_UNKNOWN &&
			    sig->sig_error != DKIM_SIGERROR_OK)
			{
				dkim_error(dkim,
				           dkim_code_to_name(sigerrors,
				                             sig->sig_error));
			}

			return DKIM_STAT_CANTVRFY;
		}
	}

	return DKIM_STAT_OK;
}

/*
**  DKIM_EOM_SIGN -- declare end-of-body; complete signing
**
**  Parameters:
**  	dkim -- DKIM handle
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

static DKIM_STAT
dkim_eom_sign(DKIM *dkim)
{
	int status;
	//char *signature = NULL;
	//size_t siglen = 0;
	DKIM_SIGINFO *sig;
	struct dkim_crypto *crypto = NULL;

	assert(dkim != NULL);

#ifdef _FFR_RESIGN
	if (dkim->dkim_resign != NULL)
	{
		if (dkim->dkim_hdrbind)
		{
			if (dkim->dkim_state != DKIM_STATE_INIT ||
			    dkim->dkim_resign->dkim_state != DKIM_STATE_EOM2)
				return DKIM_STAT_INVALID;
		}
		else
		{
			if (dkim->dkim_state < DKIM_STATE_EOH1 ||
			    dkim->dkim_resign->dkim_state != DKIM_STATE_EOM2)
				return DKIM_STAT_INVALID;
		}
	}
	else if (dkim->dkim_state >= DKIM_STATE_EOM2 ||
	         dkim->dkim_state < DKIM_STATE_EOH1)
	{
  		return DKIM_STAT_INVALID;
	}
#else /* _FFR_RESIGN */
	if (dkim->dkim_state >= DKIM_STATE_EOM2 ||
	    dkim->dkim_state < DKIM_STATE_EOH1)
		return DKIM_STAT_INVALID;
#endif /* _FFR_RESIGN */

	if (dkim->dkim_chunkstate != DKIM_CHUNKSTATE_INIT &&
	    dkim->dkim_chunkstate != DKIM_CHUNKSTATE_DONE)
		return DKIM_STAT_INVALID;

  	if (dkim->dkim_state < DKIM_STATE_EOM2)
  		dkim->dkim_state = DKIM_STATE_EOM2;

#ifdef _FFR_RESIGN
	if (dkim->dkim_resign != NULL)
	{
		_Bool found = FALSE;
		int c;
		char *hn;

		/*
		**  Verify that all the required headers are present and
		**  marked for signing.
		*/

		hn = dkim_check_requiredhdrs(dkim);
		if (hn != NULL)
		{
			dkim_error(dkim, "required header \"%s\" not found",
			           hn);
			dkim->dkim_state = DKIM_STATE_UNUSABLE;
			return DKIM_STAT_SYNTAX;
		}

		/*
		**  Fail if the verification handle didn't work.  For a
		**  multiply-signed message, we only require one passing
		**  signature (for now).
		*/

		if ((dkim->dkim_libhandle->dkiml_flags & DKIM_LIBFLAGS_STRICTRESIGN) != 0)
		{
			for (c = 0; c < dkim->dkim_resign->dkim_sigcount; c++)
			{
				sig = dkim->dkim_resign->dkim_siglist[c];
				if ((sig->sig_flags & DKIM_SIGFLAG_PASSED) != 0 &&
				    sig->sig_bh == DKIM_SIGBH_MATCH)
				{
					found = TRUE;
					break;
				}
			}

			if (!found)
				return DKIM_STAT_CANTVRFY;
		}
	}
#endif /* _FFR_RESIGN */

	/* finalize body canonicalizations */
	status = dkim_canon_closebody(dkim);
	if (status != DKIM_STAT_OK)
		return status;

	dkim->dkim_bodydone = TRUE;

	/* set signature timestamp */
	if (dkim->dkim_libhandle->dkiml_fixedtime != 0)
	{
		dkim->dkim_timestamp = dkim->dkim_libhandle->dkiml_fixedtime;
	}
	else
	{
		time_t now;

		(void) time(&now);
		dkim->dkim_timestamp = (uint64_t) now;
	}

	/* sign with l= if requested */
	if ((dkim->dkim_libhandle->dkiml_flags & DKIM_LIBFLAGS_SIGNLEN) != 0)
		dkim->dkim_partial = TRUE;

	/* get signature and canonicalization handles (either DKIM or AMS)*/
	assert(dkim->dkim_siglist != NULL);
	assert(dkim->dkim_siglist[0] != NULL);
	sig = dkim->dkim_siglist[0];

	if (dkim->dkim_keydata == NULL)
	{
		if (dkim_privkey_load(dkim) != DKIM_STAT_OK)
			return DKIM_STAT_NORESOURCE;
	}

	crypto = dkim->dkim_keydata;
#ifdef USE_GNUTLS
	if (crypto->crypto_privkey == NULL)
#else /* USE_GNUTLS */
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
	if (crypto->crypto_pkey == NULL)
#else /* OPENSSL_VERSION_NUMBER >= 0x30000000L */
	if (!(crypto->crypto_key != NULL ||
		(sig->sig_signalg == DKIM_SIGN_ED25519SHA256 &&
			crypto->crypto_pkey != NULL)))
#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */
#endif /* USE_GNUTLS */
	{
		dkim_error(dkim, "private key load failed");
		return DKIM_STAT_NORESOURCE;
	}

	sig->sig_keybits = crypto->crypto_keysize;
	sig->sig_signature = dkim->dkim_keydata;
	sig->sig_flags |= DKIM_SIGFLAG_KEYLOADED;

	if (sig->sig_signalg != DKIM_SIGN_ED25519SHA256 &&
		sig->sig_keybits < dkim->dkim_libhandle->dkiml_minkeybits)
	{
		sig->sig_error = DKIM_SIGERROR_KEYTOOSMALL;
		dkim_error(dkim,
		           "private key too small (%d bits, need at least %d)",
		           sig->sig_keybits,
		           dkim->dkim_libhandle->dkiml_minkeybits);
		return DKIM_STAT_SIGGEN;
	}

	switch (sig->sig_signalg)
	{
	  case DKIM_SIGN_RSASHA1:
	  case DKIM_SIGN_RSASHA256:
	  {
		assert(sig->sig_hashtype == DKIM_HASHTYPE_SHA1 ||
		       sig->sig_hashtype == DKIM_HASHTYPE_SHA256);

		if (sig->sig_hashtype == DKIM_HASHTYPE_SHA256)
		{
			assert(dkim_libfeature(dkim->dkim_libhandle,
		                               DKIM_FEATURE_SHA256));
		}

		sig->sig_signature = (void *) dkim->dkim_keydata;
		sig->sig_keytype = DKIM_KEYTYPE_RSA;

		break;
	  }

	  case DKIM_SIGN_ED25519SHA256:
	  {
		assert(sig->sig_hashtype == DKIM_HASHTYPE_SHA256);

		sig->sig_signature = (void *) dkim->dkim_keydata;
		sig->sig_keytype = DKIM_KEYTYPE_ED25519;

		break;
	  }

	  default:
		assert(0);
	}

	dkim->dkim_signature = sig;

	return DKIM_STAT_OK;
}

// forward decl.
static DKIM_STAT dkim_getsighdr_d(DKIM *dkim, sigtype, size_t initial);

/*
**  dkim_compute_signature -- complete signing after dkim_eom_sign
**                        (this code was the tail of that function)
**                            do the crypto.
**                            Results in dkim->b64sig[len]
**
**  Parameters:
**  	dkim -- DKIM handle
**  	type -- signature type
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

static DKIM_STAT
dkim_compute_signature(DKIM *dkim, sigtype type)
{
	/* construct the DKIM signature header to be canonicalized */
	struct dkim_dstring *tmphdr = dkim_dstring_new(dkim, BUFRSZ, MAXBUFRSZ);
	if (tmphdr == NULL)
		return DKIM_STAT_NORESOURCE;

	char const *name;
	switch (type)
	{
		case sigtype_dkim:
			name = DKIM_SIGNHEADER ": ";
			break;

		case sigtype_ams:
			name = ARC_MSGSIG_HDRNAME ": ";
			break;

		case sigtype_as:
			name = ARC_SEAL_HDRNAME ": ";
			break;

		default:
			assert(false);
			break;
	}

	dkim_dstring_copy(tmphdr, name);

	DKIM_STAT ret = dkim_getsighdr_d(dkim, type,
		dkim_dstring_len(tmphdr));
	if (ret != DKIM_STAT_OK)
	{
		dkim_dstring_free(tmphdr);
		return ret;
	}

	dkim_dstring_catd(tmphdr, dkim->dkim_hdrbuf);

	/* canonicalize */
	dkim_canon_signature(dkim, dkim_dstring_get(tmphdr));

	dkim_dstring_free(tmphdr);

	/* finalize */
	DKIM_SIGINFO *sig = dkim->dkim_signature;
	DKIM_CANON *hc = sig->sig_hdrcanon;

	char *digest;
	size_t diglen;
	ret = dkim_canon_getfinal(hc, &digest, &diglen);
	if (ret != DKIM_STAT_OK)
	{
		dkim_error(dkim, "dkim_canon_getfinal() failed");
		return ret;
	}

	/* compute and store the signature */
#ifdef USE_GNUTLS

	gnutls_datum_t dd;

	struct dkim_crypto *crypto = (struct dkim_crypto *) sig->sig_signature;

	dd.data = u_char_ptr(digest);
	dd.size = diglen;

	int alg;
	int (*signfn)(gnutls_privkey_t, gnutls_digest_algorithm_t,
		unsigned int, const gnutls_datum_t *, gnutls_datum_t *);
	switch (sig->sig_signalg)
	{
		case DKIM_SIGN_RSASHA1:
			alg = GNUTLS_DIG_SHA1;
			signfn = &gnutls_privkey_sign_hash;
			break;

		case DKIM_SIGN_RSASHA256:
			alg = GNUTLS_DIG_SHA256;
			signfn = &gnutls_privkey_sign_hash;
			break;

		case DKIM_SIGN_ED25519SHA256:
		/*
		* EdDSA cannot sign prehashed data, so we sign the hash as if it
		* were the actual data, albeit this breaks collision resistance.
		*/
			alg = GNUTLS_DIG_SHA512;
			signfn = &gnutls_privkey_sign_data;
			break;

		default:
			assert(0);
			break;
	}

	int status = (*signfn)(crypto->crypto_privkey, alg, 0, &dd,
		&crypto->crypto_out);
	if (status != GNUTLS_E_SUCCESS)
	{
		dkim_sig_load_ssl_errors(dkim, sig, status);
		dkim_error(dkim,
				   "signature generation failed (status %d)",
				   status);
		return DKIM_STAT_INTERNAL;
	}

	char *signature = char_ptr(crypto->crypto_out.data);
	size_t siglen = crypto->crypto_out.size;

#else /* USE_GNUTLS */

	struct dkim_crypto *crypto;
	int status;

#if OPENSSL_VERSION_NUMBER >= 0x30000000L

	crypto = (struct dkim_crypto *) sig->sig_signature;

#if !defined NDEBUG
	size_t assert_siglen = crypto->crypto_outlen;
#endif // NDEBUG
	if (sig->sig_signalg != DKIM_SIGN_ED25519SHA256)
	{
		EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(crypto->crypto_pkey, NULL);
		if (ctx == NULL)
		{
			dkim_load_ssl_errors(dkim, 0);
			dkim_error(dkim,
					   "failed to initialize signing context");

			BIO_CLOBBER(crypto->crypto_keydata);
			return DKIM_STAT_INTERNAL;
		}

		status = EVP_PKEY_sign_init(ctx);
		if (status > 0)
			status = EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING);
		if (status > 0)
			status = EVP_PKEY_CTX_set_signature_md(ctx,
				sig->sig_signalg == DKIM_SIGN_RSASHA1?
					EVP_sha1(): EVP_sha256());
		if (status > 0)
			status = EVP_PKEY_sign(ctx,
				u_char_ptr(crypto->crypto_out), &crypto->crypto_outlen,
				u_char_ptr(digest), diglen);
		EVP_PKEY_CTX_free(ctx);
	}
	else
	{
		EVP_MD_CTX *md_ctx = EVP_MD_CTX_new();
		if (md_ctx == NULL)
		{
			dkim_load_ssl_errors(dkim, 0);
			dkim_error(dkim,
					   "failed to initialize digest context");

			BIO_CLOBBER(crypto->crypto_keydata);
			return DKIM_STAT_INTERNAL;
		}

		status = EVP_DigestSignInit(md_ctx, NULL,
									NULL, NULL, crypto->crypto_pkey);
		if (status == 1)
			status = EVP_DigestSign(md_ctx,
				u_char_ptr(crypto->crypto_out), &crypto->crypto_outlen,
				u_char_ptr(digest), diglen);
		EVP_MD_CTX_free(md_ctx);
	}

	assert(assert_siglen >= crypto->crypto_outlen); // what was allocated
	if (status <= 0)
	{
		/* dkim_load_ssl_errors(dkim, 0); */
		dkim_error(dkim,
			"signature generation failed (status %d, length %zd, %s)",
			status,
			crypto->crypto_outlen,
			ERR_error_string(ERR_get_error(), NULL));

		BIO_CLOBBER(crypto->crypto_keydata);
		return DKIM_STAT_INTERNAL;
	}

#else /* OPENSSL_VERSION_NUMBER >= 0x30000000L */

	switch (sig->sig_signalg)
	{
	  case DKIM_SIGN_RSASHA1:
	  case DKIM_SIGN_RSASHA256:
	  {
		int nid;

		crypto = (struct dkim_crypto *) sig->sig_signature;

		nid = NID_sha1;

		if (dkim_libfeature(dkim->dkim_libhandle, DKIM_FEATURE_SHA256) &&
			sig->sig_hashtype == DKIM_HASHTYPE_SHA256)
				nid = NID_sha256;

		unsigned int siglen;
		status = RSA_sign(nid, u_char_ptr(digest), diglen,
			u_char_ptr(crypto->crypto_out), &siglen, crypto->crypto_key);
		crypto->crypto_outlen = siglen;
		if (status != 1 || siglen == 0)
		{
			dkim_load_ssl_errors(dkim, 0);
			dkim_error(dkim,
			           "signature generation failed (status %d, length %u)",
			           status, siglen);

			RSA_free(crypto->crypto_key);
			crypto->crypto_key = NULL;
			BIO_CLOBBER(crypto->crypto_keydata);

			return DKIM_STAT_INTERNAL;
		}

		break;
	  }

	  case DKIM_SIGN_ED25519SHA256:
	  {
		EVP_MD_CTX *md_ctx = NULL;

		crypto = (struct dkim_crypto *) sig->sig_signature;

		md_ctx = EVP_MD_CTX_new();
		if (md_ctx == NULL)
		{
			dkim_load_ssl_errors(dkim, 0);
			dkim_error(dkim,
			           "failed to initialize digest context");

			BIO_CLOBBER(crypto->crypto_keydata);

			return DKIM_STAT_INTERNAL;
		}

		status = EVP_DigestSignInit(md_ctx, NULL,
		                            NULL, NULL, crypto->crypto_pkey);
		if (status == 1)
			status = EVP_DigestSign(md_ctx,
				u_char_ptr(crypto->crypto_out), &crypto->crypto_outlen,
				u_char_ptr(digest), diglen);

		if (status != 1)
		{
			/* dkim_load_ssl_errors(dkim, 0); */
			dkim_error(dkim,
				"signature generation failed (status %d, length %zd, %s)",
				status,
				crypto->crypto_outlen,
				ERR_error_string(ERR_get_error(), NULL));

			BIO_CLOBBER(crypto->crypto_keydata);
			return DKIM_STAT_INTERNAL;
		}

		EVP_MD_CTX_free(md_ctx);

 		break;
 	  }

	  default:
		assert(0);
	}

#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */
	char *signature = crypto->crypto_out;
	size_t siglen = crypto->crypto_outlen;


#endif /* USE_GNUTLS */

	/* base64-encode the signature */
	dkim->dkim_b64siglen = siglen * 3 + 5;
	dkim->dkim_b64siglen += (dkim->dkim_b64siglen / 60);
	dkim->dkim_b64sig = DKIM_MALLOC(dkim, dkim->dkim_b64siglen);
	if (dkim->dkim_b64sig == NULL)
	{
#ifndef USE_GNUTLS
		BIO_CLOBBER(crypto->crypto_keydata);
#endif /* ! USE_GNUTLS */
		return DKIM_STAT_NORESOURCE;
	}
	memset(dkim->dkim_b64sig, '\0', dkim->dkim_b64siglen);

	status = dkim_base64_encode(signature, siglen, dkim->dkim_b64sig,
	                            dkim->dkim_b64siglen);

#ifndef USE_GNUTLS
	BIO_CLOBBER(crypto->crypto_keydata);
#endif /* ! USE_GNUTLS */

	if (status == -1)
	{
		dkim_error(dkim,
		           "base64 encoding error (buffer too small)");
		return DKIM_STAT_NORESOURCE;
	}

	return DKIM_STAT_OK;
}


/*
**  ARC_FREE_SEAL -- utility to free seal headers
**
**  Parameters:
**      dkim -- DKIM handle
**      rhd -- pointer to an array of three pointers to strings which
**             was filled by arc_do_seal().
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
arc_free_seal(DKIM *dkim, char *rhd[3])
{
	assert(dkim);
	assert(rhd);

	DKIM_FREE(dkim, rhd[0]);
	DKIM_FREE(dkim, rhd[1]);
	DKIM_FREE(dkim, rhd[2]);

	return DKIM_STAT_OK;
}

/*
**  ARC_CHECKFAIL -- a_r_parse callback
*/

static int
arc_checkfail(void* vdkim, int which, name_val* nv, size_t count)
{
	if (nv == NULL)
		return which;

	if (which < 0)
		return 0;

	if (count &&
		strcasecmp(nv->name, "arc") == 0 &&
		strcasecmp(nv->value, "pass") != 0)
	{
		DKIM *dkim = vdkim;
		dkim->arc_state = ARC_CHAIN_FAIL;
		return 1; // stop parsing
	}

	return 0;
}

/*
**  ARC_DO_SEAL -- alternative completion after dkim_eom_sign
**
**  Parameters:
**      dkim -- DKIM handle
**      ar -- Authentication-Results to be enshrined starting with
**            authserv-id and ending with the last value.  No line
**            terminator will be added.
**      rhd -- pointer to an array of three pointers to strings which
*              will contain allocated header fields.
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
arc_do_seal(DKIM *dkim, char const *ar, char *rhd[3])
{
	assert(dkim);
	assert(ar);

/*
*   2.  If the message already contains an Authenticated Received Chain
*       with the most recent AS reporting "cv=fail", there is no need to
*       proceed and the algorithm stops here.
*/
	if (dkim->arc_state == ARC_CHAIN_FAIL_CV)
		return ARC_CV_FAIL_ALREADY;

	rhd[0] = rhd[1] = rhd[2] = NULL;
	char const *function = NULL;

	/*
	**  Part 1: Construct a new AAR
	*/

	struct dkim_dstring *dstr = dkim_dstring_new(dkim, BUFRSZ, MAXBUFRSZ);
	if (dstr == NULL)
		return DKIM_STAT_NORESOURCE;
	dkim_dstring_printf(dstr, ARC_AR_HDRNAME ": i=%u; %s",
	                   dkim->dkim_arccount + 1, ar);
	rhd[0] = dkim_dstring_grab(dstr);

	/*
	**  Part 2: Construct a new AMS (basically a no-frills DKIM signature)
	*/
	DKIM_STAT status = DKIM_STAT_OK;
	dstr = dkim_dstring_new(dkim, BUFRSZ, MAXBUFRSZ);
	if (dstr == NULL)
		goto error_exit;

	dkim_dstring_copy(dstr, ARC_MSGSIG_HDRNAME ": ");

	status = dkim_compute_signature(dkim, sigtype_ams);
	if (status != DKIM_STAT_OK)
	{
		function = "dkim_compute_signature";
		goto error_exit;
	}

	status = dkim_getsighdr_d(dkim, sigtype_ams,
		dkim_dstring_len(dstr));
	if (status != DKIM_STAT_OK)
	{
		function = "dkim_getsighdr_d";
		goto error_exit;
	}

	dkim_dstring_catd(dstr, dkim->dkim_hdrbuf);
	rhd[1] = dkim_dstring_grab(dstr);

	if (dkim->dkim_b64sig) // clear signature for AS
	{
		DKIM_FREE(dkim, dkim->dkim_b64sig);
		dkim->dkim_b64sig = NULL;
	}

	/*
	**  Part 3: Construct a new AS
	*/
	/* reuse AMS signature */
	struct dkim_siginfo *sig = dkim->dkim_signature;
	struct dkim_crypto *crypto = sig->sig_signature;
	if (crypto != NULL)
	{
#ifdef USE_GNUTLS
		HCLOBBER(crypto->crypto_out.data);
#else /* USE_GNUTLS */
//		reuse alloced space CLOBBER(crypto->crypto_out);
#endif /* USE_GNUTLS */
	}

	sig->sig_hdrcanonalg = DKIM_CANON_RELAXED;
	sig->sig_bodycanonalg = DKIM_CANON_UNKNOWN;
	DKIM_CANON *hc;
	status = dkim_add_canon(dkim, TRUE, DKIM_CANON_RELAXED,
	                        sig->sig_hashtype, NULL, NULL, 0,
	                        &hc);
	if (status != DKIM_STAT_OK)
	{
		function = "dkim_add_canon";
		goto error_exit;
	}
	sig->sig_hdrcanon = hc;


	DKIM_LIB *lib = dkim->dkim_libhandle;
	_Bool tmp = ((lib->dkiml_flags & DKIM_LIBFLAGS_TMPFILES) != 0);
	_Bool keep = ((lib->dkiml_flags & DKIM_LIBFLAGS_KEEPFILES) != 0);
	status = dkim_canon_init(dkim, tmp, keep);
	if (status != DKIM_STAT_OK)
	{
		function = "dkim_canon_init";
		goto error_exit;
	}

	if (dkim->arc_state != ARC_CHAIN_NONE)
	{
		assert(dkim->dkim_arccount > 0);

		if (dkim->arc_state == ARC_CHAIN_SEAL_OK)
		/*
		* We cannot check the topmost AMS, as the message might have changed.
		* If the seal is ok, assume the whole chain is ok, that is, the
		* topmost AMS passed, unless there is an arc=fail in AAR.
		*/
		{
			dkim->arc_state = ARC_CHAIN_PASS;
			a_r_parse(ar, &arc_checkfail, dkim);

			if (dkim->arc_state == ARC_CHAIN_PASS)
			/*
			* In the case of a failed Authenticated Received Chain, the header
			* fields included in the signature scope of the AS header field b=
			* value MUST only include the ARC Set header fields created by the MTA
			* that detected the malformed chain, as if this newest ARC Set was the
			* only set present.
			*/
			{
				status = arc_canon_runheaders_seal_again(dkim, hc);
				if (status != DKIM_STAT_OK)
				{
					function = "arc_canon_runheaders_seal_again";
					goto error_exit;
				}
			}
		}
		else
			dkim->arc_state = ARC_CHAIN_FAIL;
	}

	status = dkim_canon_header_text(dkim, hc, rhd[0], TRUE);
	if (status == DKIM_STAT_OK)
		status = dkim_canon_header_text(dkim, hc, rhd[1], TRUE);
	if (status != DKIM_STAT_OK)
	{
		function = "dkim_canon_header_text";
		goto error_exit;
	}

	status = dkim_compute_signature(dkim, sigtype_as);
	if (status != DKIM_STAT_OK)
	{
		function = "dkim_compute_signature";
		goto error_exit;
	}

	dstr = dkim_dstring_new(dkim, BUFRSZ, MAXBUFRSZ);
	if (dstr == NULL)
		goto error_exit;

	/* AS header */
	dkim_dstring_copy(dstr, ARC_SEAL_HDRNAME ": ");
	status = dkim_getsighdr_d(dkim, sigtype_as,
		dkim_dstring_len(dstr));
	if (status != DKIM_STAT_OK)
	{
		function = "dkim_getsighdr_d";
		goto error_exit;
	}

	dkim_dstring_catd(dstr, dkim->dkim_hdrbuf);

	rhd[2] = dkim_dstring_grab(dstr);

	return DKIM_STAT_OK;


#if 0
	int rstatus;
	int siglen;
	int nid;
	u_int set;
	ARC_STAT status;
	size_t diglen;
	size_t keysize;
	size_t len;
	size_t b64siglen;
	u_char *sighdr;
	u_char *digest;
	u_char *sigout;
	u_char *b64sig;
	ARC_HDRFIELD *h;
	ARC_HDRFIELD hdr;
	struct arc_dstring *dstr;
	BIO *keydata;
	EVP_PKEY *pkey;
	RSA *rsa;

	assert(msg != NULL);
	assert(seal != NULL);
	assert(authservid != NULL);
	assert(selector != NULL);
	assert(domain != NULL);
	assert(key != NULL);
	assert(keylen > 0);

	/* feed the seal we have so far */
	status = arc_canon_add_to_seal(msg);
	if (status != ARC_STAT_OK)
	{
		arc_error(msg, "arc_canon_add_to_seal() failed");
		arc_dstring_free(dstr);
		free(sigout);
		RSA_free(rsa);
		EVP_PKEY_free(pkey);
		BIO_free(keydata);
		return status;
	}

	status = arc_getamshdr_d(msg, arc_dstring_len(dstr), &sighdr, &len,
	                         TRUE);
	if (status != ARC_STAT_OK)
	{
		arc_error(msg, "arc_getamshdr_d() failed");
		arc_dstring_free(dstr);
		free(sigout);
		RSA_free(rsa);
		EVP_PKEY_free(pkey);
		BIO_free(keydata);
		return status;
	}

	arc_dstring_catn(dstr, sighdr, len);

	hdr.hdr_text = arc_dstring_get(dstr);
	hdr.hdr_colon = hdr.hdr_text + ARC_SEAL_HDRNAMELEN;
	hdr.hdr_namelen = ARC_SEAL_HDRNAMELEN;
	hdr.hdr_textlen = len;
	hdr.hdr_flags = 0;
	hdr.hdr_next = NULL;

	/* canonicalize */
	status = arc_canon_signature(msg, &hdr, TRUE);
	if (status != ARC_STAT_OK)
	{
		arc_error(msg, "arc_canon_signature() failed");
		arc_dstring_free(dstr);
		free(sigout);
		free(sighdr);
		RSA_free(rsa);
		EVP_PKEY_free(pkey);
		BIO_free(keydata);
		return ARC_STAT_INTERNAL;
	}

	status = arc_canon_getfinal(msg->arc_sealcanon, &digest, &diglen);
	if (status != ARC_STAT_OK)
	{
		arc_error(msg, "arc_canon_getseal() failed");
		arc_dstring_free(dstr);
		free(sigout);
		RSA_free(rsa);
		EVP_PKEY_free(pkey);
		BIO_free(keydata);
		return status;
	}

	/* encrypt the digest; that's our signature */
	nid = NID_sha256;
	rstatus = RSA_sign(nid, digest, diglen, sigout, &siglen, rsa);
	if (rstatus != 1 || siglen == 0)
	{
		arc_error(msg, "RSA_sign() failed (status %d, length %d)",
		          rstatus, siglen);
		arc_dstring_free(dstr);
		free(sigout);
		RSA_free(rsa);
		EVP_PKEY_free(pkey);
		BIO_free(keydata);
		return ARC_STAT_INTERNAL;
	}

	/* base64 encode it */
	memset(b64sig, '\0', b64siglen);
	rstatus = arc_base64_encode(sigout, siglen, b64sig, b64siglen);
	if (rstatus == -1)
	{
		arc_error(msg, "signature base64 encoding failed");
		arc_dstring_free(dstr);
		free(b64sig);
		free(sigout);
		RSA_free(rsa);
		EVP_PKEY_free(pkey);
		BIO_free(keydata);
		return ARC_STAT_INTERNAL;
	}

	/* append it to the stub */
	arc_dstring_cat(dstr, b64sig);

	/* XXX -- wrapping needs to happen here */

	/* add it to the seal */
	h = malloc(sizeof hdr);
	if (h == NULL)
	{
		arc_error(msg, "can't allocate %d bytes", sizeof hdr);
		arc_dstring_free(dstr);
		free(b64sig);
		free(sigout);
		RSA_free(rsa);
		EVP_PKEY_free(pkey);
		BIO_free(keydata);
		return ARC_STAT_INTERNAL;
	}
	h->hdr_text = strdup(arc_dstring_get(dstr));
	if (h->hdr_text == NULL)
	{
		arc_error(msg, "can't allocate %d bytes", sizeof hdr);
		arc_dstring_free(dstr);
		free(b64sig);
		free(sigout);
		RSA_free(rsa);
		EVP_PKEY_free(pkey);
		BIO_free(keydata);
		return ARC_STAT_INTERNAL;
	}
	h->hdr_colon = h->hdr_text + ARC_SEAL_HDRNAMELEN;
	h->hdr_namelen = ARC_SEAL_HDRNAMELEN;
	h->hdr_textlen = len;
	h->hdr_flags = 0;
	h->hdr_next = NULL;

	msg->arc_sealtail->hdr_next = h;
	msg->arc_sealtail = h;

	/* tidy up */
	arc_dstring_free(dstr);
	free(b64sig);
	free(sigout);
	RSA_free(rsa);
		EVP_PKEY_free(pkey);
	BIO_free(keydata);

	*seal = msg->arc_sealhead;

	return ARC_STAT_OK;
#endif


error_exit:
	if (function)
		dkim_error(dkim, "%s() failed", function);
	if (dstr)
		dkim_dstring_free(dstr);
	for (int i = 0; i < 3; ++i)
	{
		if (rhd[i])
		{
			DKIM_FREE(dkim, rhd[i]);
			rhd[i] = NULL;
		}
	}
	if (dkim->memory_fault)
		return DKIM_STAT_NORESOURCE;

	return status;
}


/*
**  DKIM_EOM_VERIFY -- declare end-of-body; complete verification
**
**  Parameters:
**  	dkim -- DKIM handle
**  	testkey -- TRUE iff the a matching key was found but is marked as a
**  	           test key (returned)
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

static DKIM_STAT
dkim_eom_verify(DKIM *dkim, _Bool *testkey)
{
	DKIM_STAT ret;
	unsigned int c;
	int status;
	DKIM_SIGINFO *sig = NULL;

	assert(dkim != NULL);
	DKIM_LIB const *const lib = dkim->dkim_libhandle;

	if (dkim->dkim_state >= DKIM_STATE_EOM2 ||
	    dkim->dkim_state < DKIM_STATE_EOH1)
		return DKIM_STAT_INVALID;
	if (dkim->dkim_state < DKIM_STATE_EOM1)
		dkim->dkim_state = DKIM_STATE_EOM1;

	if (dkim->dkim_chunkstate != DKIM_CHUNKSTATE_INIT &&
	    dkim->dkim_chunkstate != DKIM_CHUNKSTATE_DONE)
		return DKIM_STAT_INVALID;

	/* finalize body canonicalizations */
	ret = dkim_canon_closebody(dkim);
	if (ret != DKIM_STAT_OK)
		return ret;

	dkim->dkim_bodydone = TRUE;

	if (dkim->dkim_sigcount == 0 && dkim->arc_state != ARC_CHAIN_SEAL_OK)
	{					/* unsigned and ARC either absent or failed */

		assert(dkim->dkim_domain); // since dkim_getsender called by dkim_eoh_verify
#if 0
		if (dkim->dkim_domain == NULL)
		{
			char *domain;
			char *user;

			struct dkim_header *hdr;
			hdr = dkim_get_header(dkim, DKIM_FROMHEADER,
			                      DKIM_FROMHEADER_LEN, 0);
			if (hdr == NULL)
			{
				dkim_error(dkim, "no %s header found",
				           DKIM_FROMHEADER);
				return DKIM_STAT_CANTVRFY;
			}

			if (hdr->hdr_colon == NULL)
			{
				dkim_error(dkim, "%s header malformed",
				           DKIM_FROMHEADER);
				return DKIM_STAT_CANTVRFY;
			}

			status = my_mail_parse(hdr->hdr_colon + 1,
			                         &user, &domain);
			if (status != 0 || domain == NULL || domain[0] == '\0')
			{
				dkim_error(dkim, "%s header malformed",
				           DKIM_FROMHEADER);
				return DKIM_STAT_CANTVRFY;
			}

			dkim->dkim_domain = dkim_strdup(dkim, domain, 0);
			if (dkim->dkim_domain == NULL)
				return DKIM_STAT_NORESOURCE;
		}
#endif

		dkim->dkim_state = DKIM_STATE_EOM2;

		return DKIM_STAT_NOSIG;
	}

	if (dkim->dkim_sigcount > 0)
	{
		/*
		**  If a signature has "l=" set but it was greater than the
		**  canonicalized body length, the signature is invalid.
		*/

		for (c = 0; c < dkim->dkim_sigcount; c++)
		{
			sig = dkim->dkim_siglist[c];

			if (sig->sig_bodycanon != NULL &&
				sig->sig_bodycanon->canon_length != (ssize_t) -1 &&
				sig->sig_bodycanon->canon_wrote < sig->sig_bodycanon->canon_length)
				sig->sig_error = DKIM_SIGERROR_TOOLARGE_L;
		}

		/* invoke the final callback if defined */
		if (lib->dkiml_final != NULL)
		{
			status = lib->dkiml_final(dkim, dkim->dkim_siglist,
									  dkim->dkim_sigcount);
			switch (status)
			{
			  case DKIM_CBSTAT_CONTINUE:
			  case DKIM_CBSTAT_DEFAULT:
				break;

			  case DKIM_CBSTAT_REJECT:
				return DKIM_STAT_CBREJECT;

			  case DKIM_CBSTAT_TRYAGAIN:
				return DKIM_STAT_CBTRYAGAIN;

			  case DKIM_CBSTAT_ERROR:
				return DKIM_STAT_CBERROR;

			  default:
				return DKIM_STAT_CBINVALID;
			}
		}
	}

	dkim->dkim_state = DKIM_STATE_EOM2;

	/*
	**  If ARC was present and ARC-Seal(s) verify, we still need at
	**  least to verify the top signature to validate the chain.
	*/
	if (dkim->arc_state == ARC_CHAIN_SEAL_OK)
	{
		assert(dkim->dkim_arccount > 0);

		int last_fail = 0;
		for (c = dkim->dkim_arccount; c > 0; )
		{
			sig = dkim->arc_sets[--c].arcsig_ams;
			status = dkim_sig_process(dkim, sig);
			if (status != DKIM_STAT_OK ||
				(sig->sig_flags & DKIM_SIGFLAG_PASSED) == 0 ||
				sig->sig_bh != DKIM_SIGBH_MATCH)
			{
				last_fail = 1;
				break;
			}
		}
		unsigned int oldest_pass = c + last_fail;
		if (oldest_pass)
			++oldest_pass;  // instance number of the oldest pass
		if (oldest_pass > dkim->dkim_arccount) // no AMS passed
		{
			if (status == DKIM_STAT_OK)
				dkim->arc_state = ARC_CHAIN_FAIL;
			else if ((sig->sig_flags & DKIM_SIGFLAG_PROCESSED) == 0)
				dkim->arc_state = ARC_CHAIN_TEMPERROR;
			else
				dkim->arc_state = ARC_CHAIN_PERMERROR;
		}
		else
		{
			dkim->arc_state = ARC_CHAIN_PASS;
			dkim->oldest_pass = oldest_pass;
		}
	}

	if (dkim->dkim_sigcount == 0)
		return DKIM_STAT_OK;


	/* see if we have a passing signature with bh match */
	for (c = 0; c < dkim->dkim_sigcount; c++)
	{
		sig = dkim->dkim_siglist[c];

		if ((sig->sig_flags & DKIM_SIGFLAG_PASSED) != 0 &&
		    (sig->sig_flags & DKIM_SIGFLAG_IGNORE) == 0 &&
		    sig->sig_bh == DKIM_SIGBH_MATCH)
			break;

		sig = NULL;
	}

	/* run 'em until we get one */
	if (sig == NULL)
	{
		DKIM_SIGINFO *firstgood = NULL;

		for (c = 0; c < dkim->dkim_sigcount; c++)
		{
			sig = dkim->dkim_siglist[c];

			/* if not ignoring */
			if ((sig->sig_flags & DKIM_SIGFLAG_IGNORE) == 0)
			{
				/* run this signature */
				status = dkim_sig_process(dkim, sig);
				if (status != DKIM_STAT_OK)
				{
					sig = NULL;
					continue;
				}

				/*
				**  If the signature has fewer than the
				**  minimum number of key bits required by
				**  configuration, the signature is invalid.
				*/

				if (sig->sig_error == 0 &&
				    sig->sig_signalg != DKIM_SIGN_ED25519SHA256 &&
				    sig->sig_keybits < lib->dkiml_minkeybits)
				{
					sig->sig_error = DKIM_SIGERROR_KEYTOOSMALL;
					sig->sig_flags &= ~DKIM_SIGFLAG_PASSED;
				}

				/* pass and bh match? */
				if ((sig->sig_flags & DKIM_SIGFLAG_PASSED) != 0 &&
				    sig->sig_bh == DKIM_SIGBH_MATCH)
				{
					if (firstgood == NULL)
						firstgood = sig;

					/* continue? */
					if ((lib->dkiml_flags & DKIM_LIBFLAGS_VERIFYONE) != 0)
						break;
				}
			}

			sig = NULL;
		}

		if (sig == NULL)
			sig = firstgood;
	}

	/*
	**  If still none, we're going to fail so just use the first one,
	**  unless an ARC chain succeeded.
	*/

	if (dkim->arc_state != ARC_CHAIN_PASS)
	{
		if (sig == NULL)
		{
			for (c = 0; c < dkim->dkim_sigcount; c++)
			{
				sig = dkim->dkim_siglist[c];
				if ((sig->sig_flags & DKIM_SIGFLAG_IGNORE) == 0)
					break;
				sig = NULL;
			}
		}

		/* caller marked everything with "ignore" */
		if (sig == NULL)
		{
			dkim_error(dkim, "all signatures ignored by caller");
			return DKIM_STAT_NOSIG;
		}

		dkim->dkim_signature = sig;

		/* things for which we return DKIM_STAT_CANTVRFY */
		if (sig->sig_error != DKIM_SIGERROR_OK &&
			sig->sig_error != DKIM_SIGERROR_UNKNOWN &&
			sig->sig_error != DKIM_SIGERROR_KEYFAIL &&
			sig->sig_error != DKIM_SIGERROR_BADSIG &&
			sig->sig_error != DKIM_SIGERROR_BODYHASH &&
			sig->sig_error != DKIM_SIGERROR_KEYREVOKED &&
			sig->sig_error != DKIM_SIGERROR_NOKEY)
		{
			if (dkim->dkim_error == NULL ||
				dkim->dkim_error[0] == '\0')
			{
				dkim_error(dkim, dkim_code_to_name(sigerrors,
												   sig->sig_error));
			}

			return DKIM_STAT_CANTVRFY;
		}
	}

	/* initialize final result */
	ret = DKIM_STAT_OK;
	if (dkim->arc_state != ARC_CHAIN_PASS)
	{
		if (sig->sig_error == DKIM_SIGERROR_NOKEY)
			ret = DKIM_STAT_NOKEY;
		else if (sig->sig_error == DKIM_SIGERROR_KEYFAIL)
			ret = DKIM_STAT_KEYFAIL;
		else if (sig->sig_error == DKIM_SIGERROR_KEYREVOKED)
			ret = DKIM_STAT_REVOKED;
		else if ((sig->sig_flags & DKIM_SIGFLAG_PASSED) == 0)
			ret = DKIM_STAT_BADSIG;
		else if (sig->sig_bh == DKIM_SIGBH_MISMATCH)
			ret = DKIM_STAT_BADSIG;
		else if (sig->sig_error == DKIM_SIGERROR_BADSIG)
			ret = DKIM_STAT_BADSIG;
	}

	/* set testkey based on the key flags */
	if (testkey != NULL &&
	    (sig->sig_flags & DKIM_SIGFLAG_TESTKEY) != 0)
		*testkey = TRUE;

	return ret;
}

/*
**  DKIM_NEW -- allocate a new message context
**
**  Parameters:
**  	libhandle -- DKIM_LIB handle
**  	id -- transaction ID string
**  	memclosure -- memory closure
**  	hdrcanon_alg -- canonicalization algorithm to use for headers
**  	bodycanon_alg -- canonicalization algorithm to use for headers
**  	sign_alg -- signature algorithm to use
**  	statp -- status (returned)
**
**  Return value:
**  	A new DKIM handle, or NULL on failure.
*/

static DKIM *
dkim_new(DKIM_LIB *libhandle, const char *id, void *memclosure,
         dkim_canon_t hdrcanon_alg, dkim_canon_t bodycanon_alg,
         dkim_alg_t sign_alg, DKIM_STAT *statp)
{
	DKIM *new;

	assert(libhandle != NULL);

	/* allocate the handle */
	new = (DKIM *) dkim_malloc(libhandle, memclosure,
	                           sizeof(struct dkim));
	if (new == NULL)
	{
		*statp = DKIM_STAT_NORESOURCE;
		return NULL;
	}

	/* populate defaults */
	memset(new, '\0', sizeof(struct dkim));
	new->dkim_id = id;
	new->dkim_signalg = sign_alg;
	new->dkim_hdrcanonalg = (hdrcanon_alg == -1 ? DKIM_CANON_DEFAULT
	                                            : hdrcanon_alg);
	new->dkim_bodycanonalg = (bodycanon_alg == -1 ? DKIM_CANON_DEFAULT
	                                              : bodycanon_alg);
	new->dkim_querymethods = NULL;
	new->dkim_mode = DKIM_MODE_UNKNOWN;
	new->dkim_chunkcrlf = DKIM_CRLF_UNKNOWN;
	new->dkim_state = DKIM_STATE_INIT;
	new->dkim_margin = (size_t) DKIM_HDRMARGIN;
	new->dkim_closure = memclosure;
	new->dkim_libhandle = libhandle;
	new->dkim_tmpdir = libhandle->dkiml_tmpdir;
	new->dkim_timeout = libhandle->dkiml_timeout;

	new->arc_state = ARC_CHAIN_UNKNOWN;
	new->arc_seal_failed = -1;

	*statp = DKIM_STAT_OK;

#ifdef QUERY_CACHE
	if ((libhandle->dkiml_flags & DKIM_LIBFLAGS_CACHE) != 0 &&
	    libhandle->dkiml_cache == NULL)
	{
		int err = 0;

		libhandle->dkiml_cache = dkim_cache_init(&err,
		                                         libhandle->dkiml_tmpdir);
	}
#endif /* QUERY_CACHE */

	if (libhandle->dkiml_dkim == NULL)
		libhandle->dkiml_dkim = new;
	return  new;
}

#ifndef USE_GNUTLS
/*
**  DKIM_INIT_OPENSSL -- initialize OpenSSL algorithms if needed
**
**  Parameters:
**  	None.
**
**  Return value:
**  	None.
*/

static pthread_mutex_t openssl_lock = PTHREAD_MUTEX_INITIALIZER;
static unsigned openssl_refcount = 0;

static void
dkim_init_openssl(void)
{
	pthread_mutex_lock(&openssl_lock);

	if (openssl_refcount == 0)
		OpenSSL_add_all_algorithms();
	openssl_refcount++;

	pthread_mutex_unlock(&openssl_lock);
}

/*
**  DKIM_CLOSE_OPENSSL -- clean up OpenSSL algorithms if needed
**
**  Parameters:
**  	None.
**
**  Return value:
**  	None.
*/

static void
dkim_close_openssl(void)
{
	assert(openssl_refcount > 0);

	pthread_mutex_lock(&openssl_lock);

	openssl_refcount--;
	if (openssl_refcount == 0)
		EVP_cleanup();

	pthread_mutex_unlock(&openssl_lock);
}
#endif /* ! USE_GNUTLS */

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

/*
**  DKIM_INIT -- initialize a DKIM library context
**
**  Parameters:
**  	caller_mallocf -- caller-provided memory allocation function
**  	caller_freef -- caller-provided memory release function
**
**  Return value:
**  	A new DKIM_LIB handle suitable for use with other DKIM functions, or
**  	NULL on failure.
**
**  Side effects:
**  	Crop circles near Birmingham.
*/

DKIM_LIB *
dkim_init(unsigned int flags,
          void *(*caller_mallocf)(void *closure, size_t nbytes),
          void (*caller_freef)(void *closure, void *p))
{
	u_char *td;
	DKIM_LIB *libhandle;

#ifndef USE_GNUTLS
	/* initialize OpenSSL algorithms */
	dkim_init_openssl();
#endif /* USE_GNUTLS */

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

	memset(libhandle, 0, sizeof(struct dkim_lib));

	td = (u_char *) getenv("DKIM_TMPDIR");
	if (td == NULL || td[0] == '\0')
		td = (u_char *) DEFTMPDIR;

	libhandle->dkiml_signre = FALSE;
	libhandle->dkiml_skipre = FALSE;
	libhandle->dkiml_malloc = caller_mallocf;
	libhandle->dkiml_free = caller_freef;
	strlcpy((char *) libhandle->dkiml_tmpdir, (char *) td, 
	        sizeof libhandle->dkiml_tmpdir);
	libhandle->dkiml_flags = DKIM_LIBFLAGS_DEFAULT;
	libhandle->dkiml_timeout = DEFTIMEOUT;
	libhandle->dkiml_requiredhdrs = dkim_required_signhdrs;
	libhandle->dkiml_oversignhdrs = NULL;
	libhandle->dkiml_mbs = NULL;
	libhandle->dkiml_querymethod = DKIM_QUERY_UNKNOWN;
	memset(libhandle->dkiml_queryinfo, '\0',
	       sizeof libhandle->dkiml_queryinfo);
#ifdef QUERY_CACHE
	libhandle->dkiml_cache = NULL;
#endif /* QUERY_CACHE */
	libhandle->dkiml_fixedtime = 0;
	libhandle->dkiml_sigttl = 0;
	libhandle->dkiml_clockdrift = DEFCLOCKDRIFT;
	libhandle->dkiml_minkeybits = DEFMINKEYBITS;

	libhandle->dkiml_key_lookup = NULL;
	libhandle->dkiml_key_retrieved = NULL;
//	libhandle->dkiml_sig_handle = NULL;
//	libhandle->dkiml_sig_handle_free = NULL;
//	libhandle->dkiml_sig_tagvalues = NULL;
	libhandle->dkiml_prescreen = NULL;
	libhandle->dkiml_final = NULL;
	libhandle->dkiml_dns_callback = NULL;
	libhandle->dkiml_dns_service = NULL;
	libhandle->dkiml_dnsinit_done = FALSE;
	libhandle->dkiml_dns_init = dkim_res_init;
	libhandle->dkiml_dns_close = dkim_res_close;
	libhandle->dkiml_dns_start = dkim_res_query;
	libhandle->dkiml_dns_cancel = dkim_res_cancel;
	libhandle->dkiml_dns_waitreply = dkim_res_waitreply;
	libhandle->dkiml_dkim = NULL;

#define FEATURE_INDEX(x)	((x) / (8 * sizeof(u_int)))
#define FEATURE_OFFSET(x)	((x) % (8 * sizeof(u_int)))
#define FEATURE_ADD(lib,x)	(lib)->dkiml_flist[FEATURE_INDEX((x))] |= (1 << FEATURE_OFFSET(x))

	libhandle->dkiml_flsize = (FEATURE_INDEX(DKIM_FEATURE_MAX)) + 1;
	libhandle->dkiml_flist = (u_int *) malloc(sizeof(u_int) * libhandle->dkiml_flsize);
	if (libhandle->dkiml_flist == NULL)
	{
		free(libhandle);
		return NULL;
	}
	memset(libhandle->dkiml_flist, '\0',
	       sizeof(u_int) * libhandle->dkiml_flsize);

#ifdef _FFR_DIFFHEADERS
	FEATURE_ADD(libhandle, DKIM_FEATURE_DIFFHEADERS);
#endif /* _FFR_DIFFHEADERS */
#ifdef QUERY_CACHE
	FEATURE_ADD(libhandle, DKIM_FEATURE_QUERY_CACHE);
#endif /* QUERY_CACHE */
#ifdef HAVE_SHA256
	FEATURE_ADD(libhandle, DKIM_FEATURE_SHA256);
#endif /* HAVE_SHA256 */
#ifdef HAVE_ED25519
	FEATURE_ADD(libhandle, DKIM_FEATURE_ED25519);
#endif /* HAVE_ED25519 */
#ifdef _FFR_DNSSEC
	FEATURE_ADD(libhandle, DKIM_FEATURE_DNSSEC);
#endif /* _FFR_DNSSEC */
#ifdef _FFR_RESIGN
	FEATURE_ADD(libhandle, DKIM_FEATURE_RESIGN);
#endif /* _FFR_RESIGN */
#ifdef _FFR_ATPS
	FEATURE_ADD(libhandle, DKIM_FEATURE_ATPS);
#endif /* _FFR_ATPS */
	FEATURE_ADD(libhandle, DKIM_FEATURE_OVERSIGN);
	FEATURE_ADD(libhandle, DKIM_FEATURE_XTAGS);

	/* Don't initialize the resolver */
//	if (flags == 0)
//		(void) res_init();

	return libhandle;
	(void)flags;
}

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

void
dkim_close(DKIM_LIB *lib)
{
	assert(lib != NULL);

#ifdef QUERY_CACHE
	if (lib->dkiml_cache != NULL)
		(void) dkim_cache_close(lib->dkiml_cache);
#endif /* QUERY_CACHE */

	if (lib->dkiml_skipre)
		(void) regfree(&lib->dkiml_skiphdrre);
	
	if (lib->dkiml_signre)
		(void) regfree(&lib->dkiml_hdrre);

	if (lib->dkiml_oversignhdrs != NULL)
		dkim_clobber_array(lib->dkiml_oversignhdrs);

	if (lib->dkiml_requiredhdrs != dkim_required_signhdrs)
		dkim_clobber_array(lib->dkiml_requiredhdrs);

	if (lib->dkiml_mbs != NULL)
		dkim_clobber_array(lib->dkiml_mbs);

	free(lib->dkiml_flist);

	if (lib->dkiml_dns_close != NULL && lib->dkiml_dns_service != NULL)
		lib->dkiml_dns_close(lib->dkiml_dns_service);
	
	free((void *) lib);

#ifndef USE_GNUTLS
	dkim_close_openssl();
#endif /* ! USE_GNUTLS */
}

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

void
dkim_error(DKIM *dkim, const char *format, ...)
{
	int flen;
	int saverr;
	char *new;
	va_list va;

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

	saverr = errno;

	if (dkim->dkim_error == NULL)
	{
		dkim->dkim_error = DKIM_MALLOC(dkim, DEFERRLEN);
		if (dkim->dkim_error == NULL)
		{
			errno = saverr;
			return;
		}
		dkim->dkim_errlen = DEFERRLEN;
	}

	for (;;)
	{
		va_start(va, format);
		flen = vsnprintf((char *) dkim->dkim_error, dkim->dkim_errlen,
		                 format, va);
		va_end(va);

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

		if (flen >= dkim->dkim_errlen)
		{
			new = DKIM_MALLOC(dkim, flen + 1);
			if (new == NULL)
			{
				errno = saverr;
				return;
			}

			DKIM_FREE(dkim, dkim->dkim_error);
			dkim->dkim_error = new;
			dkim->dkim_errlen = flen + 1;
		}
		else
		{
			break;
		}
	}

	errno = saverr;
}

/*
**  DKIM_OPTIONS -- get or set a library option
**
**  Parameters:
**  	lib -- DKIM 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 DKIM_STAT constant.
*/

DKIM_STAT
dkim_options(DKIM_LIB *lib, int op, dkim_opts_t opt, void *ptr, size_t len)
{
	assert(lib != NULL);
	assert(op == DKIM_OP_SETOPT || op == DKIM_OP_GETOPT);
	assert(len != 0);

	switch (opt)
	{
	  case DKIM_OPTS_TMPDIR:
		if (op == DKIM_OP_GETOPT)
		{
			strlcpy((char *) ptr, lib->dkiml_tmpdir, len);
		}
		else if (ptr == NULL)
		{
			strlcpy(lib->dkiml_tmpdir, DEFTMPDIR,
			        sizeof lib->dkiml_tmpdir);
		}
		else
		{
			strlcpy(lib->dkiml_tmpdir, (char *) ptr,
			        sizeof lib->dkiml_tmpdir);
		}
		return DKIM_STAT_OK;

	  case DKIM_OPTS_FIXEDTIME:
		if (ptr == NULL)
			return DKIM_STAT_INVALID;

		if (len != sizeof lib->dkiml_fixedtime)
			return DKIM_STAT_INVALID;

		if (op == DKIM_OP_GETOPT)
			memcpy(ptr, &lib->dkiml_fixedtime, len);
		else
			memcpy(&lib->dkiml_fixedtime, ptr, len);

		return DKIM_STAT_OK;

	  case DKIM_OPTS_MINKEYBITS:
		if (ptr == NULL)
			return DKIM_STAT_INVALID;

		if (len != sizeof lib->dkiml_minkeybits)
			return DKIM_STAT_INVALID;

		if (op == DKIM_OP_GETOPT)
			memcpy(ptr, &lib->dkiml_minkeybits, len);
		else
			memcpy(&lib->dkiml_minkeybits, ptr, len);

		return DKIM_STAT_OK;

	  case DKIM_OPTS_SIGNATURETTL:
		if (ptr == NULL)
			return DKIM_STAT_INVALID;

		if (len != sizeof lib->dkiml_sigttl)
			return DKIM_STAT_INVALID;

		if (op == DKIM_OP_GETOPT)
			memcpy(ptr, &lib->dkiml_sigttl, len);
		else
			memcpy(&lib->dkiml_sigttl, ptr, len);

		return DKIM_STAT_OK;

	  case DKIM_OPTS_CLOCKDRIFT:
		if (ptr == NULL)
			return DKIM_STAT_INVALID;

		if (len != sizeof lib->dkiml_clockdrift)
			return DKIM_STAT_INVALID;

		if (op == DKIM_OP_GETOPT)
			memcpy(ptr, &lib->dkiml_clockdrift, len);
		else
			memcpy(&lib->dkiml_clockdrift, ptr, len);

		return DKIM_STAT_OK;

	  case DKIM_OPTS_FLAGS:
		if (ptr == NULL)
			return DKIM_STAT_INVALID;

		if (len != sizeof lib->dkiml_flags)
			return DKIM_STAT_INVALID;

		if (op == DKIM_OP_GETOPT)
			memcpy(ptr, &lib->dkiml_flags, len);
		else
			memcpy(&lib->dkiml_flags, ptr, len);

		return DKIM_STAT_OK;

	  case DKIM_OPTS_TIMEOUT:
		if (ptr == NULL)
			return DKIM_STAT_INVALID;

		if (len != sizeof lib->dkiml_timeout)
			return DKIM_STAT_INVALID;

		if (op == DKIM_OP_GETOPT)
			memcpy(ptr, &lib->dkiml_timeout, len);
		else
			memcpy(&lib->dkiml_timeout, ptr, len);

		return DKIM_STAT_OK;

	  case DKIM_OPTS_REQUIREDHDRS:
		if (len != sizeof lib->dkiml_requiredhdrs)
			return DKIM_STAT_INVALID;

		if (op == DKIM_OP_GETOPT)
		{
			memcpy(ptr, &lib->dkiml_requiredhdrs, len);
		}
		else if (ptr == NULL)
		{
			if (lib->dkiml_requiredhdrs != dkim_required_signhdrs)
				dkim_clobber_array(lib->dkiml_requiredhdrs);

			lib->dkiml_requiredhdrs = dkim_required_signhdrs;
		}
		else
		{
			char **tmp;

			tmp = dkim_copy_array(ptr);
			if (tmp == NULL)
				return DKIM_STAT_NORESOURCE;

			if (lib->dkiml_requiredhdrs != dkim_required_signhdrs)
				dkim_clobber_array(lib->dkiml_requiredhdrs);

			lib->dkiml_requiredhdrs = tmp;
		}
		return DKIM_STAT_OK;

	  case DKIM_OPTS_OVERSIGNHDRS:
		if (len != sizeof lib->dkiml_oversignhdrs)
			return DKIM_STAT_INVALID;

		if (op == DKIM_OP_GETOPT)
		{
			memcpy(ptr, &lib->dkiml_oversignhdrs, len);
		}
		else if (ptr == NULL)
		{
			if (lib->dkiml_oversignhdrs != NULL)
				dkim_clobber_array(lib->dkiml_oversignhdrs);
			lib->dkiml_oversignhdrs = NULL;
		}
		else
		{
			char **tmp;

			tmp = dkim_copy_array(ptr);
			if (tmp == NULL)
				return DKIM_STAT_NORESOURCE;

			if (lib->dkiml_oversignhdrs != NULL)
				dkim_clobber_array(lib->dkiml_oversignhdrs);

			lib->dkiml_oversignhdrs = tmp;
		}
		return DKIM_STAT_OK;

	  case DKIM_OPTS_MUSTBESIGNED:
		if (len != sizeof lib->dkiml_mbs)
			return DKIM_STAT_INVALID;

		if (op == DKIM_OP_GETOPT)
		{
			memcpy(ptr, &lib->dkiml_mbs, len);
		}
		else if (ptr == NULL)
		{
			if (lib->dkiml_mbs != NULL)
				dkim_clobber_array(lib->dkiml_mbs);

			lib->dkiml_mbs = NULL;
		}
		else
		{
			char **tmp;

			tmp = dkim_copy_array(ptr);
			if (tmp == NULL)
				return DKIM_STAT_NORESOURCE;

			if (lib->dkiml_mbs != NULL)
				dkim_clobber_array(lib->dkiml_mbs);

			lib->dkiml_mbs = tmp;
		}
		return DKIM_STAT_OK;

	  case DKIM_OPTS_SIGNHDRS:
		if (len != sizeof(char **) || op == DKIM_OP_GETOPT)
		{
			return DKIM_STAT_INVALID;
		}
		else if (ptr == NULL)
		{
			if (lib->dkiml_signre)
			{
				(void) regfree(&lib->dkiml_hdrre);
				lib->dkiml_signre = FALSE;
			}
		}
		else
		{
			int status;
			char **hdrs;
			char **required_signhdrs;
			char buf[BUFRSZ + 1];

			if (lib->dkiml_signre)
			{
				(void) regfree(&lib->dkiml_hdrre);
				lib->dkiml_signre = FALSE;
			}

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

			hdrs = (char **) ptr;

			(void) strlcpy(buf, "^(", sizeof buf);

			required_signhdrs = lib->dkiml_requiredhdrs;
			if (!dkim_hdrlist(buf, sizeof buf, required_signhdrs, TRUE))
				return DKIM_STAT_INVALID;
			if (!dkim_hdrlist(buf, sizeof buf, hdrs, FALSE))
				return DKIM_STAT_INVALID;

			if (strlcat(buf, ")$", sizeof buf) >= sizeof buf)
				return DKIM_STAT_INVALID;

			status = regcomp(&lib->dkiml_hdrre, buf,
			                 (REG_EXTENDED|REG_ICASE));
			if (status != 0)
				return DKIM_STAT_INTERNAL;

			lib->dkiml_signre = TRUE;
		}
		return DKIM_STAT_OK;

	  case DKIM_OPTS_SKIPHDRS:
		if (len != sizeof(char **) || op == DKIM_OP_GETOPT)
		{
			return DKIM_STAT_INVALID;
		}
		else if (ptr == NULL)
		{
			if (lib->dkiml_skipre)
			{
				(void) regfree(&lib->dkiml_skiphdrre);
				lib->dkiml_skipre = FALSE;
			}
		}
		else
		{
			int status;
			char **hdrs;
			char buf[BUFRSZ + 1];

			if (lib->dkiml_skipre)
			{
				(void) regfree(&lib->dkiml_skiphdrre);
				lib->dkiml_skipre = FALSE;
			}

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

			hdrs = (char **) ptr;

			(void) strlcpy(buf, "^(", sizeof buf);

			if (!dkim_hdrlist(buf, sizeof buf, hdrs, TRUE))
				return DKIM_STAT_INVALID;

			if (strlcat(buf, ")$", sizeof buf) >= sizeof buf)
				return DKIM_STAT_INVALID;

			status = regcomp(&lib->dkiml_skiphdrre, buf,
			                 (REG_EXTENDED|REG_ICASE));
			if (status != 0)
				return DKIM_STAT_INTERNAL;

			lib->dkiml_skipre = TRUE;
		}
		return DKIM_STAT_OK;

	  case DKIM_OPTS_QUERYMETHOD:
		if (ptr == NULL)
			return DKIM_STAT_INVALID;

		if (len != sizeof lib->dkiml_querymethod)
			return DKIM_STAT_INVALID;

		if (op == DKIM_OP_GETOPT)
			memcpy(ptr, &lib->dkiml_querymethod, len);
		else
			memcpy(&lib->dkiml_querymethod, ptr, len);

		return DKIM_STAT_OK;

	  case DKIM_OPTS_QUERYINFO:
		if (ptr == NULL)
			return DKIM_STAT_INVALID;

		if (op == DKIM_OP_GETOPT)
		{
			strlcpy(ptr, (char *) lib->dkiml_queryinfo, len);
		}
		else
		{
			strlcpy((char *) lib->dkiml_queryinfo, ptr,
			        sizeof lib->dkiml_queryinfo);
		}
		return DKIM_STAT_OK;

	  default:
		return DKIM_STAT_INVALID;
	}
}

/*
**  DKIM_FREE -- destroy a DKIM handle
**
**  Parameters:
**  	dkim -- DKIM handle to destroy
**
**  Return value:
**  	A DKIM_STAT constant.
*/

DKIM_STAT
dkim_free(DKIM *dkim)
{
	assert(dkim != NULL);

#ifdef _FFR_RESIGN
	/* XXX -- this should be mutex-protected */
	if (dkim->dkim_resign != NULL)
	{
		if (dkim->dkim_resign->dkim_refcnt == 0)
			dkim_free(dkim->dkim_resign);
		else
			dkim->dkim_resign->dkim_refcnt--;
	}
	else if (dkim->dkim_refcnt != 0)
	{
		return DKIM_STAT_INVALID;
	}
#endif /* _FFR_RESIGN */

	/* blast the headers */
#ifdef _FFR_RESIGN
	if (dkim->dkim_resign == NULL && dkim->dkim_hhead != NULL)
#else /* _FFR_RESIGN */
	if (dkim->dkim_hhead != NULL)
#endif /* _FFR_RESIGN */
	{
		struct dkim_header *next;
		struct dkim_header *hdr;

		for (hdr = dkim->dkim_hhead; hdr != NULL; )
		{
			next = hdr->hdr_next;

			CLOBBER(hdr->hdr_text);
			CLOBBER(hdr);

			hdr = next;
		}
	}

	/* blast the data sets */
#ifdef _FFR_RESIGN
	if (dkim->dkim_resign == NULL && dkim->dkim_sethead != NULL)
#else /* _FFR_RESIGN */
	if (dkim->dkim_sethead != NULL)
#endif /* _FFR_RESIGN */
	{
		DKIM_SET *set;
		DKIM_SET *next;

		for (set = dkim->dkim_sethead; set != NULL; )
		{
			next = set->set_next;

			dkim_set_free(dkim, set);

			set = next;
		}
	}

	/* trash the signature list */
	if (dkim->dkim_siglist != NULL)
	{
		unsigned int c;

		for (c = 0; c < dkim->dkim_sigcount; ++c)
		{
			dkim_sig_clear(dkim, dkim->dkim_siglist[c]);
		}

		CLOBBER(dkim->dkim_siglist);
	}

	/* trash ARC signatures */
	if (dkim->arc_sets)
	{
		unsigned int c;

		for (c = 0; c < dkim->dkim_arccount; ++c)
		{
			dkim_sig_clear(dkim, dkim->arc_sets[c].arcsig_ams);
			dkim_sig_clear(dkim, dkim->arc_sets[c].arcsig_as);
		}

		CLOBBER(dkim->arc_sets);
	}

	if (dkim->dkim_querymethods != NULL)
	{
		struct dkim_qmethod *cur;
		struct dkim_qmethod *next;

		cur = dkim->dkim_querymethods;
		while (cur != NULL)
		{
			next = cur->qm_next;
			DKIM_FREE(dkim, cur->qm_type);
			if (cur->qm_options != NULL)
				DKIM_FREE(dkim, cur->qm_options);
			DKIM_FREE(dkim, cur);
			cur = next;
		}
	}

	if (dkim->dkim_xtags != NULL)
	{
		struct dkim_xtag *cur;
		struct dkim_xtag *next;

		cur = dkim->dkim_xtags;
		while (cur != NULL)
		{
			next = cur->xt_next;
			CLOBBER(cur->xt_tag);
			CLOBBER(cur->xt_value);
			free(cur);
			cur = next;
		}
	}

	if (dkim->dkim_hdrre != NULL)
	{
		regfree(dkim->dkim_hdrre);
		free(dkim->dkim_hdrre);
	}

	/* destroy canonicalizations */
	dkim_canon_cleanup(dkim);

	CLOBBER(dkim->dkim_b64sig);
	CLOBBER(dkim->dkim_selector);
	CLOBBER(dkim->dkim_domain);
	CLOBBER(dkim->dkim_user);
	CLOBBER(dkim->dkim_key);
	CLOBBER(dkim->dkim_sender);
	CLOBBER(dkim->dkim_signer);
	CLOBBER(dkim->dkim_error);
	CLOBBER(dkim->dkim_zdecode);
//	CLOBBER(dkim->dkim_hdrlist);

	DSTRING_CLOBBER(dkim->dkim_hdrbuf);
	DSTRING_CLOBBER(dkim->dkim_canonbuf);
	DSTRING_CLOBBER(dkim->dkim_sslerrbuf);

	if (dkim->dkim_libhandle->dkiml_dkim == dkim)
		dkim->dkim_libhandle->dkiml_dkim = NULL;

	dkim_mfree(dkim->dkim_libhandle, dkim->dkim_closure, dkim);

	return DKIM_STAT_OK;
}

/*
**  DKIM_SIGN -- allocate a handle for use in a signature operation
**
**  Parameters:
**  	libhandle -- DKIM_LIB handle
**  	id -- identification string (e.g. job ID) for logging
**  	memclosure -- memory closure for allocations (or NULL)
**  	X	secretkey -- secret key (PEM format)
**  	X	selector -- selector to be used when generating the signature header
**  	X	domain -- domain for which this message is being signed
**  	hdrcanonalg -- canonicalization algorithm to use for headers
**  	bodycanonalg -- canonicalization algorithm to use for body
**  	signalg -- signing algorithm to use
**  	length -- how many bytes of the body to sign (-1 for all)
**  	statp -- status (returned)
**
**  Return value:
**  	A new signing handle, or NULL.
*/

DKIM *
dkim_sign(DKIM_LIB *libhandle, const char *id, void *memclosure,
		/*
			Change for version 3.9: these parameters delayed:
			const dkim_sigkey_t secretkey, const char *selector,
			const char *domain,
		*/
			dkim_canon_t hdrcanonalg,
			dkim_canon_t bodycanonalg, dkim_alg_t signalg,
			ssize_t length, DKIM_STAT *statp)
{
	DKIM *new;

	assert(libhandle != NULL);
	/*
	assert(secretkey != NULL);
	assert(selector != NULL);
	assert(domain != NULL);
	*/
	assert(hdrcanonalg == DKIM_CANON_SIMPLE ||
	       hdrcanonalg == DKIM_CANON_RELAXED);
	assert(bodycanonalg == DKIM_CANON_SIMPLE ||
	       bodycanonalg == DKIM_CANON_RELAXED);
	assert(signalg == DKIM_SIGN_DEFAULT ||
	       signalg == DKIM_SIGN_RSASHA1 ||
               signalg == DKIM_SIGN_RSASHA256 ||
               signalg == DKIM_SIGN_ED25519SHA256);
	assert(statp != NULL);

	new = dkim_new(libhandle, id, memclosure, hdrcanonalg, bodycanonalg,
	               signalg, statp);

	if (new != NULL)
	{
		new->dkim_mode = DKIM_MODE_SIGN;

		/*
		new->dkim_keylen = strlen(secretkey);
		new->dkim_key = dkim_strdup(new, secretkey, 0);

		if (new->dkim_key == NULL)
		{
			*statp = DKIM_STAT_NORESOURCE;
			dkim_free(new);
			return NULL;
		}

		new->dkim_selector = dkim_strdup(new, selector, 0);
		new->dkim_domain = dkim_strdup(new, domain, 0);
		*/
		if (length == (ssize_t) -1)
			new->dkim_signlen = ULONG_MAX;
		else
			new->dkim_signlen = length;

	}

	return new;
}



/*
**  DKIM_SIGNKEYS -- fill a handle for use in a signature operation
**
**  Parameters:
**  	dkim
**  	secretkey -- secret key (PEM format)
**  	selector -- selector to be used when generating the signature header
**  	domain -- domain for which this message is being signed
**
*/

DKIM_STAT
dkim_signkeys(DKIM *dkim,
			const dkim_sigkey_t secretkey, const char *selector,
			const char *domain)
{
	assert(secretkey != NULL);
	assert(selector != NULL);
	assert(domain != NULL);

	if (dkim != NULL && dkim->dkim_mode == DKIM_MODE_SIGN)
	{
		dkim->dkim_keylen = strlen(secretkey);
		dkim->dkim_key = dkim_strdup(dkim, secretkey, 0);

		if (dkim->dkim_key == NULL)
		{
			return DKIM_STAT_NORESOURCE;
		}

		dkim->dkim_selector = dkim_strdup(dkim, selector, 0);
		dkim->dkim_domain = dkim_strdup(dkim, domain, 0);

		/* read key now to determine hashtype */
		if (dkim->dkim_signalg == DKIM_SIGN_DEFAULT)
		{
			DKIM_STAT status = dkim_privkey_load(dkim);
			if (status != DKIM_STAT_OK)
			{
				return status;
			}
		}
	}

	return DKIM_STAT_OK;
}


/*
**  DKIM_VERIFY -- allocate a handle for use in a verify operation
**
**  Parameters:
**  	libhandle -- DKIM_LIB handle
**  	id -- identification string (e.g. job ID) for logging
**  	memclosure -- memory closure for allocations (or NULL)
**  	statp -- status (returned)
**
**  Return value:
**  	A new signing handle, or NULL.
*/

DKIM *
dkim_verify(DKIM_LIB *libhandle, const char *id, void *memclosure,
            DKIM_STAT *statp)
{
	DKIM *new;

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

	new = dkim_new(libhandle, id, memclosure, DKIM_CANON_UNKNOWN,
	               DKIM_CANON_UNKNOWN, DKIM_SIGN_UNKNOWN, statp);

	if (new != NULL)
		new->dkim_mode = DKIM_MODE_VERIFY;

	return new;
}

/*
**  DKIM_RESIGN -- bind a new signing handle to a completed handle
**
**  Parameters:
**  	new -- new signing handle
**  	old -- old signing/verifying handle
**  	hdrbind -- bind "new"'s header canonicalization to "old" as well
**  	           as the body
**
**  Return value:
**  	DKIM_STAT_OK -- success
**  	DKIM_STAT_INVALID -- invalid state of one or both handles
**
**  Side effects:
**  	Sets up flags such that the two are bound; dkim_free() on "old"
**  	is now an invalid operation until "new" has been free'd.
*/

DKIM_STAT
dkim_resign(DKIM *new, DKIM *old, _Bool hdrbind)
{
#ifdef _FFR_RESIGN
	_Bool keep;
	_Bool tmp;
	DKIM_STAT status;
	int hashtype = DKIM_HASHTYPE_UNKNOWN;
	DKIM_CANON *bc;
	DKIM_CANON *hc;
	DKIM_LIB *lib;

	assert(new != NULL);
	assert(old != NULL);

	if (new->dkim_mode != DKIM_MODE_SIGN ||
	    new->dkim_state != DKIM_STATE_INIT)
		return DKIM_STAT_INVALID;

	if (old->dkim_state >= DKIM_STATE_EOH1 ||
	    old->dkim_resign != NULL)
		return DKIM_STAT_INVALID;

	new->dkim_resign = old;
	new->dkim_hdrbind = hdrbind;
	/* XXX -- should be mutex-protected? */
	old->dkim_refcnt++;

	if (new->dkim_hdrbind)
	{
		new->dkim_hhead = old->dkim_hhead;
		new->dkim_hdrcnt = old->dkim_hdrcnt;
	}

	lib = old->dkim_libhandle;
	assert(lib != NULL);

	tmp = ((lib->dkiml_flags & DKIM_LIBFLAGS_TMPFILES) != 0);
	keep = ((lib->dkiml_flags & DKIM_LIBFLAGS_KEEPFILES) != 0);

	new->dkim_version = lib->dkiml_version;

	/* determine hash type */
	switch (new->dkim_signalg)
	{
	  case DKIM_SIGN_RSASHA1:
		hashtype = DKIM_HASHTYPE_SHA1;
		break;

	  case DKIM_SIGN_RSASHA256:
		hashtype = DKIM_HASHTYPE_SHA256;
		break;

	  default:
		assert(0);
		/* NOTREACHED */
	}

	/* initialize signature and canonicalization for signing */
	new->dkim_siglist = DKIM_MALLOC(new, sizeof(DKIM_SIGINFO *));
	if (new->dkim_siglist == NULL)
	{
		dkim_error(new, "failed to allocate %d byte(s)",
		           sizeof(DKIM_SIGINFO *));
		return DKIM_STAT_NORESOURCE;
	}

	new->dkim_siglist[0] = DKIM_MALLOC(new, sizeof(struct dkim_siginfo));
	if (new->dkim_siglist[0] == NULL)
	{
		dkim_error(new, "failed to allocate %d byte(s)",
		           sizeof(struct dkim_siginfo));
		return DKIM_STAT_NORESOURCE;
	}

	new->dkim_sigcount = 1;
	memset(new->dkim_siglist[0], '\0', sizeof(struct dkim_siginfo));
	new->dkim_siglist[0]->sig_domain = new->dkim_domain;
	new->dkim_siglist[0]->sig_selector = new->dkim_selector;
	new->dkim_siglist[0]->sig_hashtype = hashtype;
	new->dkim_siglist[0]->sig_signalg = new->dkim_signalg;

	status = dkim_add_canon(new, TRUE, new->dkim_hdrcanonalg, hashtype,
	                        NULL, NULL, 0, &hc);
	if (status != DKIM_STAT_OK)
		return status;

	status = dkim_add_canon(old, FALSE, new->dkim_bodycanonalg,
	                        hashtype, NULL, NULL, new->dkim_signlen, &bc);
	if (status != DKIM_STAT_OK)
		return status;

	new->dkim_siglist[0]->sig_hdrcanon = hc;
	new->dkim_siglist[0]->sig_hdrcanonalg = new->dkim_hdrcanonalg;
	new->dkim_siglist[0]->sig_bodycanon = bc;
	new->dkim_siglist[0]->sig_bodycanonalg = new->dkim_bodycanonalg;

	if (new->dkim_libhandle->dkiml_fixedtime != 0)
	{
		new->dkim_siglist[0]->sig_timestamp = new->dkim_libhandle->dkiml_fixedtime;
	}
	else
	{
		time_t now;

		(void) time(&now);

		new->dkim_siglist[0]->sig_timestamp = (uint64_t) now;
	}

	if (new->dkim_hdrbind)
	{
		_Bool keep;

		keep = ((lib->dkiml_flags & DKIM_LIBFLAGS_KEEPFILES) != 0);

		/* initialize all canonicalizations */
		status = dkim_canon_init(new, tmp, keep);
		if (status != DKIM_STAT_OK)
			return status;

		/* run the headers */
		status = dkim_canon_runheaders(new);
		if (status != DKIM_STAT_OK)
			return status;
	}

	return DKIM_STAT_OK;
#else /* _FFR_RESIGN */
	return DKIM_STAT_NOTIMPLEMENT;
	(void)new;
	(void)old;
	(void)hdrbind;
#endif /* _FFR_RESIGN */
}

/*
**  DKIM_SIG_PROCESS -- process a signature
**
**  Parameters:
**  	dkim -- DKIM handle
**  	sig -- DKIM_SIGINFO handle
**
**  Return value:
**  	A DKIM_STAT_* constant.
**  	DKIM_STAT_
*/

DKIM_STAT
dkim_sig_process(DKIM *dkim, DKIM_SIGINFO *sig)
{
	DKIM_STAT status, ret_status;
	int vstat;
	size_t diglen = 0;
	char *digest = NULL;
	struct dkim_crypto *crypto;

	assert(dkim != NULL);
	assert(sig != NULL);
	assert(dkim->dkim_libhandle != NULL);

	DKIM_LIB *dkim_libhandle = dkim->dkim_libhandle;

	/* skip it if we're supposed to ignore it */
	if ((sig->sig_flags & DKIM_SIGFLAG_IGNORE) != 0)
		return DKIM_STAT_OK;

	/* skip it if there was a syntax or other error */
	if (sig->sig_error != DKIM_SIGERROR_UNKNOWN)
		return DKIM_STAT_OK;

	/*
	* To process means retrieve the public key, get the header digest,
	* and verify that the signature is valid.
	*/
	if ((sig->sig_flags & DKIM_SIGFLAG_PROCESSED) == 0)
	{
		/* get the digest */
		status = dkim_canon_getfinal(sig->sig_hdrcanon, &digest,
		                             &diglen);
		if (status != DKIM_STAT_OK)
		{
			dkim_error(dkim, "dkim_canon_getfinal() failed");
			return DKIM_STAT_INTERNAL;
		}
		assert(digest != NULL && diglen != 0);

		/* Start the DNS call.  Call back the retrieved key. */
		ret_status = DKIM_STAT_OK;
		status = dkim_get_key(dkim, sig, FALSE);
		if (status == DKIM_STAT_NOKEY)
		{
			sig->sig_flags |= DKIM_SIGFLAG_PROCESSED;
			sig->sig_error = DKIM_SIGERROR_NOKEY;
		}
		else if (status == DKIM_STAT_KEYFAIL)
		{
			sig->sig_flags |= DKIM_SIGFLAG_PROCESSED;
			sig->sig_error = DKIM_SIGERROR_KEYFAIL;
		}
		else if (status == DKIM_STAT_CANTVRFY ||
		         status == DKIM_STAT_SYNTAX)
		{
			sig->sig_flags |= DKIM_SIGFLAG_PROCESSED;
			if (sig->sig_error == DKIM_SIGERROR_UNKNOWN)
				sig->sig_error = DKIM_SIGERROR_DNSSYNTAX;
		}
		else if (status == DKIM_STAT_MULTIDNSREPLY)
		{
			sig->sig_flags |= DKIM_SIGFLAG_PROCESSED;
			sig->sig_error = DKIM_SIGERROR_MULTIREPLY;
		}
		else if (status == DKIM_STAT_REVOKED)
		{
			sig->sig_flags |= DKIM_SIGFLAG_PROCESSED;
			sig->sig_error = DKIM_SIGERROR_KEYREVOKED;
		}
		else ret_status = status;

		if (status != DKIM_STAT_OK)
		{
			if (dkim_libhandle->dkiml_key_retrieved != NULL)
				(*dkim_libhandle->dkiml_key_retrieved)(dkim, status, sig);
			return ret_status;
		}

#ifdef USE_GNUTLS
		gnutls_datum_t key;
		key.data = u_char_ptr(sig->sig_key);
		key.size = sig->sig_keylen;
#else /* USE_GNUTLS */
		/* load the public key */
		BIO *key = BIO_new_mem_buf(sig->sig_key, sig->sig_keylen);
		if (key == NULL)
		{
			dkim_error(dkim, "BIO_new_mem_buf() failed");
			if (dkim_libhandle->dkiml_key_retrieved != NULL)
				(*dkim_libhandle->dkiml_key_retrieved)
					(dkim, DKIM_STAT_NORESOURCE, sig);
			return DKIM_STAT_NORESOURCE;
		}
#endif /* USE_GNUTLS */

		/* set up to verify */
		if (sig->sig_signature == NULL)
		{
			crypto = DKIM_MALLOC(dkim, sizeof(struct dkim_crypto));
			if (crypto == NULL)
			{
#ifndef USE_GNUTLS
				BIO_free(key);
#endif /* ! USE_GNUTLS */
				if (dkim_libhandle->dkiml_key_retrieved != NULL)
					(*dkim_libhandle->dkiml_key_retrieved)
						(dkim, DKIM_STAT_NORESOURCE, sig);
				return DKIM_STAT_NORESOURCE;
			}

			sig->sig_signature = crypto;
		}
		else
		{
			crypto = sig->sig_signature;
		}
		memset(crypto, '\0', sizeof(struct dkim_crypto));

#ifdef USE_GNUTLS
		crypto->crypto_sig.data = u_char_ptr(sig->sig_sig);
		crypto->crypto_sig.size = sig->sig_siglen;

		crypto->crypto_digest.data = u_char_ptr(digest);
		crypto->crypto_digest.size = diglen;

		status = gnutls_pubkey_init(&crypto->crypto_pubkey);
		if (status != GNUTLS_E_SUCCESS)
		{
			dkim_sig_load_ssl_errors(dkim, sig, status);
			dkim_error(dkim,
			           "s=%s d=%s: gnutls_pubkey_init() failed",
			           dkim_sig_getselector(sig),
			           dkim_sig_getdomain(sig));

			sig->sig_error = DKIM_SIGERROR_KEYDECODE;
			if (dkim_libhandle->dkiml_key_retrieved != NULL)
				(*dkim_libhandle->dkiml_key_retrieved)
					(dkim, DKIM_STAT_NORESOURCE, sig);
			return DKIM_STAT_OK;
		}

		status = gnutls_pubkey_import(crypto->crypto_pubkey, &key,
		                              GNUTLS_X509_FMT_DER);
		if (status != GNUTLS_E_SUCCESS)
		{
			dkim_sig_load_ssl_errors(dkim, sig, status);
			dkim_error(dkim,
			           "s=%s d=%s: gnutls_pubkey_import(): %s",
			           dkim_sig_getselector(sig),
			           dkim_sig_getdomain(sig),
			           sig->sig_sslerrbuf? dkim_dstring_get(sig->sig_sslerrbuf): "failed");
			sig->sig_error = DKIM_SIGERROR_KEYDECODE;
			if (dkim_libhandle->dkiml_key_retrieved != NULL)
				(*dkim_libhandle->dkiml_key_retrieved)
					(dkim, DKIM_STAT_BADKEY, sig);
			return DKIM_STAT_OK;
		}

#if GNUTLS_VERSION_NUMBER < 0x030100
		vstat = gnutls_pubkey_verify_hash(crypto->crypto_pubkey, 0,
		                                    &crypto->crypto_digest,
		                                    &crypto->crypto_sig);
#else /* GNUTLS_VERSION_NUMBER < 0x030100 */
		gnutls_sign_algorithm_t alg;
		int (*vrfyfn)(gnutls_pubkey_t, gnutls_sign_algorithm_t,
			unsigned int, const gnutls_datum_t *, const gnutls_datum_t *);
		switch (sig->sig_signalg)
		{
			case DKIM_SIGN_RSASHA1:
				alg = GNUTLS_SIGN_RSA_SHA1;
				vrfyfn = &gnutls_pubkey_verify_hash2;
				break;

			case DKIM_SIGN_RSASHA256:
				alg = GNUTLS_SIGN_RSA_SHA256;
				vrfyfn = &gnutls_pubkey_verify_hash2;
				break;

			case DKIM_SIGN_ED25519SHA256:
				alg = GNUTLS_SIGN_EDDSA_ED25519;
				vrfyfn = &gnutls_pubkey_verify_data2;
				break;

			default:
				assert(0);
				break;
		}

		vstat = (*vrfyfn)(crypto->crypto_pubkey, alg, 0,
			&crypto->crypto_digest, &crypto->crypto_sig);
#endif /* GNUTLS_VERSION_NUMBER < 0x030100 */
		if (vstat < 0)
		{
			dkim_sig_load_ssl_errors(dkim, sig, vstat);
			vstat = 0;
		}
		else
			vstat = 1;  // success, same as RSA_verify()

		(void) gnutls_pubkey_get_pk_algorithm(crypto->crypto_pubkey,
		                                      &crypto->crypto_keysize);

		sig->sig_keybits = crypto->crypto_keysize;
#else /* USE_GNUTLS */

		crypto->crypto_pkey = d2i_PUBKEY_bio(key, NULL);
		if (crypto->crypto_pkey == NULL)
		{
			dkim_sig_load_ssl_errors(dkim, sig, 0);
			dkim_error(dkim,
			           "s=%s d=%s: EVP_PKEY construction failed",
			           dkim_sig_getselector(sig),
			           dkim_sig_getdomain(sig));

			BIO_free(key);
			sig->sig_error = DKIM_SIGERROR_KEYDECODE;
			if (dkim_libhandle->dkiml_key_retrieved != NULL)
				(*dkim_libhandle->dkiml_key_retrieved)
					(dkim, DKIM_STAT_NORESOURCE, sig);
			return DKIM_STAT_OK;
		}

		/* set up the key object */
#if OPENSSL_VERSION_NUMBER >= 0x30000000L

		if (sig->sig_signalg == DKIM_SIGN_ED25519SHA256)
		{
			EVP_MD_CTX *md_ctx = EVP_MD_CTX_new();
			if (md_ctx == NULL)
			{
				dkim_load_ssl_errors(dkim, 0);
				dkim_error(dkim,
						   "failed to initialize digest context");

				BIO_CLOBBER(key);

				sig->sig_error = DKIM_SIGERROR_KEYDECODE;
				if (dkim_libhandle->dkiml_key_retrieved != NULL)
					(*dkim_libhandle->dkiml_key_retrieved)
						(dkim, DKIM_STAT_NORESOURCE, sig);
				return DKIM_STAT_OK;
			}

			status = EVP_DigestVerifyInit(md_ctx, NULL, NULL, NULL,
													 crypto->crypto_pkey);
			if (status != 1)
			{
				dkim_load_ssl_errors(dkim, 0);
				dkim_error(dkim,
						   "failed to initialize digest context");

				BIO_CLOBBER(key);
				EVP_MD_CTX_free(md_ctx);

				sig->sig_error = DKIM_SIGERROR_KEYDECODE;
				if (dkim_libhandle->dkiml_key_retrieved != NULL)
					(*dkim_libhandle->dkiml_key_retrieved)
						(dkim, DKIM_STAT_NORESOURCE, sig);
				return DKIM_STAT_OK;
			}

			vstat = EVP_DigestVerify(md_ctx,
				u_char_ptr(sig->sig_sig), sig->sig_siglen,
				u_char_ptr(digest), diglen);

			EVP_MD_CTX_free(md_ctx);
		}
		else
		{
			EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(crypto->crypto_pkey, NULL);
			if (ctx == NULL)
			{
				dkim_load_ssl_errors(dkim, 0);
				dkim_error(dkim,
						   "failed to initialize signing context");

				BIO_CLOBBER(key);
				return DKIM_STAT_INTERNAL;
			}

			status = EVP_PKEY_verify_init(ctx);
			if (status > 0)
				status = EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING);
			if (status > 0)
				status = EVP_PKEY_CTX_set_signature_md(ctx,
					sig->sig_signalg == DKIM_SIGN_RSASHA1?
						EVP_sha1(): EVP_sha256());
			if (status > 0)
				vstat = EVP_PKEY_verify(ctx,
					u_char_ptr(sig->sig_sig), sig->sig_siglen,
					u_char_ptr(digest), diglen);
			EVP_PKEY_CTX_free(ctx);
		}

		crypto->crypto_keysize = EVP_PKEY_get_bits(crypto->crypto_pkey);
		dkim_sig_load_ssl_errors(dkim, sig, 0);
		sig->sig_keybits = crypto->crypto_keysize;

		BIO_CLOBBER(key);
		EVP_PKEY_free(crypto->crypto_pkey);
		crypto->crypto_pkey = NULL;

#else /* OPENSSL_VERSION_NUMBER >= 0x30000000L */
# ifdef HAVE_ED25519
		if (sig->sig_signalg == DKIM_SIGN_ED25519SHA256)
		{
			EVP_MD_CTX *md_ctx;

			if (EVP_PKEY_id(crypto->crypto_pkey) != EVP_PKEY_ED25519)
			{
				dkim_error(dkim,
				           "s=%s d=%s: not an ED25519 key",
				           dkim_sig_getselector(sig),
				           dkim_sig_getdomain(sig));

				BIO_CLOBBER(key);

				sig->sig_error = DKIM_SIGERROR_KEYDECODE;
				if (dkim_libhandle->dkiml_key_retrieved != NULL)
					(*dkim_libhandle->dkiml_key_retrieved)
						(dkim, DKIM_STAT_BADKEY, sig);
				return DKIM_STAT_OK;
			}

			crypto->crypto_in = sig->sig_sig;
			crypto->crypto_inlen = sig->sig_siglen;

			md_ctx = EVP_MD_CTX_new();
			if (md_ctx == NULL)
			{
				dkim_load_ssl_errors(dkim, 0);
				dkim_error(dkim,
				           "failed to initialize digest context");

				BIO_CLOBBER(key);

				sig->sig_error = DKIM_SIGERROR_KEYDECODE;
				if (dkim_libhandle->dkiml_key_retrieved != NULL)
					(*dkim_libhandle->dkiml_key_retrieved)
						(dkim, DKIM_STAT_NORESOURCE, sig);
				return DKIM_STAT_OK;
			}

			status = EVP_DigestVerifyInit(md_ctx, NULL, NULL, NULL,
                                                     crypto->crypto_pkey);
			if (status != 1)
			{
				dkim_load_ssl_errors(dkim, 0);
				dkim_error(dkim,
				           "failed to initialize digest context");

				BIO_CLOBBER(key);
				EVP_MD_CTX_free(md_ctx);

				sig->sig_error = DKIM_SIGERROR_KEYDECODE;
				if (dkim_libhandle->dkiml_key_retrieved != NULL)
					(*dkim_libhandle->dkiml_key_retrieved)
						(dkim, DKIM_STAT_NORESOURCE, sig);
				return DKIM_STAT_OK;
			}

			vstat = EVP_DigestVerify(md_ctx,
				u_char_ptr(crypto->crypto_in), crypto->crypto_inlen,
				u_char_ptr(digest), diglen);

			EVP_MD_CTX_free(md_ctx);

			crypto->crypto_keysize = 256;
		}
		else
# endif /* HAVE_ED25519 */
		{
			crypto->crypto_key = EVP_PKEY_get1_RSA(crypto->crypto_pkey);
			if (crypto->crypto_key == NULL)
			{
				dkim_sig_load_ssl_errors(dkim, sig, 0);
				dkim_error(dkim,
						   "s=%s d=%s: EVP_PKEY_get1_RSA() failed",
						   dkim_sig_getselector(sig),
						   dkim_sig_getdomain(sig));

				BIO_free(key);

				sig->sig_error = DKIM_SIGERROR_KEYDECODE;
				if (dkim_libhandle->dkiml_key_retrieved != NULL)
					(*dkim_libhandle->dkiml_key_retrieved)
						(dkim, DKIM_STAT_BADKEY, sig);
				return DKIM_STAT_OK;
			}

			crypto->crypto_keysize = RSA_bits(crypto->crypto_key);
			crypto->crypto_pad = RSA_PKCS1_PADDING;

			crypto->crypto_in = sig->sig_sig;
			crypto->crypto_inlen = sig->sig_siglen;

			int nid = NID_sha1;

			if (dkim_libfeature(dkim_libhandle,
								DKIM_FEATURE_SHA256) &&
				sig->sig_hashtype == DKIM_HASHTYPE_SHA256)
				nid = NID_sha256;

			vstat = RSA_verify(nid,
				u_char_ptr(digest), diglen,
				u_char_ptr(crypto->crypto_in), crypto->crypto_inlen,
				crypto->crypto_key);
		}

		dkim_sig_load_ssl_errors(dkim, sig, 0);
		sig->sig_keybits = crypto->crypto_keysize;

		BIO_CLOBBER(key);
		EVP_PKEY_free(crypto->crypto_pkey);
		crypto->crypto_pkey = NULL;
		if (crypto->crypto_key != NULL)
		{
			RSA_free(crypto->crypto_key);
			crypto->crypto_key = NULL;
		}
#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */
#endif /* USE_GNUTLS */

		if (dkim_libhandle->dkiml_key_retrieved != NULL &&
			// only call if key actually loaded
			(sig->sig_flags & DKIM_SIGFLAG_KEYLOADED) != 0)
				(*dkim_libhandle->dkiml_key_retrieved)
					(dkim, DKIM_STAT_OK, sig);

		if (vstat == 1)
			sig->sig_flags |= DKIM_SIGFLAG_PASSED;
		else
			sig->sig_error = DKIM_SIGERROR_BADSIG;

		sig->sig_flags |= DKIM_SIGFLAG_PROCESSED;
	}

	/* do the body hash check if possible */
	if (dkim->dkim_bodydone && sig->sig_bh == DKIM_SIGBH_UNTESTED)
	// -ale: check body even if not passed, for better diagnostics.
	// was: && (sig->sig_flags & DKIM_SIGFLAG_PASSED) != 0)
	{
		char *bhash;
		char b64buf[BUFRSZ];

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

		dkim_canon_getfinal(sig->sig_bodycanon, &digest, &diglen);

		bhash = dkim_param_get(sig->sig_taglist, "bh");

		dkim_base64_encode(digest, diglen, b64buf, sizeof b64buf);

		if (strcmp(bhash, b64buf) == 0)
		{
			sig->sig_bh = DKIM_SIGBH_MATCH;
		}
		else
		{
			if (sig->sig_error == DKIM_SIGERROR_UNKNOWN)
				sig->sig_error = DKIM_SIGERROR_BODYHASH; // -ale:
			sig->sig_bh = DKIM_SIGBH_MISMATCH;
		}
	}

	/*
	**  Fail if t=s was present in the key and the i= and d= domains
	**  don't match.
	*/

	if ((sig->sig_flags & DKIM_SIGFLAG_NOSUBDOMAIN) != 0)
	{
		char *d;
		char *i;

		d = dkim_param_get(sig->sig_taglist, "d");
		i = dkim_param_get(sig->sig_taglist, "i");

		if (i != NULL && d != NULL)
		{
			char *at;

			at = strchr(i, '@');
			if (at == NULL)
				at = i;
			else
				at++;

			if (strcasecmp(at, d) != 0)
				sig->sig_error = DKIM_SIGERROR_SUBDOMAIN;
		}
	}

	/*
	**  Fail if the "must be signed" list was set and this signature didn't
	**  cover a must-be-signed header which was present.
	*/

	if (dkim_libhandle->dkiml_mbs != NULL)
	{
		int c;

		for (c = 0; dkim_libhandle->dkiml_mbs[c] != NULL; c++)
		{
			if (dkim_get_header(dkim,
			                    dkim_libhandle->dkiml_mbs[c],
			                    0, 0) != NULL &&
			    !dkim_sig_hdrsigned(sig,
			                        dkim_libhandle->dkiml_mbs[c]))
			{
				sig->sig_error = DKIM_SIGERROR_MBSFAILED;
				break;
			}
		}
	}

	if (sig->sig_error == DKIM_SIGERROR_UNKNOWN &&
	    sig->sig_bh != DKIM_SIGBH_UNTESTED)
		sig->sig_error = DKIM_SIGERROR_OK;

	return DKIM_STAT_OK;
}

/*
**  DKIM_OHDRS -- extract and decode original headers
**
**  Parameters:
**  	dkim -- DKIM handle
**  	sig -- DKIM_SIGINFO handle
**  	ptrs -- user-provided array of pointers to header strings (updated)
**  	pcnt -- number of pointers available (updated)
**
**  Return value:
**  	A DKIM_STAT_* constant.
**
**  Notes:
**  	If the returned value of pcnt is greater that what it was originally,
**  	then there were more headers than there were pointers.
*/

DKIM_STAT
dkim_ohdrs(DKIM *dkim, DKIM_SIGINFO *sig, char **ptrs, int *pcnt)
{
	int n = 0;
	char *z;
	char *ch;
	char *p;
	char *q;
	char *last;

	assert(dkim != NULL);
	assert(ptrs != NULL);
	assert(pcnt != NULL);

	if (dkim->dkim_mode != DKIM_MODE_VERIFY)
		return DKIM_STAT_INVALID;

	/* pick the one we're going to use */
	if (sig == NULL)
	{
		unsigned int c;

		for (c = 0; c < dkim->dkim_sigcount; c++)
		{
			sig = dkim->dkim_siglist[c];
			if ((sig->sig_flags & DKIM_SIGFLAG_PROCESSED) != 0 &&
			    (sig->sig_flags & DKIM_SIGFLAG_IGNORE) == 0)
				break;

			sig = NULL;
		}
	}

	/* none useable; return error */
	if (sig == NULL)
		return DKIM_STAT_INVALID;

	/* find the tag */
	z = dkim_param_get(sig->sig_taglist, "z");
	if (z == NULL || *z == '\0')
	{
		*pcnt = 0;
		return DKIM_STAT_OK;
	}

	/* get memory for the decode */
	if (dkim->dkim_zdecode == NULL)
	{
		dkim->dkim_zdecode = DKIM_MALLOC(dkim, MAXHEADERS);
		if (dkim->dkim_zdecode == NULL)
		{
			return DKIM_STAT_NORESOURCE;
		}
	}

	/* copy it */
	strlcpy(dkim->dkim_zdecode, z, strlen(z));

	/* decode */
	for (ch = strtok_r(z, "|", &last);
	     ch != NULL;
	     ch = strtok_r(NULL, "|", &last))
	{
		for (p = ch, q = ch; *p != '\0'; p++)
		{
			if (*p == '=')
			{
				char c;

				int ch_1 = *(unsigned char*)(p + 1),
					ch_2 = ch_1? *(unsigned char*)(p + 2): 0;
				if (!isxdigit(ch_1) || !isxdigit(ch_2))
				{
					dkim_error(dkim,
					           "invalid trailing character (0x%02x 0x%02x) in z= tag value",
					           ch_1, ch_2);

					return DKIM_STAT_INVALID;
				}

				c = 16 * dkim_hexchar(ch_1) + dkim_hexchar(ch_2);

				p += 2;

				*q = c;
				q++;
			}
			else
			{
				if (q != p)
					*q = *p;
				q++;
			}
		}

		*q = '\0';

		if (n < *pcnt)
			ptrs[n] = ch;
		n++;
	}

	*pcnt = n;

	return DKIM_STAT_OK;
}

/*
**  DKIM_DIFFHEADERS -- compare original headers with received headers
**
**  Parameters:
**  	dkim -- DKIM handle
**  	canon -- header canonicalization mode in use
**  	maxcost -- maximum "cost" of changes to be reported
**  	ohdrs -- original headers, presumably extracted from a "z" tag
**  	nohdrs -- number of headers at "ohdrs" available
**  	out -- pointer to an array of struct dkim_hdrdiff objects (updated)
** 	nout -- counter of handles returned (updated)
**
**  Return value:
**  	A DKIM_STAT_* constant.
**
**  Side effects:
**  	A series of DKIM_HDRDIFF handles is allocated and must later be
**  	destroyed.
*/

DKIM_STAT
dkim_diffheaders(DKIM *dkim, dkim_canon_t canon, int maxcost,
                 char **ohdrs, int nohdrs,
                 struct dkim_hdrdiff **out, int *nout)
{
#ifdef _FFR_DIFFHEADERS
	int n = 0;
	int a = 0;
	int c;
	int status;
	u_char *p;
	u_char *q;
	u_char *end;
	void *cls;
	struct dkim_header *hdr;
	struct dkim_hdrdiff *diffs = NULL;
	struct dkim_dstring *tmphdr;
	struct dkim_dstring **cohdrs;
	DKIM_LIB *lib;
	regaparams_t params;
	regamatch_t matches;
	regex_t re;
	u_char restr[BUFRSZ + 1];

	assert(dkim != NULL);
	assert(out != NULL);
	assert(nout != NULL);

	if (dkim->dkim_mode != DKIM_MODE_VERIFY)
		return DKIM_STAT_INVALID;
	if (maxcost == 0)
		return DKIM_STAT_INVALID;

	tmphdr = dkim_dstring_new(dkim, BUFRSZ, MAXBUFRSZ);
	if (tmphdr == NULL)
	{
		dkim_error(dkim, "failed to allocate dynamic string");
		return DKIM_STAT_NORESOURCE;
	}

	lib = dkim->dkim_libhandle;
	cls = dkim->dkim_closure;

	memset(&params, '\0', sizeof params);

	params.cost_ins = COST_INSERT;
	params.cost_del = COST_DELETE;
	params.cost_subst = COST_SUBST;

	params.max_cost = maxcost;
	params.max_ins = DKIM_MAXHEADER;
	params.max_del = DKIM_MAXHEADER;
	params.max_subst = DKIM_MAXHEADER;
	params.max_err = maxcost;

	matches.nmatch = 0;
	matches.pmatch = NULL;

	/* canonicalize all the original header fields */
	cohdrs = DKIM_MALLOC(dkim, sizeof(struct dkim_dstring *) * nohdrs);
	if (cohdrs == NULL)
	{
		dkim_error(dkim, strerror(errno));
		return DKIM_STAT_NORESOURCE;
	}

	for (c = 0; c < nohdrs; c++)
	{
		cohdrs[c] = dkim_dstring_new(dkim, DKIM_MAXHEADER, 0);
		if (cohdrs[c] == NULL)
		{
			for (n = 0; n < c; n++)
				dkim_dstring_free(cohdrs[n]);

			DKIM_FREE(dkim, cohdrs);

			dkim_error(dkim, strerror(errno));

			return DKIM_STAT_NORESOURCE;
		}

		status = dkim_canon_header_string(cohdrs[c], canon,
		                                  ohdrs[c], strlen(ohdrs[c]),
		                                  FALSE);
		if (status != DKIM_STAT_OK)
		{
			for (n = 0; n < c; n++)
				dkim_dstring_free(cohdrs[n]);

			DKIM_FREE(dkim, cohdrs);

			dkim_error(dkim, strerror(errno));

			return status;
		}
	}

	for (hdr = dkim->dkim_hhead; hdr != NULL; hdr = hdr->hdr_next)
	{
		dkim_dstring_blank(tmphdr);

		status = dkim_canon_header_string(tmphdr, canon,
		                                  hdr->hdr_text,
		                                  hdr->hdr_textlen, FALSE);
		if (status != DKIM_STAT_OK)
		{
			dkim_dstring_free(tmphdr);
			for (c = 0; c < nohdrs; c++)
				dkim_dstring_free(cohdrs[c]);
			DKIM_FREE(dkim, cohdrs);
			return status;
		}

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

		end = restr + sizeof restr;

		for (p = dkim_dstring_get(tmphdr), q = restr;
		     *p != '\0' && q < end - 3;
		     p++)
		{
			if (q == restr)
				*q++ = '^';

			if (*p == '*' ||
			    *p == '\\' ||
			    *p == '$' ||
			    *p == '+' ||
			    *p == '[' ||
			    *p == ']' ||
			    *p == '(' ||
			    *p == ')' ||
			    *p == '.' ||
			    *p == '|')
				*q++ = '\\';

			*q++ = *p;
		}

		*q = '$';

		status = tre_regcomp(&re, restr, REG_NOSUB);
		if (status != 0)
		{
			char err[BUFRSZ + 1];

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

			(void) tre_regerror(status, &re, err, sizeof err);

			dkim_error(dkim, err);

			if (diffs != NULL)
				dkim_mfree(lib, cls, diffs);

			dkim_dstring_free(tmphdr);
			for (c = 0; c < nohdrs; c++)
				dkim_dstring_free(cohdrs[c]);
			DKIM_FREE(dkim, cohdrs);

			return DKIM_STAT_INTERNAL;
		}

		for (c = 0; c < nohdrs; c++)
		{
			/* not even the same header field */
			if (hdr->hdr_namelen != hdr->hdr_textlen &&
			    strncmp(dkim_dstring_get(cohdrs[c]),
			            dkim_dstring_get(tmphdr),
			            hdr->hdr_namelen + 1) != 0)
				continue;

			/* same, no changes at all */
			if (strcmp(dkim_dstring_get(cohdrs[c]),
			           dkim_dstring_get(tmphdr)) == 0)
				continue;

			/* check for approximate match */
			status = tre_regaexec(&re, dkim_dstring_get(cohdrs[c]),
			                      &matches, params, 0);

			if (status == 0)
			{
				if (n + 1 > a)
				{
					int sz;
					struct dkim_hdrdiff *new;

					if (a == 0)
						a = 16;
					else
						a *= 2;

					sz = a * sizeof(struct dkim_hdrdiff);

					new = (struct dkim_hdrdiff *) dkim_malloc(lib,
					                                          cls,
					                                          sz);

					if (new == NULL)
					{
						if (diffs != NULL)
						{
							dkim_mfree(lib, cls,
							           diffs);
						}

						dkim_dstring_free(tmphdr);
						for (c = 0; c < nohdrs; c++)
							dkim_dstring_free(cohdrs[c]);
						DKIM_FREE(dkim, cohdrs);

						return DKIM_STAT_NORESOURCE;
					}

					dkim_mfree(lib, cls, diffs);

					diffs = new;

					sz = (a - n) & sizeof(struct dkim_hdrdiff);
					memset(&diffs[n], '\0', sz);
				}

				diffs[n].hd_old = ohdrs[c];
				diffs[n].hd_new = hdr->hdr_text;

				n++;
			}
		}

		tre_regfree(&re);
	}

	*out = diffs;
	*nout = n;

	dkim_dstring_free(tmphdr);
	for (c = 0; c < nohdrs; c++)
		dkim_dstring_free(cohdrs[c]);
	DKIM_FREE(dkim, cohdrs);

	return DKIM_STAT_OK;
#else /* _FFR_DIFFHEADERS */
	return DKIM_STAT_NOTIMPLEMENT;
	(void)dkim;
	(void)canon;
	(void)maxcost;
    (void)ohdrs;
    (void)nohdrs;
    (void)out;
    (void)nout;
#endif /* _FFR_DIFFHEADERS */
}

/*
**  DKIM_HEADER -- process a header
**
**  Parameters:
**  	dkim -- DKIM handle
**  	hdr -- header text
**  	len -- bytes available at "hdr"
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_header(DKIM *dkim, char *hdr, size_t len)
{
	char *colon;
	char *semicolon;
	char *end = NULL;
	size_t c;
	struct dkim_header *h;

	assert(dkim != NULL);
	assert(hdr != NULL);
	assert(len != 0);

#ifdef _FFR_RESIGN
	if (dkim->dkim_hdrbind)
		return DKIM_STAT_INVALID;
#endif /* _FFR_RESIGN */

	if (dkim->dkim_state > DKIM_STATE_HEADER)
		return DKIM_STAT_INVALID;
	dkim->dkim_state = DKIM_STATE_HEADER;

	/* enforce RFC 5322, Section 2.2 */
	colon = NULL;
	for (c = 0; c < len; c++)
	{
		if (colon == NULL)
		{
			/*
			**  Field names are printable ASCII; also tolerate
			**  plain whitespace.
			*/

			if (hdr[c] < 32 || hdr[c] > 126)
				return DKIM_STAT_SYNTAX;

			/* the colon is special */
			if (hdr[c] == ':')
			{
				colon = &hdr[c];
#if !defined EAI_INCOMPATIBLE
				break;
#endif
			}
		}
#if defined EAI_INCOMPATIBLE
		else
		{
			int ch = (unsigned char)hdr[c];
			/* field bodies are printable ASCII, SP, HT, CR, LF */
			if (!(ch != 9 ||  /* HT */
			      ch != 10 || /* LF */
			      ch != 13 || /* CR */
			      (ch >= 32 && ch <= 126) /* SP, print */ ))
				return DKIM_STAT_SYNTAX;
		}
#endif
	}

	if (colon == NULL)
		return DKIM_STAT_SYNTAX;

	end = colon;

	int ch;
	while (end > hdr && isascii((ch = *(unsigned char*)(end - 1))) && isspace(ch))
		end--;

	/* don't allow a field name containing a semicolon */
	semicolon = memchr(hdr, ';', len);
	if (semicolon != NULL && colon != NULL && semicolon < colon)
		return DKIM_STAT_SYNTAX;

	/* see if this is one we should skip */
	if (dkim->dkim_mode == DKIM_MODE_SIGN &&
	    dkim->dkim_libhandle->dkiml_skipre)
	{
		int status;
		char name[DKIM_MAXHEADER + 1];

		strlcpy(name, hdr, sizeof name);
		if (end != NULL)
			name[end - hdr] = '\0';

		status = regexec(&dkim->dkim_libhandle->dkiml_skiphdrre,
		                 name, 0, NULL, 0);

		if (status == 0)
			return DKIM_STAT_OK_NOTUSED; // -ale: was DKIM_STAT_OK;
		else
			assert(status == REG_NOMATCH);
	}

	h = DKIM_MALLOC(dkim, sizeof(struct dkim_header));

	if (h == NULL)
	{
		return DKIM_STAT_NORESOURCE;
	}

	if ((dkim->dkim_libhandle->dkiml_flags & DKIM_LIBFLAGS_FIXCRLF) != 0)
	{
		char prev = '\0';
		char *p;
		char *q;
		struct dkim_dstring *tmphdr;

		tmphdr = dkim_dstring_new(dkim, BUFRSZ, MAXBUFRSZ);
		if (tmphdr == NULL)
		{
			DKIM_FREE(dkim, h);
			return DKIM_STAT_NORESOURCE;
		}

		q = hdr + len;

		for (p = hdr; p < q && *p != '\0'; p++)
		{
			if (*p == '\n' && prev != '\r')		/* bare LF */
			{
				dkim_dstring_catn(tmphdr, CRLF, 2);
			}
			else if (prev == '\r' && *p != '\n')	/* bare CR */
			{
				dkim_dstring_cat1(tmphdr, '\n');
				dkim_dstring_cat1(tmphdr, *p);
			}
			else					/* other */
			{
				dkim_dstring_cat1(tmphdr, *p);
			}

			prev = *p;
		}

		if (prev == '\r')				/* end CR */
			dkim_dstring_cat1(tmphdr, '\n');

		h->hdr_text = dkim_strdup(dkim, dkim_dstring_get(tmphdr),
		                          dkim_dstring_len(tmphdr));

		dkim_dstring_free(tmphdr);
	}
	else
	{
		h->hdr_text = dkim_strdup(dkim, hdr, len);
	}

	if (h->hdr_text == NULL)
	{
		DKIM_FREE(dkim, h);
		return DKIM_STAT_NORESOURCE;
	}

	h->hdr_namelen = end != NULL ? (size_t)(end - hdr) : len;
	h->hdr_textlen = len;
	if (colon == NULL)
		h->hdr_colon = NULL;
	else
		h->hdr_colon = h->hdr_text + (colon - hdr);
	h->hdr_flags = 0;
	h->hdr_next = NULL;

	if (dkim->dkim_hhead == NULL)
	{
		dkim->dkim_hhead = h;
		dkim->dkim_htail = h;
	}
	else
	{
		dkim->dkim_htail->hdr_next = h;
		dkim->dkim_htail = h;
	}

	dkim->dkim_hdrcnt++;

	if (h->hdr_colon != NULL)
	{
		if (h->hdr_namelen == DKIM_SIGNHEADER_LEN &&
		    strncasecmp(hdr, DKIM_SIGNHEADER,
		                DKIM_SIGNHEADER_LEN) == 0)
		{
			DKIM_SET *set;
			size_t plen;

			plen = len - (h->hdr_colon - h->hdr_text) - 1;
			set = dkim_process_set(dkim, DKIM_SETTYPE_SIGNATURE,
			                          h->hdr_colon + 1, plen, h,
			                          FALSE, NULL, NULL);

			if (set == NULL)
				return DKIM_STAT_NORESOURCE;

			if (set->set_bad)
				return DKIM_STAT_SYNTAX;
		}
		else if (h->hdr_namelen >= 8 &&
			strncasecmp(hdr, "ARC-", 4) == 0)
		{
			dkim_set_t type;
			if (h->hdr_namelen == 8 &&
				strncasecmp(hdr, "ARC-Seal", 8) == 0)
					type = DKIM_SETTYPE_ARC_AS;
			else if (h->hdr_namelen == 21 &&
				//                012345678901234567890
				strncasecmp(hdr, "ARC-Message-Signature", 21) == 0)
					type = DKIM_SETTYPE_ARC_AMS;
			else if (h->hdr_namelen == 26 &&
				//                01234567890123456789012345
				strncasecmp(hdr, "ARC-Authentication-Results", 26) == 0)
					type = DKIM_SETTYPE_ARC_AAR;
			else
				type = 0;

			if (type)
			{
				assert (*h->hdr_colon == ':');
				DKIM_SET *set;
				size_t plen;

				// AAR cannot be parsed as a set, except i=
				char *semicolon = type == DKIM_SETTYPE_ARC_AAR?
					strchr(h->hdr_colon + 1, ';'): NULL;

				plen = len - (h->hdr_colon - h->hdr_text) - 1;
				*h->hdr_colon = 0;
				if (semicolon)
					*semicolon = 0;
				set = dkim_process_set(
					dkim, type, h->hdr_colon + 1, plen, h,
					FALSE, NULL, h->hdr_text);
				*h->hdr_colon = ':';
				if (semicolon)
					*semicolon = ';';

				if (set == NULL)
					return DKIM_STAT_NORESOURCE;

				if (set->set_bad)
					return DKIM_STAT_SYNTAX;

				dkim->dkim_arccount += 1;
				if (dkim->dkim_arccount > 500)
				/*
				* The maximum number of ARC Sets that can be attached to a
				* message is 50.  If more than the maximum number exist, the
				* Chain Validation Status is "fail", and the algorithm stops
				* here.
				*/
				{
					dkim->arc_state = ARC_CHAIN_FAIL;
					dkim_error(dkim, "Too many ARC headers");
					return DKIM_STAT_INTERNAL;
				}
			}
		}
	}

	return DKIM_STAT_OK;
}

/*
**  DKIM_EOH -- declare end-of-headers
**              build arc_sets and verify seal in both modes
** 
**  Parameters:
**  	dkim -- DKIM handle
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_eoh(DKIM *dkim)
{
	assert(dkim != NULL);

	// check ARC sets
	unsigned int nsets = dkim->dkim_arccount;
	if (nsets == 0)
		dkim->arc_state = ARC_CHAIN_NONE;
	else if (dkim->arc_state == ARC_CHAIN_UNKNOWN)
	{
		nsets /= 3;
		if (3 * nsets != dkim->dkim_arccount)
			dkim->arc_state = ARC_CHAIN_FAIL;
		else
		{
			struct arc_set *arc_sets = DKIM_MALLOC(dkim, sizeof(struct arc_set) * nsets);
			if (arc_sets == NULL)
				return DKIM_STAT_NORESOURCE;
			memset(arc_sets, '\0', sizeof(struct arc_set) * nsets);

			/* allocate the siginfo elements */
			for (unsigned int c = 0; c < nsets; ++c)
			{
				DKIM_SIGINFO *as = DKIM_MALLOC(dkim, sizeof(DKIM_SIGINFO)),
					*ams = DKIM_MALLOC(dkim, sizeof(DKIM_SIGINFO));
				if (as == NULL || ams == NULL)
				{
					for (unsigned int n = 0; n < c; n++)
					{
						DKIM_FREE(dkim, arc_sets[n].arcsig_as);
						DKIM_FREE(dkim, arc_sets[n].arcsig_ams);
					}
					DKIM_FREE(dkim, arc_sets);
					return DKIM_STAT_NORESOURCE;
				}

				memset(as, '\0', sizeof(DKIM_SIGINFO));
				memset(ams, '\0', sizeof(DKIM_SIGINFO));
				arc_sets[c].arcsig_as = as;
				arc_sets[c].arcsig_ams = ams;
			}

			dkim->dkim_arccount = nsets; // number of elements
			dkim->arc_sets = arc_sets;   // array

			/* fill all ARC sets */
			for (unsigned int i = 0; i < nsets; ++i)
			{
				unsigned int type = i + 1 + DKIM_SETTYPE_ARC_AS;
				DKIM_SET *set = dkim_set_first(dkim, type);
				if (set == NULL || dkim_set_next(set, type) != NULL)
				{
					dkim_error(dkim, "%s at instance %u", set == NULL?
						"missing or incomplete ARC set":
						"multiple ARC-Seal", i + 1);
					dkim->arc_state = ARC_CHAIN_FAIL;
					break;
				}
				struct dkim_siginfo *sig = arc_sets[i].arcsig_as;
				DKIM_STAT status =
					dkim_sig_fromset(dkim, sig, set, sigtype_as);
				if (status != DKIM_STAT_OK)
					return status;

				type = i + 1 + DKIM_SETTYPE_ARC_AMS;
				set = dkim_set_first(dkim, type);
				if (set == NULL || dkim_set_next(set, type) != NULL)
				{
					dkim_error(dkim, "%s at instance %u", set == NULL?
						"missing or incomplete ARC set":
						"multiple ARC-Message-Signature", i + 1);
					dkim->arc_state = ARC_CHAIN_FAIL;
					break;
				}
				sig = arc_sets[i].arcsig_ams;
				status = dkim_sig_fromset(dkim, sig, set, sigtype_ams);
				if (status != DKIM_STAT_OK)
					return status;

				type = i + 1 + DKIM_SETTYPE_ARC_AAR;
				set = dkim_set_first(dkim, type);
				if (set == NULL || dkim_set_next(set, type) != NULL)
				{
					dkim_error(dkim, "%s at instance %u", set == NULL?
						"missing or incomplete ARC set":
						"multiple ARC-Authentication-Results", i + 1);
					dkim->arc_state = ARC_CHAIN_FAIL;
					break;
				}
				arc_sets[i].arcset_aar = set;
			}

			DKIM_LIB *lib = dkim->dkim_libhandle;
			_Bool tmp = ((lib->dkiml_flags & DKIM_LIBFLAGS_TMPFILES) != 0);
			_Bool keep = ((lib->dkiml_flags & DKIM_LIBFLAGS_KEEPFILES) != 0);

			if (dkim->arc_state == ARC_CHAIN_UNKNOWN)
			{
				/* initialize all discovered canonicalizations */
				DKIM_STAT status = dkim_canon_init(dkim, tmp, keep);
				if (status != DKIM_STAT_OK)
					return status;

				status = arc_canon_runheaders_seal(dkim);
				if (status != DKIM_STAT_OK)
					return status;
			}
			else if (arc_sets)
			/*
			* Disable all sigs'canons
			*/
			{
				assert(dkim->arc_state == ARC_CHAIN_FAIL);

				for (unsigned int i = 0; i < nsets; ++i)
				{
					if (arc_sets[i].arcsig_ams &&
						arc_sets[i].arcsig_ams->sig_hdrcanon)
							arc_sets[i].arcsig_ams->sig_hdrcanon->canon_done = TRUE;
					if (arc_sets[i].arcsig_as &&
						arc_sets[i].arcsig_as->sig_hdrcanon)
							arc_sets[i].arcsig_as->sig_hdrcanon->canon_done = TRUE;
				}
				dkim->arc_set_inconsistent = TRUE;
				DKIM_STAT status = dkim_canon_init(dkim, tmp, keep);
				if (status != DKIM_STAT_OK)
					return status;
			}
		}
	}
	else
	{
		nsets /= 3;
		if (3 * nsets == dkim->dkim_arccount)
		{
			dkim->dkim_arccount = nsets;
			if (dkim->arc_state == ARC_CHAIN_FAIL_CV)
			// check cv=fail is in the top AS, otherwise change state
			{
				dkim->arc_state = ARC_CHAIN_FAIL;

				unsigned int type = nsets + DKIM_SETTYPE_ARC_AS;
				DKIM_SET *set = dkim_set_first(dkim, type);
				if (set != NULL && dkim_set_next(set, type) == NULL)
				{
					char *p = dkim_param_get(set, "cv");
					if (p && strcasecmp(p, "fail") == 0)
						dkim->arc_state = ARC_CHAIN_FAIL_CV;
				}
			}
		}
		else
			dkim->dkim_arccount = 0;
	}

	/* one of fail, seal ok or error */
	assert(dkim->arc_state != ARC_CHAIN_UNKNOWN);

	if (dkim->dkim_mode == DKIM_MODE_VERIFY)
		return dkim_eoh_verify(dkim);
	else
		return dkim_eoh_sign(dkim);
}

/*
**  DKIM_BODY -- pass a body chunk in for processing
**
**  Parameters:
**  	dkim -- DKIM handle
**  	buf -- body chunk
**  	buflen -- number of bytes at "buf"
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_body(DKIM *dkim, char *buf, size_t buflen)
{
	assert(dkim != NULL);
	assert(buf != NULL);

#ifdef _FFR_RESIGN
	if (dkim->dkim_resign != NULL)
		return DKIM_STAT_INVALID;
#endif /* _FFR_RESIGN */

	if (dkim->dkim_state > DKIM_STATE_BODY ||
	    dkim->dkim_state < DKIM_STATE_EOH1)
		return DKIM_STAT_INVALID;
	dkim->dkim_state = DKIM_STATE_BODY;

	if (dkim->dkim_skipbody)
		return DKIM_STAT_OK;

	return dkim_canon_bodychunk(dkim, buf, buflen);
}

/*
**  DKIM_EOM -- declare end-of-body; conduct verification or signing
**
**  Parameters:
**  	dkim -- DKIM handle
**  	testkey -- TRUE iff the a matching key was found but is marked as a
**  	           test key (returned)
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_eom(DKIM *dkim, _Bool *testkey)
{
	assert(dkim != NULL);

	if (dkim->dkim_mode == DKIM_MODE_SIGN)
		return dkim_eom_sign(dkim);
	else
		return dkim_eom_verify(dkim, testkey);
}

/*
**  DKIM_CHUNK -- process a message chunk
**
**  Parameters:
**  	dkim -- DKIM handle
**  	buf -- data to process
**  	buflen -- number of bytes at "buf" to process
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_chunk(DKIM *dkim, char *buf, size_t buflen)
{
	_Bool bso;
	DKIM_STAT status;
	char *p;
	char *end;

	assert(dkim != NULL);

	bso = ((dkim->dkim_libhandle->dkiml_flags & DKIM_LIBFLAGS_BADSIGHANDLES) != 0);

	if ((dkim->dkim_libhandle->dkiml_flags & DKIM_LIBFLAGS_FIXCRLF) == 0)
		dkim->dkim_chunkcrlf = DKIM_CRLF_CRLF;

	/* verify chunking state */
	if (dkim->dkim_chunkstate >= DKIM_CHUNKSTATE_DONE)
	{
		return DKIM_STAT_INVALID;
	}
	else if (dkim->dkim_chunkstate == DKIM_CHUNKSTATE_INIT)
	{
		if (dkim_check_hdrbuf(dkim) != DKIM_STAT_OK)
			return DKIM_STAT_NORESOURCE;

		dkim->dkim_chunkstate = DKIM_CHUNKSTATE_HEADER;
		dkim->dkim_chunksm = 0;
	}

	/* process an "end" call */
	if (buf == NULL || buflen == 0)
	{
		if (dkim->dkim_chunkstate == DKIM_CHUNKSTATE_HEADER)
		{
			if (dkim_dstring_len(dkim->dkim_hdrbuf) > 0)
			{
				status = dkim_header(dkim,
				                     dkim_dstring_get(dkim->dkim_hdrbuf),
				                     dkim_dstring_len(dkim->dkim_hdrbuf));
				if (status != DKIM_STAT_OK &&
				    !(status == DKIM_STAT_SYNTAX && bso))
					return status;
			}

			status = dkim_eoh(dkim);
			if (status != DKIM_STAT_OK)
				return status;
		}

		dkim->dkim_chunkstate = DKIM_CHUNKSTATE_DONE;

		return DKIM_STAT_OK;
	}

	/* if we're in body state, just call dkim_body() */
	if (dkim->dkim_chunkstate == DKIM_CHUNKSTATE_BODY)
		return dkim_body(dkim, buf, buflen);

	assert(dkim->dkim_chunkstate == DKIM_CHUNKSTATE_HEADER);

	end = buf + buflen - 1;

	/* process headers */
	for (p = buf; p <= end; p++)
	{
		switch (dkim->dkim_chunksm)
		{
		  case 0:
			if (*p == '\n' &&
			    dkim->dkim_chunkcrlf != DKIM_CRLF_CRLF)
			{
				dkim->dkim_chunkcrlf = DKIM_CRLF_LF;

				/*
				**  If this is a CRLF up front, change state
				**  and write the rest as part of the body.
				*/

				if (dkim->dkim_hhead == NULL &&
				    dkim_dstring_len(dkim->dkim_hdrbuf) == 2)
				{
					status = dkim_eoh(dkim);
					if (status != DKIM_STAT_OK)
						return status;

					dkim->dkim_chunkstate = DKIM_CHUNKSTATE_BODY;
					if (p < end)
					{
						return dkim_body(dkim, p + 1,
						                 end - p);
					}
					else
					{
						return DKIM_STAT_OK;
					}
				}

				dkim_dstring_catn(dkim->dkim_hdrbuf, CRLF, 2);
				dkim->dkim_chunksm = 2;
			}
			else
			{
				dkim_dstring_cat1(dkim->dkim_hdrbuf, *p);
				if (*p == '\r')
					dkim->dkim_chunksm = 1;
			}
			break;

		  case 1:
			dkim_dstring_cat1(dkim->dkim_hdrbuf, *p);
			if (*p == '\n')
			{
				if (dkim->dkim_chunkcrlf == DKIM_CRLF_UNKNOWN)
					dkim->dkim_chunkcrlf = DKIM_CRLF_CRLF;

				/*
				**  If this is a CRLF up front, change state
				**  and write the rest as part of the body.
				*/

				if (dkim->dkim_hhead == NULL &&
				    dkim_dstring_len(dkim->dkim_hdrbuf) == 2)
				{
					status = dkim_eoh(dkim);
					if (status != DKIM_STAT_OK)
						return status;

					dkim->dkim_chunkstate = DKIM_CHUNKSTATE_BODY;
					if (p < end)
					{
						return dkim_body(dkim, p + 1,
						                 end - p);
					}
					else
					{
						return DKIM_STAT_OK;
					}
				}

				dkim->dkim_chunksm = 2;
			}
			else if (*p != '\r')
			{
				dkim->dkim_chunksm = 0;
			}
			break;
			
		  case 2:
			if (DKIM_ISLWSP(*(unsigned char*)p))
			{
				dkim_dstring_cat1(dkim->dkim_hdrbuf, *p);
				dkim->dkim_chunksm = 0;
				break;
			}
			else if (*p == '\r' &&
			         dkim->dkim_chunkcrlf == DKIM_CRLF_CRLF)
			{
				dkim->dkim_chunksm = 3;
				break;
			}
			else if (*p != '\n' ||
			         dkim->dkim_chunkcrlf != DKIM_CRLF_LF)
			{
				status = dkim_header(dkim,
				                     dkim_dstring_get(dkim->dkim_hdrbuf),
				                     dkim_dstring_len(dkim->dkim_hdrbuf) - 2);
				if (status != DKIM_STAT_OK &&
				    !(status == DKIM_STAT_SYNTAX && bso))
					return status;

				dkim_dstring_blank(dkim->dkim_hdrbuf);
				dkim_dstring_cat1(dkim->dkim_hdrbuf, *p);
				dkim->dkim_chunksm = 0;
				break;
			}
			/* FALLTHROUGH */
				
		  case 3:
			if (*p == '\n')
			{
				if (dkim_dstring_len(dkim->dkim_hdrbuf) > 0)
				{
					status = dkim_header(dkim,
					                     dkim_dstring_get(dkim->dkim_hdrbuf),
					                     dkim_dstring_len(dkim->dkim_hdrbuf) - 2);
					if (status != DKIM_STAT_OK &&
					    !(status == DKIM_STAT_SYNTAX &&
					      bso))
						return status;
				}

				status = dkim_eoh(dkim);
				if (status != DKIM_STAT_OK)
					return status;

				dkim->dkim_chunkstate = DKIM_CHUNKSTATE_BODY;

				if (p < end)
					return dkim_body(dkim, p + 1, end - p);
				else
					return DKIM_STAT_OK;
			}
			else
			{
				status = dkim_header(dkim,
				                     dkim_dstring_get(dkim->dkim_hdrbuf),
				                     dkim_dstring_len(dkim->dkim_hdrbuf) - 2);
				if (status != DKIM_STAT_OK &&
				    !(status == DKIM_STAT_SYNTAX && bso))
					return status;

				dkim_dstring_blank(dkim->dkim_hdrbuf);
				dkim_dstring_cat1(dkim->dkim_hdrbuf, '\r');
				dkim_dstring_cat1(dkim->dkim_hdrbuf, *p);
				dkim->dkim_chunksm = 0;
			}
			break;

		  default:
			assert(0);
			/* NOTREACHED */
		}
	}

	return DKIM_STAT_OK;
}

/*
**  DKIM_MINBODY -- return number of bytes still expected
**
**  Parameters:
**  	dkim -- DKIM handle
**
**  Return value:
**  	0 -- all canonicalizations satisfied
**  	ULONG_MAX -- at least one canonicalization wants the whole message
**  	other -- bytes required to satisfy all canonicalizations
*/

u_long
dkim_minbody(DKIM *dkim)
{
	assert(dkim != NULL);

	return dkim_canon_minbody(dkim);
}

/*
**  SET_VALUE -- interpret a dkim_process_set result
**
**  Parameters:
**  	dkim -- DKIM context in which this is performed
**  	set -- processed set
**
**  Return value:
**  	A DKIM_STAT constant.
*/
static DKIM_STAT
set_value(DKIM *dkim, DKIM_SET *set)
{
	if (set == NULL)
		return DKIM_STAT_NORESOURCE;

	DKIM_STAT rtc = dkim->memory_fault? DKIM_STAT_INTERNAL:
		set->set_bad? DKIM_STAT_SYNTAX:
		DKIM_STAT_OK;

	dkim_set_free(dkim, set);
	return rtc;
}
 
/*
**  DKIM_KEY_SYNTAX -- process a key record parameter set for valid syntax
**
**  Parameters:
**  	dkim -- DKIM context in which this is performed
**  	str -- string to be scanned
**  	len -- number of bytes available at "str"
**
**  Return value:
**  	A DKIM_STAT constant.
*/

DKIM_STAT
dkim_key_syntax(DKIM *dkim, char *str, size_t len)
{
	return set_value(dkim, dkim_process_set(dkim, DKIM_SETTYPE_KEY, str, len, NULL, TRUE,
	                        NULL, NULL));
}

/*
**  DKIM_SIG_SYNTAX -- process a signature parameter set for valid syntax
**
**  Parameters:
**  	dkim -- DKIM context in which this is performed
**  	str -- string to be scanned
**  	len -- number of bytes available at "str"
**
**  Return value:
**  	A DKIM_STAT constant.
*/

DKIM_STAT
dkim_sig_syntax(DKIM *dkim, char *str, size_t len)
{
	return set_value(dkim, dkim_process_set(dkim, DKIM_SETTYPE_SIGNATURE, str, len,
	                        NULL, TRUE, NULL, NULL));
}

/*
**  DKIM_GETID -- retrieve "id" pointer from a handle
**
**  Parameters:
**  	dkim -- DKIM handle
**
**  Return value:
**  	The "id" pointer from inside the handle, stored when it was created.
*/

const char *
dkim_getid(DKIM *dkim)
{
	assert(dkim != NULL);

	return dkim->dkim_id;
}

/*
**  DKIM_GETSIGLIST -- retrieve the list of signatures
**
**  Parameters:
**  	dkim -- DKIM handle
**   	sigs -- pointer to a vector of DKIM_SIGINFO pointers (updated)
**   	nsigs -- pointer to an integer to receive the pointer count (updated)
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_getsiglist(DKIM *dkim, DKIM_SIGINFO ***sigs, int *nsigs)
{
	assert(dkim != NULL);
	assert(sigs != NULL);
	assert(nsigs != NULL);

	if (dkim->dkim_state < DKIM_STATE_EOH2)
		return DKIM_STAT_INVALID;

	*sigs = dkim->dkim_siglist;
	*nsigs = dkim->dkim_sigcount;

	return DKIM_STAT_OK;
}

/*
**  DKIM_GETSIGNATURE -- retrieve the "final" signature
**
**  Parameters:
**  	dkim -- DKIM handle
**
**  Return value:
**  	Pointer to a DKIM_SIGINFO handle which is the one libopendkim will
**  	use to return a "final" result; NULL if none could be determined.
*/

DKIM_SIGINFO *
dkim_getsignature(DKIM *dkim)
{
	assert(dkim != NULL);

	return dkim->dkim_signature;
}

/*
**  DKIM_GETSIGHDR_D -- for signing operations, retrieve the complete signature
**                      header, doing so dynamically.
**                      The result is left in dkim->dkim_hdrbuf.
**
**  Parameters:
**  	dkim -- DKIM handle
**  	sigtype -- kind of signature
**  	initial -- initial line width
**
**  Return value:
**  	A DKIM_STAT_* constant.
**
**  Notes:
**  	Per RFC6376 Section 3.7, the signature header returned here does
**  	not contain a trailing CRLF.
*/

static DKIM_STAT
dkim_getsighdr_d(DKIM *dkim, sigtype type, size_t initial)
{
	size_t len;
	char *ctx;
	char *pv;
	DKIM_SIGINFO *sig;
	struct dkim_dstring *tmpbuf;

	assert(dkim != NULL);

	if (dkim->dkim_state != DKIM_STATE_EOM2 ||
	    dkim->dkim_mode != DKIM_MODE_SIGN)
		return DKIM_STAT_INVALID;

#define	DELIMITER	"\001"

	sig = dkim->dkim_signature;
	if (sig == NULL)
		sig = dkim->dkim_siglist[0];

	if ((sig->sig_flags & DKIM_SIGFLAG_KEYLOADED) == 0)
	{
		dkim_error(dkim, "private key load failure");
		return DKIM_STAT_INVALID;
	}

	tmpbuf = dkim_dstring_new(dkim, BUFRSZ, MAXBUFRSZ);
	if (tmpbuf == NULL)
	{
		dkim_error(dkim, "failed to allocate dynamic string");
		return DKIM_STAT_NORESOURCE;
	}

	if (dkim_check_hdrbuf(dkim) != DKIM_STAT_OK)
	{
		dkim_dstring_free(tmpbuf);
		return DKIM_STAT_NORESOURCE;
	}

	/* compute and extract the signature header */
	len = dkim_gensighdr(dkim, type, sig, tmpbuf, DELIMITER);
	if (len == 0)
	{
		dkim_dstring_free(tmpbuf);
		return DKIM_STAT_INVALID;
	}

	if (dkim->dkim_b64sig != NULL)
		dkim_dstring_cat(tmpbuf, dkim->dkim_b64sig);

	if (dkim->dkim_margin == 0)
	{
		_Bool first = TRUE;

		for (pv = strtok_r(dkim_dstring_get(tmpbuf),
		                   DELIMITER, &ctx);
		     pv != NULL;
		     pv = strtok_r(NULL, DELIMITER, &ctx))
		{
			if (!first)
				dkim_dstring_cat1(dkim->dkim_hdrbuf, ' ');

			dkim_dstring_cat(dkim->dkim_hdrbuf, pv);

			first = FALSE;
		}
	}
	else
	{
		_Bool first = TRUE;
		_Bool forcewrap;
		int pvlen;
		int whichlen;
		char *p;
		char *q;
		char *end;
		char which[MAXTAGNAME + 1];

		len = initial;
		end = which + MAXTAGNAME;

		for (pv = strtok_r((char *) dkim_dstring_get(tmpbuf),
		                   DELIMITER, &ctx);
		     pv != NULL;
		     pv = strtok_r(NULL, DELIMITER, &ctx))
		{
			for (p = pv, q = which; *p != '=' && q <= end; p++, q++)
			{
				*q = *p;
				*(q + 1) = '\0';
			}

			whichlen = strlen(which);

			/* force wrapping of "b=" */

			forcewrap = strcmp(which, "b") == 0;

			pvlen = strlen(pv);

			if (len == 0 || first)
			{
				dkim_dstring_catn(dkim->dkim_hdrbuf, pv, pvlen);
				len += pvlen;
				first = FALSE;
			}
			else if (forcewrap || len + pvlen > dkim->dkim_margin)
			{
				forcewrap = FALSE;
				dkim_dstring_catn(dkim->dkim_hdrbuf, "\r\n\t", 3);
				len = 8;

				if (strcmp(which, "h") == 0)
				{			/* break at colons */
					_Bool ifirst = TRUE;
					int tmplen;
					char *tmp;
					char *ctx2;

					for (tmp = strtok_r(pv, ":", &ctx2);
					     tmp != NULL;
					     tmp = strtok_r(NULL, ":", &ctx2))
					{
						tmplen = strlen(tmp);

						if (ifirst)
						{
							dkim_dstring_catn(dkim->dkim_hdrbuf, tmp, tmplen);
							len += tmplen;
							ifirst = FALSE;
						}
						else if (len + tmplen + 1 > dkim->dkim_margin)
						{
							dkim_dstring_cat1(dkim->dkim_hdrbuf, ':');
							len += 1;
							dkim_dstring_catn(dkim->dkim_hdrbuf, "\r\n\t ", 4);
							len = 9;
							dkim_dstring_catn(dkim->dkim_hdrbuf, tmp, tmplen);
							len += tmplen;
						}
						else
						{
							dkim_dstring_cat1(dkim->dkim_hdrbuf, ':');
							len += 1;
							dkim_dstring_catn(dkim->dkim_hdrbuf, tmp, tmplen);
							len += tmplen;
						}
					}

				}
				else if (strcmp(which, "b") == 0 ||
				         strcmp(which, "bh") == 0 ||
				         strcmp(which, "z") == 0)
				{			/* break at margins */

					dkim_dstring_catn(dkim->dkim_hdrbuf, which, whichlen);
					dkim_dstring_cat1(dkim->dkim_hdrbuf, '=');

					int offset = whichlen + 1;
					if (pvlen > offset)
					{
						len += offset;

						dkim_dstring_cat1(dkim->dkim_hdrbuf,
										  *(unsigned char*)(pv + offset));
						len++;

						char *x = pv + offset + 1;
						char *y = pv + pvlen;

						while (x < y)
						{
							if (dkim->dkim_margin - len == 0)
							{
								dkim_dstring_catn(dkim->dkim_hdrbuf, "\r\n\t ", 4);
								len = 9;
							}

							int n = MIN(dkim->dkim_margin - len, y - x);
							dkim_dstring_catn(dkim->dkim_hdrbuf, x, n);
							x += n;
							len += n;
							
						}
					}
				}
				else
				{			/* break at delimiter */
					dkim_dstring_catn(dkim->dkim_hdrbuf, pv, pvlen);
					len += pvlen;
				}
			}
			else
			{
				if (!first)
				{
					dkim_dstring_cat1(dkim->dkim_hdrbuf, ' ');
					len += 1;
				}

				first = FALSE;
				dkim_dstring_catn(dkim->dkim_hdrbuf, pv, pvlen);
				len += pvlen;
			}
		}
	}

	dkim_dstring_free(tmpbuf);

	return DKIM_STAT_OK;
}

/*
**  DKIM_GETSIGHDR -- retrieve DKIM signature header into a user-provided buffer
**
**  Parameters:
**  	dkim -- libopendkim handle
**  	buf -- buffer into which to write
**  	buflen -- bytes available at "buf"
**  	initial -- width aleady consumed for the first line
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_getsighdr(DKIM *dkim, char *buf, size_t buflen, size_t initial)
{
	DKIM_STAT status;

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

	status = dkim_compute_signature(dkim, sigtype_dkim);
	if (status != DKIM_STAT_OK)
		return status;

	status = dkim_getsighdr_d(dkim, sigtype_dkim, initial);
	if (status != DKIM_STAT_OK)
		return status;

	unsigned int len = dkim_dstring_len(dkim->dkim_hdrbuf);
	if (len > buflen)
	{
		dkim_error(dkim, "generated signature header too large");
		return DKIM_STAT_NORESOURCE;
	}

	char *p = dkim_dstring_get(dkim->dkim_hdrbuf);
	strlcpy(buf, p, buflen);

	return DKIM_STAT_OK;
}

/*
**  DKIM_GETSIGHDR_A -- retrieve DKIM signature header into a malloced buffer
**
**  Parameters:
**  	dkim -- libopendkim handle
**  	buf -- buffer into which to write
**  	buflen -- receive length of buf
**  	initial -- width aleady consumed for the first line
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_getsighdr_a(DKIM *dkim, char **buf, size_t *buflen, size_t initial)
{
	assert(dkim != NULL);
	assert(buf != NULL);
	assert(buflen != NULL);

	DKIM_STAT status = dkim_compute_signature(dkim, sigtype_dkim);
	if (status != DKIM_STAT_OK)
		return status;

	status = dkim_getsighdr_d(dkim, sigtype_dkim, initial);
	if (status == DKIM_STAT_OK)
	{
		*buflen = dkim_dstring_len(dkim->dkim_hdrbuf);
		*buf = dkim_dstring_grab(dkim->dkim_hdrbuf);
		dkim->dkim_hdrbuf = NULL;
	}

	return status;
}

/*
**  DKIM_SIG_HDRSIGNED -- retrieve the header list from a signature
**
**  Parameters:
**  	sig -- DKIM_SIGINFO handle
**  	hdr -- header name to find
**
**  Return value:
**  	TRUE iff "sig" had a header list in it and the header "hdr"
**  	appeared in that list.
*/

_Bool
dkim_sig_hdrsigned(DKIM_SIGINFO *sig, char *hdr)
{
	size_t len;
	char *c1 = NULL;
	char *c2 = NULL;
	char *start;
	char *p;
	char *hdrlist;

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

	hdrlist = dkim_param_get(sig->sig_taglist, "h");
	if (hdrlist == NULL)
		return FALSE;

	for (p = hdrlist; ; p++)
	{
		len = -1;

		if (*p == ':')
		{
			c1 = c2;
			c2 = p;

			if (c1 == NULL)
			{
				start = hdrlist;
				len = c2 - start; 
			}
			else
			{
				start = c1 + 1;
				len = c2 - c1 - 1;
			}
		}
		else if (*p == '\0')
		{
			if (c2 != NULL)
			{
				start = c2 + 1;
				len = p - c2 - 1;

				if (strncasecmp(hdr, start, len) == 0)
					return TRUE;
			}
			else
			{
				if (strcasecmp(hdr, hdrlist) == 0)
					return TRUE;
			}

			break;
		}

		if (len != (size_t)-1)
		{
			if (strncasecmp(hdr, start, len) == 0)
				return TRUE;
		}
	}

	return FALSE;
}

/*
**  DKIM_SIG_GETDNSSEC -- retrieve DNSSEC results for a signature
**
**  Parameters:
**  	sig -- DKIM_SIGINFO handle
**
**  Return value:
**  	A DKIM_DNSSEC_* constant.
*/

int
dkim_sig_getdnssec(DKIM_SIGINFO *sig)
{
	assert(sig != NULL);

	return sig->sig_dnssec_key;
}

/*
**  DKIM_SIG_SETDNSSEC -- set DNSSEC results for a signature
**
**  Parameters:
**  	sig -- DKIM_SIGINFO handle
**      dnssec_status -- A DKIM_DNSSEC_* constant
**
*/

void
dkim_sig_setdnssec(DKIM_SIGINFO *sig, int dnssec_status)
{
	assert(sig != NULL);

	switch (dnssec_status) 
	{
	  case DKIM_DNSSEC_BOGUS:
	  case DKIM_DNSSEC_INSECURE:
	  case DKIM_DNSSEC_SECURE:
		sig->sig_dnssec_key = dnssec_status;
		break;

	  default:
		/* just use the unknown value */
		sig->sig_dnssec_key = DKIM_DNSSEC_UNKNOWN;
		break;
	}
}

/*
**  DKIM_SIG_GETREPORTINFO -- retrieve reporting information for a signature
**
**  Parameters:
**  	dkim -- DKIM handle
**  	sig -- DKIM_SIGINFO handle
**  	hfd -- descriptor to canonicalized header (or NULL) (returned)
**  	bfd -- descriptor to canonicalized body (or NULL) (returned)
**  	addr -- address buffer (or NULL)
**  	addrlen -- size of addr
**  	opts -- options buffer (or NULL)
**  	optslen -- size of opts
**  	smtp -- SMTP reply text buffer (or NULL)
**  	smtplen -- size of smtp
**  	pct -- requested reporting percentage (or NULL)
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_sig_getreportinfo(DKIM *dkim, DKIM_SIGINFO *sig,
                       int *hfd, int *bfd,
                       char *addr, size_t addrlen,
                       char *opts, size_t optslen,
                       char *smtp, size_t smtplen,
                       unsigned long *pct)
{
	DKIM_STAT status;
	char *p;
	char *sdomain;
	DKIM_SET *set;
	struct timeval timeout;
	char buf[BUFRSZ];

	assert(dkim != NULL);
	assert(sig != NULL);

	if (dkim->dkim_state != DKIM_STATE_EOM2 ||
	    dkim->dkim_mode != DKIM_MODE_VERIFY)
		return DKIM_STAT_INVALID;

	sdomain = dkim_sig_getdomain(sig);

	/* report descriptors regardless of reporting parameters */
	if (sig->sig_hdrcanon != NULL)
	{
		switch (sig->sig_hashtype)
		{
#ifdef USE_GNUTLS
		  case DKIM_HASHTYPE_SHA1:
		  case DKIM_HASHTYPE_SHA256:
		  {
			struct dkim_sha *sha;

			sha = (struct dkim_sha *) sig->sig_hdrcanon->canon_hash;
			if (hfd != NULL)
				*hfd = sha->sha_tmpfd;

			if (bfd != NULL)
			{
				sha = (struct dkim_sha *) sig->sig_bodycanon->canon_hash;
				*bfd = sha->sha_tmpfd;
			}

			break;
		  }
#else /* USE_GNUTLS */
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
		  case DKIM_HASHTYPE_SHA1:
		  case DKIM_HASHTYPE_SHA256:
		  {
			struct dkim_sha *sha = sig->sig_hdrcanon->canon_hash;
			if (hfd != NULL)
				*hfd = sha->sha_tmpfd;

			if (bfd != NULL)
			{
				sha = sig->sig_bodycanon->canon_hash;
				*bfd = sha->sha_tmpfd;
			}

			break;
		  }
#else  /* OPENSSL_VERSION_NUMBER >= 0x30000000L */

		  case DKIM_HASHTYPE_SHA1:
		  {
			struct dkim_sha1 *sha1;

			sha1 = (struct dkim_sha1 *) sig->sig_hdrcanon->canon_hash;
			if (hfd != NULL)
				*hfd = sha1->sha1_tmpfd;

			if (bfd != NULL)
			{
				sha1 = (struct dkim_sha1 *) sig->sig_bodycanon->canon_hash;
				*bfd = sha1->sha1_tmpfd;
			}

			break;
		  }

# ifdef HAVE_SHA256
		  case DKIM_HASHTYPE_SHA256:
		  {
			struct dkim_sha256 *sha256;

			sha256 = (struct dkim_sha256 *) sig->sig_hdrcanon->canon_hash;
			if (hfd != NULL)
				*hfd = sha256->sha256_tmpfd;

			if (bfd != NULL)
			{
				sha256 = (struct dkim_sha256 *) sig->sig_bodycanon->canon_hash;
				*bfd = sha256->sha256_tmpfd;
			}

			break;
		  }
# endif /* HAVE_SHA256 */
#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */
#endif /* USE_GNUTLS */

		  default:
			assert(0);
		}
	}

	/* see if the signature had an "r=y" tag */
	set = sig->sig_taglist;
	if (set == NULL)
		return DKIM_STAT_INTERNAL;

	p = dkim_param_get(set, "r");
	if (p == NULL || p[0] != 'y' || p[1] != '\0')
	{
		if (addr != NULL)
			addr[0] = '\0';
		if (opts != NULL)
			opts[0] = '\0';
		if (smtp != NULL)
			smtp[0] = '\0';
		if (pct != NULL)
			*pct = (unsigned long) -1;

		return DKIM_STAT_OK;
	}

	/* see if we've grabbed this set already */
	for (set = dkim_set_first(dkim, DKIM_SETTYPE_SIGREPORT);
	     set != NULL;
	     set = dkim_set_next(set, DKIM_SETTYPE_SIGREPORT))
	{
		if (set->set_name != NULL &&
		    strcasecmp(set->set_name, sdomain) == 0)
			break;
	}

	/* guess not; go to the DNS to get reporting parameters */
	if (set == NULL)
	{
		timeout.tv_sec = dkim->dkim_timeout;
		timeout.tv_usec = 0;

		memset(buf, '\0', sizeof buf);
		status = dkim_repinfo(dkim, sig, &timeout, buf, sizeof buf);
		if (status != DKIM_STAT_OK)
			return status;
		if (buf[0] == '\0')
			return DKIM_STAT_OK;

		set = dkim_process_set(dkim, DKIM_SETTYPE_SIGREPORT,
		                          buf, strlen(buf), NULL, FALSE,
		                          sdomain, NULL);
		if (set == NULL)
			return DKIM_STAT_NORESOURCE;

		if (set->set_bad)
			return DKIM_STAT_SYNTAX;
	}

	if (addr != NULL)
	{
		p = dkim_param_get(set, "ra");
		if (p != NULL)
		{
			memset(addr, '\0', addrlen);
			(void) dkim_qp_decode(p, addr, addrlen - 1);
			p = strchr(addr, '@');
			if (p != NULL)
				*p = '\0';
		}
	}

	if (opts != NULL)
	{
		p = dkim_param_get(set, "ro");
		if (p != NULL)
			strlcpy(opts, p, optslen);
	}

	if (smtp != NULL)
	{
		p = dkim_param_get(set, "rs");
		if (p != NULL)
		{
			memset(smtp, '\0', smtplen);
			(void) dkim_qp_decode(p, smtp, smtplen - 1);
		}
	}

	if (pct != NULL)
	{
		p = dkim_param_get(set, "rp");
		if (p != NULL)
		{
			unsigned long out;
			char *q;

			out = strtoul(p, &q, 10);
			if (*q == '\0')
				*pct = out;
		}
	}

	return DKIM_STAT_OK;
}

/*
**  DKIM_SIG_GETIDENTITY -- retrieve identity of the signer
**
**  Parameters:
**  	dkim -- DKIM handle
**  	sig -- DKIM_SIGINFO handle (or NULL to choose final one)
**  	val -- destination buffer
**  	vallen -- size of destination buffer
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_sig_getidentity(DKIM *dkim, DKIM_SIGINFO *sig, char *val, size_t vallen)
{
	size_t len;
	char *param;
	struct dkim_set *set;

	assert(val != NULL);
	assert(vallen != 0);

	if (sig == NULL)
	{
		if (dkim == NULL)
			return DKIM_STAT_INVALID;

		sig = dkim->dkim_signature;
		if (sig == NULL)
			return DKIM_STAT_INVALID;
	}

	set = sig->sig_taglist;

	param = (char *) dkim_param_get(set, "i");
	if (param == NULL)
	{
		param = dkim_param_get(set, "d");
		if (param == NULL)
			return DKIM_STAT_INTERNAL;

		len = snprintf(val, vallen, "@%s", param);

		return (len < vallen ? DKIM_STAT_OK : DKIM_STAT_NORESOURCE);
	}
	else
	{
		len = dkim_qp_decode(param, val, vallen - 1);

		if (len == (size_t)-1)
		{
			return DKIM_STAT_SYNTAX;
		}
		else if (len >= vallen)
		{
			return DKIM_STAT_NORESOURCE;
		}
		else
		{
			val[len] = '\0';
			return DKIM_STAT_OK;
		}
	}
}

/*
**  DKIM_SIG_GETCANONLEN -- return canonicalized and total body lengths
**
**  Parameters:
**  	dkim -- DKIM handle
**  	sig -- DKIM_SIGINFO handle
**  	msglen -- total body length (returned)
**  	canonlen -- total canonicalized length (returned)
**  	signlen -- maximum signed length (returned)
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_sig_getcanonlen(DKIM *dkim, DKIM_SIGINFO *sig, ssize_t *msglen,
                     ssize_t *canonlen, ssize_t *signlen)
{
	assert(dkim != NULL);
	assert(sig != NULL);

	if (msglen != NULL)
		*msglen = dkim->dkim_bodylen;

	if (canonlen != NULL)
	{
		if (sig->sig_bodycanon == NULL)
			return DKIM_STAT_INTERNAL;
		*canonlen = sig->sig_bodycanon->canon_wrote;
	}

	if (signlen != NULL)
	{
		if (sig->sig_bodycanon == NULL)
			return DKIM_STAT_INTERNAL;
		*signlen = sig->sig_bodycanon->canon_length;
	}

	return DKIM_STAT_OK;
}

/*
**  DKIM_SIG_GETFLAGS -- retreive signature handle flags
**
**  Parameters:
**  	sig -- DKIM_SIGINFO handle
**
**  Return value:
**  	An unsigned integer which is a bitwise-OR of the DKIM_SIGFLAG_*
**  	constants currently set in the provided handle.
*/

unsigned int
dkim_sig_getflags(DKIM_SIGINFO *sig)
{
	assert(sig != NULL);

	return sig->sig_flags;
}

/*
**  DKIM_SIG_GETBH -- retreive signature handle "bh" test state
**
**  Parameters:
**  	sig -- DKIM_SIGINFO handle
**
**  Return value:
**  	An integer that is one of the DKIM_SIGBH_* constants
**  	indicating the current state of "bh" evaluation of the signature.
*/

int
dkim_sig_getbh(DKIM_SIGINFO *sig)
{
	assert(sig != NULL);

	return sig->sig_bh;
}

/*
**  DKIM_SIG_GETKEYSIZE -- retrieve key size (in bits) when verifying
**
**  Parameters:
**  	sig -- DKIM_SIGINFO handle
**  	bits -- number of bits in the key (returned)
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_sig_getkeysize(DKIM_SIGINFO *sig, unsigned int *bits)
{
	assert(sig != NULL);
	assert(bits != NULL);

	if (sig->sig_keybits == 0 &&
		sig->sig_signalg != DKIM_SIGN_ED25519SHA256)
			return DKIM_STAT_INVALID;

	*bits = sig->sig_keybits;

	return DKIM_STAT_OK;
}

/*
**  DKIM_SIG_GETSIGNALG -- retrieve signature algorithm when verifying
**
**  Parameters:
**  	sig -- DKIM_SIGINFO handle
**  	alg -- signature algorithm used (returned)
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_sig_getsignalg(DKIM_SIGINFO *sig, dkim_alg_t *alg)
{
	assert(sig != NULL);
	assert(alg != NULL);

	*alg = sig->sig_signalg;

	return DKIM_STAT_OK;
}

/*
**  DKIM_GETSIGNALG -- retrieve signature algorithm when signing
**
**  Parameters:
**  	dkim -- DKIM handle
**
**  Return value:
**  	A char const* constant.
*/

char const *
dkim_getsignalg(DKIM *dkim)
{
	assert(dkim != NULL);

	return dkim_code_to_name(algorithms, dkim->dkim_signalg);
}

/*
**  DKIM_SIG_GETSIGNTIME -- retrieve signature timestamp
**
**  Parameters:
**  	sig -- DKIM_SIGINFO handle
**  	when -- signature timestamp (returned)
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_sig_getsigntime(DKIM_SIGINFO *sig, uint64_t *when)
{
	assert(sig != NULL);
	assert(when != NULL);

	if (sig->sig_timestamp == 0)
		return DKIM_STAT_INVALID;

	*when = sig->sig_timestamp;

	return DKIM_STAT_OK;
}

/*
**  DKIM_SIG_GETCANONS -- retrieve canonicalizations used when signing
**
**  Parameters:
**  	sig -- DKIM_SIGINFO handle from which to retrieve canonicalizations
**  	hdr -- Pointer to a dkim_canon_t where the header canonicalization
**             should be stored
**  	body -- Pointer to a dkim_canon_t where the body canonicalization
**              should be stored
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_sig_getcanons(DKIM_SIGINFO *sig, dkim_canon_t *hdr, dkim_canon_t *body)
{
	assert(sig != NULL);

	if (hdr != NULL)
		*hdr = sig->sig_hdrcanonalg;
	if (body != NULL)
		*body = sig->sig_bodycanonalg;

	return DKIM_STAT_OK;
}

/*
**  DKIM_GET_SIGNER -- get DKIM signature's signer
**
**  Parameters:
**  	dkim -- DKIM signing handle
**
**  Parameters:
**  	Pointer to a buffer containing the signer previously requested,
**  	or NULL if none.
*/

const char *
dkim_get_signer(DKIM *dkim)
{
	assert(dkim != NULL);

	return dkim->dkim_signer;
}

/*
**  DKIM_SET_SIGNER -- set DKIM signature's signer
**
**  Parameters:
**  	dkim -- DKIM signing handle
**  	signer -- signer to store
**
**  Parameters:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_set_signer(DKIM *dkim, char const *signer)
{
	assert(dkim != NULL);
	assert(signer != NULL);

	if (dkim->dkim_mode != DKIM_MODE_SIGN)
		return DKIM_STAT_INVALID;

	if (dkim->dkim_signer == NULL)
	{
		dkim->dkim_signer = DKIM_MALLOC(dkim, MAXADDRESS + 1);
		if (dkim->dkim_signer == NULL)
		{
			return DKIM_STAT_NORESOURCE;
		}
	}

	strlcpy((char *) dkim->dkim_signer, signer, MAXADDRESS + 1);

	return DKIM_STAT_OK;
}

/*
**  DKIM_CLEARERROR -- free error
**
**  Parameters:
**  	dkim -- DKIM handle from which to retrieve an error string
**
*/

void
dkim_clearerror(DKIM *dkim)
{
	assert(dkim != NULL);

	DKIM_FREE(dkim, dkim->dkim_error);
	dkim->dkim_error = NULL;
}

/*
**  DKIM_GETERROR -- return any stored error string from within the DKIM
**                   context handle
**
**  Parameters:
**  	dkim -- DKIM handle from which to retrieve an error string
**
**  Return value:
**  	A pointer to the stored string, or NULL if none was stored.
*/

const char *
dkim_geterror(DKIM *dkim)
{
	assert(dkim != NULL);

	return (const char *) dkim->dkim_error;
}

/*
**  DKIM_GETPARTIAL -- return if the DKIM handle is to be signed using
**                     the bodylength tag (l=)
**
**  Parameters:
**      dkim -- DKIM handle
**
**  Return value:
**      True iff the signature is to include a body length tag
*/

_Bool
dkim_getpartial(DKIM *dkim)
{
	assert(dkim != NULL);

	return dkim->dkim_partial;
}

/*
**  DKIM_SETPARTIAL -- set the DKIM handle to sign using the DKIM body length
**                     tag (l=)
**
**  Parameters:
**      dkim -- DKIM handle
**      value -- new Boolean value
**
**  Return value:
**      DKIM_STAT_INVALID -- "dkim" referenced a verification handle
**      DKIM_STAT_OK -- otherwise
*/

DKIM_STAT
dkim_setpartial(DKIM *dkim, _Bool value)
{
	assert(dkim != NULL);

	if (dkim->dkim_mode != DKIM_MODE_SIGN)
		return DKIM_STAT_INVALID;

	dkim->dkim_partial = value;

	return DKIM_STAT_OK;
}

/*
**  DKIM_SET_MARGIN -- set the margin to use when generating signatures
**
**  Parameters:
**      dkim -- DKIM handle
**      value -- new margin value
**
**  Return value:
**      DKIM_STAT_INVALID -- "dkim" referenced a verification handle,
**  	                     "value" was negative, or this is being called
**  	                     after dkim_eom() completed
**      DKIM_STAT_OK -- otherwise
*/

DKIM_STAT
dkim_set_margin(DKIM *dkim, int value)
{
	assert(dkim != NULL);

	if (dkim->dkim_mode != DKIM_MODE_SIGN || value < 0 ||
	    dkim->dkim_state >= DKIM_STATE_EOM2)
		return DKIM_STAT_INVALID;

	dkim->dkim_margin = (size_t) value;

	return DKIM_STAT_OK;
}

/*
**  DKIM_GETRESULTSTR -- translate a DKIM_STAT_* constant to a string
**
**  Parameters:
**  	result -- DKIM_STAT_* constant to translate
**
**  Return value:
**  	Pointer to a text describing "result", or NULL if none exists
*/

const char *
dkim_getresultstr(DKIM_STAT result)
{
	return dkim_code_to_name(results, result);
}

/*
**  DKIM_SET_DNS_CALLBACK -- set the DNS wait callback
**
**  Parameters:
**  	libopendkim -- DKIM library handle
**  	func -- function to call; should take an opaque context pointer
**  	interval -- how often to call back
**
**  Return value:
**  	DKIM_STAT_OK -- success
**  	DKIM_STAT_INVALID -- invalid use
**  	DKIM_STAT_NOTIMPLEMENT -- underlying resolver doesn't support callbacks
*/

DKIM_STAT
dkim_set_dns_callback(DKIM_LIB *libopendkim, void (*func)(const void *context),
                      unsigned int interval)
{
	assert(libopendkim != NULL);

	if (func != NULL && interval == 0)
		return DKIM_STAT_INVALID;

	libopendkim->dkiml_dns_callback = func;
	libopendkim->dkiml_callback_int = interval;

	return DKIM_STAT_OK;
}

/*
**  DKIM_SET_USER_CONTEXT -- set user context pointer
**
**  Parameters:
**  	dkim -- DKIM handle
**  	ctx -- opaque context pointer
**
**  Return value:
**  	DKIM_STAT_OK
*/

DKIM_STAT
dkim_set_user_context(DKIM *dkim, void *ctx)
{
	assert(dkim != NULL);

	dkim->dkim_user_context = (const void *) ctx;

	return DKIM_STAT_OK;
}

/*
**  DKIM_GET_USER_CONTEXT -- get user context pointer
**
**  Parameters:
**  	dkim -- DKIM handle
**
**  Return value:
**  	User context associated with a DKIM handle
*/

void *
dkim_get_user_context(DKIM *dkim)
{
	assert(dkim != NULL);

	return (void *) dkim->dkim_user_context;
}

/*
**  DKIM_GETMODE -- return the mode (signing, verifying, etc.) of a handle
**
**  Parameters:
**  	dkim -- DKIM handle
**
**  Return value:
**  	A DKIM_MODE_* constant.
*/

int
dkim_getmode(DKIM *dkim)
{
	assert(dkim != NULL);

	return dkim->dkim_mode;
}

/*
**  DKIM_GETDOMAIN -- retrieve policy domain from a DKIM context
**
**  Parameters:
**  	dkim -- DKIM handle
**
**  Return value:
**  	Pointer to the domain used for policy checking or NULL if no domain
**  	could be determined.
*/

char *
dkim_getdomain(DKIM *dkim)
{
	assert(dkim != NULL);

	return dkim->dkim_domain;
}

/*
**  DKIM_GETUSER -- retrieve sending user (local-part) from a DKIM context
**
**  Parameters:
**  	dkim -- DKIM handle
**
**  Return value:
**  	Pointer to the apparent sending user (local-part) or NULL if not known.
*/

char *
dkim_getuser(DKIM *dkim)
{
	assert(dkim != NULL);

	return dkim->dkim_user;
}

/*
**  DKIM_SET_KEY_LOOKUP -- set the key lookup function
**
**  Parameters:
**  	libopendkim -- DKIM library handle
**  	func -- function to call
**
**  Return value:
**  	DKIM_STAT_OK
*/

DKIM_STAT
dkim_set_key_lookup(DKIM_LIB *libopendkim,
                    DKIM_STAT (*func)(DKIM *dkim, DKIM_SIGINFO *sig,
                                      char *buf, size_t buflen))
{
	assert(libopendkim != NULL);

	libopendkim->dkiml_key_lookup = func;

	return DKIM_STAT_OK;
}

/*
**  DKIM_SET_KEY_RETRIEVED -- set the key retrieved function
**
**  Parameters:
**  	libopendkim -- DKIM library handle
**  	func -- function to call
**
**  Return value:
**  	DKIM_STAT_OK
*/

DKIM_STAT
dkim_set_key_retrieved(DKIM_LIB *libopendkim,
	void (*func)(DKIM *dkim, DKIM_STAT status, DKIM_SIGINFO *sig))
{
	assert(libopendkim != NULL);

	libopendkim->dkiml_key_retrieved = func;

	return DKIM_STAT_OK;
}

/*
**  DKIM_SET_SIGNATURE_HANDLE -- set the user handle allocation function
**
**  Parameters:
**  	libopendkim -- DKIM library handle
**  	func -- function to call
**
**  Return value:
**  	DKIM_STAT_OK -- success
*/

DKIM_STAT
dkim_set_signature_handle(DKIM_LIB *libopendkim, void * (*func)(void *closure))
{
	assert(libopendkim != NULL);

//	libopendkim->dkiml_sig_handle = func;
//	return DKIM_STAT_OK;
	return DKIM_STAT_NOTIMPLEMENT;
	(void)func;
	(void)libopendkim;
}

/*
**  DKIM_SET_SIGNATURE_HANDLE_FREE -- set the user handle deallocation function
**
**  Parameters:
**  	libopendkim -- DKIM library handle
**  	func -- function to call
**
**  Return value:
**  	DKIM_STAT_OK
*/

DKIM_STAT
dkim_set_signature_handle_free(DKIM_LIB *libopendkim,
                               void (*func)(void *closure, void *user))
{
	assert(libopendkim != NULL);

//	libopendkim->dkiml_sig_handle_free = func;
//	return DKIM_STAT_OK;
	return DKIM_STAT_NOTIMPLEMENT;
	(void)func;
	(void)libopendkim;
}

/*
**  DKIM_SET_SIGNATURE_TAGVALUES -- set the user handle population function
**
**  Parameters:
**  	libopendkim -- DKIM library handle
**  	func -- function to call
**
**  Return value:
**  	DKIM_STAT_OK
*/

DKIM_STAT
dkim_set_signature_tagvalues(DKIM_LIB *libopendkim, void (*func)(void *user,
                                                                 dkim_param_t pcode,
                                                                 const char *param,
                                                                 const char *value))
{
	assert(libopendkim != NULL);

//	libopendkim->dkiml_sig_tagvalues = func;
//	return DKIM_STAT_OK;
	return DKIM_STAT_NOTIMPLEMENT;
	(void)func;
	(void)libopendkim;
}

/*
**  DKIM_SET_PRESCREEN -- set the user prescreen function
**
**  Parameters:
**  	libopendkim -- DKIM library handle
**  	func -- function to call
**
**  Return value:
**  	DKIM_STAT_OK
*/

DKIM_STAT
dkim_set_prescreen(DKIM_LIB *libopendkim, DKIM_CBSTAT (*func)(DKIM *dkim,
                                                              DKIM_SIGINFO **sigs,
                                                              int nsigs))
{
	assert(libopendkim != NULL);

	libopendkim->dkiml_prescreen = func;

	return DKIM_STAT_OK;
}

/*
**  DKIM_SET_FINAL -- set the user final scan function
**
**  Parameters:
**  	libopendkim -- DKIM library handle
**  	func -- function to call
**
**  Return value:
**  	DKIM_STAT_OK
*/

DKIM_STAT
dkim_set_final(DKIM_LIB *libopendkim, DKIM_CBSTAT (*func)(DKIM *dkim,
                                                          DKIM_SIGINFO **sigs,
                                                          int nsigs))
{
	assert(libopendkim != NULL);

	libopendkim->dkiml_final = func;

	return DKIM_STAT_OK;
}

/*
**  DKIM_SIG_GETCONTEXT -- retrieve user-provided context from a DKIM_SIGINFO
**
**  Parameters:
**  	siginfo -- pointer to a DKIM_SIGINFO from which to extract context
**
**  Return value:
**  	Pointer to the user context provided by an earlier call to the
**  	handle allocator (see above), or NULL if none was ever set.
*/

void *
dkim_sig_getcontext(DKIM_SIGINFO *siginfo)
{
	assert(siginfo != NULL);

	return siginfo->sig_context;
}

/*
**  DKIM_SIG_GETSELECTOR -- retrieve selector from a DKIM_SIGINFO
**
**  Parameters:
**  	siginfo -- pointer to a DKIM_SIGINFO from which to extract the selector
**
**  Return value:
**  	Pointer to the selector associated with the DKIM_SIGINFO.
*/

char *
dkim_sig_getselector(DKIM_SIGINFO *siginfo)
{
	assert(siginfo != NULL);

	return siginfo->sig_selector;
}

/*
**  DKIM_SIG_GETDOMAIN -- retrieve domain from a DKIM_SIGINFO
**
**  Parameters:
**  	siginfo -- pointer to a DKIM_SIGINFO from which to extract the domain
**
**  Return value:
**  	Pointer to the domain associated with the DKIM_SIGINFO.
*/

char *
dkim_sig_getdomain(DKIM_SIGINFO *siginfo)
{
	assert(siginfo != NULL);

	return siginfo->sig_domain;
}

 /*
**  DKIM_SIG_GETALGORITHM -- retrieve algorithm from a DKIM_SIGINFO
**
**  Parameters:
**  	siginfo -- pointer to a DKIM_SIGINFO from which to extract the
**  	           algorithm
**
**  Return value:
**  	Pointer to the algorithm associated with the DKIM_SIGINFO.
*/

char const*
dkim_sig_getalgorithm(DKIM_SIGINFO *siginfo)
{
	assert(siginfo != NULL);

	return dkim_code_to_name(algorithms, siginfo->sig_signalg);
}

/*
**  DKIM_SIG_SETERROR -- set an error code in a DKIM_SIGINFO
**
**  Parameters:
**  	siginfo -- pointer to a DKIM_SIGINFO from which to extract context
**  	err -- error code to store
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_sig_seterror(DKIM_SIGINFO *siginfo, int err)
{
	assert(siginfo != NULL);

// -ale: allow resetting
//	if (siginfo->sig_error != DKIM_SIGERROR_UNKNOWN)
//		return DKIM_STAT_INVALID;

	siginfo->sig_error = err;

	return DKIM_STAT_OK;
}

/*
**  DKIM_SIG_GETERROR -- retrieve an error code from a DKIM_SIGINFO
**
**  Parameters:
**  	siginfo -- pointer to a DKIM_SIGINFO from which to extract context
**
**  Return value:
**  	A DKIM_SIGERROR_* constant.
*/

int
dkim_sig_geterror(DKIM_SIGINFO *siginfo)
{
	assert(siginfo != NULL);

	return siginfo->sig_error;
}

/*
**  DKIM_SIG_GETERRORSTR -- translate a DKIM_SIGERROR_* constant to a string
**
**  Parameters:
**  	sigerr -- DKIM_SIGERROR_* constant to translate
**
**  Return value:
**  	A pointer to a human-readable expression of "sigerr", or NULL if none
**  	exists.
*/

const char *
dkim_sig_geterrorstr(DKIM_SIGERROR sigerr)
{
	return dkim_code_to_name(sigerrors, sigerr);
}

/*
**  DKIM_SIG_IGNORE -- mark a signature referenced by a DKIM_SIGINFO with
**                     an "ignore" flag
**
**  Parameters:
**  	siginfo -- pointer to a DKIM_SIGINFO to update
**
**  Return value:
**  	None.
*/

void
dkim_sig_ignore(DKIM_SIGINFO *siginfo)
{
	assert(siginfo != NULL);

	siginfo->sig_flags |= DKIM_SIGFLAG_IGNORE;
}


/*
**  DKIM_SSL_VERSION -- return version of OpenSSL that was used to build
**                      the library
**
**  Parameters:
**  	char cont ** lnk -- output the linked version string.
**
**  Return value:
**  	The constant OPENSSL_VERSION_NUMBER as defined by OpenSSL.
*/

unsigned long
dkim_ssl_version(char const **lnk)
{
#ifdef USE_GNUTLS
	if (lnk)
		*lnk = gnutls_check_version(NULL);
	return GNUTLS_VERSION_NUMBER;
#else /* USE_GNUTLS */
	if (lnk)
		*lnk = SSLeay_version(SSLEAY_VERSION);
	return OPENSSL_VERSION_NUMBER;
#endif /* USE_GNUTLS */
}

/*
**  DKIM_FLUSH_CACHE -- purge expired records from the cache
**
**  Parameters:
**  	lib -- DKIM library handle, returned by dkim_init()
**
**  Return value:
**  	-1 -- caching is not in effect
**  	>= 0 -- number of purged records
*/

int
dkim_flush_cache(DKIM_LIB *lib)
{
#ifdef QUERY_CACHE
	int err;
#endif /* QUERY_CACHE */

	assert(lib != NULL);

#ifdef QUERY_CACHE
	if (lib->dkiml_cache == NULL)
		return -1;

	return dkim_cache_expire(lib->dkiml_cache, 0, &err);
#else /* QUERY_CACHE */
	return -1;
#endif /* QUERY_CACHE */
}

/*
**  DKIM_GETCACHESTATS -- retrieve cache statistics
**
**  Parameters:
**  	lib -- DKIM library handle, returned by dkim_init()
**  	queries -- number of queries handled (returned)
**  	hits -- number of cache hits (returned)
**  	expired -- number of expired hits (returned)
**  	keys -- number of keys in the cache (returned)
**  	reset -- if TRUE, resets the queries, hits, and expired counters
**
**  Return value:
**  	DKIM_STAT_OK -- request completed
**  	DKIM_STAT_INVALID -- cache not initialized
**  	DKIM_STAT_NOTIMPLEMENT -- function not implemented
**
**  Notes:
**  	Any of the parameters may be NULL if the corresponding datum
**  	is not of interest.
*/

DKIM_STAT
dkim_getcachestats(DKIM_LIB *lib, u_int *queries, u_int *hits, u_int *expired,
                   u_int *keys, _Bool reset)
{
#ifdef QUERY_CACHE
	assert(lib != NULL);

	if (lib->dkiml_cache == NULL)
		return DKIM_STAT_INVALID;

	dkim_cache_stats(lib->dkiml_cache, queries, hits, expired, keys, reset);

	return DKIM_STAT_OK;
#else /* QUERY_CACHE */
	return DKIM_STAT_NOTIMPLEMENT;
	(void)lib;
	(void)queries;
	(void)hits;
	(void)expired;
	(void)keys;
	(void)reset;
#endif /* QUERY_CACHE */
}

/*
**  DKIM_GET_SIGSUBSTRING -- retrieve a minimal signature substring for
**                           disambiguation
**
**  Parameters:
**  	dkim -- DKIM handle
**  	sig -- DKIM_SIGINFO handle
**  	buf -- buffer into which to put the substring
**  	buflen -- bytes available at "buf"
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_get_sigsubstring(DKIM *dkim, DKIM_SIGINFO *sig, char *buf, size_t *buflen)
{
	unsigned int c;
	unsigned int d;
	unsigned int x;
	unsigned int b1len;
	unsigned int b2len;
	unsigned int minlen;
	char *b1;
	char *b2;

	assert(dkim != NULL);
	assert(sig != NULL);
	assert(buf != NULL);
	assert(buflen != NULL);

	if (dkim->dkim_minsiglen == 0)
	{
		dkim->dkim_minsiglen = MINSIGLEN;

		for (c = 0; c < dkim->dkim_sigcount - 1; c++)
		{
			b1 = (char *) dkim_param_get(dkim->dkim_siglist[c]->sig_taglist, "b");
			if (b1 == NULL)
				continue;

			b1len = strlen(b1);

			for (d = c + 1; d < dkim->dkim_sigcount; d++)
			{
				b2 = (char *) dkim_param_get(dkim->dkim_siglist[d]->sig_taglist, "b");
				if (b2 == NULL)
					continue;

				if (strcmp(b1, b2) == 0)
					break;

				if (strncmp(b1, b2, dkim->dkim_minsiglen) != 0)
					continue;

				b2len = strlen(b2);

				minlen = MIN(b1len, b2len);

				for (x = dkim->dkim_minsiglen; x < minlen; x++)
				{
					if (b1[x] != b2[x])
						break;
				}

				dkim->dkim_minsiglen = x + 1;
			}
		}
	}

	b1 = (char *) dkim_param_get(sig->sig_taglist, "b");
	if (b1 == NULL)
		return DKIM_STAT_SYNTAX;

	minlen = MIN(*buflen, dkim->dkim_minsiglen);
	strncpy(buf, b1, minlen);
	if (minlen < *buflen)
		buf[minlen] = '\0';
	*buflen = minlen;

	return DKIM_STAT_OK;
}

/*
** -ale:
**  DKIM_GET_SAMESIG -- find the signature equal to the one given
**
**  Parameters:
**  	dkim -- DKIM handle
**  	sig -- DKIM_SIGINFO handle
**
**  Return value:
**  	A DKIM_SIGINFO* if found, NULL otherwise
*/

DKIM_SIGINFO *
dkim_get_samesig(DKIM const *dkim, DKIM_SIGINFO *sig, int hint)
{
	assert(dkim != NULL);
	assert(sig != NULL);

	unsigned int c;
	size_t sig_siglen;
	char *sig_sig;

	sig_siglen = sig->sig_siglen;
	sig_sig = sig->sig_sig;

	unsigned int const count = dkim->dkim_sigcount;
	for (c = 0; c < count; ++c)
	{
		DKIM_SIGINFO *s = dkim->dkim_siglist[(c + hint)%count];
		if (s->sig_siglen == sig_siglen &&
			memcmp(s->sig_sig, sig_sig, sig_siglen) == 0)
				return s;
	}

	return NULL;
}

/*
**  DKIM_LIBFEATURE -- determine whether or not a particular library feature
**                     is actually available
**
**  Parameters:
**  	lib -- library handle
**  	fc -- feature code to check
**
**  Return value:
**  	TRUE iff the specified feature was compiled in
*/

_Bool
dkim_libfeature(DKIM_LIB *lib, u_int fc)
{
	u_int idx;
	u_int offset;

	idx = fc / (8 * sizeof(int));
	offset = fc % (8 * sizeof(int));

	if (idx > lib->dkiml_flsize)
		return FALSE;
	return ((lib->dkiml_flist[idx] & (1 << offset)) != 0);
}

/*
**  DKIM_LIBVERSION -- return version of libopendkim at runtime
**
**  Parameters:
**  	None.
**
**  Return value:
**  	Library version, i.e. value of the OPENDKIM_LIB_VERSION macro.
*/

uint32_t
dkim_libversion(void)
{
	return OPENDKIM_LIB_VERSION;
}

/*
**  DKIM_SIG_GETTAGVALUE -- retrieve a tag's value from a signature or its key
**
**  Parameters:
**  	sig -- DKIM_SIGINFO handle
**  	keytag -- TRUE iff we want a key's tag
**  	tag -- name of the tag of interest
**
**  Return value:
**  	Pointer to the string containing the value of the requested key,
**  	or NULL if not present.
**
**  Notes:
**  	This was added for use in determining whether or not a key or
**  	signature contained particular data, for gathering general statistics
**  	about DKIM use.  It is not intended to give applications direct access
**  	to unprocessed signature or key data.  The data returned has not
**  	necessarily been vetted in any way.  Caveat emptor.
*/

char *
dkim_sig_gettagvalue(DKIM_SIGINFO *sig, _Bool keytag, char *tag)
{
	DKIM_SET *set;

	assert(sig != NULL);
	assert(tag != NULL);

	if (keytag)
		set = sig->sig_keytaglist;
	else
		set = sig->sig_taglist;

	if (set == NULL)
		return NULL;
	else
		return dkim_param_get(set, tag);
}

// not used (except t-test138) -----
/*
**  DKIM_SIG_GETSIGNEDHDRS -- retrieve the signed header fields covered by
**                            a signature that passed
**
**  Parameters:
**  	dkim -- DKIM instance
**  	sig -- signature
**  	hdrs -- rectangular array of header field strings
**  	hdrlen -- length of each element of "hdrs"
**  	nhdrs -- size of "hdrs" array (updated)
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_sig_getsignedhdrs(DKIM *dkim, DKIM_SIGINFO *sig,
                       char *hdrs, size_t hdrlen, u_int *nhdrs)
{
	unsigned int count;
	u_int n;
	char *h;
	char *p;
	struct dkim_header **sighdrs;

	assert(dkim != NULL);
	assert(sig != NULL);
	assert(nhdrs != NULL);

	if ((sig->sig_flags & DKIM_SIGFLAG_PASSED) == 0 ||
	    sig->sig_bh != DKIM_SIGBH_MATCH)
		return DKIM_STAT_INVALID;

	h = dkim_param_get(sig->sig_taglist, "h");
	assert(h != NULL);

	n = 1;
	for (p = h; *p != 0; p++)
	{
		if (*p == ':')
			n++;
	}

	if (*nhdrs < n)
	{
		*nhdrs = n;
		return DKIM_STAT_NORESOURCE;
	}

	assert(hdrs != NULL);

	count = dkim_canon_selecthdrs(dkim, DKIM_HDR_SIGNED, h, &sighdrs);
	if (count == (u_int)-1)
	{
		if (sighdrs)
		{
			DKIM_FREE(dkim, sighdrs);
			return DKIM_STAT_INTERNAL;
		}
		else
			return DKIM_STAT_NORESOURCE;
	}

	*nhdrs = count;

	for (n = 0; n < count; n++)
		strlcpy(&hdrs[n * hdrlen], sighdrs[n]->hdr_text, hdrlen);

	DKIM_FREE(dkim, sighdrs);

	return DKIM_STAT_OK;
}

/*
**  DKIM_DNS_SET_QUERY_SERVICE -- stores a handle representing the DNS
**                                query service to be used, returning any
**                                previous handle
**
**  Parameters:
**  	lib -- DKIM library handle
**  	h -- handle to be used
**
**  Return value:
**  	Previously stored handle, or NULL if none.
*/

void *
dkim_dns_set_query_service(DKIM_LIB *lib, void *h)
{
	void *old;

	old = lib->dkiml_dns_service;

	lib->dkiml_dns_service = h;

	return old;
}

/*
**  DKIM_DNS_SET_QUERY_START -- stores a pointer to a query start function
**
**  Parameters:
**  	lib -- DKIM library handle
**  	func -- function to use to start queries
**
**  Return value:
**  	None.
**
**  Notes:
**  	"func" should match the following prototype:
**  		returns int (status)
**  		void *dns -- receives handle stored by
**  		             dkim_dns_set_query_service()
**  		int type -- DNS RR query type (C_IN assumed)
**  		char *query -- question to ask
**  		char *buf -- buffer into which to write reply
**  		size_t buflen -- size of buf
**  		void **qh -- returned query handle
*/

void
dkim_dns_set_query_start(DKIM_LIB *lib, int (*func)(void *, int,
                                                    char *,
                                                    unsigned char *,
                                                    size_t, void **))
{
	assert(lib != NULL);

	lib->dkiml_dns_start = func;
}

/*
**  DKIM_DNS_SET_QUERY_CANCEL -- stores a pointer to a query cancel function
**
**  Parameters:
**  	lib -- DKIM library handle
**  	func -- function to use to cancel running queries
**
**  Return value:
**  	None.
**
**  Notes:
**  	"func" should match the following prototype:
**  		returns int (status)
**  		void *dns -- DNS service handle
**  		void *qh -- query handle to be canceled
*/

void
dkim_dns_set_query_cancel(DKIM_LIB *lib, int (*func)(void *, void *))
{
	assert(lib != NULL);

	lib->dkiml_dns_cancel = func;
}

/*
**  DKIM_DNS_SET_INIT -- stores a pointer to a resolver init function
**
**  Parameters:
**  	lib -- DKIM library handle
**  	func -- function to use to initialize the resolver
**
**  Return value:
**  	None.
**
**  Notes:
**  	"func" should match the following prototype:
**  		returns int (status)
**  		void **srv -- DNS service handle (updated)
*/

void
dkim_dns_set_init(DKIM_LIB *lib, int (*func)(void **))
{
	assert(lib != NULL);

	lib->dkiml_dns_init = func;
}

/*
**  DKIM_DNS_SET_CLOSE -- stores a pointer to a resolver shutdown function
**
**  Parameters:
**  	lib -- DKIM library handle
**  	func -- function to use to initialize the resolver
**
**  Return value:
**  	None.
**
**  Notes:
**  	"func" should match the following prototype:
**  		returns void
**  		void *srv -- DNS service handle
*/

void
dkim_dns_set_close(DKIM_LIB *lib, void (*func)(void *))
{
	assert(lib != NULL);

	lib->dkiml_dns_close = func;
}

/*
**  DKIM_DNS_SET_NSLIST -- stores a pointer to a NS list update function
**
**  Parameters:
**  	lib -- DKIM library handle
**  	func -- function to use to update NS list
**
**  Return value:
**  	None.
**
**  Notes:
**  	"func" should match the following prototype:
**  		returns int
**  		void *dns -- DNS service handle
**  		const char *nslist -- comma-separated list of nameservers
*/

void
dkim_dns_set_nslist(DKIM_LIB *lib, int (*func)(void *, const char *))
{
	assert(lib != NULL);

	lib->dkiml_dns_setns = func;
}

/*
**  DKIM_DNS_SET_CONFIG -- stores a pointer to a resolver configuration
**                         function
**
**  Parameters:
**  	lib -- DKIM library handle
**  	func -- function to use to update resolver configuration
**
**  Return value:
**  	None.
**
**  Notes:
**  	"func" should match the following prototype:
**  		returns int
**  		void *dns -- DNS service handle
**  		const char *config -- opaque configuration string
*/

void
dkim_dns_set_config(DKIM_LIB *lib, int (*func)(void *, const char *))
{
	assert(lib != NULL);

	lib->dkiml_dns_config = func;
}

/*
**  DKIM_DNS_SET_TRUSTANCHOR -- stores a pointer to a trust anchor
**                              configuration function
**
**  Parameters:
**  	lib -- DKIM library handle
**  	func -- function to use to update trust anchor configuration
**
**  Return value:
**  	None.
**
**  Notes:
**  	"func" should match the following prototype:
**  		returns int
**  		void *dns -- DNS service handle
**  		const char *trustanchor -- opaque trust anchor string
*/

void
dkim_dns_set_trustanchor(DKIM_LIB *lib, int (*func)(void *, const char *))
{
	assert(lib != NULL);

	lib->dkiml_dns_trustanchor = func;
}

/*
**  DKIM_DNS_SET_QUERY_WAITREPLY -- stores a pointer to wait for a DNS reply
**
**  Parameters:
**  	lib -- DKIM library handle
**  	func -- function to use to wait for a reply
**
**  Return value:
**  	None.
**
**  Notes:
**  	"func" should match the following prototype:
**  		returns int (status)
**  		void *dns -- DNS service handle
**  		void *qh -- handle of query that has completed
**  		struct timeval *timeout -- how long to wait
**  		size_t *bytes -- bytes returned
**  		int *error -- error code returned
**  		int *dnssec -- DNSSEC status returned
*/

void
dkim_dns_set_query_waitreply(DKIM_LIB *lib, int (*func)(void *, void *,
                                                        struct timeval *,
                                                        size_t *, int *,
                                                        int *))
{
	assert(lib != NULL);

	lib->dkiml_dns_waitreply = func;
}

/*
**  DKIM_DNS_NSLIST -- requests update to a nameserver list
**
**  Parameters:
**  	lib -- DKIM library handle
**  	nslist -- comma-separated list of nameservers to use
**
**  Return value:
**  	A DKIM_DNS_* constant.
*/

int
dkim_dns_nslist(DKIM_LIB *lib, const char *nslist)
{
	int status;

	assert(lib != NULL);
	assert(nslist != NULL);

	if (lib->dkiml_dns_setns != NULL)
	{
		status = lib->dkiml_dns_setns(lib->dkiml_dns_service, nslist);
		if (status != 0)
			return DKIM_DNS_ERROR;
	}

	return DKIM_DNS_SUCCESS;
}

/*
**  DKIM_DNS_INIT -- force nameserver (re)initialization
**
**  Parameters:
**  	lib -- DKIM library handle
**
**  Return value:
**  	A DKIM_DNS_* constant.
*/

int
dkim_dns_init(DKIM_LIB *lib)
{
	int status;

	assert(lib != NULL);

	if (lib->dkiml_dnsinit_done)
		return DKIM_DNS_INVALID;

	if (lib->dkiml_dns_close != NULL && lib->dkiml_dns_service != NULL)
	{
		lib->dkiml_dns_close(lib->dkiml_dns_service);
		lib->dkiml_dns_service = NULL;
	}

	if (lib->dkiml_dns_init != NULL)
		status = lib->dkiml_dns_init(&lib->dkiml_dns_service);
	else
		status = DKIM_DNS_SUCCESS;

	if (status == DKIM_DNS_SUCCESS)
		lib->dkiml_dnsinit_done = TRUE;

	return status;
}

/*
**  DKIM_DNS_CLOSE -- force nameserver shutdown
**
**  Parameters:
**  	lib -- DKIM library handle
**
**  Return value:
**  	A DKIM_DNS_* constant.
*/

int
dkim_dns_close(DKIM_LIB *lib)
{
	assert(lib != NULL);

	if (lib->dkiml_dnsinit_done &&
	    lib->dkiml_dns_close != NULL &&
	    lib->dkiml_dns_service != NULL)
	{
		lib->dkiml_dns_close(lib->dkiml_dns_service);
		lib->dkiml_dns_service = NULL;
	}

	lib->dkiml_dnsinit_done = FALSE;

	return DKIM_DNS_SUCCESS;
}

/*
**  DKIM_DNS_CONFIG -- requests a change to resolver configuration
**
**  Parameters:
**  	lib -- DKIM library handle
**  	config -- opaque configuration string
**
**  Return value:
**  	A DKIM_DNS_* constant.
*/

int
dkim_dns_config(DKIM_LIB *lib, const char *config)
{
	int status;

	assert(lib != NULL);
	assert(config != NULL);

	if (lib->dkiml_dns_config != NULL)
	{
		status = lib->dkiml_dns_config(lib->dkiml_dns_service, config);
		if (status != 0)
			return DKIM_DNS_ERROR;
	}

	return DKIM_DNS_SUCCESS;
}

/*
**  DKIM_DNS_TRUSTANCHOR -- requests a change to resolver trust anchor data
**
**  Parameters:
**  	lib -- DKIM library handle
**  	trust -- opaque trust anchor string
**
**  Return value:
**  	A DKIM_DNS_* constant.
*/

int
dkim_dns_trustanchor(DKIM_LIB *lib, const char *trust)
{
	int status;

	assert(lib != NULL);
	assert(trust != NULL);

	if (lib->dkiml_dns_trustanchor != NULL)
	{
		status = lib->dkiml_dns_trustanchor(lib->dkiml_dns_service,
		                                    trust);
		if (status != 0)
			return DKIM_DNS_ERROR;
	}

	return DKIM_DNS_SUCCESS;
}

/*
**  DKIM_ADD_QUERYMETHOD -- add a query method
**
**  Parameters:
**  	dkim -- DKIM signing handle to extend
**  	type -- type of query to add
**  	options -- options to include
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_add_querymethod(DKIM *dkim, const char *type, const char *options)
{
	u_char *p;
	struct dkim_qmethod *q;
	struct dkim_qmethod *lastq;
	char tmp[BUFRSZ + 1];

	assert(dkim != NULL);
	assert(type != NULL);

	if (dkim->dkim_mode != DKIM_MODE_SIGN)
		return DKIM_STAT_INVALID;

	/* confirm valid syntax, per RFC6376 */
	for (p = (u_char *) type; *p != '\0'; p++)
	{
		if (!(isascii(*p) && (isalpha(*p) ||
				      (p != (u_char *) type &&
				       (isalnum(*p) ||
					(*(p+1) != '\0' && *p == '-'))))))
			return DKIM_STAT_INVALID;
	}

	/* do dkim-qp-encode step */
	if (options != NULL)
	{
		int len;

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

		len = dkim_qp_encode(options, tmp, sizeof tmp);
		if (len == -1)
		{
			dkim_error(dkim, "can't encode query options",
			           sizeof(struct dkim_qmethod));
			return DKIM_STAT_NORESOURCE;
		}
	}

	/* check for duplicates */
	lastq = NULL;
	for (q = dkim->dkim_querymethods; q != NULL; q = q->qm_next)
	{
		lastq = q;
		if (strcasecmp(q->qm_type, type) == 0 &&
		    ((q->qm_options == NULL && options == NULL) ||
		     (q->qm_options != NULL && options != NULL &&
		      strcasecmp(q->qm_options, tmp) == 0)))
			return DKIM_STAT_INVALID;
	}

	q = (struct dkim_qmethod *) DKIM_MALLOC(dkim,
	                                        sizeof(struct dkim_qmethod));
	if (q == NULL)
	{
		return DKIM_STAT_NORESOURCE;
	}

	q->qm_type = dkim_strdup(dkim, type, 0);
	if (q->qm_type == NULL)
	{
		DKIM_FREE(dkim, q);
		return DKIM_STAT_NORESOURCE;
	}
		
	if (options != NULL)
	{
		q->qm_options = dkim_strdup(dkim, tmp, 0);
		if (q->qm_options == NULL)
		{
			DKIM_FREE(dkim, q->qm_type);
			DKIM_FREE(dkim, q);
			return DKIM_STAT_NORESOURCE;
		}
	}
	else
	{
		q->qm_options = NULL;
	}

	q->qm_next = NULL;

	if (lastq == NULL)
		dkim->dkim_querymethods = q;
	else
		lastq->qm_next = q;

	return DKIM_STAT_OK;
}

/*
**  DKIM_ADD_XTAG -- add an extension tag/value
**
**  Parameters:
**  	dkim -- DKIM signing handle to extend
**  	tag -- name of tag to add
**  	value -- value to include
**
**  Return value:
**  	A DKIM_STAT_* constant.
**
**  Notes:
**  	A value that contains spaces won't be wrapped nicely by the signature
**  	generation code.  Support for this should be added later.
*/

DKIM_STAT
dkim_add_xtag(DKIM *dkim, const char *tag, const char *value)
{
	u_char last = '\0';
	dkim_param_t pcode;
	u_char *p;
	struct dkim_xtag *x;

	assert(dkim != NULL);
	assert(tag != NULL);
	assert(value != NULL);

	if (dkim->dkim_mode != DKIM_MODE_SIGN)
		return DKIM_STAT_INVALID;

	/* check that it's not in sigparams */
	if (tag[0] == '\0' || value[0] == '\0')
		return DKIM_STAT_INVALID;
	pcode = dkim_name_to_code(sigparams, tag);
	if (pcode != (dkim_param_t) -1)
		return DKIM_STAT_INVALID;

	/* confirm valid syntax, per RFC6376 */
	for (p = (u_char *) tag; *p != '\0'; p++)
	{
		if (!(isascii(*p) && (isalnum(*p) || *p == '_')))
			return DKIM_STAT_INVALID;
	}

	if (value[0] == '\n' ||
	    value[0] == '\r' ||
	    value[0] == '\t' ||
	    value[0] == ' ')
		return DKIM_STAT_INVALID;

	for (p = (u_char *) value; *p != '\0'; p++)
	{
		/* valid characters in general */
		if (!(*p == '\n' ||
		      *p == '\r' ||
		      *p == '\t' ||
		      *p == ' ' ||
		      (*p >= 0x21 && *p <= 0x7e && *p != 0x3b)))
			return DKIM_STAT_INVALID;

		/* CR has to be followed by LF */
		if (last == '\r' && *p != '\n')
			return DKIM_STAT_INVALID;

		/* LF has to be followed by space or tab */
		if (last == '\n' && *p != ' ' && *p != '\t')
			return DKIM_STAT_INVALID;

		last = *p;
	}

	/* can't end with space */
	if (last == '\n' || last == '\r' ||
	    last == '\t' || last == ' ')
		return DKIM_STAT_INVALID;

	/* check for dupicates */
	for (x = dkim->dkim_xtags; x != NULL; x = x->xt_next)
	{
		if (strcmp(x->xt_tag, tag) == 0)
			return DKIM_STAT_INVALID;
	}

	x = (struct dkim_xtag *) DKIM_MALLOC(dkim, sizeof(struct dkim_xtag));
	if (x == NULL)
	{
		return DKIM_STAT_NORESOURCE;
	}

	x->xt_tag = dkim_strdup(dkim, tag, 0);
	x->xt_value = dkim_strdup(dkim, value, 0);
	x->xt_next = dkim->dkim_xtags;
	dkim->dkim_xtags = x;

	return DKIM_STAT_OK;
}

/*
**  DKIM_QI_GETNAME -- retrieve the DNS name from a DKIM_QUERYINFO object
**
**  Parameters:
**  	query -- DKIM_QUERYINFO handle
**
**  Return value:
**  	A pointer to a NULL-terminated string indicating the name to be
**  	queried, or NULL on error.
*/

const char *
dkim_qi_getname(DKIM_QUERYINFO *query)
{
	assert(query != NULL);

	return query->dq_name;
}

/*
**  DKIM_QI_GETTYPE -- retrieve the DNS RR type from a DKIM_QUERYINFO object
**
**  Parameters:
**  	query -- DKIM_QUERYINFO handle
**
**  Return value:
**  	The DNS RR type to be queried, or -1 on error.
*/

int
dkim_qi_gettype(DKIM_QUERYINFO *query)
{
	assert(query != NULL);

	return query->dq_type;
}

/*
**  DKIM_SIG_GETQUERIES -- retrieve the queries needed to validate a signature
**
**  Parameters:
**  	dkim -- DKIM handle
**  	sig -- DKIM_SIGINFO handle
**  	qi -- DKIM_QUERYINFO handle array (returned)
**  	nqi -- number of entries in the "qi" array
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_sig_getqueries(DKIM *dkim, DKIM_SIGINFO *sig,
                    DKIM_QUERYINFO ***qi, unsigned int *nqi)
{
	DKIM_QUERYINFO **new;
	DKIM_QUERYINFO *newp;

	assert(dkim != NULL);
	assert(sig != NULL);
	assert(qi != NULL);
	assert(nqi != NULL);

	new = DKIM_MALLOC(dkim, sizeof(struct dkim_queryinfo *));
	if (new == NULL)
		return DKIM_STAT_NORESOURCE;

	newp = DKIM_MALLOC(dkim, sizeof(struct dkim_queryinfo));
	if (newp == NULL)
	{
		DKIM_FREE(dkim, new);
		return DKIM_STAT_NORESOURCE;
	}

	memset(newp, '\0', sizeof(struct dkim_queryinfo));

	if (sig->sig_selector != NULL && sig->sig_domain != NULL)
	{
		newp->dq_type = T_TXT;
		snprintf((char *) newp->dq_name, sizeof newp->dq_name,
		         "%s.%s.%s",
		         sig->sig_selector, DKIM_DNSKEYNAME, sig->sig_domain);
	}

	new[0] = newp;

	*qi = new;
	*nqi = 1;

	return DKIM_STAT_OK;
}

/*
**  DKIM_SIG_GETHASHES -- retrieve hashes
**
**  Parameters:
**  	sig -- signature from which to get completed hashes
**  	hh -- pointer to header hash buffer (returned)
**  	hhlen -- bytes used at hh (returned)
**  	bh -- pointer to body hash buffer (returned)
**  	bhlen -- bytes used at bh (returned)
**
**  Return value:
**  	DKIM_STAT_OK -- successful completion
**  	DKIM_STAT_INVALID -- hashing hasn't been completed
*/

DKIM_STAT
dkim_sig_gethashes(DKIM_SIGINFO *sig, void **hh, size_t *hhlen,
                   void **bh, size_t *bhlen)
{
	return dkim_canon_gethashes(sig, hh, hhlen, bh, bhlen);
}

// not used (except t-test152) -----
/*
**  DKIM_SIGNHDRS -- set the list of header fields to sign for a signature,
**                   overriding the library default
**
**  Parameters:
**  	dkim -- DKIM signing handle to be affected
**  	hdrlist -- array of names of header fields that should be signed
**
**  Return value:
**  	A DKIM_STAT_* constant.
**
**  Notes:
**  	"hdrlist" can be NULL if the library's default is to be used.
*/

DKIM_STAT
dkim_signhdrs(DKIM *dkim, char **hdrlist)
{
	assert(dkim != NULL);

	if (dkim->dkim_hdrre != NULL)
		regfree(dkim->dkim_hdrre);

	if (hdrlist != NULL)
	{
		int status;
		char buf[BUFRSZ + 1];

		if (dkim->dkim_hdrre == NULL)
		{
			dkim->dkim_hdrre = malloc(sizeof(regex_t));

			if (dkim->dkim_hdrre == NULL)
			{
				dkim_error(dkim, "could not allocate %d bytes",
				           sizeof(regex_t));
				return DKIM_STAT_INTERNAL;
			}
		}

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

		(void) strlcpy(buf, "^(", sizeof buf);

		if (!dkim_hdrlist(buf, sizeof buf,
		                  dkim->dkim_libhandle->dkiml_requiredhdrs,
		                  TRUE))
			return DKIM_STAT_INVALID;
		if (!dkim_hdrlist(buf, sizeof buf,
		                  hdrlist, FALSE))
			return DKIM_STAT_INVALID;

		if (strlcat(buf, ")$", sizeof buf) >= sizeof buf)
			return DKIM_STAT_INVALID;

		status = regcomp(dkim->dkim_hdrre, buf,
		                 (REG_EXTENDED|REG_ICASE));

		if (status != 0)
			return DKIM_STAT_INTERNAL;
	}

	return DKIM_STAT_OK;
}

/*
**  DKIM_SIG_GETSSLBUF -- get the SSL error buffer, if any
**
**  Parameters:
**  	sig -- signature whose buffer should be retrieved
**
**  Return value:
**  	Pointer to the string, if defined, or NULL otherwise.
*/

const char *
dkim_sig_getsslbuf(DKIM_SIGINFO *sig)
{
	assert(sig != NULL);

	if (sig->sig_sslerrbuf != NULL)
		return dkim_dstring_get(sig->sig_sslerrbuf);
	else
		return NULL;
}

/*
**  DKIM_GETSSLBUF -- get the SSL error buffer, if any
**
**  Parameters:
**  	dkim -- DKIM handle from which to get SSL error
**
**  Return value:
**  	Pointer to the string, if defined, or NULL otherwise.
*/

const char *
dkim_getsslbuf(DKIM *dkim)
{
	assert(dkim != NULL);

	if (dkim->dkim_sslerrbuf != NULL)
		return dkim_dstring_get(dkim->dkim_sslerrbuf);
	else
		return NULL;
}

/*
**  DKIM_GETARCINFO -- get ARC info on verify
**
**  Parameters:
**      dkim -- DKIM handle
**      arc_info -- pointer to an arc_info struct that receives result
**
**  Return value:
**      A DKIM_STAT_* constant.
*/

DKIM_STAT dkim_getarcinfo(DKIM *dkim, struct arc_info* arc_info)
{
	assert(dkim != NULL);

	if (dkim->dkim_mode == DKIM_MODE_VERIFY)
	{
		if (dkim->dkim_state < DKIM_STATE_EOM2)
			return DKIM_STAT_INVALID;
	}
	else if (dkim->dkim_state < DKIM_STATE_EOH2)
		return DKIM_STAT_INVALID;

	if (arc_info)
	{
		arc_info->arc_state_desc =
			dkim_code_to_name(chainstatus, dkim->arc_state);
		arc_info->arc_state = dkim->arc_state;
		arc_info->oldest_pass = dkim->oldest_pass;
		if (dkim->dkim_arcmax > dkim->dkim_arccount)
			arc_info->dkim_arccount = dkim->dkim_arcmax;
		else
			arc_info->dkim_arccount = dkim->dkim_arccount;
		arc_info->arc_data_available =
			dkim->arc_sets != NULL && !dkim->arc_set_inconsistent;
	}

	return DKIM_STAT_OK;
}

/*
**  DKIM_GETSEALDOMAIN -- get ARC sealinfg domain
**
**  Parameters:
**      dkim -- DKIM handle
**      set -- set number, 0-based
**
**  Return value:
**      domain or NULL
*/

char const *dkim_getsealdomain(DKIM *dkim, unsigned int set)
{
	assert(dkim);
	if (dkim->dkim_state < DKIM_STATE_EOH1 ||
		dkim->dkim_arccount <= set ||
		dkim->arc_sets == NULL)
			return NULL;

	return dkim_sig_getdomain(dkim->arc_sets[set].arcsig_as);
}


/*
**  DKIM_GETSEALERROR -- get ARC sealerror
**
**  Parameters:
**      dkim -- DKIM handle
**
**  Return value:
**      diagnostic stuff
*/

char const *dkim_getsealerror(DKIM *dkim)
{
	assert(dkim);
	if (dkim->dkim_state < DKIM_STATE_EOH1 ||
		dkim->arc_state == ARC_CHAIN_UNKNOWN ||
		dkim->arc_state == ARC_CHAIN_SEAL_OK ||
		dkim->arc_state == ARC_CHAIN_NONE ||
		dkim->arc_state == ARC_CHAIN_PASS)
			return "No error";

	if (dkim->arc_sets == NULL || dkim->arc_set_inconsistent)
		return "Inconsistent ARC chain";

	if (dkim->arc_seal_failed < 0)
	{
		DKIM_SIGINFO *sig = dkim->arc_sets[dkim->dkim_arccount - 1].arcsig_ams;
		dkim_error(dkim, "Top (i=%d) AMS failed: %s",
			dkim->dkim_arccount,
			dkim_code_to_name(sigerrors, sig->sig_error));
		return dkim->dkim_error;
	}

	if ((unsigned)dkim->arc_seal_failed < dkim->dkim_arccount)
	{
		DKIM_SIGINFO *sig = dkim->arc_sets[dkim->arc_seal_failed].arcsig_as;
		dkim_error(dkim, "ARC-Seal at i=%d failed: %s",
			dkim->arc_seal_failed + 1,
			dkim_code_to_name(sigerrors, sig->sig_error));
		return dkim->dkim_error;
	}

	return "Internal error";
}



/*
**  DKIM_CHECK_HDRBUF -- allocate or blank
**
**  Parameters:
**  	dkim -- DKIM handle
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT dkim_check_hdrbuf(DKIM *dkim)
{
	if (dkim->dkim_hdrbuf == NULL)
	{
		dkim->dkim_hdrbuf = dkim_dstring_new(dkim, BUFRSZ, MAXBUFRSZ);
		if (dkim->dkim_hdrbuf == NULL)
		{
			return DKIM_STAT_NORESOURCE;
		}
	}
	else
	{
		dkim_dstring_blank(dkim->dkim_hdrbuf);
	}
	return DKIM_STAT_OK;
}


