/*
**  Copyright (c) 2007-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>
#ifdef HAVE_STDBOOL_H
# include <stdbool.h>
#endif /* HAVE_STDBOOL_H */
#include <assert.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <limits.h>
#ifdef USE_TRE
# ifdef TRE_PRE_080
#  include <tre/regex.h>
# 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
#  endif /* TRE_USE_SYSTEM_REGEX_H */
# endif /* TRE_PRE_080 */
#else /* USE_TRE */
# include <regex.h>
#endif /* USE_TRE */

/* libopendkim includes */
#include "dkim-internal.h"
#include "dkim-types.h"
#include "dkim-canon.h"
#include "dkim-util.h"
#include "util.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 */

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

/* macros */
#define	DKIM_ISWSP(x)	((x) == 011 || (x) == 040)
#define	DKIM_ISLWSP(x)	((x) == 011 || (x) == 012 || (x) == 015 || (x) == 040)

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

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

/*
**  DKIM_CANON_FREE -- destroy a canonicalization
**
**  Parameters:
**  	dkim -- DKIM handle
**  	canon -- canonicalization to destroy
**
**  Return value:
**  	None.
*/

static void
dkim_canon_free(DKIM *dkim, DKIM_CANON *canon)
{
	assert(dkim != NULL);
	assert(canon != NULL);

	if (canon->canon_hash != NULL)
	{
		switch (canon->canon_hashtype)
		{
#ifdef USE_GNUTLS
		  case DKIM_HASHTYPE_SHA1:
		  case DKIM_HASHTYPE_SHA256:
		  {
			struct dkim_sha *sha;

			sha = (struct dkim_sha *) canon->canon_hash;

			if (sha->sha_tmpfd != -1)
			{
				close(sha->sha_tmpfd);
				sha->sha_tmpfd = -1;
			}

			gnutls_hash_deinit(sha->sha_hd, NULL);

			if (sha->sha_out != NULL)
			{
				DKIM_FREE(dkim, sha->sha_out);
				sha->sha_out = NULL;
			}

			break;
		  }

#else /* USE_GNUTLS */
#if OPENSSL_VERSION_NUMBER >= 0x30000000L

		  case DKIM_HASHTYPE_SHA1:
		  case DKIM_HASHTYPE_SHA256:
		  {
			struct dkim_sha *sha;

			sha = (struct dkim_sha *) canon->canon_hash;

			if (sha->sha_tmpfd != -1)
			{
				close(sha->sha_tmpfd);
				sha->sha_tmpfd = -1;
			}

			if (sha->ctx != NULL)
			{
				EVP_MD_CTX_free(sha->ctx);
				sha->ctx = NULL;
			}

			if (sha->sha_out != NULL)
			{
				DKIM_FREE(dkim, sha->sha_out);
				sha->sha_out = NULL;
			}

			break;
		  }


#else /* OPENSSL_VERSION_NUMBER >= 0x30000000L */

		  case DKIM_HASHTYPE_SHA1:
		  {
			struct dkim_sha1 *sha1;

			sha1 = (struct dkim_sha1 *) canon->canon_hash;

			if (sha1->sha1_tmpbio != NULL)
			{
				BIO_free(sha1->sha1_tmpbio);
				sha1->sha1_tmpfd = -1;
				sha1->sha1_tmpbio = NULL;
			}

			break;
		  }

# ifdef HAVE_SHA256
		  case DKIM_HASHTYPE_SHA256:
		  {
			struct dkim_sha256 *sha256;

			sha256 = (struct dkim_sha256 *) canon->canon_hash;

			if (sha256->sha256_tmpbio != NULL)
			{
				BIO_free(sha256->sha256_tmpbio);
				sha256->sha256_tmpfd = -1;
				sha256->sha256_tmpbio = NULL;
			}

			break;
		  }
# endif /* HAVE_SHA256 */
#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */
#endif /* USE_GNUTLS */

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

		dkim_mfree(dkim->dkim_libhandle, dkim->dkim_closure,
		           canon->canon_hash);
	}

	if (canon->canon_hashbuf != NULL)
	{
		dkim_mfree(dkim->dkim_libhandle, dkim->dkim_closure,
		           canon->canon_hashbuf);
	}

	if (canon->canon_buf != NULL)
		dkim_dstring_free(canon->canon_buf);

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

/*
**  DKIM_CANON_WRITE -- write data to canonicalization stream(s)
**
**  Parameters:
**  	canon -- DKIM_CANON handle
**  	buf -- buffer containing canonicalized data
**  	buflen -- number of bytes to consume
**
**  Return value:
**  	None.
*/

static void
dkim_canon_write(DKIM_CANON *canon, char *buf, size_t buflen)
{
	assert(canon != NULL);

	if (canon->canon_remain != (ssize_t) -1)
		buflen = MIN(buflen, canon->canon_remain);

	canon->canon_wrote += buflen;

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

	assert(canon->canon_hash != NULL);

	switch (canon->canon_hashtype)
	{
#ifdef USE_GNUTLS
	  case DKIM_HASHTYPE_SHA1:
	  case DKIM_HASHTYPE_SHA256:
	  {
		struct dkim_sha *sha;

		sha = (struct dkim_sha *) canon->canon_hash;

		gnutls_hash(sha->sha_hd, buf, buflen);

		if (sha->sha_tmpfd != -1)
			(void) !write(sha->sha_tmpfd, buf, buflen);

		break;
	  }
#else /* USE_GNUTLS */
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
	  case DKIM_HASHTYPE_SHA1:
	  case DKIM_HASHTYPE_SHA256:
	  {
		struct dkim_sha *sha;

		sha = (struct dkim_sha *) canon->canon_hash;

		if (EVP_DigestUpdate(sha->ctx, buf, buflen) == 0)
			sha->failed = TRUE;

		if (sha->sha_tmpfd != -1)
			(void) !write(sha->sha_tmpfd, buf, buflen);

		break;
	  }
#else /* OPENSSL_VERSION_NUMBER >= 0x30000000L */

	  case DKIM_HASHTYPE_SHA1:
	  {
		struct dkim_sha1 *sha1;

		sha1 = (struct dkim_sha1 *) canon->canon_hash;
		SHA1_Update(&sha1->sha1_ctx, buf, buflen);

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

		break;
	  }

# ifdef HAVE_SHA256
	  case DKIM_HASHTYPE_SHA256:
	  {
		struct dkim_sha256 *sha256;

		sha256 = (struct dkim_sha256 *) canon->canon_hash;
		SHA256_Update(&sha256->sha256_ctx, buf, buflen);

		if (sha256->sha256_tmpbio != NULL)
			BIO_write(sha256->sha256_tmpbio, buf, buflen);

		break;
	  }
# endif /* HAVE_SHA256 */
#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */
#endif /* USE_GNUTLS */
	}

	if (canon->canon_remain != (ssize_t) -1)
		canon->canon_remain -= buflen;
}

/*
**  DKIM_CANON_BUFFER -- buffer for dkim_canon_write()
**
**  Parameters:
**  	canon -- DKIM_CANON handle
**  	buf -- buffer containing canonicalized data
**  	buflen -- number of bytes to consume
**
**  Return value:
**  	None.
*/

static void
dkim_canon_buffer(DKIM_CANON *canon, char *buf, size_t buflen)
{
	assert(canon != NULL);

	/* NULL buffer or 0 length means flush */
	if (buf == NULL || buflen == 0)
	{
		if (canon->canon_hashbuflen > 0)
		{
			dkim_canon_write(canon, canon->canon_hashbuf,
			                 canon->canon_hashbuflen);
			canon->canon_hashbuflen = 0;
		}
		return;
	}

	/* not enough buffer space; write the buffer out */
	if (canon->canon_hashbuflen + buflen > canon->canon_hashbufsize)
	{
		dkim_canon_write(canon, canon->canon_hashbuf,
		                 canon->canon_hashbuflen);
		canon->canon_hashbuflen = 0;
	}

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

	if (buflen >= canon->canon_hashbufsize)
	{
		dkim_canon_write(canon, buf, buflen);
	}
	else
	{
		memcpy(&canon->canon_hashbuf[canon->canon_hashbuflen],
		       buf, buflen);
		canon->canon_hashbuflen += buflen;
	}
}

/*
**  DKIM_CANON_HEADER_STRING -- canonicalize a header field
**
**  Parameters:
**  	dstr -- dkim_dstring to use for output
**  	canon -- canonicalization mode to apply
**  	hdr -- header field input
**  	hdrlen -- bytes to process at "hdr"
**  	crlf -- write a CRLF at the end?
**
**  Return value:
**  	A DKIM_STAT constant.
*/

DKIM_STAT
dkim_canon_header_string(struct dkim_dstring *dstr, dkim_canon_t canon,
                         char const *hdr, size_t hdrlen, _Bool crlf)
{
	_Bool space;
	char const *p;
	char *tmp;
	char *end;
	char tmpbuf[BUFRSZ];

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

	tmp = tmpbuf;
	end = tmpbuf + sizeof tmpbuf - 1;

	switch (canon)
	{
	  case DKIM_CANON_SIMPLE:
		if (!dkim_dstring_catn(dstr, hdr, hdrlen) ||
		    (crlf && !dkim_dstring_catn(dstr, CRLF, 2)))
			return DKIM_STAT_NORESOURCE;
		break;

	  case DKIM_CANON_RELAXED:
		/* process header field name (before colon) first */
		for (p = hdr; p < hdr + hdrlen; p++)
		{
			/*
			**  Discard spaces before the colon or before the end
			**  of the first word.
			*/

			int ch = *(unsigned char*)p;
			if (isascii(ch))
			{
				/* discard spaces */
				if (DKIM_ISLWSP(ch))
					continue;

				/* convert to lowercase */
				if (isupper(ch))
					*tmp++ = tolower(ch);
				else
					*tmp++ = ch;
			}
			else
			{
				*tmp++ = ch;
			}

			/* reaching the end of the cache buffer, flush it */
			if (tmp == end)
			{
				*tmp = '\0';

				if (!dkim_dstring_catn(dstr,
				                       tmpbuf, tmp - tmpbuf))
					return DKIM_STAT_NORESOURCE;

				tmp = tmpbuf;
			}
			
			if (ch == ':')
			{
				p++;
				break;
			}
		}

		/* skip all spaces before first word */
		while (*p != '\0' && DKIM_ISLWSP(*(unsigned char*)p))
			p++;

		space = FALSE;				/* just saw a space */

		for ( ; *p != '\0'; p++)
		{
			if (isascii(*(unsigned char*)p) && isspace(*(unsigned char*)p))
			{
				/* mark that there was a space and continue */
				space = TRUE;

				continue;
			}

			/*
			**  Any non-space marks the beginning of a word.
			**  If there's a stored space, use it up.
			*/

			if (space)
			{
				*tmp++ = ' ';

				/* flush buffer? */
				if (tmp == end)
				{
					*tmp = '\0';

					if (!dkim_dstring_catn(dstr,
					                       tmpbuf,
					                       tmp - tmpbuf))
						return DKIM_STAT_NORESOURCE;

					tmp = tmpbuf;
				}

				space = FALSE;
			}

			/* copy the byte */
			*tmp++ = *p;

			/* flush buffer? */
			if (tmp == end)
			{
				*tmp = '\0';

				if (!dkim_dstring_catn(dstr,
				                       tmpbuf, tmp - tmpbuf))
					return DKIM_STAT_NORESOURCE;

				tmp = tmpbuf;
			}
		}

		/* flush any cached data */
		if (tmp != tmpbuf)
		{
			*tmp = '\0';

			if (!dkim_dstring_catn(dstr,
			                       tmpbuf, tmp - tmpbuf))
				return DKIM_STAT_NORESOURCE;
		}

		if (crlf && !dkim_dstring_catn(dstr, CRLF, 2))
			return DKIM_STAT_NORESOURCE;

		break;
	}

	return DKIM_STAT_OK;
}

/*
**  DKIM_CHECK_CANONBUF -- allocate or blank
**
**  Parameters:
**  	dkim -- DKIM handle
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

static DKIM_STAT dkim_check_canonbuf(DKIM *dkim, int buflen)
{
	if (dkim->dkim_canonbuf == NULL)
	{
		dkim->dkim_canonbuf = dkim_dstring_new(dkim, buflen, 0);
		if (dkim->dkim_canonbuf == NULL)
		{
			return DKIM_STAT_NORESOURCE;
		}
	}
	else
	{
		dkim_dstring_blank(dkim->dkim_canonbuf);
	}
	return DKIM_STAT_OK;
}

/*
**  DKIM_CANON_HEADER -- canonicalize a header and write it
**
**  Parameters:
**  	dkim -- DKIM handle
**  	canon -- DKIM_CANON handle
**  	hdr -- header handle
**  	crlf -- write a CRLF at the end?
**
**  Return value:
**  	A DKIM_STAT constant.
*/

static DKIM_STAT
dkim_canon_header(DKIM *dkim, DKIM_CANON *canon, struct dkim_header *hdr,
                  _Bool crlf)
{
	DKIM_STAT status;

	assert(dkim != NULL);
	assert(canon != NULL);
	assert(hdr != NULL);

	if (dkim_check_canonbuf(dkim, hdr->hdr_textlen) != DKIM_STAT_OK)
		return DKIM_STAT_NORESOURCE;

	dkim_canon_buffer(canon, NULL, 0);

	status = dkim_canon_header_string(dkim->dkim_canonbuf,
	                                  canon->canon_canon,
	                                  hdr->hdr_text, hdr->hdr_textlen,
	                                  crlf);
	if (status != DKIM_STAT_OK)
		return status;

	dkim_canon_buffer(canon, dkim_dstring_get(dkim->dkim_canonbuf),
	                  dkim_dstring_len(dkim->dkim_canonbuf));

	return DKIM_STAT_OK;
}

/*
**  DKIM_CANON_HEADER_TEXT -- canonicalize a header and write it
**
**  Parameters:
**  	dkim -- DKIM handle
**  	canon -- DKIM_CANON handle
**  	TXT -- header text
**  	crlf -- write a CRLF at the end?
**
**  Return value:
**  	A DKIM_STAT constant.
*/

DKIM_STAT
dkim_canon_header_text(DKIM *dkim, DKIM_CANON *canon, char const *txt,
                  _Bool crlf)
{
	DKIM_STAT status;

	assert(dkim != NULL);
	assert(canon != NULL);
	assert(txt != NULL);

	int len = strlen(txt);

	if (dkim_check_canonbuf(dkim, len) != DKIM_STAT_OK)
		return DKIM_STAT_NORESOURCE;

	dkim_canon_buffer(canon, NULL, 0);

	status = dkim_canon_header_string(dkim->dkim_canonbuf,
	                                  canon->canon_canon,
	                                  txt, len,
	                                  crlf);
	if (status != DKIM_STAT_OK)
		return status;

	dkim_canon_buffer(canon, dkim_dstring_get(dkim->dkim_canonbuf),
	                  dkim_dstring_len(dkim->dkim_canonbuf));

	return DKIM_STAT_OK;
}

/*
**  DKIM_CANON_FLUSHBLANKS -- use accumulated blank lines in canonicalization
**
**  Parameters:
**  	canon -- DKIM_CANON handle
**
**  Return value:
**  	None.
*/

static void
dkim_canon_flushblanks(DKIM_CANON *canon)
{
	unsigned int c;

	assert(canon != NULL);

	for (c = 0; c < canon->canon_blanks; c++)
		dkim_canon_buffer(canon, CRLF, 2);
	canon->canon_blanks = 0;
}

/*
**  DKIM_CANON_FIXCRLF -- rebuffer a body chunk, fixing "naked" CRs and LFs
**
**  Parameters:
**  	dkim -- DKIM handle
**  	canon -- canonicalization being handled
**  	buf -- buffer to be fixed
**  	buflen -- number of bytes at "buf"
**
**  Return value:
**  	A DKIM_STAT_* constant.
**
**  Side effects:
**  	dkim->dkim_canonbuf will be initialized and used.
*/

static DKIM_STAT
dkim_canon_fixcrlf(DKIM *dkim, DKIM_CANON *canon, char *buf, size_t buflen)
{
	char prev;
	char *p;
	char *eob;

	assert(dkim != NULL);
	assert(canon != NULL);
	assert(buf != NULL);

	if (dkim_check_canonbuf(dkim, buflen) != DKIM_STAT_OK)
		return DKIM_STAT_NORESOURCE;

	eob = buf + buflen - 1;

	prev = canon->canon_lastchar;

	for (p = buf; p <= eob; p++)
	{
		if (*p == '\n' && prev != '\r')
		{
			/* fix a solitary LF */
			dkim_dstring_catn(dkim->dkim_canonbuf, CRLF, 2);
		}
		else if (*p == '\r')
		{
			if (p < eob && *(p + 1) != '\n')
				/* fix a solitary CR */
				dkim_dstring_catn(dkim->dkim_canonbuf, CRLF, 2);
			else
				/* CR at EOL, or CR followed by a LF */
				dkim_dstring_cat1(dkim->dkim_canonbuf, *p);
		}
		else
		{
			/* something else */
			dkim_dstring_cat1(dkim->dkim_canonbuf, *p);
		}

		prev = *p;
	}

	return DKIM_STAT_OK;
}

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

/*
**  DKIM_CANON_INIT -- initialize all canonicalizations
**
**  Parameters:
**  	dkim -- DKIM handle
**  	tmp -- make temp files?
**  	keep -- keep temp files?
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_canon_init(DKIM *dkim, _Bool tmp, _Bool keep)
{
	int fd;
	DKIM_STAT status;
	DKIM_CANON *cur;

	assert(dkim != NULL);

	for (cur = dkim->dkim_canonhead; cur != NULL; cur = cur->canon_next)
	{
		if (cur->canon_hashbuf) // already initialized
			continue;

		cur->canon_hashbuf = DKIM_MALLOC(dkim, DKIM_HASHBUFSIZE);
		if (cur->canon_hashbuf == NULL)
		{
			dkim_error(dkim, "unable to allocate %d byte(s)",
			           DKIM_HASHBUFSIZE);
			return DKIM_STAT_NORESOURCE;
		}
		cur->canon_hashbufsize = DKIM_HASHBUFSIZE;
		cur->canon_hashbuflen = 0;

		assert(cur->canon_buf == NULL);
		cur->canon_buf = dkim_dstring_new(dkim, BUFRSZ, BUFRSZ);
		if (cur->canon_buf == NULL)
			return DKIM_STAT_NORESOURCE;

		switch (cur->canon_hashtype)
		{
#ifdef USE_GNUTLS
		  case DKIM_HASHTYPE_SHA1:
		  case DKIM_HASHTYPE_SHA256:
		  {
			struct dkim_sha *sha;

			sha = (struct dkim_sha *) DKIM_MALLOC(dkim,
			                                      sizeof(struct dkim_sha));
			if (sha == NULL)
			{
				dkim_error(dkim,
				           "unable to allocate %d byte(s)",
				           sizeof(struct dkim_sha));
				return DKIM_STAT_NORESOURCE;
			}

			memset(sha, '\0', sizeof(struct dkim_sha));
			sha->sha_tmpfd = -1;

			/* XXX -- test for errors */
			if (cur->canon_hashtype == DKIM_HASHTYPE_SHA1)
			{
				(void) gnutls_hash_init(&sha->sha_hd,
				                        GNUTLS_DIG_SHA1);
			}
			else
			{
				(void) gnutls_hash_init(&sha->sha_hd,
				                        GNUTLS_DIG_SHA256);
			}

			if (sha->sha_hd == NULL)
			{
				DKIM_FREE(dkim, sha);
				return DKIM_STAT_INTERNAL;
			}
				
			if (tmp)
			{
				status = dkim_tmpfile(dkim, &fd, keep);
				if (status != DKIM_STAT_OK)
				{
					gnutls_hash_deinit(sha->sha_hd, sha->sha_out);
					DKIM_FREE(dkim, sha);
					return status;
				}

				sha->sha_tmpfd = fd;
			}

			cur->canon_hash = sha;

		  	break;
		  }
#else /* USE_GNUTLS */
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
		  case DKIM_HASHTYPE_SHA1:
		  case DKIM_HASHTYPE_SHA256:
		  {
			struct dkim_sha *sha;

			sha = (struct dkim_sha *) DKIM_MALLOC(dkim,
			                                      sizeof(struct dkim_sha));
			if (sha == NULL)
			{
				dkim_error(dkim,
				           "unable to allocate %d byte(s)",
				           sizeof(struct dkim_sha));
				return DKIM_STAT_NORESOURCE;
			}

			memset(sha, '\0', sizeof(struct dkim_sha));
			sha->sha_tmpfd = -1;

			const EVP_MD *md = cur->canon_hashtype == DKIM_HASHTYPE_SHA1?
				EVP_sha1(): EVP_sha256();
			sha->ctx = EVP_MD_CTX_new();
			if (!EVP_DigestInit_ex2(sha->ctx, md, NULL))
			{
				dkim_error(dkim,
					"Message digest initialization failed.");
				EVP_MD_CTX_free(sha->ctx);
				DKIM_FREE(dkim, sha);
				return DKIM_STAT_INTERNAL;
			}

			if (tmp)
			{
				status = dkim_tmpfile(dkim, &fd, keep);
				if (status != DKIM_STAT_OK)
				{
					EVP_MD_CTX_free(sha->ctx);
					DKIM_FREE(dkim, sha);
					return status;
				}

				sha->sha_tmpfd = fd;
			}

			cur->canon_hash = sha;

		  	break;
		  }

#else /* OPENSSL_VERSION_NUMBER >= 0x30000000L */

		  case DKIM_HASHTYPE_SHA1:
		  {
			struct dkim_sha1 *sha1;

			sha1 = (struct dkim_sha1 *) DKIM_MALLOC(dkim,
			                                        sizeof(struct dkim_sha1));
			if (sha1 == NULL)
			{
				dkim_error(dkim,
				           "unable to allocate %d byte(s)",
				           sizeof(struct dkim_sha1));
				return DKIM_STAT_NORESOURCE;
			}

			memset(sha1, '\0', sizeof(struct dkim_sha1));
			SHA1_Init(&sha1->sha1_ctx);

			if (tmp)
			{
				status = dkim_tmpfile(dkim, &fd, keep);
				if (status != DKIM_STAT_OK)
				{
					DKIM_FREE(dkim, sha1);
					return status;
				}

				sha1->sha1_tmpfd = fd;
				sha1->sha1_tmpbio = BIO_new_fd(fd, 1);
			}

			cur->canon_hash = sha1;

		  	break;
		  }

# ifdef HAVE_SHA256
		  case DKIM_HASHTYPE_SHA256:
		  {
			struct dkim_sha256 *sha256;

			sha256 = (struct dkim_sha256 *) DKIM_MALLOC(dkim,
			                                            sizeof(struct dkim_sha256));
			if (sha256 == NULL)
			{
				dkim_error(dkim,
				           "unable to allocate %d byte(s)",
				           sizeof(struct dkim_sha256));
				return DKIM_STAT_NORESOURCE;
			}

			memset(sha256, '\0', sizeof(struct dkim_sha256));
			SHA256_Init(&sha256->sha256_ctx);

			if (tmp)
			{
				status = dkim_tmpfile(dkim, &fd, keep);
				if (status != DKIM_STAT_OK)
				{
					DKIM_FREE(dkim, sha256);
					return status;
				}

				sha256->sha256_tmpfd = fd;
				sha256->sha256_tmpbio = BIO_new_fd(fd, 1);
			}

			cur->canon_hash = sha256;

		  	break;
		  }
# endif /* HAVE_SHA256 */
#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */
#endif /* USE_GNUTLS */

		  default:
			assert(0);
		}
	}

	return DKIM_STAT_OK;
}

