/* This file is part of Mailfromd.
   Copyright (C) 2020-2025 Sergey Poznyakoff

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <nettle/sha2.h>
#include <nettle/buffer.h>
#include <nettle/rsa.h>
#include <nettle/base64.h>
#include <nettle/asn1.h>
#include <nettle/bignum.h>
#include "mailfromd.h"
#include <mailutils/imaputil.h>
#include "dkim.h"

/*
 * Read public and private rsa keys from file.
 * Matching code derived from pkcs1-conv.c in Nettle.
 */

static const uint8_t pem_start_pattern[] = "-----BEGIN ";
static int pem_start_pattern_length = sizeof(pem_start_pattern) - 1;
static const uint8_t pem_end_pattern[] = "-----END ";
static int pem_end_pattern_length = sizeof(pem_end_pattern) - 1;
static const uint8_t pem_trailer_pattern[] = "-----";
static int pem_trailer_pattern_length = sizeof(pem_trailer_pattern) - 1;

enum {
	READ_PEM_OK,        /* Keys read successfully. */
	READ_PEM_SYSERROR,  /* System error when reading keyfile. */
	READ_PEM_ERROR,     /* Parse error (malformed PEM, etc.) */
	READ_PEM_EOF,       /* Unexpected end of file. */
	READ_PEM_BADVER,    /* Bad version */
};

static void
report_pem_error(char const *priv_file, int rc, int ec)
{
	switch (rc) {
	case READ_PEM_OK:
		break;

	case READ_PEM_SYSERROR:
		mu_error(_("can't read private key from %s: %s"),
			 priv_file, strerror(errno));
		break;

	case READ_PEM_ERROR:
		mu_error(_("error parsing file %s"), priv_file);
		break;

	case READ_PEM_EOF:
		mu_error(_("unexpected end of file in %s"), priv_file);
		break;

	case READ_PEM_BADVER:
		mu_error(_("unsupported RSA version in file %s"), priv_file);
		break;

	default:
		mu_error(_("INTERNAL ERROR: unrecognized PEM error code: %d"),
			 rc);
		break;
	}
}

/* Returns READ_PEM_OK on match. */
static int
match_pem_start(size_t length, const uint8_t *line,
		size_t *marker_start,
		size_t *marker_length)
{
	while (length > 0 && mu_isspace(line[length - 1]))
		length--;

	if (length > (pem_start_pattern_length + pem_trailer_pattern_length)
	    && memcmp(line, pem_start_pattern, pem_start_pattern_length) == 0
	    && memcmp(line + length - pem_trailer_pattern_length,
		      pem_trailer_pattern, pem_trailer_pattern_length) == 0) {
		*marker_start = pem_start_pattern_length;
		*marker_length = length -
			(pem_start_pattern_length + pem_trailer_pattern_length);
		return READ_PEM_OK;
	}
	return READ_PEM_ERROR;
}

/* Returns READ_PEM_OK on match, READ_PEM_EOF if the line is of the right
   form except for the marker, otherwise READ_PEM_ERROR. */
static int
match_pem_end(size_t length, const uint8_t *line,
	      size_t marker_length,
	      const uint8_t *marker)
{
	while (length > 0 && mu_isspace(line[length - 1]))
		length--;

	if (length > (pem_end_pattern_length + pem_trailer_pattern_length)
	    && memcmp(line, pem_end_pattern, pem_end_pattern_length) == 0
	    && memcmp(line + length - pem_trailer_pattern_length,
		      pem_trailer_pattern, pem_trailer_pattern_length) == 0) {
		if (length == marker_length +
		    (pem_end_pattern_length + pem_trailer_pattern_length)
		    && memcmp(line + pem_end_pattern_length, marker,
			      marker_length) == 0)
			return READ_PEM_OK;
		else
			return READ_PEM_EOF;
	}

	return READ_PEM_ERROR;
}

struct pem_info {
	size_t marker_start;
	size_t marker_length;
	size_t data_start;
	size_t data_length;
};

/* Read a single line from file into buffer. */
static int
read_line(FILE *fp, struct nettle_buffer *buffer)
{
	int c;

	while ((c = getc(fp)) != EOF) {
		if (!NETTLE_BUFFER_PUTC(buffer, c))
			return READ_PEM_SYSERROR;

		if (c == '\n')
			return READ_PEM_OK;
	}
	if (ferror(fp))
		return READ_PEM_SYSERROR;

	return READ_PEM_EOF;
}

/* Read PEM file into buffer and parse it.  Arguments:
 *
 * fp          input file.
 * buffer      buffer to read PEM into.
 * info        fill this structure with information about PEM structure.
 */
static int
read_pem(FILE *fp, struct nettle_buffer *buffer, struct pem_info *info)
{
	int rc;

	/* Find start line */
	for (;;) {
		nettle_buffer_reset(buffer);
		rc = read_line(fp, buffer);
		if (rc != READ_PEM_OK)
			return rc;

		if (match_pem_start(buffer->size, buffer->contents,
				    &info->marker_start,
				    &info->marker_length) == READ_PEM_OK)
			break;
	}

	buffer->contents[info->marker_start + info->marker_length] = 0;

	info->data_start = buffer->size;

	for (;;) {
		size_t line_start = buffer->size;

		if ((rc = read_line(fp, buffer)) != READ_PEM_OK)
			return rc;

		switch (match_pem_end(buffer->size - line_start,
				      buffer->contents + line_start,
				      info->marker_length,
				      buffer->contents + info->marker_start)) {
		case READ_PEM_OK:
			info->data_length = line_start - info->data_start;
			return READ_PEM_OK;
		case READ_PEM_ERROR:
			break;
		case READ_PEM_EOF:
			return READ_PEM_EOF;
		}
	}
	return READ_PEM_ERROR;
}

static inline int
base64_decode_in_place (struct base64_decode_ctx *ctx, size_t *dst_length,
			size_t length, uint8_t *data)
{
	return base64_decode_update(ctx, dst_length,
				    data, length,
				    (const uint8_t *) data);
}

static int
decode_base64(struct nettle_buffer *buffer, size_t start, size_t *length)
{
	struct base64_decode_ctx ctx;

	base64_decode_init(&ctx);

	/* Decode in place */
	if (base64_decode_in_place(&ctx, length, *length,
				   buffer->contents + start)
	    && base64_decode_final(&ctx))
		return READ_PEM_OK;
	return READ_PEM_ERROR;
}

/* Convert PKCS#1 */
static int
convert_rsa_private_key(uint8_t *buffer, size_t size,
			struct rsa_public_key *pub,
			struct rsa_private_key *priv)
{
	rsa_public_key_init(pub);
	rsa_private_key_init(priv);
	return rsa_keypair_from_der(pub, priv, 0, size, buffer)
		? READ_PEM_OK : READ_PEM_ERROR;
}

/*
 * iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1)  pkcs-1(1) modules(0)
 *   pkcs-1(1)
 */

static const uint8_t id_rsaEncryption[9] =
    { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 };

/*
 * RFC-5208:
 *
 * AlgorithmIdentifier ::= SEQUENCE {
 *   algorithm       OBJECT IDENTIFIER,
 *   parameters      ANY DEFINED BY algorithm OPTIONAL
 * }
 */
static int
parse_AlgorithmIdentifier(struct asn1_der_iterator *i)
{
	struct asn1_der_iterator j;

	if (asn1_der_iterator_next(i) == ASN1_ITERATOR_CONSTRUCTED
	    && i->type == ASN1_SEQUENCE
	    && asn1_der_decode_constructed(i, &j) == ASN1_ITERATOR_PRIMITIVE
	    && j.type == ASN1_IDENTIFIER
	    && j.length == sizeof(id_rsaEncryption)
	    && memcmp(j.data, id_rsaEncryption,
		      sizeof(id_rsaEncryption)) == 0) {
		return READ_PEM_OK;
	}
	return READ_PEM_ERROR;
}

/*
 * PrivateKeyInfo ::= SEQUENCE {
 *   version         Version,
 *   algorithm       AlgorithmIdentifier,
 *   PrivateKey      BIT STRING
 * }
 */
static int
parse_PrivateKeyInfo(struct asn1_der_iterator *i, struct rsa_public_key *pub,
		     struct rsa_private_key *priv)
{
	struct asn1_der_iterator j, k;
	uint32_t version;
	int rc;

