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