DKIM: support multiple signing, by selector
authorJeremy Harris <jgh146exb@wizmail.org>
Tue, 12 Sep 2017 16:49:58 +0000 (17:49 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Tue, 12 Sep 2017 19:01:30 +0000 (20:01 +0100)
16 files changed:
doc/doc-docbook/spec.xfpt
doc/doc-txt/NewStuff
src/src/dkim.c
src/src/dkim.h
src/src/dkim_transport.c
src/src/expand.c
src/src/pdkim/pdkim.c
src/src/pdkim/pdkim.h
src/src/pdkim/signing.c
test/confs/4520
test/confs/4524 [new symlink]
test/log/4520
test/log/4524 [new file with mode: 0644]
test/scripts/4500-DKIM/4520
test/scripts/4500-DKIM/4524 [new file with mode: 0644]
test/stderr/4520

index 61a6f0e..44a274b 100644 (file)
@@ -38526,13 +38526,15 @@ while expanding the remaining signing options.
 .wen
 If it is empty after expansion, DKIM signing is not done.
 
-.option dkim_selector smtp string&!! unset
+.option dkim_selector smtp string list&!! unset
 This sets the key selector string.
-You can use the &%$dkim_domain%& expansion variable to look up a matching selector.
-The result is put in the expansion
+.new
+After expansion, which can use &$dkim_domain$&, this can be a list.
+Each element in turn is put in the expansion
 variable &%$dkim_selector%& which may be used in the &%dkim_private_key%&
 option along with &%$dkim_domain%&.
-If the option is empty after expansion, DKIM signing is not done.
+If the option is empty after expansion, DKIM signing is not done for this domain.
+.wen
 
 .option dkim_private_key smtp string&!! unset
 This sets the private key to use.
@@ -38585,7 +38587,7 @@ Verification of DKIM signatures in SMTP incoming email is implemented via the
 syntactically(!) correct signature in the incoming message.
 A missing ACL definition defaults to accept.
 If any ACL call does not accept, the message is not accepted.
-If a cutthrough delivery was in progress for the message it is
+If a cutthrough delivery was in progress for the message, that is
 summarily dropped (having wasted the transmission effort).
 
 To evaluate the signature in the ACL a large number of expansion variables
index c10649e..1948641 100644 (file)
@@ -52,7 +52,8 @@ Version 4.90
     is opened with a TFO cookie.  Support varies between platforms
     (Linux does both. FreeBSD server only, others unknown).
 
-13. DKIM support for multiple hashes.
+13. DKIM support for multiple signing, by domain and/or key-selector.
+    DKIM support for multiple hashes.
 
 
 Version 4.89
index 2b7f55a..ea20072 100644 (file)
@@ -453,26 +453,19 @@ switch (what)
 If a prefix is given, prepend it to the file for the calculations.
 */
 
-uschar *
+blob *
 dkim_exim_sign(int fd, off_t off, uschar * prefix,
   struct ob_dkim * dkim, const uschar ** errstr)
 {
 const uschar * dkim_domain;
 int sep = 0;
-uschar *seen_items = NULL;
-int seen_items_size = 0;
-int seen_items_offset = 0;
-uschar *dkim_canon_expanded;
-uschar *dkim_sign_headers_expanded;
-uschar *dkim_private_key_expanded;
-uschar *dkim_hash_expanded;
-pdkim_ctx *ctx = NULL;
-uschar *rc = NULL;
-uschar *sigbuf = NULL;
+uschar * seen_doms = NULL;
+int seen_doms_size = 0;
+int seen_doms_offset = 0;
+pdkim_ctx ctx;
+pdkim_signature * sig;
+blob * sigbuf = NULL;
 int sigsize = 0;
-int sigptr = 0;
-pdkim_signature *signature;
-int pdkim_canon;
 int pdkim_rc;
 int sread;
 uschar buf[4096];
@@ -481,6 +474,8 @@ int old_pool = store_pool;
 
 store_pool = POOL_MAIN;
 
+pdkim_init_context(&ctx, dkim->dot_stuffed, &dkim_exim_query_dns_txt);
+
 if (!(dkim_domain = expand_cstring(dkim->dkim_domain)))
   {
   /* expansion error, do not send message. */
@@ -493,31 +488,26 @@ if (!(dkim_domain = expand_cstring(dkim->dkim_domain)))
 
 while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, NULL, 0)))
   {
+  const uschar * dkim_sel;
+  int sel_sep = 0;
+
   if (dkim_signing_domain[0] == '\0')
     continue;
 
   /* Only sign once for each domain, no matter how often it
   appears in the expanded list. */
 
-  if (seen_items)
-    {
-    const uschar *seen_items_list = seen_items;
-    if (match_isinlist(dkim_signing_domain,
-                       &seen_items_list, 0, NULL, NULL, MCL_STRING, TRUE,
-                       NULL) == OK)
-      continue;
-
-    seen_items =
-      string_append(seen_items, &seen_items_size, &seen_items_offset, 1, ":");
-    }
+  if (match_isinlist(dkim_signing_domain, CUSS &seen_doms,
+      0, NULL, NULL, MCL_STRING, TRUE, NULL) == OK)
+    continue;
 
-  seen_items =
-    string_append(seen_items, &seen_items_size, &seen_items_offset, 1,
-                dkim_signing_domain);
-  seen_items[seen_items_offset] = '\0';
+  seen_doms = string_append_listele(seen_doms, &seen_doms_size,
+    &seen_doms_offset, ':', dkim_signing_domain);
 
-  /* Set up $dkim_selector expansion variable. */
+  /* Set $dkim_selector expansion variable to each selector in list,
+  for this domain. */
 
+  if (!(dkim_sel = expand_string(dkim->dkim_selector)))
   if (!(dkim_signing_selector = expand_string(dkim->dkim_selector)))
     {
     log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
@@ -525,163 +515,184 @@ while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, NULL, 0)))
     goto bad;
     }
 
-  /* Get canonicalization to use */
-
-  dkim_canon_expanded = dkim->dkim_canon
-    ? expand_string(dkim->dkim_canon) : US"relaxed";
-  if (!dkim_canon_expanded)
+  while ((dkim_signing_selector = string_nextinlist(&dkim_sel, &sel_sep,
+         NULL, 0)))
     {
-    /* expansion error, do not send message. */
-    log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
-              "dkim_canon: %s", expand_string_message);
-    goto bad;
-    }
+    uschar * dkim_canon_expanded;
+    int pdkim_canon;
+    uschar * dkim_sign_headers_expanded = NULL;
+    uschar * dkim_private_key_expanded;
+    uschar * dkim_hash_expanded;
 
-  if (Ustrcmp(dkim_canon_expanded, "relaxed") == 0)
-    pdkim_canon = PDKIM_CANON_RELAXED;
-  else if (Ustrcmp(dkim_canon_expanded, "simple") == 0)
-    pdkim_canon = PDKIM_CANON_SIMPLE;
-  else
-    {
-    log_write(0, LOG_MAIN,
-              "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",
-              dkim_canon_expanded);
-    pdkim_canon = PDKIM_CANON_RELAXED;
-    }
+    /* Get canonicalization to use */
 