	if (asn1_der_decode_constructed(i, &j) == ASN1_ITERATOR_PRIMITIVE
	    && j.type == ASN1_INTEGER
	    && asn1_der_get_uint32(&j, &version)) {
		if (version != 0)
			return READ_PEM_BADVER;
		if ((rc = parse_AlgorithmIdentifier(&j)) != READ_PEM_OK)
			return rc;
		if (asn1_der_iterator_next(&j) == ASN1_ITERATOR_PRIMITIVE
		    && j.type == ASN1_OCTETSTRING) {
			if (asn1_der_iterator_first(&k, j.length, j.data) == ASN1_ITERATOR_CONSTRUCTED
			    && k.type == ASN1_SEQUENCE) {
				rsa_public_key_init(pub);
				rsa_private_key_init(priv);
				if (rsa_private_key_from_der_iterator(pub, priv, 0, &k))
					return READ_PEM_OK;
			}
		}
	}
	return READ_PEM_ERROR;
}

/* Convert PKCS#8 */
static int
convert_private_key(uint8_t *buffer, size_t size,
		    struct rsa_public_key *pub,
		    struct rsa_private_key *priv)
{
	struct asn1_der_iterator i;
	int rc;

	if (asn1_der_iterator_first(&i, size, buffer) == ASN1_ITERATOR_CONSTRUCTED
	    && i.type == ASN1_SEQUENCE) {
		if ((rc = parse_PrivateKeyInfo(&i, pub, priv)) != READ_PEM_OK)
			return rc;
		if (asn1_der_iterator_next(&i) == ASN1_ITERATOR_END)
			return READ_PEM_OK;
	}

	return READ_PEM_ERROR;
}

/* Read a private key PEM file and return RSA key pair. */
static int
read_keys(FILE *fp, struct rsa_public_key *pub, struct rsa_private_key *priv)
{
	struct nettle_buffer buffer;
	struct pem_info info;

	static struct keyconv {
		char const *marker;
		size_t marker_length;
		int (*fconv)(uint8_t *buffer, size_t size,
			     struct rsa_public_key *pub,
			     struct rsa_private_key *priv);
	} keyconv[] = {
#define S(s) s, sizeof(s)-1
		{ S("RSA PRIVATE KEY"), convert_rsa_private_key },
		{ S("PRIVATE KEY"), convert_private_key },
		{ NULL }
	};
	struct keyconv *kp;
	int rc;

	nettle_buffer_init_realloc(&buffer, NULL, nettle_xrealloc);//FIXME
	if ((rc = read_pem(fp, &buffer, &info)) != READ_PEM_OK)
		return rc;

	for (kp = keyconv; kp->marker; kp++) {
		if (info.marker_length == kp->marker_length &&
		    memcmp(buffer.contents + info.marker_start, kp->marker, kp->marker_length) == 0)
			break;
	}

	if (kp->marker == NULL) {
		nettle_buffer_clear(&buffer);
		return READ_PEM_ERROR;
	}

	if (decode_base64(&buffer, info.data_start,
			  &info.data_length) == READ_PEM_OK
	    && kp->fconv(buffer.contents + info.data_start, info.data_length,
			 pub, priv) == READ_PEM_OK)
		rc = READ_PEM_OK;
	else
		rc = READ_PEM_ERROR;
	nettle_buffer_clear(&buffer);

	return rc;
}

static int
pubkey_from_base64(struct rsa_public_key *pub, const char *str)
{
	struct nettle_buffer buffer;
	size_t length = strlen(str);
	struct asn1_der_iterator i, j;
	int result = READ_PEM_ERROR;

	nettle_buffer_init_realloc(&buffer, NULL, nettle_xrealloc);
	nettle_buffer_write(&buffer, strlen(str), (const uint8_t*) str);
	if (decode_base64(&buffer, 0, &length) == READ_PEM_OK

	/* SubjectPublicKeyInfo ::= SEQUENCE {
	       algorithm		AlgorithmIdentifier,
	       subjectPublicKey		BIT STRING
	   }

	   AlgorithmIdentifier ::= SEQUENCE {
	       algorithm	OBJECT IDENTIFIER,
	       parameters	OPTIONAL
	   }
	*/

	    && asn1_der_iterator_first(&i, length, buffer.contents) == ASN1_ITERATOR_CONSTRUCTED
	    && i.type == ASN1_SEQUENCE
	    && asn1_der_decode_constructed_last(&i) == ASN1_ITERATOR_CONSTRUCTED
	    && i.type == ASN1_SEQUENCE

	    /* Use the j iterator to parse the algorithm identifier */
	    && asn1_der_decode_constructed(&i, &j) == ASN1_ITERATOR_PRIMITIVE
	    && j.type == ASN1_IDENTIFIER
	    && asn1_der_iterator_next(&i) == ASN1_ITERATOR_PRIMITIVE
	    && i.type == ASN1_BITSTRING

	    /* Use i to parse the object wrapped in the bit string.*/
	    && asn1_der_decode_bitstring_last(&i)) {
		/* pkcs-1 {
		       iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1)
		       pkcs-1(1) modules(0) pkcs-1(1)
		   }

		   --
		   -- When rsaEncryption is used in an AlgorithmIdentifier the
		   -- parameters MUST be present and MUST be NULL.
		   --
		   rsaEncryption    OBJECT IDENTIFIER ::= { pkcs-1 1 }
		*/
		if (j.length == sizeof(id_rsaEncryption)
		    && memcmp(j.data, id_rsaEncryption,
			      sizeof(id_rsaEncryption)) == 0
		    && asn1_der_iterator_next(&j) == ASN1_ITERATOR_PRIMITIVE
		    && j.type == ASN1_NULL
		    && j.length == 0
		    && asn1_der_iterator_next(&j) == ASN1_ITERATOR_END) {
			rsa_public_key_init(pub);

			if (rsa_public_key_from_der_iterator(pub, 0, &i))
				result = READ_PEM_OK;
		}
	}
	nettle_buffer_clear(&buffer);
	return result;
}

/* State of iteration over a colon-separated list of headers. */
struct h_list_buf {
	char const *ptr;  /* Current position in the header list. */
	char *base;       /* Return memory pointer. */
	size_t size;      /* Size of memory allocated for base. */
};

/*
 * dkim_header_list_next(SAVE)
 * ---------------------------
 * Free the state buffer allocated by dkim_header_list_first.
 */
void
dkim_header_list_end(void *save)
{
	struct h_list_buf *hbuf = save;
	free(hbuf->base);
	free(hbuf);
}

/*
 * Return next header from the header list, or NULL if the list is exhausted.
 * SAVE is the iteration state pointer returned by dkim_header_list_first.
 */
static char *
dkim_header_list_next(void *save)
{
	struct h_list_buf *hbuf = save;
	char const *hp = hbuf->ptr;

	while (*hp && (*hp == ' ' || *hp == '\t' || *hp == ':'))
		hp++;
	if (*hp) {
		size_t len = strcspn(hp, " \t:");
		if (len + 1 > hbuf->size) {
			hbuf->base = mu_realloc(hbuf->base, len + 1);
			hbuf->size = len + 1;
		}
		memcpy(hbuf->base, hp, len);
		hbuf->base[len] = 0;
		hbuf->ptr = hp + len;
	} else {
		hbuf->ptr = hp;
		return NULL;
	}
	return hbuf->base;
}

/*
 * Start iteration over a list of header names (H_LIST), delimited by
 * colons with optional whitespace around them.  Return first header
 * name and save the state in the memory location pointed to by SAVE.
 * No matter the return value, dkim_header_list_end must be called to
 * reclaim the allocated memory.
 */
char *
dkim_header_list_first(char const *h_list, void *save)
{
	struct h_list_buf *hbuf, **hbuf_ptr = save;

	hbuf = mu_alloc(sizeof(hbuf[0]));
	hbuf->ptr = h_list;
	hbuf->base = NULL;
	hbuf->size = 0;
	*hbuf_ptr = hbuf;
	return dkim_header_list_next(hbuf);
}

int
dkim_header_list_match(char const *h_list, char const *h)
{
	size_t len = strlen (h);
	while (*h_list) {
		size_t n;

		while (*h_list && (*h_list == ' ' || *h_list == '\t'))
			h_list++;
		if (*h_list == 0)
			break;

		n = strcspn(h_list, " \t:");
		if (n == len && mu_c_strncasecmp (h_list, h, len) == 0)
			return 1;
		h_list += n;

		while (*h_list && (*h_list == ' ' || *h_list == '\t'))
			h_list++;
		if (*h_list != ':')
			break;
		++h_list;
	}
	return 0;
}

char const *dkim_algo_str[] = { "rsa-sha1", "rsa-sha256" };

#define DKIM_DIGEST_BUFFER_SIZE \
  (sizeof(union { char s1[SHA1_DIGEST_SIZE]; char s2[SHA256_DIGEST_SIZE]; }))