/*
**  DKIM_CANON_CLEANUP -- discard canonicalizations
**
**  Parameters:
**  	dkim -- DKIM handle
**
**  Return value:
**  	None.
*/

void
dkim_canon_cleanup(DKIM *dkim)
{
	DKIM_CANON *cur;
	DKIM_CANON *next;

	assert(dkim != NULL);

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

	cur = dkim->dkim_canonhead;
	while (cur != NULL)
	{
		next = cur->canon_next;

#ifdef _FFR_RESIGN
		/* skip if resigning and body */
		if (dkim->dkim_resign == NULL || cur->canon_hdr)
			dkim_canon_free(dkim, cur);
#else /* _FFR_RESIGN */
		dkim_canon_free(dkim, cur);
#endif /* _FFR_RESIGN */

		cur = next;
	}

	dkim->dkim_canonhead = NULL;
}

/*
**  DKIM_ADD_CANON -- add a new canonicalization handle if needed
**
**  Parameters:
**  	dkim -- handle
**  	hdr -- TRUE iff this is specifying a header canonicalization
**  	canon -- canonicalization mode
**  	hashtype -- hash type
**  	hdrlist -- for header canonicalization, the header list
**  	sighdr -- pointer to header being verified (NULL for signing)
**  	length -- for body canonicalization, the length limit (-1 == all)
**  	cout -- DKIM_CANON handle (returned)
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_add_canon(DKIM *dkim, _Bool hdr, dkim_canon_t canon, int hashtype,
               char *hdrlist, struct dkim_header *sighdr,
               ssize_t length, DKIM_CANON **cout)
{
	DKIM_CANON *cur;
	DKIM_CANON *new;

	assert(dkim != NULL);
	assert(canon == DKIM_CANON_SIMPLE || canon == DKIM_CANON_RELAXED);
	if (dkim_libfeature(dkim->dkim_libhandle, DKIM_FEATURE_SHA256))
	{
		assert(hashtype == DKIM_HASHTYPE_SHA1 ||
		       hashtype == DKIM_HASHTYPE_SHA256);
	}
	else
	{
		assert(hashtype == DKIM_HASHTYPE_SHA1);
	}

	if (!hdr)
	/*
	* Body:  can only be simple or relaxed and have a length
	* (header can have a wide variety of h=, so assume they're unique?)
	*/
	{
		for (cur = dkim->dkim_canonhead; cur != NULL; cur = cur->canon_next)
		{
			if (cur->canon_hdr ||
			    cur->canon_hashtype != hashtype ||
			    cur->canon_canon != canon)
				continue;

			if (length != cur->canon_length)
				continue;

			if (cout != NULL)
				*cout = cur;

			return DKIM_STAT_OK;
		}
	}

	new = (DKIM_CANON *) dkim_malloc(dkim->dkim_libhandle,
	                                 dkim->dkim_closure, sizeof *new);
	if (new == NULL)
	{
		dkim_error(dkim, "unable to allocate %d byte(s)", sizeof *new);
		return DKIM_STAT_NORESOURCE;
	}

	new->canon_done = FALSE;
	new->canon_hdr = hdr;
	new->canon_canon = canon;
	new->canon_hashtype = hashtype;
	new->canon_hash = NULL;
	new->canon_wrote = 0;
	if (hdr)
	{
		new->canon_length = (ssize_t) -1;
		new->canon_remain = (ssize_t) -1;
	}
	else
	{
		new->canon_length = length;
		new->canon_remain = length;
	}
	new->canon_sigheader = sighdr;
	new->canon_hdrlist = hdrlist;
	new->canon_buf = NULL;
	new->canon_next = NULL;
	new->canon_blankline = TRUE;
	new->canon_blanks = 0;
	new->canon_bodystate = 0;
	new->canon_hashbuflen = 0;
	new->canon_hashbufsize = 0;
	new->canon_hashbuf = NULL;
	new->canon_lastchar = '\0';

	if (dkim->dkim_canonhead == NULL)
	{
		dkim->dkim_canontail = new;
		dkim->dkim_canonhead = new;
	}
	else
	{
		dkim->dkim_canontail->canon_next = new;
		dkim->dkim_canontail = new;
	}

	if (cout != NULL)
		*cout = new;

	return DKIM_STAT_OK;
}