-  dkim_sign_headers_expanded = NULL;
-  if (dkim->dkim_sign_headers)
-    if (!(dkim_sign_headers_expanded = expand_string(dkim->dkim_sign_headers)))
+    dkim_canon_expanded = dkim->dkim_canon
+      ? expand_string(dkim->dkim_canon) : US"relaxed";
+    if (!dkim_canon_expanded)
       {
+      /* expansion error, do not send message. */
       log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
-                "dkim_sign_headers: %s", expand_string_message);
+                "dkim_canon: %s", expand_string_message);
       goto bad;
       }
-                       /* else pass NULL, which means default header list */
-
-  /* Get private key to use. */
 
-  if (!(dkim_private_key_expanded = expand_string(dkim->dkim_private_key)))
-    {
-    log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
-              "dkim_private_key: %s", expand_string_message);
-    goto bad;
-    }
-
-  if (  Ustrlen(dkim_private_key_expanded) == 0
-     || Ustrcmp(dkim_private_key_expanded, "0") == 0
-     || Ustrcmp(dkim_private_key_expanded, "false") == 0
-     )
-    continue;          /* don't sign, but no error */
-
-  if (dkim_private_key_expanded[0] == '/')
-    {
-    int privkey_fd, off = 0, len;
+    if (Ustrcmp(dkim_canon_expanded, "relaxed") == 0)
+      pdkim_canon = PDKIM_CANON_RELAXED;
+    else if (Ustrcmp(dkim_canon_expanded, "simple") == 0)
+      pdkim_canon = PDKIM_CANON_SIMPLE;
+    else
+      {
+      log_write(0, LOG_MAIN,
+                "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",
+                dkim_canon_expanded);
+      pdkim_canon = PDKIM_CANON_RELAXED;
+      }
 
-    /* Looks like a filename, load the private key. */
+    if (dkim->dkim_sign_headers)
+      if (!(dkim_sign_headers_expanded = expand_string(dkim->dkim_sign_headers)))
+       {
+       log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
+                  "dkim_sign_headers: %s", expand_string_message);
+       goto bad;
+       }
+                         /* else pass NULL, which means default header list */
 
-    memset(big_buffer, 0, big_buffer_size);
+    /* Get private key to use. */
 
-    if ((privkey_fd = open(CS dkim_private_key_expanded, O_RDONLY)) < 0)
+    if (!(dkim_private_key_expanded = expand_string(dkim->dkim_private_key)))
       {
-      log_write(0, LOG_MAIN | LOG_PANIC, "unable to open "
-                "private key file for reading: %s",
-                dkim_private_key_expanded);
+      log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
+                "dkim_private_key: %s", expand_string_message);
       goto bad;
       }
 
-    do
+    if (  Ustrlen(dkim_private_key_expanded) == 0
+       || Ustrcmp(dkim_private_key_expanded, "0") == 0
+       || Ustrcmp(dkim_private_key_expanded, "false") == 0
+       )
+      continue;                /* don't sign, but no error */
+
+    if (dkim_private_key_expanded[0] == '/')
       {
-      if ((len = read(privkey_fd, big_buffer + off, big_buffer_size - 2 - off)) < 0)
+      int privkey_fd, off = 0, len;
+
+      /* Looks like a filename, load the private key. */
+
+      memset(big_buffer, 0, big_buffer_size);
+
+      if ((privkey_fd = open(CS dkim_private_key_expanded, O_RDONLY)) < 0)
        {
-       (void) close(privkey_fd);
-       log_write(0, LOG_MAIN|LOG_PANIC, "unable to read private key file: %s",
+       log_write(0, LOG_MAIN | LOG_PANIC, "unable to open "
+                  "private key file for reading: %s",
                   dkim_private_key_expanded);
        goto bad;
        }
-      off += len;
+
+      do
+       {
+       if ((len = read(privkey_fd, big_buffer + off, big_buffer_size - 2 - off)) < 0)
+         {
+         (void) close(privkey_fd);
+         log_write(0, LOG_MAIN|LOG_PANIC, "unable to read private key file: %s",
+                    dkim_private_key_expanded);
+         goto bad;
+         }
+       off += len;
+       }
+      while (len > 0);
+
+      (void) close(privkey_fd);
+      big_buffer[off] = '\0';
+      dkim_private_key_expanded = big_buffer;
       }
-    while (len > 0);
 
-    (void) close(privkey_fd);
-    big_buffer[off] = '\0';
-    dkim_private_key_expanded = big_buffer;
-    }
+    if (!(dkim_hash_expanded = expand_string(dkim->dkim_hash)))
+      {
+      log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
+                "dkim_hash: %s", expand_string_message);
+      goto bad;
+      }
 
-  if (!(dkim_hash_expanded = expand_string(dkim->dkim_hash)))
-    {
-    log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
-              "dkim_hash: %s", expand_string_message);
-    goto bad;
-    }
+  /*XXX so we currently nail signing to RSA + this hash.
+  Need to extract algo from privkey and check for disallowed combos. */
 
-/*XXX so we currently nail signing to RSA + given hash.
-Need to extract algo from privkey and check for disallowed combos. */
-
-  if (!(ctx = pdkim_init_sign(dkim_signing_domain,
-                       dkim_signing_selector,
-                       dkim_private_key_expanded,
-                       dkim_hash_expanded,
-                       dkim->dot_stuffed,
-                       &dkim_exim_query_dns_txt,
-                       errstr
-                       )))
-    goto bad;
-  dkim_private_key_expanded[0] = '\0';
-  pdkim_set_optional(ctx,
-                     CS dkim_sign_headers_expanded,
-                     NULL,
-                     pdkim_canon,
-                     pdkim_canon, -1, 0, 0);
-
-  if (prefix)
-    pdkim_feed(ctx, prefix, Ustrlen(prefix));
-
-  if (lseek(fd, off, SEEK_SET) < 0)
-    sread = -1;
-  else
-    while ((sread = read(fd, &buf, sizeof(buf))) > 0)
-      if ((pdkim_rc = pdkim_feed(ctx, buf, sread)) != PDKIM_OK)
-       goto pk_bad;
-
-  /* Handle failed read above. */
-  if (sread == -1)
-    {
-    debug_printf("DKIM: Error reading -K file.\n");
-    save_errno = errno;
-    goto bad;
+    if (!(sig = pdkim_init_sign(&ctx, dkim_signing_domain,
+                         dkim_signing_selector,
+                         dkim_private_key_expanded,
+                         dkim_hash_expanded,
+                         errstr
+                         )))
+      goto bad;
+    dkim_private_key_expanded[0] = '\0';
+
+    pdkim_set_optional(sig,
+                       CS dkim_sign_headers_expanded,
+                       NULL,
+                       pdkim_canon,
+                       pdkim_canon, -1, 0, 0);
+
+    if (!ctx.sig)              /* link sig to context chain */
+      ctx.sig = sig;
+    else
+      {
+      pdkim_signature * n = ctx.sig;
+      while (n->next) n = n->next;
+      n->next = sig;
+      }
     }
+  }
 
-  if ((pdkim_rc = pdkim_feed_finish(ctx, &signature, errstr)) != PDKIM_OK)
-    goto pk_bad;
+if (prefix)
+  pdkim_feed(&ctx, prefix, Ustrlen(prefix));
 
