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