/*
**  DKIM_CANON_SELECTHDRS -- choose headers to be included in canonicalization
**
**  Parameters:
**  	dkim -- DKIM context in which this is performed
**  	hdrlist -- string containing headers that should be marked, separated
**  	           by the ":" character
**  	ptrs -- array of header pointers (modified)
**  	nptr -- number of pointers available at "ptrs"
**
**  Return value:
**  	Count of headers added to "ptrs", or -1 on error.
**
**  Notes:
**  	Selects headers to be passed to canonicalization and the order in
**  	which this is done.  "ptrs" is populated by pointers to headers
**  	in the order in which they should be fed to canonicalization.
**
**  	If any of the returned pointers is NULL, then a header named by
**  	"hdrlist" was not found.
*/

int
dkim_canon_selecthdrs(DKIM *dkim, unsigned int apply_flag,
	char *hdrlist, struct dkim_header ***ptrs_entry)
{
	int n;
	int shcnt;
	size_t len;
	char *colon;
	struct dkim_header *hdr;

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

	struct dkim_header **ptrs;

	/* if there are no headers named, use them all */
	if (hdrlist == NULL)
	{
		n = 0;

		*ptrs_entry = ptrs = DKIM_MALLOC(dkim, dkim->dkim_hdrcnt * sizeof(struct dkim_header *));
		if (ptrs == NULL)
			return -1;

		for (hdr = dkim->dkim_hhead; hdr != NULL; hdr = hdr->hdr_next)
		{
			ptrs[n] = hdr;
			n++;
		}

		return n;
	}

	char *hdrlist_copy = dkim_strdup(dkim, hdrlist, 0);
	if (hdrlist_copy == NULL)
		return -1;

	shcnt = 1;
	for (colon = hdrlist_copy; *colon != 0; ++colon)
	{
		if (*colon == ':')
			shcnt++;
	}
	n = sizeof(char *) * shcnt;

	char **hdrs = DKIM_MALLOC(dkim, n);
	if (hdrs == NULL)
	{
		DKIM_FREE(dkim, hdrlist_copy);
		return -1;
	}
	memset(hdrs, '\0', n);

	n = sizeof(struct dkim_header *) * shcnt;
	struct dkim_header **lhdrs = DKIM_MALLOC(dkim, n);
	if (lhdrs == NULL)
	{
		DKIM_FREE(dkim, hdrlist_copy);
		DKIM_FREE(dkim, hdrs);
		return -1;
	}
	memset(lhdrs, '\0', n);



	/* make a split-out copy of hdrlist */
	n = 0;
	char *ctx = NULL;
	for (char *bar = strtok_r(hdrlist_copy, ":", &ctx);
	     bar != NULL;
	     bar = strtok_r(NULL, ":", &ctx))
	{
		hdrs[n] = bar;
		n++;
	}

	assert(n == shcnt);

	/* mark all headers as not used */
	for (hdr = dkim->dkim_hhead; hdr != NULL; hdr = hdr->hdr_next)
		hdr->hdr_flags &= ~apply_flag;


	/* for each named header, find the last unused one and use it up */
	n = 0;
	for (int c = 0; c < shcnt; c++)
	{
		struct dkim_header *lh = NULL;

		len = MIN(DKIM_MAXHEADER, strlen(hdrs[c]));
		while (len > 0 && DKIM_ISWSP(hdrs[c][len - 1]))
			len--;

		for (hdr = dkim->dkim_hhead; hdr != NULL; hdr = hdr->hdr_next)
		{
			if (hdr->hdr_flags & apply_flag)
				continue;

			if (len == hdr->hdr_namelen &&
			    strncasecmp(hdr->hdr_text, hdrs[c], len) == 0)
			{
				lh = hdr;
			}
		}

		if (lh != NULL)
		{
			lh->hdr_flags |= apply_flag;
			lhdrs[n] = lh;
			n++;
		}
	}

	*ptrs_entry = ptrs = DKIM_MALLOC(dkim, n * sizeof(struct dkim_header *));

	if (ptrs)
	{
		/* copy to the caller's buffers */
		for (int c = 0; c < n; c++)
		{
			assert(lhdrs[c] != NULL);
			if (lhdrs[c] != NULL)
			{
				ptrs[c] = lhdrs[c];
			}
		}
	}
	else
		n = -1;

	DKIM_FREE(dkim, hdrlist_copy);
	DKIM_FREE(dkim, lhdrs);
	DKIM_FREE(dkim, hdrs);

	return n;
}