-  sigbuf = string_append(sigbuf, &sigsize, &sigptr, 2,
-                         US signature->signature_header, US"\r\n");
+if (lseek(fd, off, SEEK_SET) < 0)
+  sread = -1;
+else
+  while ((sread = read(fd, &buf, sizeof(buf))) > 0)
+    if ((pdkim_rc = pdkim_feed(&ctx, buf, sread)) != PDKIM_OK)
+      goto pk_bad;
 
-  pdkim_free_ctx(ctx);
-  ctx = NULL;
+/* Handle failed read above. */
+if (sread == -1)
+  {
+  debug_printf("DKIM: Error reading -K file.\n");
+  save_errno = errno;
+  goto bad;
   }
 
-if (sigbuf)
+/* Build string of headers, one per signature */
+
+if ((pdkim_rc = pdkim_feed_finish(&ctx, &sig, errstr)) != PDKIM_OK)
+  goto pk_bad;
+
+sigbuf = store_get(sizeof(blob));
+sigbuf->data = NULL;
+sigbuf->len = 0;
+
+while (sig)
   {
-  sigbuf[sigptr] = '\0';
-  rc = sigbuf;
+  int len = sigbuf->len;
+  sigbuf->data = string_append(sigbuf->data, &sigsize, &len, 2,
+                       US sig->signature_header, US"\r\n");
+  sigbuf->len = len;
+  sig = sig->next;
   }
+
+if (sigbuf->data)
+  sigbuf->data[sigbuf->len] = '\0';
 else
-  rc = US"";
+  sigbuf->data = US"";
 
 CLEANUP:
-  if (ctx)
-    pdkim_free_ctx(ctx);
   store_pool = old_pool;
   errno = save_errno;
-  return rc;
+  return sigbuf;
 
 pk_bad:
   log_write(0, LOG_MAIN|LOG_PANIC,
                "DKIM: signing failed: %.100s", pdkim_errstr(pdkim_rc));
 bad:
-  rc = NULL;
+  sigbuf = NULL;
   goto CLEANUP;
 }
 
index 83c68a7..a3419db 100644 (file)
@@ -6,7 +6,7 @@
 /* See the file NOTICE for conditions of use and distribution. */
 
 void    dkim_exim_init(void);
-uschar *dkim_exim_sign(int, off_t, uschar *, struct ob_dkim *, const uschar **);
+blob *  dkim_exim_sign(int, off_t, uschar *, struct ob_dkim *, const uschar **);
 void    dkim_exim_verify_init(BOOL);
 void    dkim_exim_verify_feed(uschar *, int);
 void    dkim_exim_verify_finish(void);