struct dkim_ctx {
	int type;
	union {
		struct sha256_ctx sha256;
		struct sha1_ctx sha1;
	} ctx;
};

int
dkim_algo(char const *a)
{
	int i;

	for (i = 0; i < sizeof(dkim_algo_str)/sizeof(dkim_algo_str[0]); i++)
		if (strcmp(a, dkim_algo_str[i]) == 0)
			return i;
	return -1;
}

static void
dkim_ctx_init(struct dkim_ctx *ctx, int type)
{
	ctx->type = type;
	switch (ctx->type) {
	case DKIM_ALGO_RSA_SHA1:
		sha1_init(&ctx->ctx.sha1);
		break;

	case DKIM_ALGO_RSA_SHA256:
		sha256_init(&ctx->ctx.sha256);
		break;

	default:
		abort();
	}
}

static size_t
dkim_ctx_digest(struct dkim_ctx *ctx, uint8_t *data)
{
	size_t size;

	switch (ctx->type) {
	case DKIM_ALGO_RSA_SHA1:
		size = SHA1_DIGEST_SIZE;
		sha1_digest(&ctx->ctx.sha1, size, data);
		break;

	case DKIM_ALGO_RSA_SHA256:
		size = SHA256_DIGEST_SIZE;
		sha256_digest(&ctx->ctx.sha256, size, data);
		break;

	default:
		abort();
	}
	return size;
}

static void
dkim_ctx_update(struct dkim_ctx *ctx, size_t length, const uint8_t *data)
{
	switch (ctx->type) {
	case DKIM_ALGO_RSA_SHA1:
		sha1_update(&ctx->ctx.sha1, length, data);
		break;

	case DKIM_ALGO_RSA_SHA256:
		sha256_update(&ctx->ctx.sha256, length, data);
		break;

	default:
		abort();
	}
}

static int
dkim_ctx_sign(const struct rsa_private_key *key, struct dkim_ctx *hash, mpz_t signature)
{
	int rc;

	switch (hash->type) {
	case DKIM_ALGO_RSA_SHA1:
		rc = rsa_sha1_sign(key, &hash->ctx.sha1, signature);
		break;

	case DKIM_ALGO_RSA_SHA256:
		rc = rsa_sha256_sign(key, &hash->ctx.sha256, signature);
		break;

	default:
		abort();
	}
	return rc;
}

static int
dkim_ctx_verify(const struct rsa_public_key *key,
		struct dkim_ctx *hash, const mpz_t signature)
{
	int rc;

	switch (hash->type) {
	case DKIM_ALGO_RSA_SHA1:
		rc = rsa_sha1_verify(key, &hash->ctx.sha1, signature);
		break;

	case DKIM_ALGO_RSA_SHA256:
		rc = rsa_sha256_verify(key, &hash->ctx.sha256, signature);
		break;

	default:
		abort();
	}
	return rc;
}

/*
 * SHA256 hashing functions.
 */

#define BUF_SIZE 1024

/* Hash the contents read from the mailutils stream STR starting from
 * the current position and up to the end of file.
 */
static int
hash_stream(mu_stream_t str, struct dkim_ctx *ctx)
{
	uint8_t buffer[BUF_SIZE];
	size_t count;
	int rc;

	while ((rc = mu_stream_read(str, buffer, sizeof(buffer), &count)) == 0
	       && count > 0) {
//		mu_error("READ %*.*s", (int)count, (int)count, buffer);
		dkim_ctx_update(ctx, count, buffer);
	}

	if (rc) {
		mu_diag_funcall(MU_DIAG_ERROR, "mu_stream_read", NULL, rc);
		return -1;
	}

	return 0;
}

/* Hash LEN bytes from the current position in stream STR. */
static int
hash_stream_segment(mu_stream_t str, size_t len, struct dkim_ctx *ctx)
{
	while (len) {
		uint8_t buffer[BUF_SIZE];
		int rc;
		size_t n = sizeof(buffer), count;
		if (n > len)
			n = len;
		rc = mu_stream_read(str, buffer, n, &count);
		//printf("HASH %*.*s\n",(int)count,(int)count,buffer);
		if (rc) {
			mu_diag_funcall(MU_DIAG_ERROR, "mu_stream_read", NULL,
					rc);
			return -1;
		}
		if (count == 0) {
			mu_error(_("unexpected end of file in canonical stream"));
			return -1;
		}
		dkim_ctx_update(ctx, count, buffer);
		len -= count;
	}

	return 0;
}

/* Hash the remaining content of the stream from the current position
 * and store a null-terminated base64 encoded result in outbuf.
 * Outbuf must have at least BASE64_ENCODE_RAW_LENGTH(DKIM_DIGEST_BUFFER_SIZE)
 * bytes of capacity,
 */
static int
dkim_body_hash(mu_stream_t str, size_t len, int type, uint8_t *outbuf, size_t *outlen)
{
	struct dkim_ctx ctx;
	uint8_t body_digest[DKIM_DIGEST_BUFFER_SIZE];
	size_t ds;

	dkim_ctx_init(&ctx, type);
	if (len == DKIM_LENGTH_ALL)
		hash_stream(str, &ctx);
	else
		hash_stream_segment(str, len, &ctx);
	ds = dkim_ctx_digest(&ctx, body_digest);
	base64_encode_raw(outbuf, ds, body_digest);
	*outlen = BASE64_ENCODE_RAW_LENGTH(ds);
	return 0;
}

/*
 * RSA SHA256 digest.
 */

/* Given the RSA private key and a pointer to SHA256 context, create the
 * RSA digest in base64.  Return value is stored in RET_B64 (malloced).
 */
int
dkim_digest(struct rsa_private_key *priv, struct dkim_ctx *ctx,
	    uint8_t **ret_b64)
{
	int rc;
	mpz_t sig;
	size_t i;
	struct nettle_buffer buffer;
	uint8_t *outbuf;

	mpz_init(sig);
	rc = dkim_ctx_sign(priv, ctx, sig);
	if (!rc)
		return -1;

	nettle_buffer_init_realloc(&buffer, NULL, nettle_xrealloc);//FIXME
	for (i = mpz_size(sig); i > 0; i--) {
		mp_limb_t limb = mpz_getlimbn(sig, i - 1);
		size_t j;
		uint8_t *p = nettle_buffer_space(&buffer, sizeof(mp_limb_t))
			     + sizeof(mp_limb_t) - 1;
		for (j = 0; j < sizeof(mp_limb_t); j++) {
			*p-- = limb & 0xff;
			limb >>= 8;
		}
	}
	mpz_clear(sig);

	outbuf = malloc(BASE64_ENCODE_RAW_LENGTH(buffer.size) + 1);
	if (!outbuf) {
		nettle_buffer_clear(&buffer);
		return -1;
	}

	base64_encode_raw(outbuf, buffer.size, buffer.contents);
	outbuf[BASE64_ENCODE_RAW_LENGTH(buffer.size)] = 0;
	nettle_buffer_clear(&buffer);

	*ret_b64 = outbuf;

	return 0;
}

/*
 * Canonicalization names
 * re. "DKIM-Signature Canonicalization Header" IANA registry.
 */
static char const *dkim_canon_string[] = { "simple", "relaxed", NULL };

/*
 * Buffer length for holding the longest possible canonicalization
 * header.
 */
#define DKIM_CANON_STRING_MAX sizeof("relaxed/relaxed")

/*
 * Convert STR to a DKIM_CANON_* constant (return DKIM_CANON_ERR on error).
 * If ENDP is NULL, STR must be one of the strings from dkim_canon_string.
 * Otherwise, STR may be followed by '\0' or '/'.  The pointer to that
 * character will be returned in the memory location pointed to by ENDP.
 */
int
dkim_str_to_canon_type(char const *str, char **endp)
{
	int i;
	size_t len = strcspn(str, "/");
	if (endp || str[len] == 0) {
		for (i = 0; dkim_canon_string[i]; i++)
			if (len == strlen(dkim_canon_string[i]) &&
			    memcmp(str, dkim_canon_string[i], len) == 0) {
				if (endp)
					*endp = (char*)(str + len);
				return i;
			}
	}
	return DKIM_CANON_ERR;
}

/* Format a struct dkim_signature as a DKIM-Signature header. */

#define MAX_LINE_LEN 78

struct dkim_format_buf {
	mu_stream_t str;
	mu_stream_stat_buffer stat;
	int crlf;
};