/*
**  ARC_CANON_FINALIZE -- finalize a canonicalization
**
**  Parameters:
**  	canon -- canonicalization to finalize
**
**  Return value:
**  	None.
*/

static DKIM_STAT dkim_canon_finalize(DKIM *dkim, DKIM_CANON *cur)
{
		/* finalize */
		switch (cur->canon_hashtype)
		{
#ifdef USE_GNUTLS
		  case DKIM_HASHTYPE_SHA1:
		  case DKIM_HASHTYPE_SHA256:
		  {
			int alg;
			struct dkim_sha *sha;

			sha = (struct dkim_sha *) cur->canon_hash;

			if (cur->canon_hashtype == DKIM_HASHTYPE_SHA1)
				alg = GNUTLS_DIG_SHA1;
			else
				alg = GNUTLS_DIG_SHA256;

			sha->sha_outlen = gnutls_hash_get_len(alg);

			sha->sha_out = DKIM_MALLOC(dkim, sha->sha_outlen);
			if (sha->sha_out == NULL)
			{
				dkim_error(dkim, "unable to allocate %u bytes",
				           sha->sha_outlen);
				return DKIM_STAT_NORESOURCE;
			}

			gnutls_hash_output(sha->sha_hd, sha->sha_out);

			break;
		  }

#else /* USE_GNUTLS */
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
		  case DKIM_HASHTYPE_SHA1:
		  case DKIM_HASHTYPE_SHA256:
		  {
			struct dkim_sha *sha = cur->canon_hash;
			unsigned char md_value[EVP_MAX_MD_SIZE];
			if (!EVP_DigestFinal_ex(sha->ctx, md_value, &sha->sha_outlen))
				sha->failed = TRUE;

			if (sha->failed) // How on earth?!
			{
				dkim_error(dkim, "Message digest failed.");
				EVP_MD_CTX_free(sha->ctx);
				sha->ctx = NULL;
				return DKIM_STAT_INTERNAL;
			}

			sha->sha_out = DKIM_MALLOC(dkim, sha->sha_outlen);
			if (sha->sha_out == NULL)
			{
				dkim_error(dkim, "unable to allocate %u bytes",
				           sha->sha_outlen);
				EVP_MD_CTX_free(sha->ctx);
				sha->ctx = NULL;
				return DKIM_STAT_NORESOURCE;
			}

			memcpy(sha->sha_out, md_value, sha->sha_outlen);

			break;
		  }
#else /* OPENSSL_VERSION_NUMBER >= 0x30000000L */

		  case DKIM_HASHTYPE_SHA1:
		  {
			struct dkim_sha1 *sha1;

			sha1 = (struct dkim_sha1 *) cur->canon_hash;
			SHA1_Final(char2unsigned_char(sha1->sha1_out), &sha1->sha1_ctx);

			if (sha1->sha1_tmpbio != NULL)
				(void) BIO_flush(sha1->sha1_tmpbio);

			break;
		  }

# ifdef HAVE_SHA256
		  case DKIM_HASHTYPE_SHA256:
		  {
			struct dkim_sha256 *sha256;

			sha256 = (struct dkim_sha256 *) cur->canon_hash;
			SHA256_Final(char2unsigned_char(sha256->sha256_out), &sha256->sha256_ctx);

			if (sha256->sha256_tmpbio != NULL)
				(void) BIO_flush(sha256->sha256_tmpbio);

			break;
		  }
# endif /* HAVE_SHA256 */
#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */
			(void)dkim;
#endif /* USE_GNUTLS */

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

	return DKIM_STAT_OK;
}

/*
**  ARC_CANON_STRIP_B -- strip "b=" value from a header field
**
**  Parameters:
**  	msg -- ARC_MESSAGE handle
**  	text -- string containing header field to strip
**
**  Return value:
**  	An ARC_STAT_* constant.
**
**  Side effects:
**  	The stripped header field is left in msg->arc_hdrbuf.
*/

static DKIM_STAT
arc_canon_strip_b(DKIM *dkim, char const *text)
{

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

	DKIM_STAT status = dkim_check_hdrbuf(dkim);
	if (status != DKIM_STAT_OK)
		return status;

	char tmpbuf[BUFRSZ];
	char *tmp = tmpbuf;
	char *end = tmpbuf + sizeof tmpbuf;

	char in = '\0';
	char last = 0;
	for (char const *p = text; *p != '\0'; ++p)
	{
		if (*p == ';')
			in = '\0';

		if (in == 'b')
		{
			last = *p;
			continue;
		}

		if (in == '\0' && *p == '=')
			in = last;

		*tmp++ = *p;

		/* flush buffer? */
		if (tmp == end)
		{
			if (!dkim_dstring_catn(dkim->dkim_hdrbuf,
			                      tmpbuf, tmp - tmpbuf))
				return DKIM_STAT_NORESOURCE;

			tmp = tmpbuf;
		}

		last = *p;
	}

	/* flush anything cached */
	if (tmp != tmpbuf)
	{
		if (!dkim_dstring_catn(dkim->dkim_hdrbuf,
		                      tmpbuf, tmp - tmpbuf))
			return DKIM_STAT_NORESOURCE;
	}

	return DKIM_STAT_OK;
}


/*
**  DKIM_CANON_RUNHEADERS -- run the headers through all header
**                           canonicalizations
**
**  Parameters:
**  	dkim -- DKIM handle
**
**  Return value:
**  	A DKIM_STAT_* constant.
**
**  Note:
**  	Header canonicalizations are finalized by this function when
**  	verifying.  In signing mode, header canonicalizations are finalized
**  	by a subsequent call to dkim_canon_signature().
*/

DKIM_STAT
dkim_canon_runheaders(DKIM *dkim)
{
	int c;
	int nhdrs = 0;
	DKIM_STAT status;
	char *tmp;
	DKIM_CANON *cur;
	struct dkim_header *hdr;
	struct dkim_header **hdrset = NULL;
	struct dkim_header tmphdr;

	assert(dkim != NULL);

	status = dkim_check_hdrbuf(dkim);
	if (status != DKIM_STAT_OK)
		return status;

	for (cur = dkim->dkim_canonhead; cur != NULL; cur = cur->canon_next)
	{
		/* skip done hashes and those which are of the body type */
		if (cur->canon_done || !cur->canon_hdr)
			continue;

		_Bool const signing = (cur->canon_sigheader == NULL);
		unsigned int apply_flag;

		/* clear header selection flags if verifying */
		if (!signing)
		{
			assert(cur->canon_hdrlist);

			/* do header selection */
			apply_flag = DKIM_HDR_SIGNED;
			nhdrs = dkim_canon_selecthdrs(dkim, apply_flag,
			                              cur->canon_hdrlist,
			                              &hdrset);
		}
		else
		{
			regex_t *hdrtest;

			DKIM_LIB *lib = dkim->dkim_libhandle;
			if (dkim->dkim_hdrre != NULL)
				hdrtest = dkim->dkim_hdrre;
			else
				hdrtest = &lib->dkiml_hdrre;

			bool all_headers =
				hdrtest == &lib->dkiml_hdrre && !lib->dkiml_signre;

			nhdrs = 0;

			/* tag headers to be signed */
			status = dkim_check_hdrbuf(dkim);
			if (status != DKIM_STAT_OK)
				return status;

			for (hdr = dkim->dkim_hhead;
			     hdr != NULL;
			     hdr = hdr->hdr_next)
			{
				if (all_headers)
				{
					tmp = dkim_dstring_get(dkim->dkim_hdrbuf);

					if (tmp[0] != '\0')
					{
						dkim_dstring_cat1(dkim->dkim_hdrbuf,
						                 ':');
					}

					dkim_dstring_catn(dkim->dkim_hdrbuf,
					                  hdr->hdr_text,
					                  hdr->hdr_namelen);
					continue;
				}

				/* could be space, could be colon ... */
				char const savechar = hdr->hdr_text[hdr->hdr_namelen];

				/* terminate the header field name and test */
				hdr->hdr_text[hdr->hdr_namelen] = '\0';
				status = regexec(hdrtest,
				                 (char *) hdr->hdr_text,
				                 0, NULL, 0);

				/* restore the character */
				hdr->hdr_text[hdr->hdr_namelen] = savechar;

				if (status == 0)
				{
					tmp = dkim_dstring_get(dkim->dkim_hdrbuf);

					if (tmp[0] != '\0')
					{
						dkim_dstring_cat1(dkim->dkim_hdrbuf,
						                 ':');
					}

					dkim_dstring_catn(dkim->dkim_hdrbuf,
					                  hdr->hdr_text,
							  hdr->hdr_namelen);
				}
				else
				{
					assert(status == REG_NOMATCH);
				}
			}

			/* do header selection */
			apply_flag = DKIM_HDR_TO_BE_SIGNED;
			nhdrs = dkim_canon_selecthdrs(dkim, apply_flag,
			                              dkim_dstring_get(dkim->dkim_hdrbuf),
			                              &hdrset);
		}

		if (nhdrs == -1)
		{
			dkim_error(dkim,
					   "dkim_canon_selecthdrs() failed during canonicalization");
			if (hdrset)
				DKIM_FREE(dkim, hdrset);
			else
				return DKIM_STAT_NORESOURCE;

			return DKIM_STAT_INTERNAL;
		}


		/* canonicalize each marked header */
		for (c = 0; c < nhdrs; c++)
		{
			if (hdrset[c] != NULL &&
			    (hdrset[c]->hdr_flags & apply_flag) != 0)
			{
				status = dkim_canon_header(dkim, cur,
				                           hdrset[c], TRUE);
				if (status != DKIM_STAT_OK)
				{
					DKIM_FREE(dkim, hdrset);
					return status;
				}
			}
		}

		DKIM_FREE(dkim, hdrset);
		hdrset = NULL;

		/* if signing, we can't do the rest of this yet */
		if (signing)
			continue;

		/*
		**  We need to copy the DKIM-Signature: header being verified,
		**  minus the contents of the "b=" part, and include it in the
		**  canonicalization.  However, skip this if no hashing was
		**  done.
		*/

		status = arc_canon_strip_b(dkim, cur->canon_sigheader->hdr_text);
		if (status != DKIM_STAT_OK)
			return status;

		/* canonicalize */
		tmphdr.hdr_text = dkim_dstring_get(dkim->dkim_hdrbuf);
		tmphdr.hdr_namelen = cur->canon_sigheader->hdr_namelen;
		tmphdr.hdr_colon = tmphdr.hdr_text + (cur->canon_sigheader->hdr_colon - cur->canon_sigheader->hdr_text);
		tmphdr.hdr_textlen = dkim_dstring_len(dkim->dkim_hdrbuf);
		tmphdr.hdr_flags = 0;
		tmphdr.hdr_next = NULL;

		if (cur->canon_canon == DKIM_CANON_RELAXED)
			dkim_lowerhdr(tmphdr.hdr_text);
		(void) dkim_canon_header(dkim, cur, &tmphdr, FALSE);
		dkim_canon_buffer(cur, NULL, 0);

		status = dkim_canon_finalize(dkim, cur);
		if (status != DKIM_STAT_OK)
			return status;

		cur->canon_done = TRUE;
	}

	return DKIM_STAT_OK;
}

/*
**  DKIM_CANON_SIGNATURE -- append a signature header when signing
**
**  Parameters:
**  	dkim -- DKIM handle
**  	hdr -- header
**
**  Return value:
**  	A DKIM_STAT_* constant.
**
**  Notes:
**  	Header canonicalizations are finalized by this function.
*/

DKIM_STAT
dkim_canon_signature(DKIM *dkim, char const *hdr)
{
	DKIM_STAT status;
	DKIM_CANON *cur;

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

	//status = dkim_check_hdrbuf(dkim);
	//if (status != DKIM_STAT_OK)
		//return status;

	for (cur = dkim->dkim_canonhead; cur != NULL; cur = cur->canon_next)
	{
		/* skip done hashes and those which are of the body type */
		if (cur->canon_done || !cur->canon_hdr)
			continue;

		/* prepare the data */
		// dkim_dstring_copy(dkim->dkim_hdrbuf, hdr->hdr_text);
		//char *txt = dkim_dstring_get(dkim->dkim_hdrbuf);
		/* canonicalize the signature */
		if (cur->canon_canon == DKIM_CANON_RELAXED)
		{
			char *txt = dkim_strdup(dkim, hdr, 0);
			if (txt == NULL)
				return DKIM_STAT_NORESOURCE;
			dkim_lowerhdr(txt);
			status = dkim_canon_header_text(dkim, cur, txt, FALSE);
			DKIM_FREE(dkim, txt);
		}
		else
			status = dkim_canon_header_text(dkim, cur, hdr, FALSE);

		if (status != DKIM_STAT_OK)
			return status;
		dkim_canon_buffer(cur, NULL, 0);

		/* finalize */
		status = dkim_canon_finalize(dkim, cur);
		if (status != DKIM_STAT_OK)
			return status;

		cur->canon_done = TRUE;
	}

	return DKIM_STAT_OK;
}

/*
**  ARC_CANON_RUNHEADERS_SEAL -- run the ARC-specific header fields through
**                               seal canonicalization(s)
**
**  Parameters:
**  	dkim -- ARC_MESSAGE handle
**
**  Return value:
**  	An ARC_STAT_* constant.
**
**  For each ARC set number N, apply it to seal canonicalization handles 0
**  through N-1.  That way the first one is only set 1, the second one is
**  sets 1 and 2, etc.  For the final one in each set, strip "b=".  Then
**  also do one more complete one so that can be used for re-sealing.
*/
DKIM_STAT
arc_canon_runheaders_seal(DKIM *dkim)
{
	assert(dkim != NULL);
	assert(dkim->arc_state == ARC_CHAIN_UNKNOWN);

	unsigned int nsets = dkim->dkim_arccount;

	for (unsigned int n = 0; n < nsets; ++n)
	{
		struct arc_set *arc = &dkim->arc_sets[n];
		struct dkim_canon *cur = arc->arcsig_as->sig_hdrcanon;
		if (cur == NULL || cur->canon_done)
			continue;

		DKIM_STAT status;

		/* build up the canonicalized seals for verification */
		for (unsigned int m = 0; m <= n; m++)
		{
			struct arc_set *arc2 = &dkim->arc_sets[m];
			status = dkim_canon_header(dkim, cur,
			                          arc2->arcset_aar->set_udata,
			                          TRUE);
			if (status != DKIM_STAT_OK)
				return status;

			status = dkim_canon_header(dkim, cur,
			                          arc2->arcsig_ams->sig_taglist->set_udata,
			                          TRUE);
			if (status != DKIM_STAT_OK)
				return status;

			if (m != n)
			{
				status = dkim_canon_header(dkim, cur,
			                              arc2->arcsig_as->sig_taglist->set_udata,
				                          TRUE);
			}
			else
			{
				struct dkim_header tmphdr;
				arc_canon_strip_b(dkim, // result in dkim_hdrbuf
				                  arc2->arcsig_as->sig_taglist->set_udata->hdr_text);

				tmphdr.hdr_text = dkim_dstring_get(dkim->dkim_hdrbuf);
				tmphdr.hdr_namelen = cur->canon_sigheader->hdr_namelen;
				tmphdr.hdr_colon = tmphdr.hdr_text + (cur->canon_sigheader->hdr_colon - cur->canon_sigheader->hdr_text);
				tmphdr.hdr_textlen = dkim_dstring_len(dkim->dkim_hdrbuf);
				tmphdr.hdr_flags = 0;
				tmphdr.hdr_next = NULL;

				dkim_lowerhdr(tmphdr.hdr_text);
				/* XXX -- void? */
				dkim_canon_header(dkim, cur, &tmphdr, FALSE);
				dkim_canon_buffer(cur, NULL, 0);
			}

			if (status != DKIM_STAT_OK)
				return status;
		}

		status = dkim_canon_finalize(dkim, cur);
		if (status != DKIM_STAT_OK)
			return status;

		cur->canon_done = TRUE;

		if (dkim->arc_state == ARC_CHAIN_UNKNOWN)
		{
			status = dkim_sig_process(dkim, arc->arcsig_as);
			if (status != DKIM_STAT_OK)
			{
				if ((arc->arcsig_as->sig_flags & DKIM_SIGFLAG_PROCESSED) == 0)
					dkim->arc_state = ARC_CHAIN_TEMPERROR;
				else
					dkim->arc_state = ARC_CHAIN_PERMERROR;
				dkim->arc_seal_failed = n;
				return status;
			}

			if ((arc->arcsig_as->sig_flags & DKIM_SIGFLAG_PASSED) == 0)
			{
				dkim->arc_state = ARC_CHAIN_FAIL;
				dkim->arc_seal_failed = n;
			}
		}
	}

	dkim->arc_state = ARC_CHAIN_SEAL_OK;
	return DKIM_STAT_OK;
}

/*
**  ARC_CANON_RUNHEADERS_SEAL_AGAIN -- run the ARC-specific header fields through
**                               seal canonicalization(s)
**
**  Parameters:
**  	dkim -- ARC_MESSAGE handle
**
**  Return value:
**  	An ARC_STAT_* constant.
**
**  For each ARC set number N, apply it to seal canonicalization handles 0
**  through N-1.  That way the first one is only set 1, the second one is
**  sets 1 and 2, etc.  For the final one in each set, strip "b=".  Then
**  also do one more complete one so that can be used for re-sealing.
*/
DKIM_STAT
arc_canon_runheaders_seal_again(DKIM *dkim, DKIM_CANON *cur)
{
	assert(dkim != NULL);

	if (cur == NULL || cur->canon_done)
		return DKIM_STAT_OK;

	unsigned int nsets = dkim->dkim_arccount;

	for (unsigned int n = 0; n < nsets; ++n)
	{
		/* write all the ARC sets once more for re-sealing */

		struct arc_set *arc = &dkim->arc_sets[n];
		DKIM_STAT status;

		status = dkim_canon_header(dkim, cur,
			                      arc->arcset_aar->set_udata,
		                          TRUE);
		if (status != DKIM_STAT_OK)
			return status;

		status = dkim_canon_header(dkim, cur,
			                      arc->arcsig_ams->sig_taglist->set_udata,
		                          TRUE);
		if (status != DKIM_STAT_OK)
			return status;

		status = dkim_canon_header(dkim, cur,
			                      arc->arcsig_as->sig_taglist->set_udata,
		                          TRUE);
		if (status != DKIM_STAT_OK)
			return status;
	}

	return DKIM_STAT_OK;
}





/*
**  DKIM_CANON_MINBODY -- return number of bytes required to satisfy all
**                        canonicalizations
**
**  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_canon_minbody(DKIM *dkim)
{
	u_long minbody = 0;
	DKIM_CANON *cur;

	assert(dkim != NULL);

	for (cur = dkim->dkim_canonhead; cur != NULL; cur = cur->canon_next)
	{
		/* skip done hashes and those which are of the wrong type */
		if (cur->canon_done || cur->canon_hdr)
			continue;

		/* if this one wants the whole message, short-circuit */
		if (cur->canon_remain == (ssize_t) -1)
			return ULONG_MAX;

		/* compare to current minimum */
		minbody = MAX(minbody, (u_long) cur->canon_remain);
	}

	return minbody;
}

