SPDX: license tags (mostly by guesswork)
[exim.git] / src / src / pdkim / pdkim.c
index 239532bb699998ac3e96ff125a10163a94a9904c..eb26b3864651e117b675e89a6642a9d4af9dc97e 100644 (file)
@@ -1,8 +1,10 @@
 /*
  *  PDKIM - a RFC4871 (DKIM) implementation
  *
+ *  Copyright (c) The Exim Maintainers 2021 - 2022
  *  Copyright (C) 2009 - 2016  Tom Kistner <tom@duncanthrax.net>
- *  Copyright (C) 2016 - 2018  Jeremy Harris <jgh@exim.org>
+ *  Copyright (C) 2016 - 2020  Jeremy Harris <jgh@exim.org>
+ *  SPDX-License-Identifier: GPL-2.0-or-later
  *
  *  http://duncanthrax.net/pdkim/
  *
@@ -107,7 +109,7 @@ pdkim_combined_canon_entry pdkim_combined_canons[] = {
 };
 
 
-static blob lineending = {.data = US"\r\n", .len = 2};
+static const blob lineending = {.data = US"\r\n", .len = 2};
 
 /* -------------------------------------------------------------------------- */
 uschar *
@@ -121,6 +123,14 @@ return string_sprintf("%s-%s",
 }
 
 