static inline void
dkim_format_nl(struct dkim_format_buf *fb)
{
	if (fb->crlf)
		mu_stream_write(fb->str, "\r\n ", 3, NULL);
	else
		mu_stream_write(fb->str, "\n ", 2, NULL);
	fb->stat[MU_STREAM_STAT_OUT] = 0;
}

static inline int
dkim_format_wrap(struct dkim_format_buf *fb, size_t len)
{
	if (fb->stat[MU_STREAM_STAT_OUT]
	    && fb->stat[MU_STREAM_STAT_OUT] + len > MAX_LINE_LEN) {
		dkim_format_nl(fb);
		return 1;
	}
	return 0;
}

static void
dkim_format_tag(struct dkim_format_buf *fb, char const *tag, char const *val)
{
	if (!val)
		return;
	if (!dkim_format_wrap(fb, strlen(tag) + strlen(val) + 2))
		dkim_format_wrap(fb, strlen(tag) + 1);
	mu_stream_write(fb->str, tag, strlen(tag), NULL);
	mu_stream_write(fb->str, "=", 1, NULL);
	dkim_format_wrap(fb, strlen(val) + 1);
	mu_stream_write(fb->str, val, strlen(val), NULL);
	mu_stream_write(fb->str, ";", 1, NULL);
}

static void
dkim_format_tag_base64(struct dkim_format_buf *fb, char const *tag,
		       char const *val)
{
	size_t len = strlen(val);

	if (!dkim_format_wrap(fb, strlen(tag) + strlen(val) + 2))
		dkim_format_wrap(fb, strlen(tag) + 1);
	mu_stream_write(fb->str, tag, strlen(tag), NULL);
	mu_stream_write(fb->str, "=", 1, NULL);

	while (len > 0) {
		size_t n = MAX_LINE_LEN - fb->stat[MU_STREAM_STAT_OUT];
		if (len < n)
			n = len;
		mu_stream_write(fb->str, val, n, NULL);
		dkim_format_wrap(fb, 1);
		val += n;
		len -= n;
	}
	mu_stream_write(fb->str, ";", 1, NULL);
}

void
dkim_signature_free(struct dkim_signature *sig)
{
	free(sig->a);
	free(sig->b);
	free(sig->bh);
	free(sig->d);
	free(sig->s);
	free(sig->h);
	free(sig->i);
	free(sig->q);
	free(sig->v);
}

/*
 * Auxiliary functions for parsing and formatting dkim_signature fields.
 *
 * Each parser is declared as
 *   int P(char const *value, void *data)
 * Its arguments are:
 *   value  -  actual tag value obtained from the header.
 *   data   -  pointer to the member of struct dkim_signature.
 * The parser returns 0 on success and non-zero on error.  It is not supposed
 * to emit any diagnostic messages.
 *
 * Each formatter is declared as
 *   int F(struct dkim_format_buf *fb, char const *tag, void *data)
 * Its arguments are:
 *   fb     -  formatting buffer,
 *   tag    -  the tag name,
 *   value  -  pointer to the member of struct dkim_signature.
 */

/* General purpose parser for char* (or uint8_t*) fields */
static int
dkim_tag_char_parser(char const *value, void *data)
{
	char **cptr = data;
	*cptr = mu_strdup(value);
	return 0;
}

/* General purpose formatter for char* (or uint8_t*) fields */
static void
dkim_tag_char_formatter(struct dkim_format_buf *fb,
			char const *tag,
			void *data)
{
	char **cptr = data;
	dkim_format_tag(fb, tag, *cptr);
}

/* Formatter for the a= tag */
static void
dkim_tag_a_formatter(struct dkim_format_buf *fb,
		     char const *tag,
		     void *data)
{
	char **cptr = data;
	dkim_format_tag(fb, tag, *cptr ? *cptr : dkim_algo_str[DKIM_ALGO_DEFAULT]);
}

/* Formatter for the q= tag */
static void
dkim_tag_q_formatter(struct dkim_format_buf *fb,
		     char const *tag,
		     void *data)
{
	char **cptr = data;
	dkim_format_tag(fb, tag, *cptr ? *cptr : DKIM_QUERY_METHOD);
}

/* Special formatter for the h= tag, that ensures proper wrapping. */
static void
dkim_tag_h_formatter(struct dkim_format_buf *fb,
		     char const *tag,
		     void *data)
{
	char **cptr = data;
	void *save;
	char const *hval;

	if (!dkim_format_wrap(fb, 2))
		dkim_format_wrap(fb, strlen(*cptr) + 3);

	mu_stream_write(fb->str, "h=", 2, NULL);

	if ((hval = dkim_header_list_first(*cptr, &save)) != NULL) {
		dkim_format_wrap(fb, strlen(hval));
		mu_stream_write(fb->str, hval, strlen(hval), NULL);
		while ((hval = dkim_header_list_next(save)) != NULL) {
			dkim_format_wrap(fb, strlen(hval) + 1);
			mu_stream_write(fb->str, ":", 1, NULL);
			mu_stream_write(fb->str, hval, strlen(hval), NULL);
		}
	}
	dkim_header_list_end(save);
	dkim_format_wrap(fb, 1);
	mu_stream_write(fb->str, ";", 1, NULL);
}

/* Parser and formatter for the c= tag. */
static int
dkim_tag_c_parser(char const *value, void *data)
{
	int *canon = data;
	char *s;

	if ((canon[0] = dkim_str_to_canon_type(value, &s)) == DKIM_CANON_ERR)
		return -1;
	if (*s == 0)
		canon[1] = canon[0];
	else if (*s != '/' ||
		 (canon[1] = dkim_str_to_canon_type(s + 1, NULL)) == DKIM_CANON_ERR)
		return -1;
	return 0;
}

static void
dkim_tag_c_formatter(struct dkim_format_buf *fb,
		     char const *tag,
		     void *data)
{
	int *canon = data;
	char v[DKIM_CANON_STRING_MAX];
	strcpy(v, dkim_canon_string[canon[0]]);
	strcat(v, "/");
	strcat(v, dkim_canon_string[canon[1]]);
	dkim_format_tag(fb, tag, v);
}

/* General-purpose parser and formatter for time_t members (t and x) */
static int
dkim_tag_time_parser(char const *value, void *data)
{
	time_t *tptr = data;
	unsigned long n;
	char *p;
	errno = 0;
	n = strtoul(value, &p, 10);
	if (errno || *p)
		return -1;
	*tptr = n;
	return 0;
}

static void
dkim_tag_time_formatter(struct dkim_format_buf *fb,
			char const *tag,
			void *data)
{
	time_t *tptr = data;

	if (*tptr) {
		char tbuf[80];
		snprintf(tbuf, sizeof(tbuf), "%lu", (long unsigned) *tptr);
		dkim_format_tag(fb, tag, tbuf);
	}
}

/* Parser and formatter for the l= tag */
static int
dkim_tag_l_parser(char const *value, void *data)
{
	size_t *sptr = data;
	unsigned long n;
	char *endp;
	errno = 0;
	n = strtoul(value, &endp, 10);
	if (errno || *endp)
		return -1;
	*sptr = n;
	return 0;
}

static void
dkim_tag_l_formatter(struct dkim_format_buf *fb,
		     char const *tag,
		     void *data)
{
	size_t *sptr = data;
	char tbuf[80];
	if (*sptr != DKIM_LENGTH_ALL) {
		snprintf(tbuf, sizeof(tbuf), "%zu", *sptr);
		dkim_format_tag(fb, tag, tbuf);
	}
}

/* Formatter for the bh= tag. */
static void
dkim_tag_bh_formatter(struct dkim_format_buf *fb,
		      char const *tag,
		      void *data)
{
	char **sptr = data;
	dkim_format_tag_base64(fb, tag, *sptr);
}

/* Formatter for the b= tag. */
static void
dkim_tag_b_formatter(struct dkim_format_buf *fb,
		     char const *tag,
		      void *data)
{
	char **sptr = data;
	dkim_format_nl(fb);
	dkim_format_tag_base64(fb, tag, *sptr ? *sptr : "");
}

/* Tag definition structure */
struct dkim_tag_descr {
	char *tag;       /* Tag name. */
	size_t off;      /* Field offset in struct dkim_signature. */
	int (*parser)(char const *, void *);
			 /* Parser function. */
	void (*formatter)(struct dkim_format_buf *, char const *, void *);
			 /* Formatter function. */
};