/*
**  DKIM_CANON_BODYCHUNK -- run a body chunk through all body
**                          canonicalizations
**
**  Parameters:
**  	dkim -- DKIM handle
**  	buf -- pointer to bytes to canonicalize
**  	buflen -- number of bytes to canonicalize
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_canon_bodychunk(DKIM *dkim, char *buf, size_t buflen)
{
	_Bool fixcrlf;
	DKIM_STAT status;
	u_int wlen;
	DKIM_CANON *cur;
	size_t plen;
	char *wrote;
	char *eob;
	char *start;

	assert(dkim != NULL);

	dkim->dkim_bodylen += buflen;

	fixcrlf = (dkim->dkim_libhandle->dkiml_flags & DKIM_LIBFLAGS_FIXCRLF);

	for (cur = dkim->dkim_canonhead; cur != NULL; cur = cur->canon_next)
	{
		/* skip done hashes and those which are of the header type */
		if (cur->canon_done || cur->canon_hdr)
			continue;

		start = buf;
		plen = buflen;

		if (fixcrlf)
		{
			status = dkim_canon_fixcrlf(dkim, cur, buf, buflen);
			if (status != DKIM_STAT_OK)
				return status;

			start = dkim_dstring_get(dkim->dkim_canonbuf);
			plen = dkim_dstring_len(dkim->dkim_canonbuf);
		}

		eob = start + plen - 1;
		wrote = start;
		wlen = 0;

		switch (cur->canon_canon)
		{
		  case DKIM_CANON_SIMPLE:
			for (char *p = start; p <= eob; ++p)
			{
				if (*p == '\n')
				{
					if (cur->canon_lastchar == '\r')
					{
						if (cur->canon_blankline)
						{
							cur->canon_blanks++;
						}
						else if (wlen == 1 ||
						         p == start)
						{
							dkim_canon_buffer(cur,
							                  CRLF,
							                  2);
						}
						else
						{
							dkim_canon_buffer(cur,
							                  wrote,
							                  wlen + 1);
						}

						wrote = p + 1;
						wlen = 0;
						cur->canon_blankline = TRUE;
					}
				}
				else
				{
					if (p == start &&
					    cur->canon_lastchar == '\r')
					{
						if (fixcrlf)
						{
							dkim_canon_buffer(cur, CRLF, 2);
							cur->canon_lastchar = '\n';
							cur->canon_blankline = TRUE;
						}
						else
						{
							dkim_canon_buffer(cur, "\r", 1);
						}
					}

					if (*p != '\r')
					{
						if (cur->canon_blanks > 0)
							dkim_canon_flushblanks(cur);
						cur->canon_blankline = FALSE;
					}

					wlen++;
				}

				cur->canon_lastchar = *(unsigned char*)p;
			}

			if (wlen > 0 && wrote[wlen - 1] == '\r')
				wlen--;

			dkim_canon_buffer(cur, wrote, wlen);

			break;

		  case DKIM_CANON_RELAXED:
			for (char *p = start; p <= eob; ++p)
			{
				switch (cur->canon_bodystate)
				{
				  case 0:
					if (DKIM_ISWSP(*(unsigned char*)p))
					{
						cur->canon_bodystate = 1;
					}
					else if (*p == '\r')
					{
						cur->canon_bodystate = 2;
					}
					else
					{
						cur->canon_blankline = FALSE;
						dkim_dstring_cat1(cur->canon_buf, *p);
						cur->canon_bodystate = 3;
					}
					break;

				  case 1:
					if (DKIM_ISWSP(*(unsigned char*)p))
					{
						break;
					}
					else if (*p == '\r')
					{
						cur->canon_bodystate = 2;
					}
					else
					{
						dkim_canon_flushblanks(cur);
						dkim_canon_buffer(cur, SP, 1);
						cur->canon_blankline = FALSE;
						dkim_dstring_cat1(cur->canon_buf, *p);
						cur->canon_bodystate = 3;
					}
					break;

				  case 2:
					if (fixcrlf || *p == '\n')
					{
						if (cur->canon_blankline)
						{
							cur->canon_blanks++;
							cur->canon_bodystate = 0;
						}
						else
						{
							dkim_canon_flushblanks(cur);
							dkim_canon_buffer(cur,
							                  dkim_dstring_get(cur->canon_buf),
							                  dkim_dstring_len(cur->canon_buf));
							dkim_canon_buffer(cur,
							                  CRLF,
							                  2);
							dkim_dstring_blank(cur->canon_buf);

							if (*p == '\n')
							{
								cur->canon_blankline = TRUE;
								cur->canon_bodystate = 0;
							}
							else if (*p == '\r')
							{
								cur->canon_blankline = TRUE;
							}
							else
							{
								if (DKIM_ISWSP(*(unsigned char*)p))
								{
									cur->canon_bodystate = 1;
								}
								else
								{
									dkim_dstring_cat1(cur->canon_buf, *p);
									cur->canon_bodystate = 3;
								}
							}
						}
					}
					else if (*p == '\r')
					{
						cur->canon_blankline = FALSE;
						dkim_dstring_cat1(cur->canon_buf, *p);
					}
					else if (DKIM_ISWSP(*(unsigned char*)p))
					{
						dkim_canon_flushblanks(cur);
						dkim_canon_buffer(cur,
						                  dkim_dstring_get(cur->canon_buf),
						                  dkim_dstring_len(cur->canon_buf));
						dkim_dstring_blank(cur->canon_buf);
						cur->canon_bodystate = 1;
					}
					else
					{
						cur->canon_blankline = FALSE;
						dkim_dstring_cat1(cur->canon_buf, *p);
						cur->canon_bodystate = 3;
					}
					break;

				  case 3:
					if (DKIM_ISWSP(*(unsigned char*)p))
					{
						dkim_canon_flushblanks(cur);
						dkim_canon_buffer(cur,
						                  dkim_dstring_get(cur->canon_buf),
						                  dkim_dstring_len(cur->canon_buf));
						dkim_dstring_blank(cur->canon_buf);
						cur->canon_bodystate = 1;
					}
					else if (*p == '\r')
					{
						cur->canon_bodystate = 2;
					}
					else
					{
						dkim_dstring_cat1(cur->canon_buf, *p);
					}
					break;
				}

				cur->canon_lastchar = *(unsigned char*)p;
			}

			dkim_canon_buffer(cur, NULL, 0);

			break;

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

	return DKIM_STAT_OK;
}

