.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.
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
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
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];
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. */
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 "
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;
}
/* 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);
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;
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
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;
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;
/* 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)
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))
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;
expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
return NULL;
}
+ }
return s;
}
};
+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))
#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;
/* -------------------------------------------------------------------------- */
-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)
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);
}
-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 */
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;
}
/* -------------------------------------------------------------------------- */
/* 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;
}
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)
/* -------------------------------------------------------------------------- */
static uschar *
-pdkim_create_header(pdkim_signature *sig, BOOL final)
+pdkim_create_header(pdkim_signature * sig, BOOL final)
{
uschar * base64_bh;
uschar * base64_b;
{
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));
}
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(
/* 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"";
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.
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));
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);
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);
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);
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;
debug_printf("\n");
}
}
-
- sig = sig->next;
}
/* If requested, set return pointer to signature(s) */
/*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);
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);
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;
}
/* -------------------------------------------------------------------------- */
/* Signature as it appears in a DKIM-Signature header */
typedef struct pdkim_signature {
+ struct pdkim_signature * next;
/* Bits stored in a DKIM signature header --------------------------- */
/* (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(). */
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 */
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;
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);
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
}
void
exim_dkim_init(void)
{
+ERR_load_crypto_strings();
}
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)
{
/* 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);
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
.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
--- /dev/null
+4520
\ No newline at end of file
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
--- /dev/null
+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
****
#
# 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
--- /dev/null
+# 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
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
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