/* Order of entries in this array defines the order in which tags are
   formatted.
*/
static struct dkim_tag_descr tag_descr[] = {
	{
		"v",
		offsetof(struct dkim_signature, v),
		dkim_tag_char_parser,
		dkim_tag_char_formatter,
	},
	{
		"a",
		offsetof(struct dkim_signature, a),
		dkim_tag_char_parser,
		dkim_tag_a_formatter
	},
	{
		"d",
		offsetof(struct dkim_signature, d),
		dkim_tag_char_parser,
		dkim_tag_char_formatter,
	},
	{
		"s",
		offsetof(struct dkim_signature, s),
		dkim_tag_char_parser,
		dkim_tag_char_formatter,
	},
	{
		"c",
		offsetof(struct dkim_signature, canon),
		dkim_tag_c_parser,
		dkim_tag_c_formatter
	},
	{
		"q",
		offsetof(struct dkim_signature, q),
		dkim_tag_char_parser,
		dkim_tag_q_formatter
	},
	{
		"h",
		offsetof(struct dkim_signature, h),
		dkim_tag_char_parser,
		dkim_tag_h_formatter,
	},
	{
		"i",
		offsetof(struct dkim_signature, i),
		dkim_tag_char_parser,
		dkim_tag_char_formatter,
	},
	{
		"l",
		offsetof(struct dkim_signature, l),
		dkim_tag_l_parser,
		dkim_tag_l_formatter
	},
	{
		"t",
		offsetof(struct dkim_signature, t),
		dkim_tag_time_parser,
		dkim_tag_time_formatter
	},
	{
		"x",
		offsetof(struct dkim_signature, x),
		dkim_tag_time_parser,
		dkim_tag_time_formatter
	},
	{
		"bh",
		offsetof(struct dkim_signature, bh),
		dkim_tag_char_parser,
		dkim_tag_bh_formatter
	},
	{
		"b",
		offsetof(struct dkim_signature, b),
		dkim_tag_char_parser,
		dkim_tag_b_formatter
	},
	{ NULL }
};

/* Table-driven DKIM-Signature parser. */
int
dkim_signature_parse(char *str, struct dkim_signature *ret_sig)
{
	struct mu_wordsplit ws;
	int i;
	int rc = 0;
	struct dkim_signature sig;

	/*
	 * tag-list  =  tag-spec *( ";" tag-spec ) [ ";" ]
	 * tag-spec  =  [FWS] tag-name [FWS] "=" [FWS] tag-value [FWS]
	 *
	 * The mu_wordsplit call splits the value on ';' and removes
	 * whitespace at both sides of each token.  Internal FWS (around
	 * the equals sign) is removed later.
	 */
	ws.ws_delim = ";";
	rc = mu_wordsplit(str,
			  &ws,
			  MU_WRDSF_DELIM |
			  MU_WRDSF_NOVAR |
			  MU_WRDSF_WS |
			  MU_WRDSF_NOCMD);
	if (rc) {
		mu_wordsplit_free(&ws);
		return rc;
	}

	memset(&sig, 0, sizeof(sig));
	sig.canon[0] = sig.canon[1] = DKIM_CANON_SIMPLE;
	sig.l = DKIM_LENGTH_ALL;
	for (i = 0; i < ws.ws_wordc; i++) {
		struct dkim_tag_descr *tg;
		char *k = ws.ws_wordv[i];
		char *p = strchr(k, '=');
		if (!p) {
			rc = -1;
			goto end;
		}
		*p++ = 0;
		/* Remove internal FWS */
		mu_rtrim_class(k, MU_CTYPE_BLANK);
		mu_ltrim_class(p, MU_CTYPE_BLANK);
		/* Handle known tags */
		for (tg = tag_descr; tg->tag; tg++) {
			if (strcmp(k, tg->tag) == 0) {
				rc = tg->parser(p, (char*)&sig + tg->off);
				if (rc)
					goto end;
				break;
			}
		}
	}
end:
	mu_wordsplit_free(&ws);
	if (rc)
		dkim_signature_free(&sig);
	else
		*ret_sig = sig;
	return rc;
}

/*
 * Table-driven DKIM-Signature formatter.
 * The CRLF parameter defines what delimiter to use for wrapping.
 */
int
dkim_signature_format(struct dkim_signature *sig, int crlf, char **result)
{
	struct dkim_format_buf fb;
	int rc;
	static char header_field[] = DKIM_SIGNATURE_HEADER ": ";
	mu_off_t off;
	size_t size;
	char *text;
	struct dkim_tag_descr *tg;

	/* Initialize format buffer */
	fb.crlf = crlf;
	rc = mu_memory_stream_create(&fb.str, MU_STREAM_RDWR);
	if (rc) {
		mu_diag_funcall(MU_DIAG_ERROR,
				"mu_memory_stream_create",
				NULL, rc);
		return rc;
	}
	mu_stream_set_stat(fb.str,
			   MU_STREAM_STAT_MASK (MU_STREAM_STAT_OUT),
			   fb.stat);
	mu_stream_write(fb.str, header_field, strlen(header_field), NULL);
	for (tg = tag_descr; tg->tag; tg++) {
		tg->formatter(&fb, tg->tag, ((char*)sig + tg->off));
	}

	if (mu_stream_err(fb.str)) {
		rc = mu_stream_last_error(fb.str);
		mu_diag_funcall(MU_DIAG_ERROR,
				"dkim_signature_format", NULL, rc);
	} else {
		rc = 0;
		mu_stream_seek(fb.str, 0, MU_SEEK_CUR, &off);

		//FIXME: assert(size < (size_t)~0)
		size = off;
		text = mu_alloc(size + 1);

		mu_stream_seek(fb.str, 0, MU_SEEK_SET, NULL);

		mu_stream_read(fb.str, text, size, NULL);
		text[size] = 0;
		*result = text;
	}
	mu_stream_destroy(&fb.str);

	return rc;
}

/* Canonicalize the message into a stream.
 * Arguments:
 *
 * msg          input message.
 * canon        canonicalization algorithms for header and body.
 * canon_str    output stream.
 */
int
canonicalize(mu_message_t msg, int canon[2], mu_stream_t *canon_str)
{
	mu_stream_t mstr, flt, in;
	int rc;

	rc = mu_temp_file_stream_create(&mstr, NULL, 0);
	if (rc) {
		mu_diag_funcall(MU_DIAG_ERROR,
				"mu_temp_file_stream_create",
				NULL, rc);
		return -1;
	}

	rc = mu_message_get_streamref(msg, &in);
	if (rc) {
		mu_diag_funcall(MU_DIAG_ERROR,
				"mu_message_get_streamref",
				NULL, rc);
		mu_stream_destroy(&mstr);
		return -1;
	}

	rc = dkim_canonicalizer_create(&flt, in,
				       canon[0],
				       canon[1],
				       MU_STREAM_READ);
	mu_stream_unref(in);
	if (rc) {
		mu_error("dkim_canonicalizer_create: %s",
			 mu_strerror(rc));
		mu_stream_destroy(&mstr);
		return -1;
	}

	in = flt;
	rc = mu_filter_create(&flt, in, "CRLF",
			      MU_FILTER_ENCODE, MU_STREAM_READ);
	mu_stream_unref(in);
	if (rc) {
		mu_error("mu_filter_stream_create: %s", mu_strerror(rc));
		mu_stream_destroy(&mstr);
		return -1;
	}

	rc = mu_stream_copy(mstr, flt, 0, NULL);
	mu_stream_unref(flt);
	if (rc) {
		mu_error("mu_stream_copy: %s", mu_strerror(rc));
		mu_stream_destroy(&mstr);
		return -1;
	}
	mu_stream_seek(mstr, 0, MU_SEEK_SET, NULL);
	*canon_str = mstr;
	return 0;
}

/*
 * Header maps.
 *
 * Message headers form a doubly-linked list of struct header_map
 * pointers.  The list itself is referenced by its head structure,
 * which is a regular header_map where only prev and next pointers
 * are valid.  In a header, next points to the first and prev to the
 * last element in the list.
 */
struct header_map {
	struct header_map *prev, *next;  /* List of elements */
	char *header;         /* Header name */
	mu_off_t start;       /* Header start offset */
	mu_off_t end;         /* Header end offset (points past the
				 final CRLF) */
};

/* Header_map is initialized using this macro. */
#define HEADER_MAP_INITIALIZER(h) { &(h), &(h) }

/* Remove the header_map from the list */
static inline void
header_map_remove(struct header_map *hp)
{
	hp->prev->next = hp->next;
	hp->next->prev = hp->prev;
}

/* Insert header_map B to the list after A. */
static inline void
header_map_insert_after(struct header_map *a, struct header_map *b)
{
	a->next->prev = b;
	b->next = a->next;
	a->next = b;
	b->prev = a;
}