index 1ef4cfa..85a73dc 100644 (file)
@@ -117,8 +117,9 @@ dkt_direct(transport_ctx * tctx, struct ob_dkim * dkim,
 int save_fd = tctx->u.fd;
 int save_options = tctx->options;
 BOOL save_wireformat = spool_file_wireformat;
-uschar * hdrs, * dkim_signature;
-int siglen = 0, hsize;
+uschar * hdrs;
+blob * dkim_signature;
+int hsize;
 const uschar * errstr;
 BOOL rc;
 
@@ -143,14 +144,13 @@ if (!rc) return FALSE;
 
 dkim->dot_stuffed = !!(save_options & topt_end_dot);
 
-if ((dkim_signature = dkim_exim_sign(deliver_datafile, SPOOL_DATA_START_OFFSET,
+if (!(dkim_signature = dkim_exim_sign(deliver_datafile, SPOOL_DATA_START_OFFSET,
                                    hdrs, dkim, &errstr)))
-  siglen = Ustrlen(dkim_signature);
-else if (!(rc = dkt_sign_fail(dkim, &errno)))
-  {
-  *err = errstr;
-  return FALSE;
-  }
+  if (!(rc = dkt_sign_fail(dkim, &errno)))
+    {
+    *err = errstr;
+    return FALSE;
+    }
 
 /* Write the signature and headers into the deliver-out-buffer.  This should
 mean they go out in the same packet as the MAIL, RCPT and (first) BDAT commands
@@ -162,8 +162,12 @@ temporarily set the marker for possible already-CRLF input. */
 tctx->options &= ~topt_escape_headers;
 spool_file_wireformat = TRUE;
 transport_write_reset(0);
-if (  siglen > 0 && !write_chunk(tctx, dkim_signature, siglen)
-   || !write_chunk(tctx, hdrs, hsize))
+if (  (  dkim_signature
+      && dkim_signature->len > 0
+      && !write_chunk(tctx, dkim_signature->data, dkim_signature->len)
+      )
+   || !write_chunk(tctx, hdrs, hsize)
+   )
   return FALSE;
 
 spool_file_wireformat = save_wireformat;
@@ -199,8 +203,9 @@ dkt_via_kfile(transport_ctx * tctx, struct ob_dkim * dkim, const uschar ** err)
 int dkim_fd;
 int save_errno = 0;
 BOOL rc;
-uschar * dkim_spool_name, * dkim_signature;
-int siglen = 0, options;
+uschar * dkim_spool_name;
+blob * dkim_signature;
+int options, dlen;
 off_t k_file_size;
 const uschar * errstr;
 
@@ -243,13 +248,17 @@ if (!rc)
 /* Feed the file to the goats^W DKIM lib */
 
 dkim->dot_stuffed = !!(options & topt_end_dot);
-if ((dkim_signature = dkim_exim_sign(dkim_fd, 0, NULL, dkim, &errstr)))
-  siglen = Ustrlen(dkim_signature);
-else if (!(rc = dkt_sign_fail(dkim, &save_errno)))
+if (!(dkim_signature = dkim_exim_sign(dkim_fd, 0, NULL, dkim, &errstr)))
   {
-  *err = errstr;
-  goto CLEANUP;
+  dlen = 0;
+  if (!(rc = dkt_sign_fail(dkim, &save_errno)))
+    {
+    *err = errstr;
+    goto CLEANUP;
+    }
   }
+else
+  dlen = dkim_signature->len;
 
 #ifndef OS_SENDFILE
 if (options & topt_use_bdat)
@@ -266,24 +275,26 @@ if (options & topt_use_bdat)
   MAIL & RCPT commands flushed, then reap the responses so we can
   error out on RCPT rejects before sending megabytes. */
 
-  if (siglen + k_file_size > DELIVER_OUT_BUFFER_SIZE && siglen > 0)
+  if (  dlen + k_file_size > DELIVER_OUT_BUFFER_SIZE
+     && dlen > 0)
     {
-    if (  tctx->chunk_cb(tctx, siglen, 0) != OK
-       || !transport_write_block(tctx, dkim_signature, siglen, FALSE)
+    if (  tctx->chunk_cb(tctx, dlen, 0) != OK
+       || !transport_write_block(tctx,
+                   dkim_signature->data, dlen, FALSE)
        || tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
        )
       goto err;
-    siglen = 0;
+    dlen = 0;
     }
 
   /* Send the BDAT command for the entire message, as a single LAST-marked
   chunk. */
 
-  if (tctx->chunk_cb(tctx, siglen + k_file_size, tc_chunk_last) != OK)
+  if (tctx->chunk_cb(tctx, dlen + k_file_size, tc_chunk_last) != OK)
     goto err;
   }
 
-if(siglen > 0 && !transport_write_block(tctx, dkim_signature, siglen, TRUE))
+if(dlen > 0 && !transport_write_block(tctx, dkim_signature->data, dlen, TRUE))
   goto err;
 
 if (!dkt_send_file(tctx->u.fd, dkim_fd, 0, k_file_size))
index c51c1ff..04bb929 100644 (file)
@@ -2377,8 +2377,10 @@ switch(cond_type)
       case 3: return NULL;
       }
 
-    *resetok = FALSE;  /* eval_acl() might allocate; do not reclaim */
-    if (yield != NULL) switch(eval_acl(sub, nelem(sub), &user_msg))
+    if (yield != NULL)
+      {
+      *resetok = FALSE;        /* eval_acl() might allocate; do not reclaim */
+      switch(eval_acl(sub, nelem(sub), &user_msg))
        {
        case OK:
          cond = TRUE;
@@ -2399,6 +2401,7 @@ switch(cond_type)
           expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
          return NULL;
        }
+      }
     return s;
     }
 
index bef6b6a..f107a59 100644 (file)
@@ -110,9 +110,11 @@ pdkim_combined_canon_entry pdkim_combined_canons[] = {
 };
 
 
+static blob lineending = {.data = US"\r\n", .len = 2};
+
 /* -------------------------------------------------------------------------- */
 uschar *
-dkim_sig_to_a_tag(pdkim_signature * sig)
+dkim_sig_to_a_tag(const pdkim_signature * sig)
 {
 if (  sig->keytype < 0  || sig->keytype > nelem(pdkim_keytypes)
    || sig->hashtype < 0 || sig->hashtype > nelem(pdkim_hashes))
@@ -426,7 +428,7 @@ return b64encode(b->data, b->len);
 #define PDKIM_HDR_VALUE 2
 
 static pdkim_signature *
-pdkim_parse_sig_header(pdkim_ctx *ctx, uschar * raw_hdr)
+pdkim_parse_sig_header(pdkim_ctx * ctx, uschar * raw_hdr)
 {
 pdkim_signature * sig;
 uschar *p, *q;
@@ -675,87 +677,87 @@ return NULL;
 
 /* -------------------------------------------------------------------------- */
 
-static int
-pdkim_update_bodyhash(pdkim_ctx * ctx, const char * data, int len)
+/* Update the bodyhash for one sig, with some additional data.
+If we have to relax the data for this sig, return our copy of it. */
+
+/*XXX Currently we calculate a hash for each sig.  But it is possible
+that multi-signing will be wanted using different signing algos
+(rsa, ec) using the same hash and canonicalization.  Consider in future
+hanging the hash+cacnon from the ctx and only referencing from the sig,
+so that it can be calculated only once - being over the body this
+caould be meagbytes, hence expensive. */
+
+static blob *
+pdkim_update_sig_bodyhash(pdkim_signature * sig, blob * orig_data, blob * relaxed_data)
 {
-pdkim_signature * sig;
-uschar * relaxed_data = NULL;  /* Cache relaxed version of data */
-int relaxed_len = 0;
+blob * canon_data = orig_data;
+/* Defaults to simple canon (no further treatment necessary) */
 
-/* Traverse all signatures, updating their hashes. */
-for (sig = ctx->sig; sig; sig = sig->next)
+if (sig->canon_body == PDKIM_CANON_RELAXED)
   {
-  /* Defaults to simple canon (no further treatment necessary) */
-  const uschar *canon_data = CUS data;
-  int           canon_len = len;
-
-  if (sig->canon_body == PDKIM_CANON_RELAXED)
+  /* Relax the line if not done already */
+  if (!relaxed_data)
     {
-    /* Relax the line if not done already */
-    if (!relaxed_data)
-      {
-      BOOL seen_wsp = FALSE;
-      const char *p;
-      int q = 0;
+    BOOL seen_wsp = FALSE;
+    const char *p;
+    int q = 0;
 
-      /* We want to be able to free this else we allocate
-      for the entire message which could be many MB. Since
-      we don't know what allocations the SHA routines might
-      do, not safe to use store_get()/store_reset(). */
+    /* We want to be able to free this else we allocate
+    for the entire message which could be many MB. Since
+    we don't know what allocations the SHA routines might
+    do, not safe to use store_get()/store_reset(). */
 
-      relaxed_data = store_malloc(len+1);
+    relaxed_data = store_malloc(sizeof(blob) + orig_data->len+1);
+    relaxed_data->data = US (relaxed_data+1);
 
-      for (p = data; *p; p++)
-        {
-       char c = *p;
-       if (c == '\r')
-         {
-         if (q > 0 && relaxed_data[q-1] == ' ')
-           q--;
-         }
-       else if (c == '\t' || c == ' ')
-         {
-         c = ' '; /* Turns WSP into SP */
-         if (seen_wsp)
-           continue;
-         seen_wsp = TRUE;
-         }
-       else
-         seen_wsp = FALSE;
-       relaxed_data[q++] = c;
+    for (p = orig_data->data; *p; p++)
+      {
+      char c = *p;
+      if (c == '\r')
+       {
+       if (q > 0 && relaxed_data->data[q-1] == ' ')
+         q--;
+       }
+      else if (c == '\t' || c == ' ')
+       {
+       c = ' '; /* Turns WSP into SP */
+       if (seen_wsp)
+         continue;
+       seen_wsp = TRUE;
        }
-      relaxed_data[q] = '\0';
-      relaxed_len = q;
+      else
+       seen_wsp = FALSE;
+      relaxed_data->data[q++] = c;
       }
-    canon_data = relaxed_data;
-    canon_len  = relaxed_len;
+    relaxed_data->data[q] = '\0';
+    relaxed_data->len = q;
     }
+  canon_data = relaxed_data;
+  }
 
-  /* Make sure we don't exceed the to-be-signed body length */
-  if (  sig->bodylength >= 0
-     && sig->signed_body_bytes + (unsigned long)canon_len > sig->bodylength
-     )
-    canon_len = sig->bodylength - sig->signed_body_bytes;
+/* Make sure we don't exceed the to-be-signed body length */
+if (  sig->bodylength >= 0
+   && sig->signed_body_bytes + (unsigned long)canon_data->len > sig->bodylength
+   )
+  canon_data->len = sig->bodylength - sig->signed_body_bytes;
 
-  if (canon_len > 0)
-    {
-    exim_sha_update(&sig->body_hash_ctx, CUS canon_data, canon_len);
-    sig->signed_body_bytes += canon_len;
-    DEBUG(D_acl) pdkim_quoteprint(canon_data, canon_len);
-    }
+if (canon_data->len > 0)
+  {
+  exim_sha_update(&sig->body_hash_ctx, CUS canon_data->data, canon_data->len);
+  sig->signed_body_bytes += canon_data->len;
+  DEBUG(D_acl) pdkim_quoteprint(canon_data->data, canon_data->len);
   }
 
-if (relaxed_data) store_free(relaxed_data);
-return PDKIM_OK;
+return relaxed_data;
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 static void
-pdkim_finish_bodyhash(pdkim_ctx *ctx)
+pdkim_finish_bodyhash(pdkim_ctx * ctx)
 {
-pdkim_signature *sig;
+pdkim_signature * sig;
 
 /* Traverse all signatures */
 for (sig = ctx->sig; sig; sig = sig->next)
@@ -767,8 +769,9 @@ for (sig = ctx->sig; sig; sig = sig->next)
   DEBUG(D_acl)
     {
     debug_printf("PDKIM [%s] Body bytes hashed: %lu\n"
-                "PDKIM [%s] Body hash computed: ",
-               sig->domain, sig->signed_body_bytes, sig->domain);
+                "PDKIM [%s] Body %s computed: ",
+               sig->domain, sig->signed_body_bytes,
+               sig->domain, pdkim_hashes[sig->hashtype].dkim_hashname);
     pdkim_hexprint(CUS bh.data, bh.len);
     }
 
@@ -807,10 +810,10 @@ for (sig = ctx->sig; sig; sig = sig->next)
 
 
 
-static int
+static void
 pdkim_body_complete(pdkim_ctx * ctx)
 {
-pdkim_signature * sig = ctx->sig;      /*XXX assumes only one sig */
+pdkim_signature * sig;
 
 /* In simple body mode, if any empty lines were buffered,
 replace with one. rfc 4871 3.4.3 */
@@ -818,15 +821,15 @@ replace with one. rfc 4871 3.4.3 */
 it indicates that all linebreaks should be buffered, including
 the one terminating a text line */
 
-if (  sig && sig->canon_body == PDKIM_CANON_SIMPLE
-   && sig->signed_body_bytes == 0
-   && ctx->num_buffered_crlf > 0
-   )
-  pdkim_update_bodyhash(ctx, "\r\n", 2);
+for (sig = ctx->sig; sig; sig = sig->next)
+  if (  sig->canon_body == PDKIM_CANON_SIMPLE
+     && sig->signed_body_bytes == 0
+     && sig->num_buffered_blanklines > 0
+     )
+    (void) pdkim_update_sig_bodyhash(sig, &lineending, NULL);
 
 ctx->flags |= PDKIM_SEEN_EOD;
 ctx->linebuf_offset = 0;
-return PDKIM_OK;
 }
 
 
@@ -834,70 +837,78 @@ return PDKIM_OK;
 /* -------------------------------------------------------------------------- */
 /* Call from pdkim_feed below for processing complete body lines */
 
-static int
-pdkim_bodyline_complete(pdkim_ctx *ctx)
+static void
+pdkim_bodyline_complete(pdkim_ctx * ctx)
 {
-char *p = ctx->linebuf;
-int   n = ctx->linebuf_offset;
-pdkim_signature *sig = ctx->sig;       /*XXX assumes only one sig */
+blob line = {.data = ctx->linebuf, .len = ctx->linebuf_offset};
+pdkim_signature * sig;
+blob * rnl = NULL;
+blob * rline = NULL;
 
 /* Ignore extra data if we've seen the end-of-data marker */
-if (ctx->flags & PDKIM_SEEN_EOD) goto BAIL;
+if (ctx->flags & PDKIM_SEEN_EOD) goto all_skip;
 
 /* We've always got one extra byte to stuff a zero ... */
-ctx->linebuf[ctx->linebuf_offset] = '\0';
+ctx->linebuf[line.len] = '\0';
 
 /* Terminate on EOD marker */
 if (ctx->flags & PDKIM_DOT_TERM)
   {
-  if (memcmp(p, ".\r\n", 3) == 0)
-    return pdkim_body_complete(ctx);
+  if (memcmp(line.data, ".\r\n", 3) == 0)
+    { pdkim_body_complete(ctx); return; }
 
   /* Unstuff dots */
-  if (memcmp(p, "..", 2) == 0)
-    {
-    p++;
-    n--;
-    }
+  if (memcmp(line.data, "..", 2) == 0)
+    { line.data++; line.len--; }
   }
 
 /* Empty lines need to be buffered until we find a non-empty line */
-if (memcmp(p, "\r\n", 2) == 0)
+if (memcmp(line.data, "\r\n", 2) == 0)
   {
-  ctx->num_buffered_crlf++;
-  goto BAIL;
+  for (sig = ctx->sig; sig; sig = sig->next) sig->num_buffered_blanklines++;
+  goto all_skip;
   }
 
-if (sig && sig->canon_body == PDKIM_CANON_RELAXED)
+/* Process line for each sig separately */
+for (sig = ctx->sig; sig; sig = sig->next)
   {
-  /* Lines with just spaces need to be buffered too */
-  char *check = p;
-  while (memcmp(check, "\r\n", 2) != 0)
+  if (sig->canon_body == PDKIM_CANON_RELAXED)
     {
-    char c = *check;
+    /* Lines with just spaces need to be buffered too */
+    char * cp = line.data;
+    char c;
 
-    if (c != '\t' && c != ' ')
-      goto PROCESS;
-    check++;
+    while ((c = *cp))
+      {
+      if (c == '\r' && cp[1] == '\n') break;
+      if (c != ' ' && c != '\t') goto sig_process;
+      cp++;
+      }
+
+    sig->num_buffered_blanklines++;
+    goto sig_skip;
     }
 
-  ctx->num_buffered_crlf++;
-  goto BAIL;
-}
+sig_process:
+  /* At this point, we have a non-empty line, so release the buffered ones. */
 
-PROCESS:
-/* At this point, we have a non-empty line, so release the buffered ones. */
-while (ctx->num_buffered_crlf)
-  {
-  pdkim_update_bodyhash(ctx, "\r\n", 2);
-  ctx->num_buffered_crlf--;
+  while (sig->num_buffered_blanklines)
+    {
+    rnl = pdkim_update_sig_bodyhash(sig, &lineending, rnl);
+    sig->num_buffered_blanklines--;
+    }
+
+  rline = pdkim_update_sig_bodyhash(sig, &line, rline);
+sig_skip: ;
   }
 
-pdkim_update_bodyhash(ctx, p, n);
+if (rnl) store_free(rnl);
+if (rline) store_free(rline);
+
+all_skip:
 
-BAIL:
 ctx->linebuf_offset = 0;
-return PDKIM_OK;
+return;
 }
 
 
@@ -1002,8 +1013,7 @@ else for (p = 0; p<len; p++)
     else if (c == '\n')
       {
       ctx->flags &= ~PDKIM_SEEN_CR;
-      if ((rc = pdkim_bodyline_complete(ctx)) != PDKIM_OK)
-       return rc;
+      pdkim_bodyline_complete(ctx);
       }
 
     if (ctx->linebuf_offset == PDKIM_MAX_BODY_LINE_LEN-1)
@@ -1183,7 +1193,7 @@ return str;
 /* -------------------------------------------------------------------------- */
 
 static uschar *
-pdkim_create_header(pdkim_signature *sig, BOOL final)
+pdkim_create_header(pdkim_signature * sig, BOOL final)
 {
 uschar * base64_bh;
 uschar * base64_b;
@@ -1324,7 +1334,9 @@ DEBUG(D_acl)
   {
   debug_printf(
     "PDKIM >> Parsing public key record >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
-    " Raw record: ");
+    " %s\n"
+    " Raw record: ",
+    dns_txt_name);
   pdkim_quoteprint(CUS dns_txt_reply, Ustrlen(dns_txt_reply));
   }
 
@@ -1369,16 +1381,22 @@ DLLEXPORT int
 pdkim_feed_finish(pdkim_ctx * ctx, pdkim_signature ** return_signatures,
   const uschar ** err)
 {
-pdkim_signature *sig = ctx->sig;
+pdkim_signature * sig;
 
 /* Check if we must still flush a (partial) header. If that is the
    case, the message has no body, and we must compute a body hash
    out of '<CR><LF>' */
 if (ctx->cur_header && ctx->cur_header_len)
   {
-  int rc = pdkim_header_complete(ctx);
-  if (rc != PDKIM_OK) return rc;
-  pdkim_update_bodyhash(ctx, "\r\n", 2);
+  blob * rnl = NULL;
+  int rc;
+
+  if ((rc = pdkim_header_complete(ctx)) != PDKIM_OK)
+    return rc;
+
+  for (sig = ctx->sig; sig; sig = sig->next)
+    rnl = pdkim_update_sig_bodyhash(sig, &lineending, rnl);
+  if (rnl) store_free(rnl);
   }
 else
   DEBUG(D_acl) debug_printf(
@@ -1387,7 +1405,7 @@ else
 /* Build (and/or evaluate) body hash */
 pdkim_finish_bodyhash(ctx);
 
-while (sig)
+for (sig = ctx->sig; sig; sig = sig->next)
   {
   hctx hhash_ctx;
   uschar * sig_hdr = US"";
@@ -1405,9 +1423,16 @@ while (sig)
     break;
     }
 
+  if (ctx->flags & PDKIM_MODE_SIGN)
+    DEBUG(D_acl) debug_printf(
+       "PDKIM >> Headers to be signed:                            >>>>>>>>>>>>\n"
+       " %s\n",
+       sig->sign_headers);
+
   DEBUG(D_acl) debug_printf(
       "PDKIM >> Header data for hash, canonicalized, in sequence >>>>>>>>>>>>\n");
 
+
   /* SIGNING ---------------------------------------------------------------- */
   /* When signing, walk through our header list and add them to the hash. As we
      go, construct a list of the header's names to use for the h= parameter.
@@ -1416,26 +1441,26 @@ while (sig)
 
   if (ctx->flags & PDKIM_MODE_SIGN)
     {
-    uschar * headernames = NULL;       /* Collected signed header names */
     int hs = 0, hl = 0;
     pdkim_stringlist *p;
     const uschar * l;
     uschar * s;
     int sep = 0;
 
+    sig->headernames = NULL;           /* Collected signed header names */
+
     for (p = sig->headers; p; p = p->next)
-      if (header_name_match(p->value, sig->sign_headers) == PDKIM_OK)
+      {
+      uschar * rh = p->value;
+
+      if (header_name_match(rh, sig->sign_headers) == PDKIM_OK)
        {
-       uschar * rh;
        /* Collect header names (Note: colon presence is guaranteed here) */
-       uschar * q = Ustrchr(p->value, ':');
+       sig->headernames = string_append_listele_n(sig->headernames, &hs, &hl,
+                               ':', rh, Ustrchr(rh, ':') - rh);
 
-       headernames = string_catn(headernames, &hs, &hl,
-                       p->value, (q - US p->value) + (p->next ? 1 : 0));
-
-       rh = sig->canon_headers == PDKIM_CANON_RELAXED
-         ? pdkim_relax_header(p->value, TRUE) /* cook header for relaxed canon */
-         : string_copy(CUS p->value);      /* just copy it for simple canon */
+       if (sig->canon_headers == PDKIM_CANON_RELAXED)
+         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));
@@ -1445,20 +1470,14 @@ while (sig)
 
        DEBUG(D_acl) pdkim_quoteprint(rh, Ustrlen(rh));
        }
+      }
 
+    /* Any headers we wanted to sign but were not present must also be listed */
     l = sig->sign_headers;
     while((s = string_nextinlist(&l, &sep, NULL, 0)))
       if (*s != '_')
-       {                       /*SSS string_append_listele() */
-       if (hl > 0 && headernames[hl-1] != ':')
-         headernames = string_catn(headernames, &hs, &hl, US":", 1);
-
-       headernames = string_cat(headernames, &hs, &hl, s);
-       }
-    headernames[hl] = '\0';
-
-    /* Copy headernames to signature struct */
-    sig->headernames = headernames;
+       sig->headernames = string_append_listele(sig->headernames, &hs, &hl, ':', s);
+    sig->headernames[hl] = '\0';
 
     /* Create signature header with b= omitted */
     sig_hdr = pdkim_create_header(sig, FALSE);
@@ -1517,6 +1536,15 @@ while (sig)
   DEBUG(D_acl) debug_printf(
            "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
 
+  DEBUG(D_acl)
+    {
+    debug_printf(
+           "PDKIM >> Signed DKIM-Signature header, pre-canonicalized >>>>>>>>>>>>>\n");
+    pdkim_quoteprint(CUS sig_hdr, Ustrlen(sig_hdr));
+    debug_printf(
+           "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+    }
+
   /* Relax header if necessary */
   if (sig->canon_headers == PDKIM_CANON_RELAXED)
     sig_hdr = pdkim_relax_header(sig_hdr, FALSE);
@@ -1536,12 +1564,13 @@ while (sig)
 
   DEBUG(D_acl)
     {
-    debug_printf("PDKIM [%s] Header hash computed: ", sig->domain);
+    debug_printf("PDKIM [%s] Header %s computed: ",
+      sig->domain, pdkim_hashes[sig->hashtype].dkim_hashname);
     pdkim_hexprint(hhash.data, hhash.len);
     }
 
-  /* Remember headers block for signing (when the library cannot do incremental)  */
-/*XXX is this assuing algo == RSA? */
+  /* Remember headers block for signing (when the signing library cannot do
+  incremental)  */
   if (ctx->flags & PDKIM_MODE_SIGN)
     (void) exim_dkim_data_append(&hdata, &hdata_alloc, US sig_hdr);
 
@@ -1621,6 +1650,12 @@ while (sig)
       goto NEXT_VERIFY;
       }
 
+    DEBUG(D_acl)
+      {
+      debug_printf( "PDKIM [%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)))
       goto NEXT_VERIFY;
 
@@ -1675,8 +1710,6 @@ NEXT_VERIFY:
        debug_printf("\n");
       }
     }
-
-  sig = sig->next;
   }
 
 /* If requested, set return pointer to signature(s) */
@@ -1709,31 +1742,23 @@ return ctx;
 
 /*XXX ? needs extension to cover non-RSA algo?  */
 
-DLLEXPORT pdkim_ctx *
-pdkim_init_sign(uschar * domain, uschar * selector, uschar * privkey,
-  uschar * hashname, BOOL dot_stuffed, int(*dns_txt_callback)(char *, char *),
-  const uschar ** errstr)
+DLLEXPORT pdkim_signature *
+pdkim_init_sign(pdkim_ctx * ctx,
+  uschar * domain, uschar * selector, uschar * privkey,
+  uschar * hashname, const uschar ** errstr)
 {
 int hashtype;
-pdkim_ctx * ctx;
 pdkim_signature * sig;
 
 if (!domain || !selector || !privkey)
   return NULL;
 
-ctx = store_get(sizeof(pdkim_ctx) + PDKIM_MAX_BODY_LINE_LEN + sizeof(pdkim_signature));
-memset(ctx, 0, sizeof(pdkim_ctx));
-
-ctx->flags = dot_stuffed ? PDKIM_MODE_SIGN | PDKIM_DOT_TERM : PDKIM_MODE_SIGN;
-ctx->linebuf = CS (ctx+1);
+/* Allocate & init one signature struct */
 
-DEBUG(D_acl) ctx->dns_txt_callback = dns_txt_callback;
-
-sig = (pdkim_signature *)(ctx->linebuf + PDKIM_MAX_BODY_LINE_LEN);
+sig = store_get(sizeof(pdkim_signature));
 memset(sig, 0, sizeof(pdkim_signature));
 
 sig->bodylength = -1;
-ctx->sig = sig;
 
 sig->domain = string_copy(US domain);
 sig->selector = string_copy(US selector);
@@ -1767,24 +1792,22 @@ DEBUG(D_acl)
     debug_printf("WARNING: bad dkim key in dns\n");
   debug_printf("PDKIM (finished checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
   }
-return ctx;
+return sig;
 }
 
 
 /* -------------------------------------------------------------------------- */
 
-DLLEXPORT int
-pdkim_set_optional(pdkim_ctx *ctx,
-                       char *sign_headers,
-                       char *identity,
+DLLEXPORT void
+pdkim_set_optional(pdkim_signature * sig,
+                       char * sign_headers,
+                       char * identity,
                        int canon_headers,
                        int canon_body,
                        long bodylength,
                        unsigned long created,
                        unsigned long expires)
 {
-pdkim_signature * sig = ctx->sig;
-
 if (identity)
   sig->identity = string_copy(US identity);
 
@@ -1797,7 +1820,19 @@ sig->bodylength = bodylength;
 sig->created = created;
 sig->expires = expires;
 
-return PDKIM_OK;
+return;
+}
+
+
+
+void
+pdkim_init_context(pdkim_ctx * ctx, BOOL dot_stuffed,
+  int(*dns_txt_callback)(char *, char *))
+{
+memset(ctx, 0, sizeof(pdkim_ctx));
+ctx->flags = dot_stuffed ? PDKIM_MODE_SIGN | PDKIM_DOT_TERM : PDKIM_MODE_SIGN;
+ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN);
+DEBUG(D_acl) ctx->dns_txt_callback = dns_txt_callback;
 }
 
 
index 3c420ae..a350e6b 100644 (file)
@@ -113,6 +113,7 @@ typedef struct pdkim_pubkey {
 /* -------------------------------------------------------------------------- */
 /* Signature as it appears in a DKIM-Signature header */
 typedef struct pdkim_signature {
+  struct pdkim_signature * next;
 
   /* Bits stored in a DKIM signature header --------------------------- */
 
@@ -166,7 +167,7 @@ typedef struct pdkim_signature {
   /* (bh=) Raw body hash data, along with its length in bytes */
   blob bodyhash;
 
-  /* Folded DKIM-Signature: header. Singing only, NULL for verifying.
+  /* Folded DKIM-Signature: header. Signing only, NULL for verifying.
      Ready for insertion into the message. Note: Folded using CRLFTB,
      but final line terminator is NOT included. Note2: This buffer is
      free()d when you call pdkim_free_ctx(). */
@@ -223,17 +224,15 @@ typedef struct pdkim_signature {
      Caution: is NULL if signing or if no record was retrieved. */
   pdkim_pubkey *pubkey;
 
-  /* Pointer to the next pdkim_signature signature. NULL if signing or if
-     this is the last signature. */
-  void *next;
-
   /* Properties below this point are used internally only ------------- */
 
   /* Per-signature helper variables ----------------------------------- */
   hctx         body_hash_ctx;
 
   unsigned long signed_body_bytes; /* How many body bytes we hashed     */
+  int num_buffered_blanklines;
   pdkim_stringlist *headers; /* Raw headers included in the sig         */
+
   /* Signing specific ------------------------------------------------- */
   uschar * privkey;         /* Private key                                 */
   uschar * sign_headers;    /* To-be-signed header names                   */
@@ -265,7 +264,6 @@ typedef struct pdkim_ctx {
   int        cur_header_len;
   char      *linebuf;
   int        linebuf_offset;
-  int        num_buffered_crlf;
   int        num_headers;
   pdkim_stringlist *headers; /* Raw headers for verification         */
 } pdkim_ctx;
@@ -282,15 +280,18 @@ extern "C" {
 
 void      pdkim_init         (void);
 
+void      pdkim_init_context (pdkim_ctx *, BOOL, int(*)(char *, char *));
+
 DLLEXPORT
-pdkim_ctx *pdkim_init_sign    (uschar *, uschar *, uschar *, uschar *,
-                             BOOL, int(*)(char *, char *), const uschar **);
+pdkim_signature *pdkim_init_sign    (pdkim_ctx *,
+                              uschar *, uschar *, uschar *, uschar *,
+                              const uschar **);
 
 DLLEXPORT
 pdkim_ctx *pdkim_init_verify  (int(*)(char *, char *), BOOL);
 
 DLLEXPORT
-int        pdkim_set_optional (pdkim_ctx *, char *, char *,int, int,
+void       pdkim_set_optional (pdkim_signature *, char *, char *,int, int,
                                long,
                                unsigned long,
                                unsigned long);
@@ -306,7 +307,7 @@ void       pdkim_free_ctx     (pdkim_ctx *);
 
 const uschar * pdkim_errstr(int);
 
-uschar *       dkim_sig_to_a_tag(pdkim_signature * sig);
+uschar *       dkim_sig_to_a_tag(const pdkim_signature * sig);
 
 #ifdef __cplusplus
 }
index ec68414..77728ba 100644 (file)
@@ -587,6 +587,7 @@ return NULL;
 void
 exim_dkim_init(void)
 {
+ERR_load_crypto_strings();
 }
 
 
@@ -618,7 +619,7 @@ return NULL;
 OR
 sign hash.
 
-Return: NULL for success, or an error string */
+Return: NULL for success with the signaature in the sig blob, or an error string */
 
 const uschar *
 exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, blob * data, blob * sig)
@@ -644,10 +645,13 @@ if (  (ctx = EVP_PKEY_CTX_new(sign_ctx->key, NULL))
   {
   /* Allocate mem for signature */
   sig->data = store_get(siglen);
-  sig->len = siglen;
 
   if (EVP_PKEY_sign(ctx, sig->data, &siglen, data->data, data->len) > 0)
-    { EVP_PKEY_CTX_free(ctx); return NULL; }
+    {
+    EVP_PKEY_CTX_free(ctx);
+    sig->len = siglen;
+    return NULL;
+    }
   }
 
 if (ctx) EVP_PKEY_CTX_free(ctx);
index 4497028..5f46498 100644 (file)
@@ -12,6 +12,7 @@ primary_hostname = myhost.test.ex
 acl_smtp_rcpt = accept
 acl_smtp_dkim = accept logwrite = signer: $dkim_cur_signer bits: $dkim_key_length h=$dkim_headernames
 
+DDIR=DIR/aux-fixed/dkim
 
 # ----- Routers
 
@@ -42,7 +43,11 @@ send_to_server:
 .else
   dkim_selector =      sel
 .endif
-  dkim_private_key =   DIR/aux-fixed/dkim/dkim.private
+
+  dkim_private_key =   ${if match {$dkim_selector}{^ses}       {DDIR/dkim512.private} \
+                         {${if match {$dkim_selector}{^sel} {DDIR/dkim.private} \
+                         {}}}}
+
 .ifndef HEADERS_MAXSIZE
   dkim_sign_headers =  OPT
 .endif
diff --git a/test/confs/4524 b/test/confs/4524
new file mode 120000 (symlink)
index 0000000..072f5fa
--- /dev/null
@@ -0,0 +1 @@
+4520
\ No newline at end of file
index e9736fd..73854cf 100644 (file)
@@ -29,7 +29,7 @@
 1999-03-02 09:44:33 10HmbC-0005vi-00 => :blackhole: <c@test.ex> R=server_dump
 1999-03-02 09:44:33 10HmbC-0005vi-00 Completed
 1999-03-02 09:44:33 10HmbE-0005vi-00 DKIM: d=test.ex s=sel_bad c=relaxed/relaxed a=rsa-sha256 b=1024 [invalid - syntax error in public key record]
-1999-03-02 09:44:33 10HmbE-0005vi-00 signer: test.ex bits: 1024 h=Date:Sender:Message-Id:From:Reply-To:Subject:To:Cc:MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:List-Subscribe:List-Post:List-Owner:List-Archive
+1999-03-02 09:44:33 10HmbE-0005vi-00 signer: test.ex bits: 1024 h=From
 1999-03-02 09:44:33 10HmbE-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtp S=sss id=E10HmbD-0005vi-00@myhost.test.ex
 1999-03-02 09:44:33 10HmbE-0005vi-00 => :blackhole: <d@test.ex> R=server_dump
 1999-03-02 09:44:33 10HmbE-0005vi-00 Completed
diff --git a/test/log/4524 b/test/log/4524
new file mode 100644 (file)
index 0000000..a6d687c
--- /dev/null
@@ -0,0 +1,12 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaX-0005vi-00 => c@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 10HmaY-0005vi-00 DKIM: d=test.ex s=ses c=relaxed/relaxed a=rsa-sha256 b=512 [verification succeeded]
+1999-03-02 09:44:33 10HmaY-0005vi-00 DKIM: d=test.ex s=sel c=relaxed/relaxed a=rsa-sha256 b=1024 [verification succeeded]
+1999-03-02 09:44:33 10HmaY-0005vi-00 signer: test.ex bits: 512 h=From:To:Subject
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtp S=sss id=E10HmaX-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <c@test.ex> R=server_dump
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
index 6efe354..3e58799 100644 (file)
@@ -26,7 +26,7 @@ content
 ****
 #
 # check that on signing we warn in debug mode about verify problems
-exim -d-all+acl -DHEADERS_MAXSIZE=y -DSELECTOR=sel_bad -odf d@test.ex
+exim -d-all+acl -DOPT=From -DSELECTOR=sel_bad -odf d@test.ex
 From: nobody@example.com
 
 content
diff --git a/test/scripts/4500-DKIM/4524 b/test/scripts/4500-DKIM/4524
new file mode 100644 (file)
index 0000000..9737ad5
--- /dev/null
@@ -0,0 +1,14 @@
+# DKIM signing, multiple
+#
+exim -bd -DSERVER=server -oX PORT_D
+****
+#
+exim -DSELECTOR=ses:sel -DOPT=From:To:Subject -odf c@test.ex
+From: nobody@example.com
+
+content
+****
+#
+millisleep 500
+killdaemon
+no_msglog_check
index fc64a9e..d8d2d7a 100644 (file)
@@ -25,6 +25,7 @@ cmd buf flush ddd bytes
   SMTP<< 354 Enter message, ending with "." on a line by itself
 PDKIM (checking verify key)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
 PDKIM >> Parsing public key record >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+ sel_bad._domainkey.test.ex.
  Raw record: v=DKIM1\;{SP}p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDXRFf+VhT+lCgFhhSkinZKcFNeRzjYdW8vT29Rbb3NadvTFwAd+cVLPFwZL8H5tUD/7JbUPqNTCPxmpgIL+V5T4tEZMorHatvvUM2qfcpQ45IfsZ+YdhbIiAslHCpy4xNxIR3zylgqRUF4+Dtsaqy3a5LhwMiKCLrnzhXk1F1hxwIDAQAB
  v=DKIM1\
  p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDXRFf+VhT+lCgFhhSkinZKcFNeRzjYdW8vT29Rbb3NadvTFwAd+cVLPFwZL8H5tUD/7JbUPqNTCPxmpgIL+V5T4tEZMorHatvvUM2qfcpQ45IfsZ+YdhbIiAslHCpy4xNxIR3zylgqRUF4+Dtsaqy3a5LhwMiKCLrnzhXk1F1hxwIDAQAB
@@ -34,13 +35,16 @@ WARNING: bad dkim key in dns
 PDKIM (finished checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
 content{CR}{LF}
 PDKIM [test.ex] Body bytes hashed: 9
-PDKIM [test.ex] Body hash computed: fc06f48221d98ad6106c3845b33a2a41152482ab9e697f736ad26db4853fa657
+PDKIM [test.ex] Body sha256 computed: fc06f48221d98ad6106c3845b33a2a41152482ab9e697f736ad26db4853fa657
+PDKIM >> Headers to be signed:                            >>>>>>>>>>>>
+ From
 PDKIM >> Header data for hash, canonicalized, in sequence >>>>>>>>>>>>
-sender:CALLER_NAME{SP}<CALLER@myhost.test.ex>{CR}{LF}
-message-id:<E10HmbD-0005vi-00@myhost.test.ex>{CR}{LF}
 from:nobody@example.com{CR}{LF}
+PDKIM >> Signed DKIM-Signature header, pre-canonicalized >>>>>>>>>>>>>
+DKIM-Signature:{SP}v=1;{SP}a=rsa-sha256;{SP}q=dns/txt;{SP}c=relaxed/relaxed;{SP}d=test.ex;{CR}{LF}{TB}s=sel_bad;{SP}h=From;{SP}bh=/Ab0giHZitYQbDhFszoqQRUkgqueaX9zatJttIU/plc=;{SP}b=;
 PDKIM >> Signed DKIM-Signature header, canonicalized >>>>>>>>>>>>>>>>>
-dkim-signature:v=1;{SP}a=rsa-sha256;{SP}q=dns/txt;{SP}c=relaxed/relaxed;{SP}d=test.ex;{SP}s=sel_bad;{SP}h=Date:Sender:Message-Id:From:Reply-To:Subject:To:Cc:MIME-Version:{SP}Content-Type:Content-Transfer-Encoding:Content-ID:Content-Description:{SP}Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:{SP}In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:List-Subscribe:{SP}List-Post:List-Owner:List-Archive;{SP}bh=/Ab0giHZitYQbDhFszoqQRUkgqueaX9zatJttIU/plc=;{SP}b=;
+dkim-signature:v=1;{SP}a=rsa-sha256;{SP}q=dns/txt;{SP}c=relaxed/relaxed;{SP}d=test.ex;{SP}s=sel_bad;{SP}h=From;{SP}bh=/Ab0giHZitYQbDhFszoqQRUkgqueaX9zatJttIU/plc=;{SP}b=;
+PDKIM [test.ex] Header sha256 computed: 241e16230df5723d899cfae9474c6b376a2ab1f81d1094e358f50ffd0e0067b3
   SMTP<< 250 OK id=10HmbE-0005vi-00
   SMTP>> QUIT
 cmd buf flush ddd bytes