From: Jeremy Harris Date: Tue, 12 Sep 2017 16:49:58 +0000 (+0100) Subject: DKIM: support multiple signing, by selector X-Git-Tag: exim-4_90_RC1~75 X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=9e70917d0aa5e51f584b2af69ce80df458ac5c79;p=exim.git DKIM: support multiple signing, by selector --- diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index 61a6f0e83..44a274b98 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -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 diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index c10649edd..1948641c9 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -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 diff --git a/src/src/dkim.c b/src/src/dkim.c index 2b7f55ae8..ea2007225 100644 --- a/src/src/dkim.c +++ b/src/src/dkim.c @@ -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; } diff --git a/src/src/dkim.h b/src/src/dkim.h index 83c68a76c..a3419db42 100644 --- a/src/src/dkim.h +++ b/src/src/dkim.h @@ -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); diff --git a/src/src/dkim_transport.c b/src/src/dkim_transport.c index 1ef4cfa6c..85a73dcae 100644 --- a/src/src/dkim_transport.c +++ b/src/src/dkim_transport.c @@ -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)) diff --git a/src/src/expand.c b/src/src/expand.c index c51c1ff1b..04bb92916 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -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; } diff --git a/src/src/pdkim/pdkim.c b/src/src/pdkim/pdkim.c index bef6b6a69..f107a5948 100644 --- a/src/src/pdkim/pdkim.c +++ b/src/src/pdkim/pdkim.c @@ -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; pflags &= ~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 '' */ 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; } diff --git a/src/src/pdkim/pdkim.h b/src/src/pdkim/pdkim.h index 3c420ae63..a350e6b7e 100644 --- a/src/src/pdkim/pdkim.h +++ b/src/src/pdkim/pdkim.h @@ -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 } diff --git a/src/src/pdkim/signing.c b/src/src/pdkim/signing.c index ec68414c8..77728bab1 100644 --- a/src/src/pdkim/signing.c +++ b/src/src/pdkim/signing.c @@ -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); diff --git a/test/confs/4520 b/test/confs/4520 index 449702855..5f4649846 100644 --- a/test/confs/4520 +++ b/test/confs/4520 @@ -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 index 000000000..072f5faf2 --- /dev/null +++ b/test/confs/4524 @@ -0,0 +1 @@ +4520 \ No newline at end of file diff --git a/test/log/4520 b/test/log/4520 index e9736fd6f..73854cfc1 100644 --- a/test/log/4520 +++ b/test/log/4520 @@ -29,7 +29,7 @@ 1999-03-02 09:44:33 10HmbC-0005vi-00 => :blackhole: 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: 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 index 000000000..a6d687c83 --- /dev/null +++ b/test/log/4524 @@ -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: R=server_dump +1999-03-02 09:44:33 10HmaY-0005vi-00 Completed diff --git a/test/scripts/4500-DKIM/4520 b/test/scripts/4500-DKIM/4520 index 6efe3545a..3e5879972 100644 --- a/test/scripts/4500-DKIM/4520 +++ b/test/scripts/4500-DKIM/4520 @@ -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 index 000000000..9737ad583 --- /dev/null +++ b/test/scripts/4500-DKIM/4524 @@ -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 diff --git a/test/stderr/4520 b/test/stderr/4520 index fc64a9e93..d8d2d7a03 100644 --- a/test/stderr/4520 +++ b/test/stderr/4520 @@ -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}{CR}{LF} -message-id:{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