/* Append header_map to the end of the list. */
static inline void
header_map_append(struct header_map *link, struct header_map *hp)
{
	header_map_insert_after(link->prev, hp);
}

/* Free the list.  List head is not freed. */
static inline void
header_map_free(struct header_map *link)
{
	struct header_map *hmap = link->next;
	while (hmap != link) {
		header_map_remove(hmap);
		free(hmap);
		hmap = link->next;
	}
}

/*
 * Two iterators.
 *
 * Arguments:
 * link     the head element of the list.
 * h        iteration variable
 */

/* Iterate over the list in natural direction. */
#define HEADER_MAP_FOREACH(h,link) \
	for (h = (link)->next; h != (link); h = (h)->next)
/* Iterate over the list in reverse direction. */
#define HEADER_MAP_FOREACH_REV(h,link)				\
	for (h = (link)->prev; h != (link); h = (h)->prev)

/* Return true if a message can have multiple instances of the named header.
 *
 * RFC 6376 mandates that such headers be processed in reverse order.
 * Refer to subsection 5.4.2.  "Signatures Involving Multiple Instances of
 * a Field" on page 41 for details.
 */
static int
is_rev_header(char const *name)
{
	static char *rev_headers[] = {
		"Received",
		DKIM_SIGNATURE_HEADER,
		"Resent-*",
		NULL
	};
	int i;

	for (i = 0; rev_headers[i]; i++) {
		if (mu_imap_wildmatch_ci(rev_headers[i], name, ':') == 0)
			return 1;
	}
	return 0;
}

static int
dkim_tag_find(char const *sigstr, char const *tag, size_t *ret_len)
{
	int i;
	size_t tag_len = strlen(tag);
	size_t sig_len = strlen(sigstr);

	for (i = 0; i + tag_len + 1 < sig_len; i++) {
		if (!(sigstr[i] == ' ' || sigstr[i] == '\t' ||
		      sigstr[i] == '\r' || sigstr[i] == '\n')) {
			size_t n = strcspn(sigstr + i, ";");
			if (memcmp(sigstr + i, tag, tag_len) == 0 &&
			    sigstr[i + tag_len] == '=') {
				*ret_len = n - tag_len - 1;
				return i;
			}
			i += n;
		}
	}
	return -1;
}

enum {
	DKIM_HASH_ERR = -1,
	DKIM_HASH_OK,
	DKIM_HASH_DIFF
};

/*
 * dkim_hash(MSG, SIG, SIGSTR, CTX)
 * --------------------------------
 * Compute a message hash of MSG as per RFC 6376 section 3.7.
 *
 * Parameters:
 *  MSG    - (input) message
 *  SIG    - (input/output) parsed out DKIM signature
 *  SIGSTR - (input) original value of the DKIM signature header.
 *  CTX    - (output) SHA256 context to leave the hash in.
 *
 * The function is used both for message signing and verification.
 *
 * When signing, SIGSTR is NULL.  Before return, the malloced copy
 * of the computed body hash is left in SIG->bh.  The caller is
 * responsible for freeing it when no longer needed.  In this mode,
 * the function returns DKIM_HASH_OK on success and DKIM_HASH_ERR
 * on error.
 *
 * When verifying, SIGSTR is not NULL and SIG is the validated broken
 * out DKIM signature obtained from SIGSTR.  In this case, the function
 * does not modify SIG in any way.  Instead, it checks whether the computed
 * body hash matches SIG->bh and returns DKIM_HASH_DIFF if it does not.
 */
static int
dkim_hash(mu_message_t msg, struct dkim_signature *sig, char const *sigstr,
	  struct dkim_ctx *ctx)
{
	mu_stream_t canon_stream = NULL;
	char *hp;
	void *hstate;
	struct header_map h_all = HEADER_MAP_INITIALIZER(h_all);
	struct header_map h_sel = HEADER_MAP_INITIALIZER(h_sel);
	struct header_map *hmap;
	uint8_t bh[BASE64_ENCODE_RAW_LENGTH(DKIM_DIGEST_BUFFER_SIZE)];
	size_t bhs;
	mu_opool_t op = NULL;
	char c;
	int rc;
	int result = DKIM_HASH_ERR;
	size_t count;
	enum { H_INIT, H_HEADER, H_CR1, H_CR2, H_NL } state = H_INIT;
	mu_stream_t sigcanon, str;
	char *sig_str_buf;

	/* Create a canonical representation of the message */
	if (canonicalize(msg, sig->canon, &canon_stream))
		goto err;

	/*
	 * Scan the header part of the canonicalized stream and record
	 * the headers in the h_all list.
	 *
	 * Header names its elements refer to are stored in the object
	 * pool.
	 */
	mu_opool_create(&op, MU_OPOOL_DEFAULT);
	hmap = NULL;

	while ((rc = mu_stream_read(canon_stream, &c, 1, &count)) == 0 &&
	       count == 1) {
		switch (state) {
		case H_INIT:
			if (c == ':') {
				c = 0;
				mu_opool_append(op, &c, 1);
				hmap = calloc(1, sizeof(hmap[0]));
				hmap->header = mu_opool_finish(op, NULL);
				mu_strlower(hmap->header);
				mu_stream_seek(canon_stream, 0, MU_SEEK_CUR,
					       &hmap->start);
				hmap->start -= strlen(hmap->header) + 1;
				header_map_append(&h_all, hmap);
				state = H_HEADER;
			} else
				mu_opool_append(op, &c, 1);
			break;

		case H_HEADER:
			if (c == '\r')
				state = H_CR1;
			break;

		case H_CR1:
			if (c == '\n')
				state = H_NL;
			break;

		case H_NL:
			if (mu_isblank(c))
				state = H_HEADER;
			else {
				mu_stream_seek(canon_stream, 0, MU_SEEK_CUR,
					       &hmap->end);
				hmap->end--;
				if (c == '\r')
					state = H_CR2;
				else {
					state = H_INIT;
					mu_opool_append(op, &c, 1);
				}
			}
			break;

		case H_CR2:
			if (c != '\n') {
				goto badstream;
			}
			goto end;
		}
	}

	/* Error exit from the above loop */
	if (rc)
		mu_diag_funcall(MU_DIAG_ERROR, "mu_stream_read", NULL, rc);
badstream:
	mu_error(_("malformed canonical stream"));
	goto err;

end:
	/*
	 * Scanning terminated successfully.  Current position in the
	 * canon_stream is left at the start of the body, which will come
	 * handy later.
	 *
	 * Select headers to hash according to the h= tag.  Selected headers
	 * are removed from the h_all and added to the h_sel list.
	 */
	for (hp = dkim_header_list_first(sig->h, &hstate); hp;
	     hp = dkim_header_list_next(hstate)) {
		if (is_rev_header(hp)) {
			HEADER_MAP_FOREACH_REV(hmap, &h_all) {
				if (mu_c_strcasecmp(hmap->header, hp) == 0) {
					header_map_remove(hmap);
					header_map_append(&h_sel, hmap);
					break;
				}
			}
		} else {
			HEADER_MAP_FOREACH(hmap, &h_all) {
				if (mu_c_strcasecmp(hmap->header, hp) == 0) {
					header_map_remove(hmap);
					header_map_append(&h_sel, hmap);
					break;
				}
			}
		}
	}
	dkim_header_list_end(hstate);

	/* Hash the body */
	if (dkim_body_hash(canon_stream, sig->l, sig->algo, bh, &bhs))
		goto err;
	if (sig->bh) {
		if (memcmp(sig->bh, bh, bhs)) {
			result = DKIM_HASH_DIFF;
			goto err;
		}
	} else {
		sig->bh = malloc(bhs + 1);
		if (!sig->bh)
			goto err;
		memcpy(sig->bh, bh, bhs);
		sig->bh[bhs] = 0;
	}

	/* Hash the selected headers */
	HEADER_MAP_FOREACH(hmap, &h_sel) {
		mu_stream_seek(canon_stream, hmap->start, MU_SEEK_SET, NULL);
		hash_stream_segment(canon_stream, hmap->end - hmap->start,
				    ctx);
	}

