DKIM: rename internal signing api
[exim.git] / src / src / dkim.c
1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
4
5 /* Copyright (c) University of Cambridge, 1995 - 2017 */
6 /* See the file NOTICE for conditions of use and distribution. */
7
8 /* Code for DKIM support. Other DKIM relevant code is in
9 receive.c, transport.c and transports/smtp.c */
10
11 #include "exim.h"
12
13 #ifndef DISABLE_DKIM
14
15 #include "pdkim/pdkim.h"
16
17 int dkim_verify_oldpool;
18 pdkim_ctx *dkim_verify_ctx = NULL;
19 pdkim_signature *dkim_signatures = NULL;
20 pdkim_signature *dkim_cur_sig = NULL;
21 static const uschar * dkim_collect_error = NULL;
22
23 static int
24 dkim_exim_query_dns_txt(char *name, char *answer)
25 {
26 dns_answer dnsa;
27 dns_scan dnss;
28 dns_record *rr;
29
30 lookup_dnssec_authenticated = NULL;
31 if (dns_lookup(&dnsa, US name, T_TXT, NULL) != DNS_SUCCEED)
32 return PDKIM_FAIL; /*XXX better error detail? logging? */
33
34 /* Search for TXT record */
35
36 for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
37 rr;
38 rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
39 if (rr->type == T_TXT)
40 {
41 int rr_offset = 0;
42 int answer_offset = 0;
43
44 /* Copy record content to the answer buffer */
45
46 while (rr_offset < rr->size)
47 {
48 uschar len = rr->data[rr_offset++];
49 snprintf(answer + answer_offset,
50 PDKIM_DNS_TXT_MAX_RECLEN - answer_offset,
51 "%.*s", (int)len, CS (rr->data + rr_offset));
52 rr_offset += len;
53 answer_offset += len;
54 if (answer_offset >= PDKIM_DNS_TXT_MAX_RECLEN)
55 return PDKIM_FAIL; /*XXX better error detail? logging? */
56 }
57 return PDKIM_OK;
58 }
59
60 return PDKIM_FAIL; /*XXX better error detail? logging? */
61 }
62
63
64 void
65 dkim_exim_init(void)
66 {
67 pdkim_init();
68 }
69
70
71
72 void
73 dkim_exim_verify_init(BOOL dot_stuffing)
74 {
75 /* There is a store-reset between header & body reception
76 so cannot use the main pool. Any allocs done by Exim
77 memory-handling must use the perm pool. */
78
79 dkim_verify_oldpool = store_pool;
80 store_pool = POOL_PERM;
81
82 /* Free previous context if there is one */
83
84 if (dkim_verify_ctx)
85 pdkim_free_ctx(dkim_verify_ctx);
86
87 /* Create new context */
88
89 dkim_verify_ctx = pdkim_init_verify(&dkim_exim_query_dns_txt, dot_stuffing);
90 dkim_collect_input = !!dkim_verify_ctx;
91 dkim_collect_error = NULL;
92
93 /* Start feed up with any cached data */
94 receive_get_cache();
95
96 store_pool = dkim_verify_oldpool;
97 }
98
99
100 void
101 dkim_exim_verify_feed(uschar * data, int len)
102 {
103 int rc;
104
105 store_pool = POOL_PERM;
106 if ( dkim_collect_input
107 && (rc = pdkim_feed(dkim_verify_ctx, data, len)) != PDKIM_OK)
108 {
109 dkim_collect_error = pdkim_errstr(rc);
110 log_write(0, LOG_MAIN,
111 "DKIM: validation error: %.100s", dkim_collect_error);
112 dkim_collect_input = FALSE;
113 }
114 store_pool = dkim_verify_oldpool;
115 }
116
117
118 void
119 dkim_exim_verify_finish(void)
120 {
121 pdkim_signature * sig = NULL;
122 int dkim_signers_size = 0, dkim_signers_ptr = 0, rc;
123 const uschar * errstr;
124
125 store_pool = POOL_PERM;
126
127 /* Delete eventual previous signature chain */
128
129 dkim_signers = NULL;
130 dkim_signatures = NULL;
131
132 if (dkim_collect_error)
133 {
134 log_write(0, LOG_MAIN,
135 "DKIM: Error during validation, disabling signature verification: %.100s",
136 dkim_collect_error);
137 dkim_disable_verify = TRUE;
138 goto out;
139 }
140
141 dkim_collect_input = FALSE;
142
143 /* Finish DKIM operation and fetch link to signatures chain */
144
145 rc = pdkim_feed_finish(dkim_verify_ctx, &dkim_signatures, &errstr);
146 if (rc != PDKIM_OK)
147 {
148 log_write(0, LOG_MAIN, "DKIM: validation error: %.100s%s%s", pdkim_errstr(rc),
149 errstr ? ": " : "", errstr ? errstr : US"");
150 goto out;
151 }
152
153 for (sig = dkim_signatures; sig; sig = sig->next)
154 {
155 int size = 0, ptr = 0;
156 uschar * logmsg = NULL, * s;
157
158 /* Log a line for each signature */
159
160 if (!(s = sig->domain)) s = US"<UNSET>";
161 logmsg = string_append(logmsg, &size, &ptr, 2, "d=", s);
162 if (!(s = sig->selector)) s = US"<UNSET>";
163 logmsg = string_append(logmsg, &size, &ptr, 2, " s=", s);
164 logmsg = string_append(logmsg, &size, &ptr, 7,
165 " c=", sig->canon_headers == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
166 "/", sig->canon_body == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
167 " a=", sig->algo == PDKIM_ALGO_RSA_SHA256
168 ? "rsa-sha256"
169 : sig->algo == PDKIM_ALGO_RSA_SHA1 ? "rsa-sha1" : "err",
170 string_sprintf(" b=%d",
171 (int)sig->sighash.len > -1 ? sig->sighash.len * 8 : 0));
172 if ((s= sig->identity)) logmsg = string_append(logmsg, &size, &ptr, 2, " i=", s);
173 if (sig->created > 0) logmsg = string_append(logmsg, &size, &ptr, 1,
174 string_sprintf(" t=%lu", sig->created));
175 if (sig->expires > 0) logmsg = string_append(logmsg, &size, &ptr, 1,
176 string_sprintf(" x=%lu", sig->expires));
177 if (sig->bodylength > -1) logmsg = string_append(logmsg, &size, &ptr, 1,
178 string_sprintf(" l=%lu", sig->bodylength));
179
180 switch (sig->verify_status)
181 {
182 case PDKIM_VERIFY_NONE:
183 logmsg = string_append(logmsg, &size, &ptr, 1, " [not verified]");
184 break;
185
186 case PDKIM_VERIFY_INVALID:
187 logmsg = string_append(logmsg, &size, &ptr, 1, " [invalid - ");
188 switch (sig->verify_ext_status)
189 {
190 case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
191 logmsg = string_append(logmsg, &size, &ptr, 1,
192 "public key record (currently?) unavailable]");
193 break;
194
195 case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
196 logmsg = string_append(logmsg, &size, &ptr, 1,
197 "overlong public key record]");
198 break;
199
200 case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:
201 case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT:
202 logmsg = string_append(logmsg, &size, &ptr, 1,
203 "syntax error in public key record]");
204 break;
205
206 case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR:
207 logmsg = string_append(logmsg, &size, &ptr, 1,
208 "signature tag missing or invalid]");
209 break;
210
211 case PDKIM_VERIFY_INVALID_DKIM_VERSION:
212 logmsg = string_append(logmsg, &size, &ptr, 1,
213 "unsupported DKIM version]");
214 break;
215
216 default:
217 logmsg = string_append(logmsg, &size, &ptr, 1,
218 "unspecified problem]");
219 }
220 break;
221
222 case PDKIM_VERIFY_FAIL:
223 logmsg =
224 string_append(logmsg, &size, &ptr, 1, " [verification failed - ");
225 switch (sig->verify_ext_status)
226 {
227 case PDKIM_VERIFY_FAIL_BODY:
228 logmsg = string_append(logmsg, &size, &ptr, 1,
229 "body hash mismatch (body probably modified in transit)]");
230 break;
231
232 case PDKIM_VERIFY_FAIL_MESSAGE:
233 logmsg = string_append(logmsg, &size, &ptr, 1,
234 "signature did not verify (headers probably modified in transit)]");
235 break;
236
237 default:
238 logmsg = string_append(logmsg, &size, &ptr, 1, "unspecified reason]");
239 }
240 break;
241
242 case PDKIM_VERIFY_PASS:
243 logmsg =
244 string_append(logmsg, &size, &ptr, 1, " [verification succeeded]");
245 break;
246 }
247
248 logmsg[ptr] = '\0';
249 log_write(0, LOG_MAIN, "DKIM: %s", logmsg);
250
251 /* Build a colon-separated list of signing domains (and identities, if present) in dkim_signers */
252
253 if (sig->domain)
254 dkim_signers = string_append_listele(dkim_signers, &dkim_signers_size,
255 &dkim_signers_ptr, ':', sig->domain);
256
257 if (sig->identity)
258 dkim_signers = string_append_listele(dkim_signers, &dkim_signers_size,
259 &dkim_signers_ptr, ':', sig->identity);
260
261 /* Process next signature */
262 }
263
264 out:
265 store_pool = dkim_verify_oldpool;
266 }
267
268
269 void
270 dkim_exim_acl_setup(uschar * id)
271 {
272 pdkim_signature * sig;
273 uschar * cmp_val;
274
275 dkim_cur_sig = NULL;
276 dkim_cur_signer = id;
277
278 if (dkim_disable_verify || !id || !dkim_verify_ctx)
279 return;
280
281 /* Find signature to run ACL on */
282
283 for (sig = dkim_signatures; sig; sig = sig->next)
284 if ( (cmp_val = Ustrchr(id, '@') != NULL ? US sig->identity : US sig->domain)
285 && strcmpic(cmp_val, id) == 0
286 )
287 {
288 dkim_cur_sig = sig;
289
290 /* The "dkim_domain" and "dkim_selector" expansion variables have
291 related globals, since they are used in the signing code too.
292 Instead of inventing separate names for verification, we set
293 them here. This is easy since a domain and selector is guaranteed
294 to be in a signature. The other dkim_* expansion items are
295 dynamically fetched from dkim_cur_sig at expansion time (see
296 function below). */
297
298 dkim_signing_domain = US sig->domain;
299 dkim_signing_selector = US sig->selector;
300 dkim_key_length = sig->sighash.len * 8;
301 return;
302 }
303 }
304
305
306 static uschar *
307 dkim_exim_expand_defaults(int what)
308 {
309 switch (what)
310 {
311 case DKIM_ALGO: return US"";
312 case DKIM_BODYLENGTH: return US"9999999999999";
313 case DKIM_CANON_BODY: return US"";
314 case DKIM_CANON_HEADERS: return US"";
315 case DKIM_COPIEDHEADERS: return US"";
316 case DKIM_CREATED: return US"0";
317 case DKIM_EXPIRES: return US"9999999999999";
318 case DKIM_HEADERNAMES: return US"";
319 case DKIM_IDENTITY: return US"";
320 case DKIM_KEY_GRANULARITY: return US"*";
321 case DKIM_KEY_SRVTYPE: return US"*";
322 case DKIM_KEY_NOTES: return US"";
323 case DKIM_KEY_TESTING: return US"0";
324 case DKIM_NOSUBDOMAINS: return US"0";
325 case DKIM_VERIFY_STATUS: return US"none";
326 case DKIM_VERIFY_REASON: return US"";
327 default: return US"";
328 }
329 }
330
331
332 uschar *
333 dkim_exim_expand_query(int what)
334 {
335 if (!dkim_verify_ctx || dkim_disable_verify || !dkim_cur_sig)
336 return dkim_exim_expand_defaults(what);
337
338 switch (what)
339 {
340 case DKIM_ALGO:
341 switch (dkim_cur_sig->algo)
342 {
343 case PDKIM_ALGO_RSA_SHA1: return US"rsa-sha1";
344 case PDKIM_ALGO_RSA_SHA256:
345 default: return US"rsa-sha256";
346 }
347
348 case DKIM_BODYLENGTH:
349 return dkim_cur_sig->bodylength >= 0
350 ? string_sprintf(OFF_T_FMT, (LONGLONG_T) dkim_cur_sig->bodylength)
351 : dkim_exim_expand_defaults(what);
352
353 case DKIM_CANON_BODY:
354 switch (dkim_cur_sig->canon_body)
355 {
356 case PDKIM_CANON_RELAXED: return US"relaxed";
357 case PDKIM_CANON_SIMPLE:
358 default: return US"simple";
359 }
360
361 case DKIM_CANON_HEADERS:
362 switch (dkim_cur_sig->canon_headers)
363 {
364 case PDKIM_CANON_RELAXED: return US"relaxed";
365 case PDKIM_CANON_SIMPLE:
366 default: return US"simple";
367 }
368
369 case DKIM_COPIEDHEADERS:
370 return dkim_cur_sig->copiedheaders
371 ? US dkim_cur_sig->copiedheaders : dkim_exim_expand_defaults(what);
372
373 case DKIM_CREATED:
374 return dkim_cur_sig->created > 0
375 ? string_sprintf("%llu", dkim_cur_sig->created)
376 : dkim_exim_expand_defaults(what);
377
378 case DKIM_EXPIRES:
379 return dkim_cur_sig->expires > 0
380 ? string_sprintf("%llu", dkim_cur_sig->expires)
381 : dkim_exim_expand_defaults(what);
382
383 case DKIM_HEADERNAMES:
384 return dkim_cur_sig->headernames
385 ? dkim_cur_sig->headernames : dkim_exim_expand_defaults(what);
386
387 case DKIM_IDENTITY:
388 return dkim_cur_sig->identity
389 ? US dkim_cur_sig->identity : dkim_exim_expand_defaults(what);
390
391 case DKIM_KEY_GRANULARITY:
392 return dkim_cur_sig->pubkey
393 ? dkim_cur_sig->pubkey->granularity
394 ? US dkim_cur_sig->pubkey->granularity
395 : dkim_exim_expand_defaults(what)
396 : dkim_exim_expand_defaults(what);
397
398 case DKIM_KEY_SRVTYPE:
399 return dkim_cur_sig->pubkey
400 ? dkim_cur_sig->pubkey->srvtype
401 ? US dkim_cur_sig->pubkey->srvtype
402 : dkim_exim_expand_defaults(what)
403 : dkim_exim_expand_defaults(what);
404
405 case DKIM_KEY_NOTES:
406 return dkim_cur_sig->pubkey
407 ? dkim_cur_sig->pubkey->notes
408 ? US dkim_cur_sig->pubkey->notes
409 : dkim_exim_expand_defaults(what)
410 : dkim_exim_expand_defaults(what);
411
412 case DKIM_KEY_TESTING:
413 return dkim_cur_sig->pubkey
414 ? dkim_cur_sig->pubkey->testing
415 ? US"1"
416 : dkim_exim_expand_defaults(what)
417 : dkim_exim_expand_defaults(what);
418
419 case DKIM_NOSUBDOMAINS:
420 return dkim_cur_sig->pubkey
421 ? dkim_cur_sig->pubkey->no_subdomaining
422 ? US"1"
423 : dkim_exim_expand_defaults(what)
424 : dkim_exim_expand_defaults(what);
425
426 case DKIM_VERIFY_STATUS:
427 switch (dkim_cur_sig->verify_status)
428 {
429 case PDKIM_VERIFY_INVALID: return US"invalid";
430 case PDKIM_VERIFY_FAIL: return US"fail";
431 case PDKIM_VERIFY_PASS: return US"pass";
432 case PDKIM_VERIFY_NONE:
433 default: return US"none";
434 }
435
436 case DKIM_VERIFY_REASON:
437 switch (dkim_cur_sig->verify_ext_status)
438 {
439 case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
440 return US"pubkey_unavailable";
441 case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:return US"pubkey_dns_syntax";
442 case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT: return US"pubkey_der_syntax";
443 case PDKIM_VERIFY_FAIL_BODY: return US"bodyhash_mismatch";
444 case PDKIM_VERIFY_FAIL_MESSAGE: return US"signature_incorrect";
445 }
446
447 default:
448 return US"";
449 }
450 }
451
452
453 /* Generate signatures for the given file, returning a string.
454 If a prefix is given, prepend it to the file for the calculations.
455 */
456
457 uschar *
458 dkim_exim_sign(int fd, off_t off, uschar * prefix,
459 struct ob_dkim * dkim, const uschar ** errstr)
460 {
461 const uschar * dkim_domain;
462 int sep = 0;
463 uschar *seen_items = NULL;
464 int seen_items_size = 0;
465 int seen_items_offset = 0;
466 uschar *dkim_canon_expanded;
467 uschar *dkim_sign_headers_expanded;
468 uschar *dkim_private_key_expanded;
469 pdkim_ctx *ctx = NULL;
470 uschar *rc = NULL;
471 uschar *sigbuf = NULL;
472 int sigsize = 0;
473 int sigptr = 0;
474 pdkim_signature *signature;
475 int pdkim_canon;
476 int pdkim_rc;
477 int sread;
478 uschar buf[4096];
479 int save_errno = 0;
480 int old_pool = store_pool;
481
482 store_pool = POOL_MAIN;
483
484 if (!(dkim_domain = expand_cstring(dkim->dkim_domain)))
485 {
486 /* expansion error, do not send message. */
487 log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
488 "dkim_domain: %s", expand_string_message);
489 goto bad;
490 }
491
492 /* Set $dkim_domain expansion variable to each unique domain in list. */
493
494 while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, NULL, 0)))
495 {
496 if (dkim_signing_domain[0] == '\0')
497 continue;
498
499 /* Only sign once for each domain, no matter how often it
500 appears in the expanded list. */
501
502 if (seen_items)
503 {
504 const uschar *seen_items_list = seen_items;
505 if (match_isinlist(dkim_signing_domain,
506 &seen_items_list, 0, NULL, NULL, MCL_STRING, TRUE,
507 NULL) == OK)
508 continue;
509
510 seen_items =
511 string_append(seen_items, &seen_items_size, &seen_items_offset, 1, ":");
512 }
513
514 seen_items =
515 string_append(seen_items, &seen_items_size, &seen_items_offset, 1,
516 dkim_signing_domain);
517 seen_items[seen_items_offset] = '\0';
518
519 /* Set up $dkim_selector expansion variable. */
520
521 if (!(dkim_signing_selector = expand_string(dkim->dkim_selector)))
522 {
523 log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
524 "dkim_selector: %s", expand_string_message);
525 goto bad;
526 }
527
528 /* Get canonicalization to use */
529
530 dkim_canon_expanded = dkim->dkim_canon
531 ? expand_string(dkim->dkim_canon) : US"relaxed";
532 if (!dkim_canon_expanded)
533 {
534 /* expansion error, do not send message. */
535 log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
536 "dkim_canon: %s", expand_string_message);
537 goto bad;
538 }
539
540 if (Ustrcmp(dkim_canon_expanded, "relaxed") == 0)
541 pdkim_canon = PDKIM_CANON_RELAXED;
542 else if (Ustrcmp(dkim_canon_expanded, "simple") == 0)
543 pdkim_canon = PDKIM_CANON_SIMPLE;
544 else
545 {
546 log_write(0, LOG_MAIN,
547 "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",
548 dkim_canon_expanded);
549 pdkim_canon = PDKIM_CANON_RELAXED;
550 }
551
552 dkim_sign_headers_expanded = NULL;
553 if (dkim->dkim_sign_headers)
554 if (!(dkim_sign_headers_expanded = expand_string(dkim->dkim_sign_headers)))
555 {
556 log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
557 "dkim_sign_headers: %s", expand_string_message);
558 goto bad;
559 }
560 /* else pass NULL, which means default header list */
561
562 /* Get private key to use. */
563
564 if (!(dkim_private_key_expanded = expand_string(dkim->dkim_private_key)))
565 {
566 log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
567 "dkim_private_key: %s", expand_string_message);
568 goto bad;
569 }
570
571 if ( Ustrlen(dkim_private_key_expanded) == 0
572 || Ustrcmp(dkim_private_key_expanded, "0") == 0
573 || Ustrcmp(dkim_private_key_expanded, "false") == 0
574 )
575 continue; /* don't sign, but no error */
576
577 if (dkim_private_key_expanded[0] == '/')
578 {
579 int privkey_fd, off = 0, len;
580
581 /* Looks like a filename, load the private key. */
582
583 memset(big_buffer, 0, big_buffer_size);
584
585 if ((privkey_fd = open(CS dkim_private_key_expanded, O_RDONLY)) < 0)
586 {
587 log_write(0, LOG_MAIN | LOG_PANIC, "unable to open "
588 "private key file for reading: %s",
589 dkim_private_key_expanded);
590 goto bad;
591 }
592
593 do
594 {
595 if ((len = read(privkey_fd, big_buffer + off, big_buffer_size - 2 - off)) < 0)
596 {
597 (void) close(privkey_fd);
598 log_write(0, LOG_MAIN|LOG_PANIC, "unable to read private key file: %s",
599 dkim_private_key_expanded);
600 goto bad;
601 }
602 off += len;
603 }
604 while (len > 0);
605
606 (void) close(privkey_fd);
607 big_buffer[off] = '\0';
608 dkim_private_key_expanded = big_buffer;
609 }
610
611 /*XXX so we currently nail signing to RSA + SHA256. Need to extract algo
612 from privkey, and provide means for selecting hash-method.
613 Check for disallowed combos.
614 Will need new dkim_ transport option for hash. */
615
616 if (!(ctx = pdkim_init_sign(CS dkim_signing_domain,
617 CS dkim_signing_selector,
618 CS dkim_private_key_expanded,
619 PDKIM_ALGO_RSA_SHA256,
620 dkim->dot_stuffed,
621 &dkim_exim_query_dns_txt,
622 errstr
623 )))
624 goto bad;
625 dkim_private_key_expanded[0] = '\0';
626 pdkim_set_optional(ctx,
627 CS dkim_sign_headers_expanded,
628 NULL,
629 pdkim_canon,
630 pdkim_canon, -1, 0, 0);
631
632 if (prefix)
633 pdkim_feed(ctx, prefix, Ustrlen(prefix));
634
635 if (lseek(fd, off, SEEK_SET) < 0)
636 sread = -1;
637 else
638 while ((sread = read(fd, &buf, sizeof(buf))) > 0)
639 if ((pdkim_rc = pdkim_feed(ctx, buf, sread)) != PDKIM_OK)
640 goto pk_bad;
641
642 /* Handle failed read above. */
643 if (sread == -1)
644 {
645 debug_printf("DKIM: Error reading -K file.\n");
646 save_errno = errno;
647 goto bad;
648 }
649
650 if ((pdkim_rc = pdkim_feed_finish(ctx, &signature, errstr)) != PDKIM_OK)
651 goto pk_bad;
652
653 sigbuf = string_append(sigbuf, &sigsize, &sigptr, 2,
654 US signature->signature_header, US"\r\n");
655
656 pdkim_free_ctx(ctx);
657 ctx = NULL;
658 }
659
660 if (sigbuf)
661 {
662 sigbuf[sigptr] = '\0';
663 rc = sigbuf;
664 }
665 else
666 rc = US"";
667
668 CLEANUP:
669 if (ctx)
670 pdkim_free_ctx(ctx);
671 store_pool = old_pool;
672 errno = save_errno;
673 return rc;
674
675 pk_bad:
676 log_write(0, LOG_MAIN|LOG_PANIC,
677 "DKIM: signing failed: %.100s", pdkim_errstr(pdkim_rc));
678 bad:
679 rc = NULL;
680 goto CLEANUP;
681 }
682
683 #endif