/*
**  DKIM_CANON_CLOSEBODY -- close all body canonicalizations
**
**  Parameters:
**  	dkim -- DKIM handle
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_canon_closebody(DKIM *dkim)
{
	DKIM_CANON *cur;

	assert(dkim != NULL);

	for (cur = dkim->dkim_canonhead; cur != NULL; cur = cur->canon_next)
	{
		/* skip done hashes or header canonicalizations */
		if (cur->canon_done || cur->canon_hdr)
			continue;

		/* handle unprocessed content */
		if (dkim_dstring_len(cur->canon_buf) > 0)
		{
			if ((dkim->dkim_libhandle->dkiml_flags & DKIM_LIBFLAGS_FIXCRLF) != 0)
			{
				dkim_canon_buffer(cur,
				                  dkim_dstring_get(cur->canon_buf),
				                  dkim_dstring_len(cur->canon_buf));
				dkim_canon_buffer(cur, CRLF, 2);
			}
			else
			{
				dkim_error(dkim, "CRLF at end of body missing");
				return DKIM_STAT_SYNTAX;
			}
		}

		/* "simple" canonicalization must include at least a CRLF */
		if (cur->canon_canon == DKIM_CANON_SIMPLE &&
		    cur->canon_wrote == 0)
			dkim_canon_buffer(cur, CRLF, 2);

		dkim_canon_buffer(cur, NULL, 0);

		DKIM_STAT status = dkim_canon_finalize(dkim, cur);
		if (status != DKIM_STAT_OK)
			return status;

		cur->canon_done = TRUE;
	}

	return DKIM_STAT_OK;
}