	/* Add to the hash the DKIM-Signature header with empty b= tag. */
	if (sigstr) {
		size_t len, blen;
		char *vp;
		int n = dkim_tag_find(sigstr, "b", &blen);
		if (n == -1)
			goto err;
		len = strlen(sigstr);
		sig_str_buf = malloc(sizeof(DKIM_SIGNATURE_HEADER) + 1
				     + len - blen + 1);
		if (!sig_str_buf)
			goto err;
		strcpy(sig_str_buf, DKIM_SIGNATURE_HEADER ": ");
		vp = sig_str_buf + sizeof(DKIM_SIGNATURE_HEADER) + 1;
		memcpy(vp, sigstr, n);
		memcpy(vp + n, "b=", 2);
		strcpy(vp + n + 2, sigstr + n + 2 + blen);
	} else {
		dkim_signature_format(sig, 0, &sig_str_buf);
	}
	mu_fixed_memory_stream_create(&str, sig_str_buf, strlen(sig_str_buf),
				      MU_STREAM_RDWR|MU_STREAM_SEEK);
	rc = dkim_canonicalizer_create(&sigcanon, str,
				       sig->canon[0], sig->canon[1],
				       MU_STREAM_READ);
	mu_stream_unref(str);
	if (rc) {
		mu_error("dkim_canonicalizer_create: %s", mu_strerror(rc));
		goto err;
	}

	rc = mu_filter_create(&str, sigcanon, "CRLF", MU_FILTER_ENCODE,
			      MU_STREAM_READ);
	mu_stream_unref(sigcanon);
	if (rc) {
		mu_diag_funcall(MU_DIAG_ERROR,
				"mu_filter_stream_create", NULL, c);
		goto err;
	}
	sigcanon = str;

	hash_stream(sigcanon, ctx);
	mu_stream_unref(sigcanon);
	free(sig_str_buf);
	result = DKIM_HASH_OK;
err:
	/* Reclaim the allocated memory. */
	mu_opool_destroy(&op);
	header_map_free(&h_all);
	header_map_free(&h_sel);
	mu_stream_destroy(&canon_stream);
	return result;
}

/* Auxiliary function for debugging key reading routines. */
int
dkim_read_priv_key(char const *priv_file)
{
	struct rsa_private_key priv;
	struct rsa_public_key pub;
	FILE *fp;
	int rc, ec;

	fp = fopen(priv_file, "r");
	if (!fp) {
		mu_error(_("can't open %s: %s"), priv_file, strerror(errno));
		return -1;
	}

	rc = read_keys(fp, &pub, &priv);
	ec = errno;
	fclose(fp);
	if (rc != READ_PEM_OK) {
		report_pem_error(priv_file, rc, ec);
		return -1;
	}
	rsa_public_key_clear(&pub);
	rsa_private_key_clear(&priv);
	return 0;
}

/* Sign the message.  Arguments:
 *
 * msg          message to sign.
 * sig          initialized struct dkim_signature.
 * priv_file    name of a disk file with the RSA private key in PEM format.
 * ret_sighdr   return pointer.
 *
 * On success, a malloced copy of DKIM-Signature header line will be stored
 * in ret_sighdr and 0 will be returned.
 *
 * Side effects: sig->bh is filled with SHA256 digest of the message body.
 */
int
mfd_dkim_sign(mu_message_t msg, struct dkim_signature *sig,
	      char *priv_file,
	      char **ret_sighdr)
{
	int rc, ec;
	struct rsa_private_key priv;
	struct rsa_public_key pub;
	FILE *fp;
	struct dkim_ctx ctx;
	int result = -1;

	fp = fopen(priv_file, "r");
	if (!fp) {
		mu_error(_("can't open %s: %s"), priv_file, strerror(errno));
		return -1;
	}

	rc = read_keys(fp, &pub, &priv);
	ec = errno;
	fclose(fp);
	if (rc != READ_PEM_OK) {
		report_pem_error(priv_file, rc, ec);
		return -1;
	}
	rsa_public_key_clear(&pub);

	dkim_ctx_init(&ctx, sig->algo);
	if (dkim_hash(msg, sig, NULL, &ctx) == DKIM_HASH_OK) {
		/* Create the RSA-SHA256 signature in b */
		dkim_digest(&priv, &ctx, &sig->b);

		/* Create the header */
		dkim_signature_format(sig, 1, ret_sighdr);
		result = 0;
	}
	/* Reclaim the allocated memory. */
	free(sig->b);
	sig->b = NULL;
	free(sig->bh);
	sig->bh = NULL;
	rsa_private_key_clear(&priv);
	return result;
}

char const *dkim_explanation_str[] = {
	[DKIM_EXPL_OK] = "DKIM verification passed",
	[DKIM_EXPL_NO_SIG] = "No DKIM signature",
	[DKIM_EXPL_INTERNAL_ERROR] = "internal error",
	[DKIM_EXPL_SIG_SYNTAX] = "signature syntax error",
	[DKIM_EXPL_SIG_MISS] = "signature is missing required tag",
	[DKIM_EXPL_DOMAIN_MISMATCH] = "domain mismatch",
	[DKIM_EXPL_BAD_VERSION] = "incompatible version",
	[DKIM_EXPL_BAD_ALGORITHM] = "unsupported signing algorithm",
	[DKIM_EXPL_BAD_QUERY] = "unsupported query method",
	[DKIM_EXPL_FROM] = "From field not signed",
	[DKIM_EXPL_EXPIRED] = "signature expired",
	[DKIM_EXPL_DNS_UNAVAIL] = "public key unavailable",
	[DKIM_EXPL_DNS_NOTFOUND] = "public key not found",
	[DKIM_EXPL_KEY_SYNTAX] = "key syntax error",
	[DKIM_EXPL_KEY_REVOKED] = "key revoked",
	[DKIM_EXPL_BAD_BODY] = "body hash did not verify",
	[DKIM_EXPL_BAD_BASE64] = "can't decode b= tag",
	[DKIM_EXPL_BAD_SIG] = "signature did not verify",
	[DKIM_EXPL_BAD_KEY_TYPE] = "unsupported public key type",
};

int dkim_result_trans[] = {
	[DKIM_EXPL_OK] =		DKIM_VERIFY_OK,
	[DKIM_EXPL_BAD_ALGORITHM] =	DKIM_VERIFY_PERMFAIL,
	[DKIM_EXPL_BAD_BASE64] =	DKIM_VERIFY_PERMFAIL,
	[DKIM_EXPL_BAD_BODY] =		DKIM_VERIFY_PERMFAIL,
	[DKIM_EXPL_BAD_SIG] =		DKIM_VERIFY_PERMFAIL,
	[DKIM_EXPL_BAD_QUERY] =		DKIM_VERIFY_PERMFAIL,
	[DKIM_EXPL_BAD_VERSION] =	DKIM_VERIFY_PERMFAIL,
	[DKIM_EXPL_DNS_NOTFOUND] =	DKIM_VERIFY_PERMFAIL,
	[DKIM_EXPL_DOMAIN_MISMATCH] =	DKIM_VERIFY_PERMFAIL,
	[DKIM_EXPL_EXPIRED] =		DKIM_VERIFY_PERMFAIL,
	[DKIM_EXPL_FROM] =		DKIM_VERIFY_PERMFAIL,
	[DKIM_EXPL_KEY_REVOKED] =	DKIM_VERIFY_PERMFAIL,
	[DKIM_EXPL_KEY_SYNTAX] =	DKIM_VERIFY_PERMFAIL,
	[DKIM_EXPL_SIG_MISS] =		DKIM_VERIFY_PERMFAIL,
	[DKIM_EXPL_SIG_SYNTAX] =	DKIM_VERIFY_PERMFAIL,
	[DKIM_EXPL_NO_SIG] =            DKIM_VERIFY_TEMPFAIL,
	[DKIM_EXPL_DNS_UNAVAIL] =	DKIM_VERIFY_TEMPFAIL,
	[DKIM_EXPL_INTERNAL_ERROR] =	DKIM_VERIFY_TEMPFAIL,
	[DKIM_EXPL_BAD_KEY_TYPE] =      DKIM_VERIFY_PERMFAIL,
};

static int
dkim_sig_validate(struct dkim_signature *sig)
{
	int algo;

	if (!sig->a
	    || !sig->b
	    || !sig->bh
	    || !sig->d
	    || !sig->h
	    || !sig->s
	    || !sig->v) {
		return DKIM_EXPL_SIG_MISS;
	}

	if (strcmp(sig->v, DKIM_VERSION))
		return DKIM_EXPL_BAD_VERSION;

	if ((algo = dkim_algo(sig->a)) == -1)
		return DKIM_EXPL_BAD_ALGORITHM;
	sig->algo = algo;

	if (!sig->q)
		sig->q = mu_strdup(DKIM_QUERY_METHOD);
	else if (strcmp(sig->q, DKIM_QUERY_METHOD))
		return DKIM_EXPL_BAD_QUERY;

	if (sig->i) {
		char *p = strchr(sig->i, '@');
		size_t ilen, dlen;
		if (!p)
			return DKIM_EXPL_SIG_SYNTAX;
		p++;
		ilen = strlen(p);
		dlen = strlen(sig->d);
		if (!(dlen <= ilen &&
		      mu_c_strcasecmp(sig->d, p + ilen - dlen) == 0 &&
		      (p[ilen - dlen - 1] == '.' || p[ilen - dlen - 1] == '@')))
			return DKIM_EXPL_DOMAIN_MISMATCH;
	}

	if (!dkim_header_list_match(sig->h, MU_HEADER_FROM))
		return DKIM_EXPL_FROM;

	if (sig->x && time(NULL) > sig->x)
		return DKIM_EXPL_EXPIRED;

	return DKIM_EXPL_OK;
}