+static int
+pdkim_keyname_to_keytype(const uschar * s)
+{
+for (int i = 0; i < nelem(pdkim_keytypes); i++)
+  if (Ustrcmp(s, pdkim_keytypes[i]) == 0) return i;
+return -1;
+}
+
 int
 pdkim_hashname_to_hashtype(const uschar * s, unsigned len)
 {
@@ -173,6 +183,7 @@ switch(ext_status)
   case PDKIM_VERIFY_INVALID_BUFFER_SIZE: return "PDKIM_VERIFY_INVALID_BUFFER_SIZE";
   case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD: return "PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD";
   case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT: return "PDKIM_VERIFY_INVALID_PUBKEY_IMPORT";
+  case PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE: return "PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE";
   case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR: return "PDKIM_VERIFY_INVALID_SIGNATURE_ERROR";
   case PDKIM_VERIFY_INVALID_DKIM_VERSION: return "PDKIM_VERIFY_INVALID_DKIM_VERSION";
   default: return "PDKIM_VERIFY_UNKNOWN";
@@ -238,7 +249,7 @@ debug_printf("\n");
 static pdkim_stringlist *
 pdkim_prepend_stringlist(pdkim_stringlist * base, const uschar * str)
 {
-pdkim_stringlist * new_entry = store_get(sizeof(pdkim_stringlist), FALSE);
+pdkim_stringlist * new_entry = store_get(sizeof(pdkim_stringlist), GET_UNTAINTED);
 
 memset(new_entry, 0, sizeof(pdkim_stringlist));
 new_entry->value = string_copy(str);
@@ -328,7 +339,7 @@ pdkim_relax_header_n(const uschar * header, int len, BOOL append_crlf)
 {
 BOOL past_field_name = FALSE;
 BOOL seen_wsp = FALSE;
-uschar * relaxed = store_get(len+3, TRUE);     /* tainted */
+uschar * relaxed = store_get(len+3, GET_TAINTED);
 uschar * q = relaxed;
 
 for (const uschar * p = header; p - header < len; p++)
@@ -408,7 +419,7 @@ pdkim_decode_qp(const uschar * str)
 int nchar = 0;
 uschar * q;
 const uschar * p = str;
-uschar * n = store_get(Ustrlen(str)+1, TRUE);
+uschar * n = store_get(Ustrlen(str)+1, GET_TAINTED);
 
 *n = '\0';
 q = n;
@@ -465,7 +476,7 @@ BOOL past_hname = FALSE;
 BOOL in_b_val = FALSE;
 int where = PDKIM_HDR_LIMBO;
 
-sig = store_get(sizeof(pdkim_signature), FALSE);
+sig = store_get(sizeof(pdkim_signature), GET_UNTAINTED);
 memset(sig, 0, sizeof(pdkim_signature));
 sig->bodylength = -1;
 
@@ -474,7 +485,7 @@ sig->version = 0;
 sig->keytype = -1;
 sig->hashtype = -1;
 
-q = sig->rawsig_no_b_val = store_get(Ustrlen(raw_hdr)+1, TRUE);        /* tainted */
+q = sig->rawsig_no_b_val = store_get(Ustrlen(raw_hdr)+1, GET_TAINTED);
 
 for (uschar * p = raw_hdr; ; p++)
   {
@@ -561,9 +572,7 @@ for (uschar * p = raw_hdr; ; p++)
            uschar * elem;
 
            if ((elem = string_nextinlist(&list, &sep, NULL, 0)))
-             for (int i = 0; i < nelem(pdkim_keytypes); i++)
-               if (Ustrcmp(elem, pdkim_keytypes[i]) == 0)
-                 { sig->keytype = i; break; }
+             sig->keytype = pdkim_keyname_to_keytype(elem);
            if ((elem = string_nextinlist(&list, &sep, NULL, 0)))
              for (int i = 0; i < nelem(pdkim_hashes); i++)
                if (Ustrcmp(elem, pdkim_hashes[i].dkim_hashname) == 0)
@@ -632,12 +641,12 @@ while (--q > sig->rawsig_no_b_val  && (*q == '\r' || *q == '\n'))
 DEBUG(D_acl)
   {
   debug_printf(
-         "PDKIM >> Raw signature w/o b= tag value >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+         "DKIM >> Raw signature w/o b= tag value >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
   pdkim_quoteprint(US sig->rawsig_no_b_val, Ustrlen(sig->rawsig_no_b_val));
   debug_printf(
-         "PDKIM >> Sig size: %4u bits\n", (unsigned) sig->sighash.len*8);
+         "DKIM >> Sig size: %4u bits\n", (unsigned) sig->sighash.len*8);
   debug_printf(
-         "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+         "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
   }
 
 if (!pdkim_set_sig_bodyhash(ctx, sig))
@@ -656,7 +665,7 @@ const uschar * ele;
 int sep = ';';
 pdkim_pubkey * pub;
 
-pub = store_get(sizeof(pdkim_pubkey), TRUE);   /* tainted */
+pub = store_get(sizeof(pdkim_pubkey), GET_TAINTED);
 memset(pub, 0, sizeof(pdkim_pubkey));
 
 while ((ele = string_nextinlist(&raw_record, &sep, NULL, 0)))
@@ -713,9 +722,11 @@ return NULL;
 If we have to relax the data for this sig, return our copy of it. */
 
 static blob *
-pdkim_update_ctx_bodyhash(pdkim_bodyhash * b, blob * orig_data, blob * relaxed_data)
+pdkim_update_ctx_bodyhash(pdkim_bodyhash * b, const blob * orig_data, blob * relaxed_data)
 {
-blob * canon_data = orig_data;
+const blob * canon_data = orig_data;
+size_t left;
+
 /* Defaults to simple canon (no further treatment necessary) */
 
 if (b->canon_method == PDKIM_CANON_RELAXED)
@@ -760,16 +771,17 @@ if (b->canon_method == PDKIM_CANON_RELAXED)
   }
 
 /* Make sure we don't exceed the to-be-signed body length */
+left = canon_data->len;
 if (  b->bodylength >= 0
-   && b->signed_body_bytes + (unsigned long)canon_data->len > b->bodylength
+   && left > (unsigned long)b->bodylength - b->signed_body_bytes
    )
-  canon_data->len = b->bodylength - b->signed_body_bytes;
+  left = (unsigned long)b->bodylength - b->signed_body_bytes;
 
-if (canon_data->len > 0)
+if (left > 0)
   {
-  exim_sha_update(&b->body_hash_ctx, CUS canon_data->data, canon_data->len);
-  b->signed_body_bytes += canon_data->len;
-  DEBUG(D_acl) pdkim_quoteprint(canon_data->data, canon_data->len);
+  exim_sha_update(&b->body_hash_ctx, CUS canon_data->data, left);
+  b->signed_body_bytes += left;
+  DEBUG(D_acl) pdkim_quoteprint(canon_data->data, left);
   }
 
 return relaxed_data;
@@ -783,8 +795,9 @@ pdkim_finish_bodyhash(pdkim_ctx * ctx)
 {
 for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)     /* Finish hashes */
   {
-  DEBUG(D_acl) debug_printf("PDKIM: finish bodyhash %d/%d/%ld len %ld\n",
-           b->hashtype, b->canon_method, b->bodylength, b->signed_body_bytes);
+  DEBUG(D_acl) debug_printf("DKIM: finish bodyhash %s/%s/%ld len %ld\n",
+      pdkim_hashes[b->hashtype].dkim_hashname, pdkim_canons[b->canon_method],
+      b->bodylength, b->signed_body_bytes);
   exim_sha_finish(&b->body_hash_ctx, &b->bh);
   }
 
@@ -795,10 +808,10 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
 
   DEBUG(D_acl)
     {
-    debug_printf("PDKIM [%s] Body bytes (%s) hashed: %lu\n"
-                "PDKIM [%s] Body %s computed: ",
-       sig->domain, pdkim_canons[b->canon_method], b->signed_body_bytes,
-       sig->domain, pdkim_hashes[b->hashtype].dkim_hashname);
+    debug_printf("DKIM [%s]%s Body bytes (%s) hashed: %lu\n"
+                "DKIM [%s]%s Body %s computed: ",
+       sig->domain, sig->selector, pdkim_canons[b->canon_method], b->signed_body_bytes,
+       sig->domain, sig->selector, pdkim_hashes[b->hashtype].dkim_hashname);
     pdkim_hexprint(CUS b->bh.data, b->bh.len);
     }
 
@@ -815,18 +828,18 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
   /* VERIFICATION --------------------------------------------------------- */
   /* Be careful that the header sig included a bodyash */
 
-    if (  sig->bodyhash.data
+    if (sig->bodyhash.data && sig->bodyhash.len == b->bh.len
        && memcmp(b->bh.data, sig->bodyhash.data, b->bh.len) == 0)
       {
-      DEBUG(D_acl) debug_printf("PDKIM [%s] Body hash compared OK\n", sig->domain);
+      DEBUG(D_acl) debug_printf("DKIM [%s] Body hash compared OK\n", sig->domain);
       }
     else
       {
       DEBUG(D_acl)
         {
-       debug_printf("PDKIM [%s] Body hash signature from headers: ", sig->domain);
+       debug_printf("DKIM [%s] Body hash signature from headers: ", sig->domain);
        pdkim_hexprint(sig->bodyhash.data, sig->bodyhash.len);
-       debug_printf("PDKIM [%s] Body hash did NOT verify\n", sig->domain);
+       debug_printf("DKIM [%s] Body hash did NOT verify\n", sig->domain);
        }
       sig->verify_status     = PDKIM_VERIFY_FAIL;
       sig->verify_ext_status = PDKIM_VERIFY_FAIL_BODY;
@@ -970,7 +983,7 @@ else
 #ifdef notdef
   DEBUG(D_acl)
     {
-    debug_printf("PDKIM >> raw hdr: ");
+    debug_printf("DKIM >> raw hdr: ");
     pdkim_quoteprint(CUS ctx->cur_header->s, ctx->cur_header->ptr);
     }
 #endif
@@ -984,7 +997,7 @@ else
     fail verification of it later. */
 
     DEBUG(D_acl) debug_printf(
-       "PDKIM >> Found sig, trying to parse >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+       "DKIM >> Found sig, trying to parse >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
 
     sig = pdkim_parse_sig_header(ctx, ctx->cur_header->s);
 
@@ -996,7 +1009,7 @@ else
       last_sig->next = sig;
       }
 
-    if (--dkim_collect_input == 0)
+    if (dkim_collect_input && --dkim_collect_input == 0)
       {
       ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->s);
       ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0';
@@ -1069,7 +1082,7 @@ else for (int p = 0; p < len; p++)
 
        ctx->flags = (ctx->flags & ~(PDKIM_SEEN_LF|PDKIM_SEEN_CR)) | PDKIM_PAST_HDRS;
        DEBUG(D_acl) debug_printf(
-           "PDKIM >> Body data for hash, canonicalized >>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+           "DKIM >> Body data for hash, canonicalized >>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
        continue;
        }
       else
@@ -1104,14 +1117,14 @@ return string_catn(str, US"\r\n\t", 3);
 
 /*
  * RFC 5322 specifies that header line length SHOULD be no more than 78
- * lets make it so!
  *  pdkim_headcat
  *
- * returns uschar * (not nul-terminated)
+ * Returns gstring (not nul-terminated) appending to one supplied
  *
  * col: this int holds and receives column number (octets since last '\n')
  * str: partial string to append to
- * pad: padding, split line or space after before or after eg: ";"
+ * pad: padding, split line or space after before or after eg: ";".
+ *               Only the initial charater is used.
  * intro: - must join to payload eg "h=", usually the tag name
  * payload: eg base64 data - long data can be split arbitrarily.
  *
@@ -1120,7 +1133,7 @@ return string_catn(str, US"\r\n\t", 3);
  * pairs and inside long values. it also always spaces or breaks after the
  * "pad"
  *
- * no guarantees are made for output given out-of range input. like tag
+ * No guarantees are made for output given out-of range input. like tag
  * names longer than 78, or bogus col. Input is assumed to be free of line breaks.
  */
 
@@ -1128,92 +1141,64 @@ static gstring *
 pdkim_headcat(int * col, gstring * str,
   const uschar * pad, const uschar * intro, const uschar * payload)
 {
-size_t l;
-
-if (pad)
-  {
-  l = Ustrlen(pad);
-  if (*col + l > 78)
-    str = pdkim_hdr_cont(str, col);
-  str = string_catn(str, pad, l);
-  *col += l;
-  }
+int len, chomp, padded = 0;
 
-l = (pad?1:0) + (intro?Ustrlen(intro):0);
-
-if (*col + l > 78)
-  { /*can't fit intro - start a new line to make room.*/
-  str = pdkim_hdr_cont(str, col);
-  l = intro?Ustrlen(intro):0;
-  }
-
-l += payload ? Ustrlen(payload):0 ;
-
-while (l>77)
-  { /* this fragment will not fit on a single line */
-  if (pad)
-    {
-    str = string_catn(str, US" ", 1);
-    *col += 1;
-    pad = NULL; /* only want this once */
-    l--;
-    }
-
-  if (intro)
-    {
-    size_t sl = Ustrlen(intro);
-
-    str = string_catn(str, intro, sl);
-    *col += sl;
-    l -= sl;
-    intro = NULL; /* only want this once */
-    }
+/* If we can fit at least the pad at the end of current line, do it now.
+Otherwise, wrap if there is a pad. */
 
-  if (payload)
+if (pad)
+  if (*col + 1 <= 78)
     {
-    size_t sl = Ustrlen(payload);
-    size_t chomp = *col+sl < 77 ? sl : 78-*col;
-
-    str = string_catn(str, payload, chomp);
-    *col += chomp;
-    payload += chomp;
-    l -= chomp-1;
+    str = string_catn(str, pad, 1);
+    (*col)++;
+    pad = NULL;
+    padded = 1;
     }
+  else
+    str = pdkim_hdr_cont(str, col);
 
-  /* the while precondition tells us it didn't fit. */
-  str = pdkim_hdr_cont(str, col);
-  }
+/* Special case: if the whole addition does not fit at the end of the current
+line, but could fit on a new line, wrap to give it its full, dedicated line.  */
 
-if (*col + l > 78)
+len = (pad ? 2 : padded)
+    + (intro ? Ustrlen(intro) : 0)
+    + (payload ? Ustrlen(payload) : 0);
+if (len <= 77 && *col+len > 78)
   {
   str = pdkim_hdr_cont(str, col);
-  pad = NULL;
+  padded = 0;
   }
 
+/* Either we already dealt with the pad or we know there is room */
+
 if (pad)
   {
+  str = string_catn(str, pad, 1);
   str = string_catn(str, US" ", 1);
-  *col += 1;
-  pad = NULL;
+  *col += 2;
   }
-
-if (intro)
+else if (padded && *col < 78)
   {
-  size_t sl = Ustrlen(intro);
-
-  str = string_catn(str, intro, sl);
-  *col += sl;
-  l -= sl;
-  intro = NULL;
+  str = string_catn(str, US" ", 1);
+  (*col)++;
   }
 
-if (payload)
-  {
-  size_t sl = Ustrlen(payload);
+/* Call recursively with intro as payload: it gets the same, special treatment
+(that is, not split if < 78).  */
 
-  str = string_catn(str, payload, sl);
-  *col += sl;
-  }
+if (intro)
+  str = pdkim_headcat(col, str, NULL, NULL, intro);
+
+if (payload)
+  for (len = Ustrlen(payload); len; len -= chomp)
+    {
+    if (*col >= 78)
+      str = pdkim_hdr_cont(str, col);
+    chomp = *col+len > 78 ? 78 - *col : len;
+    str = string_catn(str, payload, chomp);
+    *col += chomp;
+    payload += chomp;
+    }
 
 return str;
 }
@@ -1282,7 +1267,7 @@ if (sig->identity)
 
 if (sig->created > 0)
   {
-  uschar minibuf[20];
+  uschar minibuf[21];
 
   snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->created);
   hdr = pdkim_headcat(&col, hdr, US";", US"t=", minibuf);
@@ -1290,7 +1275,7 @@ if (sig->created > 0)
 
 if (sig->expires > 0)
   {
-  uschar minibuf[20];
+  uschar minibuf[21];
 
   snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->expires);
   hdr = pdkim_headcat(&col, hdr, US";", US"x=", minibuf);
@@ -1298,7 +1283,7 @@ if (sig->expires > 0)
 
 if (sig->bodylength >= 0)
   {
-  uschar minibuf[20];
+  uschar minibuf[21];
 
   snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->bodylength);
   hdr = pdkim_headcat(&col, hdr, US";", US"l=", minibuf);
@@ -1345,7 +1330,8 @@ check_bare_ed25519_pubkey(pdkim_pubkey * p)
 int excess = p->key.len - 32;
 if (excess > 0)
   {
-  DEBUG(D_acl) debug_printf("PDKIM: unexpected pubkey len %lu\n", p->key.len);
+  DEBUG(D_acl)
+    debug_printf("DKIM: unexpected pubkey len %lu\n", (unsigned long) p->key.len);
   p->key.data += excess; p->key.len = 32;
   }
 }
@@ -1374,7 +1360,7 @@ if (  !(dns_txt_reply = ctx->dns_txt_callback(dns_txt_name))
 DEBUG(D_acl)
   {
   debug_printf(
-    "PDKIM >> Parsing public key record >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
+    "DKIM >> Parsing public key record >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
     " %s\n"
     " Raw record: ",
     dns_txt_name);
@@ -1395,13 +1381,13 @@ if (  !(p = pdkim_parse_pubkey_record(CUS dns_txt_reply))
     else
       debug_printf(" Error while parsing public key record\n");
     debug_printf(
-      "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+      "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
     }
   return NULL;
   }
 
 DEBUG(D_acl) debug_printf(
-      "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+      "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
 
 /* Import public key */
 
@@ -1411,23 +1397,20 @@ time we do not have a signature so we must interpret the pubkey k= tag
 instead.  Assume writing on the sig is ok in that case. */
 
 if (sig->keytype < 0)
-  {
-  for(int i = 0; i < nelem(pdkim_keytypes); i++)
-    if (Ustrcmp(p->keytype, pdkim_keytypes[i]) == 0)
-      { sig->keytype = i; goto k_ok; }
-  DEBUG(D_acl) debug_printf("verify_init: unhandled keytype %s\n", p->keytype);
-  sig->verify_status =      PDKIM_VERIFY_INVALID;
-  sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_IMPORT;
-  return NULL;
-  }
-k_ok:
+  if ((sig->keytype = pdkim_keyname_to_keytype(p->keytype)) < 0)
+    {
+    DEBUG(D_acl) debug_printf("verify_init: unhandled keytype %s\n", p->keytype);
+    sig->verify_status =      PDKIM_VERIFY_INVALID;
+    sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_IMPORT;
+    return NULL;
+    }
 
 if (sig->keytype == KEYTYPE_ED25519)
   check_bare_ed25519_pubkey(p);
 
 if ((*errstr = exim_dkim_verify_init(&p->key,
            sig->keytype == KEYTYPE_ED25519 ? KEYFMT_ED25519_BARE : KEYFMT_DER,
-           vctx)))
+           vctx, &sig->keybits)))
   {
   DEBUG(D_acl) debug_printf("verify_init: %s\n", *errstr);
   sig->verify_status =      PDKIM_VERIFY_INVALID;
@@ -1440,6 +1423,61 @@ return p;
 }
 
 
+/* -------------------------------------------------------------------------- */
+/* Sort and filter the sigs developed from the message */
+
+static pdkim_signature *
+sort_sig_methods(pdkim_signature * siglist)
+{
+pdkim_signature * yield, ** ss;
+const uschar * prefs;
+uschar * ele;
+int sep;
+
+if (!siglist) return NULL;
+
+/* first select in order of hashtypes */
+DEBUG(D_acl) debug_printf("DKIM: dkim_verify_hashes   '%s'\n", dkim_verify_hashes);
+for (prefs = dkim_verify_hashes, sep = 0, yield = NULL, ss = &yield;
+     ele = string_nextinlist(&prefs, &sep, NULL, 0); )
+  {
+  int i = pdkim_hashname_to_hashtype(CUS ele, 0);
+  for (pdkim_signature * s = siglist, * next, ** prev = &siglist; s;
+       s = next)
+    {
+    next = s->next;
+    if (s->hashtype == i)
+      { *prev = next; s->next = NULL; *ss = s; ss = &s->next; }
+    else
+      prev = &s->next;
+    }
+  }
+
+/* then in order of keytypes */
+siglist = yield;
+DEBUG(D_acl) debug_printf("DKIM: dkim_verify_keytypes '%s'\n", dkim_verify_keytypes);
+for (prefs = dkim_verify_keytypes, sep = 0, yield = NULL, ss = &yield;
+     ele = string_nextinlist(&prefs, &sep, NULL, 0); )
+  {
+  int i = pdkim_keyname_to_keytype(CUS ele);
+  for (pdkim_signature * s = siglist, * next, ** prev = &siglist; s;
+       s = next)
+    {
+    next = s->next;
+    if (s->keytype == i)
+      { *prev = next; s->next = NULL; *ss = s; ss = &s->next; }
+    else
+      prev = &s->next;
+    }
+  }
+
+DEBUG(D_acl) for (pdkim_signature * s = yield; s; s = s->next)
+  debug_printf(" retain d=%s s=%s a=%s\n",
+    s->domain, s->selector, dkim_sig_to_a_tag(s));
+return yield;
+}
+
+
 /* -------------------------------------------------------------------------- */
 
 DLLEXPORT int
@@ -1465,16 +1503,21 @@ if (ctx->cur_header && ctx->cur_header->ptr > 0)
   }
 else
   DEBUG(D_acl) debug_printf(
-      "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+      "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
 
 /* Build (and/or evaluate) body hash.  Do this even if no DKIM sigs, in case we
 have a hash to do for ARC. */
 
 pdkim_finish_bodyhash(ctx);
 
+/* Sort and filter the recived signatures */
+
+if (!(ctx->flags & PDKIM_MODE_SIGN))
+  ctx->sig = sort_sig_methods(ctx->sig);
+
 if (!ctx->sig)
   {
-  DEBUG(D_acl) debug_printf("PDKIM: no signatures\n");
+  DEBUG(D_acl) debug_printf("DKIM: no signatures\n");
   *return_signatures = NULL;
   return PDKIM_OK;
   }
@@ -1491,12 +1534,12 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
      && sig->verify_status == PDKIM_VERIFY_FAIL)
     {
     DEBUG(D_acl)
-       debug_printf("PDKIM: [%s] abandoning this signature\n", sig->domain);
+       debug_printf("DKIM: [%s] abandoning this signature\n", sig->domain);
     continue;
     }
 
   /*XXX The hash of the headers is needed for GCrypt (for which we can do RSA
-  suging only, as it happens) and for either GnuTLS and OpenSSL when we are
+  signing only, as it happens) and for either GnuTLS and OpenSSL when we are
   signing with EC (specifically, Ed25519).  The former is because the GCrypt
   signing operation is pure (does not do its own hash) so we must hash.  The
   latter is because we (stupidly, but this is what the IETF draft is saying)
@@ -1506,7 +1549,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
   do this hash incrementally.
   We don't need the hash we're calculating here for the GnuTLS and OpenSSL
   cases of RSA signing, since those library routines can do hash-and-sign.
+
   Some time in the future we could easily avoid doing the hash here for those
   cases (which will be common for a long while.  We could also change from
   the current copy-all-the-headers-into-one-block, then call the hash-and-sign
@@ -1519,18 +1562,18 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
   if (!exim_sha_init(&hhash_ctx, pdkim_hashes[sig->hashtype].exim_hashmethod))
     {
     log_write(0, LOG_MAIN|LOG_PANIC,
-      "PDKIM: hash setup error, possibly nonhandled hashtype");
+      "DKIM: hash setup error, possibly nonhandled hashtype");
     break;
     }
 
   if (ctx->flags & PDKIM_MODE_SIGN)
     DEBUG(D_acl) debug_printf(
-       "PDKIM >> Headers to be signed:                            >>>>>>>>>>>>\n"
+       "DKIM >> Headers to be signed:                            >>>>>>>>>>>>\n"
        " %s\n",
        sig->sign_headers);
 
   DEBUG(D_acl) debug_printf(
-      "PDKIM >> Header data for hash, canonicalized (%-7s), in sequence >>\n",
+      "DKIM >> Header data for hash, canonicalized (%-7s), in sequence >>\n",
        pdkim_canons[sig->canon_headers]);
 
 
@@ -1550,7 +1593,6 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
     /* Import private key, including the keytype which we need for building
     the signature header  */
 
-/*XXX extend for non-RSA algos */
     if ((*err = exim_dkim_signing_init(CUS sig->privkey, &sctx)))
       {
       log_write(0, LOG_MAIN|LOG_PANIC, "signing_init: %s", *err);
@@ -1572,7 +1614,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
          rh = pdkim_relax_header(rh, TRUE);    /* cook header for relaxed canon */
 
        /* Feed header to the hash algorithm */
-       exim_sha_update(&hhash_ctx, CUS rh, Ustrlen(rh));
+       exim_sha_update_string(&hhash_ctx, CUS rh);
 
        /* Remember headers block for signing (when the library cannot do incremental)  */
        /*XXX we could avoid doing this for all but the GnuTLS/RSA case */
@@ -1633,7 +1675,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
              : string_copy(CUS hdrs->value);
 
            /* Feed header to the hash algorithm */
-           exim_sha_update(&hhash_ctx, CUS rh, Ustrlen(rh));
+           exim_sha_update_string(&hhash_ctx, CUS rh);
 
            DEBUG(D_acl) pdkim_quoteprint(rh, Ustrlen(rh));
            hdrs->tag = 1;
@@ -1649,15 +1691,15 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
     }
 
   DEBUG(D_acl) debug_printf(
-           "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+           "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
 
   DEBUG(D_acl)
     {
     debug_printf(
-           "PDKIM >> Signed DKIM-Signature header, pre-canonicalized >>>>>>>>>>>>>\n");
+           "DKIM >> Signed DKIM-Signature header, pre-canonicalized >>>>>>>>>>>>>\n");
     pdkim_quoteprint(CUS sig_hdr, Ustrlen(sig_hdr));
     debug_printf(
-           "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+           "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
     }
 
   /* Relax header if necessary */
@@ -1666,20 +1708,20 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
 
   DEBUG(D_acl)
     {
-    debug_printf("PDKIM >> Signed DKIM-Signature header, canonicalized (%-7s) >>>>>>>\n",
+    debug_printf("DKIM >> Signed DKIM-Signature header, canonicalized (%-7s) >>>>>>>\n",
            pdkim_canons[sig->canon_headers]);
     pdkim_quoteprint(CUS sig_hdr, Ustrlen(sig_hdr));
     debug_printf(
-           "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+           "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
     }
 
   /* Finalize header hash */
-  exim_sha_update(&hhash_ctx, CUS sig_hdr, Ustrlen(sig_hdr));
+  exim_sha_update_string(&hhash_ctx, CUS sig_hdr);
   exim_sha_finish(&hhash_ctx, &hhash);
 
   DEBUG(D_acl)
     {
-    debug_printf("PDKIM [%s] Header %s computed: ",
+    debug_printf("DKIM [%s] Header %s computed: ",
       sig->domain, pdkim_hashes[sig->hashtype].dkim_hashname);
     pdkim_hexprint(hhash.data, hhash.len);
     }
@@ -1719,7 +1761,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
 
     DEBUG(D_acl)
       {
-      debug_printf( "PDKIM [%s] b computed: ", sig->domain);
+      debug_printf( "DKIM [%s] b computed: ", sig->domain);
       pdkim_hexprint(sig->sighash.data, sig->sighash.len);
       }
 
@@ -1748,7 +1790,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
 
       DEBUG(D_acl) debug_printf(
          " Error in DKIM-Signature header: tags missing or invalid (%s)\n"
-         "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n",
+         "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n",
          !(sig->domain && *sig->domain) ? "d="
          : !(sig->selector && *sig->selector) ? "s="
          : !(sig->headernames && *sig->headernames) ? "h="
@@ -1759,7 +1801,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
          );
       goto NEXT_VERIFY;
       }
+
     /* Make sure sig uses supported DKIM version (only v1) */
     if (sig->version != 1)
       {
@@ -1768,19 +1810,19 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
 
       DEBUG(D_acl) debug_printf(
           " Error in DKIM-Signature header: unsupported DKIM version\n"
-          "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+          "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
       goto NEXT_VERIFY;
       }
 
     DEBUG(D_acl)
       {
-      debug_printf( "PDKIM [%s] b from mail: ", sig->domain);
+      debug_printf( "DKIM [%s] b from mail: ", sig->domain);
       pdkim_hexprint(sig->sighash.data, sig->sighash.len);
       }
 
     if (!(sig->pubkey = pdkim_key_from_dns(ctx, sig, &vctx, err)))
       {
-      log_write(0, LOG_MAIN, "PDKIM: %s%s %s%s [failed key import]",
+      log_write(0, LOG_MAIN, "DKIM: %s%s %s%s [failed key import]",
        sig->domain   ? "d=" : "", sig->domain   ? sig->domain   : US"",
        sig->selector ? "s=" : "", sig->selector ? sig->selector : US"");
       goto NEXT_VERIFY;
@@ -1824,6 +1866,19 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
       sig->verify_ext_status =  PDKIM_VERIFY_FAIL_MESSAGE;
       goto NEXT_VERIFY;
       }
+    if (*dkim_verify_min_keysizes)
+      {
+      unsigned minbits;
+      uschar * ss = expand_getkeyed(US pdkim_keytypes[sig->keytype],
+                                   dkim_verify_min_keysizes);
+      if (ss &&  (minbits = atoi(CS ss)) > sig->keybits)
+       {
+       DEBUG(D_acl) debug_printf("Key too short: Actual: %s %u  Minima '%s'\n",
+         pdkim_keytypes[sig->keytype], sig->keybits, dkim_verify_min_keysizes);
+       sig->verify_status =      PDKIM_VERIFY_FAIL;
+       sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE;
+       }
+      }
 
 
     /* We have a winner! (if bodyhash was correct earlier) */
@@ -1831,13 +1886,14 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
       {
       sig->verify_status = PDKIM_VERIFY_PASS;
       verify_pass = TRUE;
+      if (dkim_verify_minimal) break;
       }
 
 NEXT_VERIFY:
 
     DEBUG(D_acl)
       {
-      debug_printf("PDKIM [%s] %s signature status: %s",
+      debug_printf("DKIM [%s] %s signature status: %s",
              sig->domain, dkim_sig_to_a_tag(sig),
              pdkim_verify_status_str(sig->verify_status));
       if (sig->verify_ext_status > 0)
@@ -1861,17 +1917,18 @@ return ctx->flags & PDKIM_MODE_SIGN  ||  verify_pass
 /* -------------------------------------------------------------------------- */
 
 DLLEXPORT pdkim_ctx *
-pdkim_init_verify(uschar * (*dns_txt_callback)(uschar *), BOOL dot_stuffing)
+pdkim_init_verify(uschar * (*dns_txt_callback)(const uschar *), BOOL dot_stuffing)
 {
 pdkim_ctx * ctx;
 
-ctx = store_get(sizeof(pdkim_ctx), FALSE);
+ctx = store_get(sizeof(pdkim_ctx), GET_UNTAINTED);
 memset(ctx, 0, sizeof(pdkim_ctx));
 
 if (dot_stuffing) ctx->flags = PDKIM_DOT_TERM;
 /* The line-buffer is for message data, hence tainted */
-ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, TRUE);
+ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, GET_TAINTED);
 ctx->dns_txt_callback = dns_txt_callback;
+ctx->cur_header = string_get_tainted(36, GET_TAINTED);
 
 return ctx;
 }
@@ -1892,7 +1949,7 @@ if (!domain || !selector || !privkey)
 
 /* Allocate & init one signature struct */
 
-sig = store_get(sizeof(pdkim_signature), FALSE);
+sig = store_get(sizeof(pdkim_signature), GET_UNTAINTED);
 memset(sig, 0, sizeof(pdkim_signature));
 
 sig->bodylength = -1;
@@ -1908,7 +1965,7 @@ for (hashtype = 0; hashtype < nelem(pdkim_hashes); hashtype++)
 if (hashtype >= nelem(pdkim_hashes))
   {
   log_write(0, LOG_MAIN|LOG_PANIC,
-    "PDKIM: unrecognised hashname '%s'", hashname);
+    "DKIM: unrecognised hashname '%s'", hashname);
   return NULL;
   }
 
@@ -1917,10 +1974,10 @@ DEBUG(D_acl)
   pdkim_signature s = *sig;
   ev_ctx vctx;
 
-  debug_printf("PDKIM (checking verify key)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+  debug_printf("DKIM (checking verify key)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
   if (!pdkim_key_from_dns(ctx, &s, &vctx, errstr))
     debug_printf("WARNING: bad dkim key in dns\n");
-  debug_printf("PDKIM (finished checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+  debug_printf("DKIM (finished checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
   }
 return sig;
 }
@@ -1966,19 +2023,21 @@ pdkim_set_bodyhash(pdkim_ctx * ctx, int hashtype, int canon_method,
 {
 pdkim_bodyhash * b;
 
+if (hashtype == -1 || canon_method == -1) return NULL;
+
 for (b = ctx->bodyhash; b; b = b->next)
   if (  hashtype == b->hashtype
      && canon_method == b->canon_method
      && bodylength == b->bodylength)
     {
-    DEBUG(D_receive) debug_printf("PDKIM: using existing bodyhash %d/%d/%ld\n",
-                                 hashtype, canon_method, bodylength);
+    DEBUG(D_receive) debug_printf("DKIM: using existing bodyhash %s/%s/%ld\n",
+      pdkim_hashes[hashtype].dkim_hashname, pdkim_canons[canon_method], bodylength);
     return b;
     }
 
-DEBUG(D_receive) debug_printf("PDKIM: new bodyhash %d/%d/%ld\n",
-                             hashtype, canon_method, bodylength);
-b = store_get(sizeof(pdkim_bodyhash), FALSE);
+DEBUG(D_receive) debug_printf("DKIM: new bodyhash %s/%s/%ld\n",
+    pdkim_hashes[hashtype].dkim_hashname, pdkim_canons[canon_method], bodylength);
+b = store_get(sizeof(pdkim_bodyhash), GET_UNTAINTED);
 b->next = ctx->bodyhash;
 b->hashtype = hashtype;
 b->canon_method = canon_method;
@@ -1987,7 +2046,7 @@ if (!exim_sha_init(&b->body_hash_ctx,             /*XXX hash method: extend for sha512 */
                  pdkim_hashes[hashtype].exim_hashmethod))
   {
   DEBUG(D_acl)
-    debug_printf("PDKIM: hash init error, possibly nonhandled hashtype\n");
+    debug_printf("DKIM: hash init error, possibly nonhandled hashtype\n");
   return NULL;
   }
 b->signed_body_bytes = 0;
@@ -2018,12 +2077,12 @@ return b;
 
 void
 pdkim_init_context(pdkim_ctx * ctx, BOOL dot_stuffed,
-  uschar * (*dns_txt_callback)(uschar *))
+  uschar * (*dns_txt_callback)(const uschar *))
 {
 memset(ctx, 0, sizeof(pdkim_ctx));
 ctx->flags = dot_stuffed ? PDKIM_MODE_SIGN | PDKIM_DOT_TERM : PDKIM_MODE_SIGN;
 /* The line buffer is for message data, hence tainted */
-ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, TRUE);
+ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, GET_TAINTED);
 DEBUG(D_acl) ctx->dns_txt_callback = dns_txt_callback;
 }