/*
**  Copyright (c) 2007 Sendmail, Inc. and its suppliers.
**    All rights reserved.
*/

#ifndef lint
static char dkim_canon_c_id[] = "@(#)$Id: dkim-canon.c,v 1.36 2007/12/18 02:15:47 msk Exp $";
#endif /* !lint */

/* system includes */
#include <sys/param.h>
#include <sys/types.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <limits.h>

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

/* libdkim includes */
#include "dkim.h"
#include "dkim-types.h"
#include "dkim-canon.h"
#include "dkim-util.h"
#include "util.h"

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

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

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

/*
**  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, u_char *buf, size_t buflen)
{
	assert(canon != NULL);

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

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

	assert(canon->canon_hash != NULL);

	switch (canon->canon_hashtype)
	{
	  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 SHA256_DIGEST_LENGTH
	  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 /*SHA256_DIGEST_LENGTH */
	}

	canon->canon_wrote += buflen;
	if (canon->canon_remain != (off_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, u_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 -- 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)
{
	bool space;
	int n;
	u_char *p;
	u_char *tmp;
	u_char *end;
	u_char tmpbuf[BUFRSZ];

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

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

	if (dkim->dkim_canonbuf == NULL)
	{
		dkim->dkim_canonbuf = dkim_dstring_new(dkim, hdr->hdr_textlen,
		                                       0);
		if (dkim->dkim_canonbuf == NULL)
			return DKIM_STAT_NORESOURCE;
	}
	else
	{
		dkim_dstring_blank(dkim->dkim_canonbuf);
	}

	n = 0;

	dkim_canon_buffer(canon, NULL, 0);

	switch (canon->canon_canon)
	{
	  case DKIM_CANON_SIMPLE:
		if (!dkim_dstring_catn(dkim->dkim_canonbuf,
		                       hdr->hdr_text, hdr->hdr_textlen) ||
		    (crlf && !dkim_dstring_catn(dkim->dkim_canonbuf, CRLF, 2)))
			return DKIM_STAT_NORESOURCE;
		break;

	  case DKIM_CANON_RELAXED:
		/* process header field name (before colon) first */
		for (p = hdr->hdr_text; *p != '\0'; p++)
		{
			/*
			**  Discard spaces before the colon or before the end
			**  of the first word.
			*/

			if (isascii(*p))
			{
				/* discard spaces */
				if (isspace(*p))
					continue;

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

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

				if (!dkim_dstring_catn(dkim->dkim_canonbuf,
				                       tmpbuf, tmp - tmpbuf))
					return DKIM_STAT_NORESOURCE;

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

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

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

		for ( ; *p != '\0'; p++)
		{
			if (isascii(*p) && isspace(*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(dkim->dkim_canonbuf,
					                       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(dkim->dkim_canonbuf,
				                       tmpbuf, tmp - tmpbuf))
					return DKIM_STAT_NORESOURCE;

				tmp = tmpbuf;
			}
		}

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

			if (!dkim_dstring_catn(dkim->dkim_canonbuf,
			                       tmpbuf, tmp - tmpbuf))
				return DKIM_STAT_NORESOURCE;
		}

		if (crlf && !dkim_dstring_catn(dkim->dkim_canonbuf, CRLF, 2))
			return DKIM_STAT_NORESOURCE;

		break;
	}

	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)
{
	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_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.
*/

static int
dkim_canon_selecthdrs(DKIM *dkim, u_char *hdrlist, struct dkim_header **ptrs,
                      int nptrs)
{
	u_char *bar;
	char *ctx;
	char *colon;
	int c;
	int n;
	int m;
	size_t len;
	struct dkim_header *hdr;
	struct dkim_header **lhdrs;
	u_char **hdrs;

	assert(dkim != NULL);
	assert(ptrs != NULL);
	assert(nptrs != 0);

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

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

		return n;
	}

	if (dkim->dkim_hdrlist == NULL)
	{
		dkim->dkim_hdrlist = dkim_malloc(dkim->dkim_libhandle,
		                                 dkim->dkim_closure,
		                                 DKIM_MAXHEADER);
		if (dkim->dkim_hdrlist == NULL)
		{
			dkim_error(dkim, "unable to allocate %d bytes(s)",
			           DKIM_MAXHEADER);

			return -1;
		}
	}

	sm_strlcpy(dkim->dkim_hdrlist, hdrlist, DKIM_MAXHEADER);

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

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

	n = 1;
	for (colon = dkim->dkim_hdrlist; *colon != '\0'; colon++)
	{
		if (*colon == ':')
			n++;
	}
	n = dkim->dkim_hdrcnt * n;
	hdrs = DKIM_MALLOC(dkim, n);
	if (hdrs == NULL)
		return -1;

	n = 0;

	/* make a split-out copy of hdrlist */
	memset(hdrs, '\0', sizeof hdrs);
	for (bar = strtok_r(dkim->dkim_hdrlist, ":", &ctx);
	     bar != NULL;
	     bar = strtok_r(NULL, ":", &ctx))
	{
		hdrs[n] = (u_char *) bar;
		n++;
	}

	/* bounds check */
	if (n > nptrs)
	{
		dkim_error(dkim, "too many headers (max %d)", nptrs);

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

		return -1;
	}

	/* for each named header, find the last unused one and use it up */
	for (c = 0; c < n; c++)
	{
		lhdrs[c] = NULL;

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

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

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

		if (lhdrs[c] != NULL)
			lhdrs[c]->hdr_flags |= DKIM_HDR_SIGNED;
	}

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

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

	return m;
}

/* ========================= 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)
	{
		cur->canon_hashbuf = DKIM_MALLOC(dkim, DKIM_HASHBUFSIZE);
		if (cur->canon_hashbuf == NULL)
		{
			dkim_error(dkim,
			           "unable to allocate %d byte(s)",
			           sizeof(struct dkim_sha1));
			return DKIM_STAT_NORESOURCE;
		}
		cur->canon_hashbufsize = DKIM_HASHBUFSIZE;
		cur->canon_hashbuflen = 0;

		switch (cur->canon_hashtype)
		{
		  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 DKIM_HASHTYPE_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 /* DKIM_HASHTYPE_SHA256 */

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

	cur = dkim->dkim_canonhead;
	while (cur != NULL)
	{
		next = cur->canon_next;
		dkim_mfree(dkim->dkim_libhandle, dkim->dkim_closure,
		           cur->canon_hash);
		cur = next;
	}
}

/*
**  DKIM_ADD_CANON -- add a new canonicalization handle if needed
**
**  Parameters:
**  	dkim -- verification handle
**  	hdr -- TRUE iff this is specifying a header canonicalization
**  	canon -- canonicalization mode
**  	hashtype -- hash type
**  	hdrlist -- for header canonicalization, the header list
**  	hdr -- 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,
               u_char *hdrlist, struct dkim_header *sighdr,
               off_t length, DKIM_CANON **cout)
{
	DKIM_CANON *cur;
	DKIM_CANON *new;

	assert(dkim != NULL);
	assert(canon == DKIM_CANON_SIMPLE || canon == DKIM_CANON_RELAXED);
#ifdef DKIM_HASHTYPE_SHA256
	assert(hashtype == DKIM_HASHTYPE_SHA1 ||
	       hashtype == DKIM_HASHTYPE_SHA256);
#else /* DKIM_HASHTYPE_SHA256 */
	assert(hashtype == DKIM_HASHTYPE_SHA1);
#endif /* DKIM_HASHTYPE_SHA256 */

	for (cur = dkim->dkim_canonhead; cur != NULL; cur = cur->canon_next)
	{
		if (cur->canon_hdr != hdr ||
		    cur->canon_hashtype != hashtype ||
		    cur->canon_canon != canon)
			continue;

		if (hdr && 
                    ((hdrlist == NULL && cur->canon_hdrlist != NULL) ||
                     (hdrlist != NULL && cur->canon_hdrlist == NULL) ||
                     strcasecmp(hdrlist, cur->canon_hdrlist) != 0))
			continue;

		if (!hdr && 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 = (off_t) -1;
		new->canon_remain = (off_t) -1;
	}
	else
	{
		new->canon_length = length;
		new->canon_remain = length;
	}
	new->canon_sigheader = sighdr;
	new->canon_hdrlist = hdrlist;
	new->canon_next = NULL;
	new->canon_done = FALSE;
	new->canon_blankline = FALSE;
	new->canon_blanks = 0;
	new->canon_wrote = 0;
	new->canon_hashbuflen = 0;
	new->canon_hashbufsize = 0;
	new->canon_hashbuf = NULL;

	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_RUNHEADERS -- run the headers through all header
**                           canonicalizations
**
**  Parameters:
**  	dkim -- DKIM handle
**  	signing -- TRUE iff we're signing
**
**  Return value:
**  	A DKIM_STAT_* constant.
**
**  Note:
**  	Header canonicalizations are finalized by this function.
*/

DKIM_STAT
dkim_canon_runheaders(DKIM *dkim, bool signing)
{
	u_char savechar;
	int c;
	int n;
	int in;
	int nhdrs;
	int last = '\0';
	DKIM_STAT status;
	u_char *tmp;
	u_char *end;
	DKIM_CANON *cur;
	u_char *p;
	struct dkim_header *hdr;
	struct dkim_header **hdrset;
	struct dkim_header tmphdr;
	u_char tmpbuf[BUFRSZ];

	assert(dkim != NULL);

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

	n = dkim->dkim_hdrcnt * sizeof(struct dkim_header *);
	hdrset = DKIM_MALLOC(dkim, n);
	if (hdrset == NULL)
		return DKIM_STAT_NORESOURCE;

	if (dkim->dkim_hdrbuf == NULL)
	{
		dkim->dkim_hdrbuf = dkim_dstring_new(dkim, BUFRSZ, MAXBUFRSZ);
		if (dkim->dkim_hdrbuf == NULL)
		{
			DKIM_FREE(dkim, hdrset);
			return DKIM_STAT_NORESOURCE;
		}
	}
	else
	{
		dkim_dstring_blank(dkim->dkim_hdrbuf);
	}

	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;

		/* clear header selection flags if verifying */
		if (!signing)
		{
			if (cur->canon_hdrlist == NULL)
			{
				for (hdr = dkim->dkim_hhead;
				     hdr != NULL;
				     hdr = hdr->hdr_next)
					hdr->hdr_flags |= ~DKIM_HDR_SIGNED;
			}
			else
			{
				for (hdr = dkim->dkim_hhead;
				     hdr != NULL;
				     hdr = hdr->hdr_next)
					hdr->hdr_flags &= ~DKIM_HDR_SIGNED;

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

				/* do header selection */
				nhdrs = dkim_canon_selecthdrs(dkim,
				                              cur->canon_hdrlist,
				                              hdrset,
				                              dkim->dkim_hdrcnt);

				if (nhdrs == -1)
				{
					dkim_error(dkim,
					           "dkim_canon_selecthdrs() failed during canonicalization");
					DKIM_FREE(dkim, hdrset);
					return DKIM_STAT_INTERNAL;
				}
			}
		}
		else
		{
			DKIM_LIB *lib;
			char *tmp;

			lib = dkim->dkim_libhandle;

			memset(hdrset, '\0', sizeof hdrset);
			nhdrs = 0;

			/* tag headers to be signed */
			for (hdr = dkim->dkim_hhead;
			     hdr != NULL && nhdrs < MAXHEADERS;
			     hdr = hdr->hdr_next)
			{
				if (!lib->dkiml_signre)
				{
					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 ... */
				savechar = hdr->hdr_text[hdr->hdr_namelen];

				/* terminate the header field name and test */
				hdr->hdr_text[hdr->hdr_namelen] = '\0';
				status = regexec(&lib->dkiml_hdrre,
				                 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);
				}
			}

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

			/* do header selection */
			nhdrs = dkim_canon_selecthdrs(dkim,
			                              dkim_dstring_get(dkim->dkim_hdrbuf),
			                              hdrset,
			                              dkim->dkim_hdrcnt);

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

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

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

		dkim_dstring_blank(dkim->dkim_hdrbuf);

		tmp = tmpbuf;

		n = 0;
		in = '\0';
		for (p = cur->canon_sigheader->hdr_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)
			{
				*tmp = '\0';

				if (!dkim_dstring_catn(dkim->dkim_hdrbuf,
				                       tmpbuf, tmp - tmpbuf))
					return DKIM_STAT_NORESOURCE;

				tmp = tmpbuf;
			}

			last = *p;
		}

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

			if (!dkim_dstring_catn(dkim->dkim_hdrbuf,
			                       tmpbuf, tmp - tmpbuf))
				return DKIM_STAT_NORESOURCE;
		}

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

		/* finalize */
		switch (cur->canon_hashtype)
		{
		  case DKIM_HASHTYPE_SHA1:
		  {
			struct dkim_sha1 *sha1;

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

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

			break;
		  }

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

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

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

			break;
		  }
#endif /* DKIM_HASHTYPE_SHA256 */

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

		cur->canon_done = TRUE;
	}

	DKIM_FREE(dkim, hdrset);

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

DKIM_STAT
dkim_canon_signature(DKIM *dkim, struct dkim_header *hdr)
{
	DKIM_STAT status;
	DKIM_CANON *cur;
	struct dkim_header tmphdr;

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

	if (dkim->dkim_hdrbuf == NULL)
	{
		dkim->dkim_hdrbuf = dkim_dstring_new(dkim, DKIM_MAXHEADER, 0);
		if (dkim->dkim_hdrbuf == NULL)
			return DKIM_STAT_NORESOURCE;
	}
	else
	{
		dkim_dstring_blank(dkim->dkim_hdrbuf);
	}

	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;

		/* prepare the data */
		dkim_dstring_copy(dkim->dkim_hdrbuf, hdr->hdr_text);
		tmphdr.hdr_text = dkim_dstring_get(dkim->dkim_hdrbuf);
		tmphdr.hdr_namelen = hdr->hdr_namelen;
		tmphdr.hdr_colon = tmphdr.hdr_text + (hdr->hdr_colon - hdr->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);
		
		/* canonicalize the signature */
		status = dkim_canon_header(dkim, cur, &tmphdr, FALSE);
		if (status != DKIM_STAT_OK)
			return status;
		dkim_canon_buffer(cur, NULL, 0);

		/* now close it */
		switch (cur->canon_hashtype)
		{
		  case DKIM_HASHTYPE_SHA1:
		  {
			struct dkim_sha1 *sha1;

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

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

			break;
		  }

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

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

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

			break;
		  }
#endif /* DKIM_HASHTYPE_SHA256 */

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

		cur->canon_done = TRUE;
	}

	return DKIM_STAT_OK;
}

/*
**  DKIM_CANON_MINBODY -- return number of bytes required to satisfy
**                        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 == (off_t) -1)
			return ULONG_MAX;

		/* compare to current minimum */
		minbody = MIN(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, u_char *buf, size_t buflen)
{
	u_int wlen;
	DKIM_CANON *cur;
	u_char *p;
	u_char *wrote;
	u_char *eob;

	assert(dkim != NULL);

	dkim->dkim_bodylen += buflen;

	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;

		/* short-circuit completed canonicalizations */
		if (cur->canon_remain == 0)
			continue;

		eob = buf + buflen - 1;
		wrote = buf;
		wlen = 0;

		switch (cur->canon_canon)
		{
		  case DKIM_CANON_SIMPLE:
			for (p = buf; p <= eob; p++)
			{
				if (*p == '\n')
				{
					if (cur->canon_lastchar == '\r')
					{
						if (cur->canon_blankline)
						{
							cur->canon_blanks++;
						}
						else if (wlen == 1)
						{
							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 != '\r')
					{
						if (cur->canon_blanks > 0)
							dkim_canon_flushblanks(cur);
						cur->canon_blankline = FALSE;
					}
					wlen++;
				}

				cur->canon_lastchar = *p;
			}

			break;

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

						wrote = p + 1;
						wlen = 0;
						cur->canon_blankline = TRUE;
					}
				}
				else
				{
					/* non-space after a space */
					if (dkim_islwsp(cur->canon_lastchar) &&
					    !dkim_islwsp(*p) && *p != '\r')
					{
						if (cur->canon_blanks > 0)
							dkim_canon_flushblanks(cur);
						dkim_canon_buffer(cur, SP, 1);
						wrote = p;
						wlen = 1;
						cur->canon_blankline = FALSE;
					}

					/* space after a non-space */
					else if (!dkim_islwsp(cur->canon_lastchar) &&
					         dkim_islwsp(*p))
					{
						if (cur->canon_blanks > 0)
							dkim_canon_flushblanks(cur);
						dkim_canon_buffer(cur, wrote,
							          wlen);
						wlen = 0;
						wrote = p + 1;
					}

					/* space after a space */
					else if (dkim_islwsp(cur->canon_lastchar) &&
					         dkim_islwsp(*p))
					{
						/* XXX -- fix logic */
						;
					}

					/* non-space after a non-space */
					else
					{
						wlen++;
						if (cur->canon_blankline &&
						    (wlen > 1 || *p != '\r'))
						{
							if (cur->canon_blanks > 0)
								dkim_canon_flushblanks(cur);
							cur->canon_blankline = FALSE;
						}
					}
				}

				cur->canon_lastchar = *p;
			}

			break;

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

		dkim_canon_buffer(cur, wrote, wlen);
	}

	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;

		/* "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);

		/* finalize */
		switch (cur->canon_hashtype)
		{
		  case DKIM_HASHTYPE_SHA1:
		  {
			struct dkim_sha1 *sha1;

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

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

			break;
		  }

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

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

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

			break;
		  }
#endif /* DKIM_HASHTYPE_SHA256 */

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

		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, u_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)
	{
	  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 DKIM_HASHTYPE_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 /* DKIM_HASHTYPE_SHA256 */

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

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

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

	if (canon->canon_hash != NULL)
	{
		switch (canon->canon_hashtype)
		{
		  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 DKIM_HASHTYPE_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 /* DKIM_HASHTYPE_SHA256 */

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

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


syntax highlighted by Code2HTML, v. 0.9.1