static int
dnsrec_parse(char *rec, mu_assoc_t *pa)
{
	mu_assoc_t a;
	struct mu_wordsplit ws;
	int result;

	if (mu_assoc_create (&a, 0))
		mu_alloc_die ();
	mu_assoc_set_destroy_item (a, mu_list_free_item);

	ws.ws_delim = ";";
	if (mu_wordsplit(rec,
			 &ws,
			 MU_WRDSF_DELIM |
			 MU_WRDSF_NOVAR |
			 MU_WRDSF_WS |
			 MU_WRDSF_NOCMD)) {
		result = 1;
	} else {
		size_t i;

		for (i = 0; i < ws.ws_wordc; i++) {
			char *p = strchr(ws.ws_wordv[i], '=');
			char **slot;
			int rc;

			if (!p) {
				result = 1;
				break;
			}
			*p++ = 0;
			mu_rtrim_class(ws.ws_wordv[i], MU_CTYPE_BLANK);
			mu_ltrim_class(p, MU_CTYPE_BLANK);

			rc = mu_assoc_install_ref(a, ws.ws_wordv[i], &slot);
			if (rc == ENOMEM)
				mu_alloc_die ();
			else if (rc) {
				result = 1;
				break;
			} else
				result = 0;
			*slot = mu_strdup(p);
		}
	}

	mu_wordsplit_free(&ws);
	if (result)
		mu_assoc_destroy (&a);
	else
		*pa = a;
	return result;
}

static int
pubkey_validate(mu_assoc_t a, struct dkim_signature const *sig)
{
	char *s;
	size_t n;

	if ((s = mu_assoc_get(a, "v")) != NULL &&
	    strcmp(s, DKIM_KEYRECORD_VERSION))
		return DKIM_EXPL_KEY_SYNTAX;

	if ((s = mu_assoc_get(a, "p")) == NULL)
		return DKIM_EXPL_KEY_SYNTAX;

	if (s[0] == 0)
		return DKIM_EXPL_KEY_REVOKED;

	if ((s = mu_assoc_get(a, "k")) != NULL && strcmp(s, DKIM_KEY_TYPE))
		return DKIM_EXPL_BAD_KEY_TYPE;

	n = strcspn(sig->a, "-");
	if ((s = mu_assoc_get(a, "h")) != NULL &&
	    !dkim_header_list_match(s, sig->a + n + 1))
		return DKIM_EXPL_BAD_ALGORITHM;
	return DKIM_EXPL_OK;
}

static int
dkim_sig_key_verify(mu_message_t msg, struct dkim_signature *sig,
		    int type,
		    struct rsa_public_key *pub)
{
	struct dkim_ctx ctx;
	mpz_t bs;
	int rc;
	struct nettle_buffer buffer;
	size_t length;
	mu_header_t hdr;
	char const *sigstr;

	rc = mu_message_get_header(msg, &hdr);
	if (rc) {
		mu_diag_funcall(MU_DIAG_ERROR,
				"mu_message_get_header", NULL, rc);
		return DKIM_EXPL_INTERNAL_ERROR;
	}
	rc = mu_header_sget_value(hdr, DKIM_SIGNATURE_HEADER, &sigstr);
	if (rc) {
		mu_diag_funcall(MU_DIAG_ERROR,
				"mu_header_sget_value",
				DKIM_SIGNATURE_HEADER, rc);
		return DKIM_EXPL_INTERNAL_ERROR;
	}

	dkim_ctx_init(&ctx, type);
	switch (dkim_hash(msg, sig, sigstr, &ctx)) {
	case DKIM_HASH_OK:
		break;

	case DKIM_HASH_DIFF:
		return DKIM_EXPL_BAD_BODY;

	case DKIM_HASH_ERR:
		return DKIM_EXPL_INTERNAL_ERROR;
	}

	length = strlen((char*)sig->b);
	nettle_buffer_init_realloc(&buffer, NULL, nettle_xrealloc);
	nettle_buffer_write(&buffer, length, sig->b);
	if (decode_base64(&buffer, 0, &length) != READ_PEM_OK)
		return DKIM_EXPL_BAD_BASE64;

	nettle_mpz_init_set_str_256_u(bs, length, buffer.contents);
	rc = dkim_ctx_verify(pub, &ctx, bs);
	nettle_buffer_clear(&buffer);
	mpz_clear(bs);

	return rc ? DKIM_EXPL_OK : DKIM_EXPL_BAD_SIG;
}

static int
dkim_sig_verify(mu_message_t msg, struct dkim_signature *sig)
{
	char **dnsrec;
	int i;
	int result = DKIM_EXPL_DNS_UNAVAIL;

	/* Get the DKIM DNS record */
	switch (dkim_lookup(sig->d, sig->s, &dnsrec)) {
	case dns_success:
		break;

	case dns_not_found:
		return DKIM_EXPL_DNS_NOTFOUND;

	default:
		return DKIM_EXPL_DNS_UNAVAIL;
	}

	for (i = 0; dnsrec[i]; i++) {
		mu_assoc_t a;
		struct rsa_public_key pub;
		int rc;

		if (dnsrec_parse(dnsrec[i], &a))
			continue;

		if ((rc = pubkey_validate(a, sig)) != DKIM_EXPL_OK) {
			result = rc;
		} else if (pubkey_from_base64(&pub, mu_assoc_get(a, "p"))
			   != READ_PEM_OK) {
			result = DKIM_EXPL_KEY_SYNTAX;
		} else {
			result = dkim_sig_key_verify(msg, sig, sig->algo, &pub);
			rsa_public_key_clear(&pub);
		}
		mu_assoc_destroy(&a);

		if (result == DKIM_EXPL_OK)
			break;
	}

	for (i = 0; dnsrec[i]; i++)
		free(dnsrec[i]);
	free(dnsrec);

	return result;
}

static void
wselim(char *s)
{
	char *p = s; /* current destination pointer */
	char *q = s; /* q - current source pointer */
	while (*q) {
		size_t len = strcspn(q, " \t");
		if (p != q)
			memmove(p, q, len);
		p += len;
		q += len;
		q += strspn(q, " \t");
	}
	*p = 0;
}

int
mfd_dkim_verify(mu_message_t msg, char **ret_sig, char const **algo)
{
	mu_header_t hdr;
	int rc;
	int result = DKIM_EXPL_NO_SIG;
	size_t i;

	if (algo) {
		*algo = NULL;
	}

	/* Get the DKIM-Signature header from the message */
	rc = mu_message_get_header(msg, &hdr);
	if (rc) {
		mu_diag_funcall(MU_DIAG_ERROR,
				"mu_message_get_header", NULL, rc);
		return DKIM_EXPL_INTERNAL_ERROR;
	}

	for (i = 1; result != DKIM_EXPL_OK; i++) {
		struct dkim_signature sig;
		char *sig_str;

		rc = mu_header_aget_value_unfold_n(hdr,
						   DKIM_SIGNATURE_HEADER,
						   i,
						   &sig_str);
		if (rc == MU_ERR_NOENT)
			break;
		else if (rc) {
			mu_diag_funcall(MU_DIAG_ERROR,
					"mu_header_aget_value_unfold",
					NULL, rc);
			result = DKIM_EXPL_INTERNAL_ERROR;
			break;
		}
		wselim(sig_str);

		/* Parse the DKIM signature */
		if (dkim_signature_parse(sig_str, &sig)) {
			result = DKIM_EXPL_SIG_SYNTAX;
		} else {
			/* Validate the signature */
			result = dkim_sig_validate(&sig);
			if (result == DKIM_EXPL_OK) {
				if (algo) {
					*algo = dkim_algo_str[sig.algo];
				}
				result = dkim_sig_verify(msg, &sig);
			}
			if (result == DKIM_EXPL_OK) {
				if (ret_sig) {
					*ret_sig = sig_str;
					sig_str = NULL;
				}
			}
			dkim_signature_free(&sig);
		}
		free(sig_str);
	}
	return result;
}