/*
**  DKIM_CANON_GETFINAL -- retrieve final digest
**
**  Parameters:
**  	canon -- DKIM_CANON handle
**  	digest -- pointer to the digest (returned)
**  	dlen -- digest length (returned)
**
**  Return value:
**  	A DKIM_STAT_* constant.
*/

DKIM_STAT
dkim_canon_getfinal(DKIM_CANON *canon, char **digest, size_t *dlen)
{
	assert(canon != NULL);
	assert(digest != NULL);
	assert(dlen != NULL);

	if (!canon->canon_done)
		return DKIM_STAT_INVALID;

	switch (canon->canon_hashtype)
	{
#ifdef USE_GNUTLS
	  case DKIM_HASHTYPE_SHA1:
	  case DKIM_HASHTYPE_SHA256:
	  {
		struct dkim_sha *sha;

		sha = canon->canon_hash;
		*digest = sha->sha_out;
		*dlen = sha->sha_outlen;

		return DKIM_STAT_OK;
	  }
#else /* USE_GNUTLS */
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
	  case DKIM_HASHTYPE_SHA1:
	  case DKIM_HASHTYPE_SHA256:
	  {
		struct dkim_sha *sha = canon->canon_hash;
		if (sha->failed)
			return DKIM_STAT_INTERNAL;

		*digest = sha->sha_out;
		*dlen = sha->sha_outlen;
		return DKIM_STAT_OK;
	  }
#else /* OPENSSL_VERSION_NUMBER >= 0x30000000L */

	  case DKIM_HASHTYPE_SHA1:
	  {
		struct dkim_sha1 *sha1;

		sha1 = (struct dkim_sha1 *) canon->canon_hash;
		*digest = sha1->sha1_out;
		*dlen = sizeof sha1->sha1_out;

		return DKIM_STAT_OK;
	  }

# ifdef HAVE_SHA256
	  case DKIM_HASHTYPE_SHA256:
	  {
		struct dkim_sha256 *sha256;

		sha256 = (struct dkim_sha256 *) canon->canon_hash;
		*digest = sha256->sha256_out;
		*dlen = sizeof sha256->sha256_out;

		return DKIM_STAT_OK;
	  }
# endif /* HAVE_SHA256 */
#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */
#endif /* USE_GNUTLS */

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

// not used (except dkim_sig_gethashes()) -----

/*
**  DKIM_CANON_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_canon_gethashes(DKIM_SIGINFO *sig, void **hh, size_t *hhlen,
                     void **bh, size_t *bhlen)
{
	DKIM_STAT status;
	struct dkim_canon *hdc;
	struct dkim_canon *bdc;
	char *hd;
	char *bd;
	size_t hdlen;
	size_t bdlen;

	hdc = sig->sig_hdrcanon;
	bdc = sig->sig_bodycanon;

	status = dkim_canon_getfinal(hdc, &hd, &hdlen);
	if (status != DKIM_STAT_OK)
		return status;

	status = dkim_canon_getfinal(bdc, &bd, &bdlen);
	if (status != DKIM_STAT_OK)
		return status;

	*hh = hd;
	*hhlen = hdlen;
	*bh = bd;
	*bhlen = bdlen;

	return DKIM_STAT_OK;
}
