LDAP: Fix debug messages
[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,
130 string_sprintf("d=%s s=%s c=%s/%s a=%s ",
131 sig->domain,
132 sig->selector,
133 sig->canon_headers == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
134 sig->canon_body == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
135 sig->algo == PDKIM_ALGO_RSA_SHA256 ? "rsa-sha256" : "rsa-sha1"),
136
137 sig->identity ? string_sprintf("i=%s ", sig->identity) : US"",
138 sig->created > 0 ? string_sprintf("t=%lu ", sig->created) : US"",
139 sig->expires > 0 ? string_sprintf("x=%lu ", sig->expires) : US"",
140 sig->bodylength > -1 ? string_sprintf("l=%lu ", sig->bodylength) : US""
141 );
142
143 switch (sig->verify_status)
144 {
145 case PDKIM_VERIFY_NONE:
146 logmsg = string_append(logmsg, &size, &ptr, 1, "[not verified]");
80a47a2c 147 break;
1cfe5c1c
JH
148
149 case PDKIM_VERIFY_INVALID:
150 logmsg = string_append(logmsg, &size, &ptr, 1, "[invalid - ");
151 switch (sig->verify_ext_status)
152 {
153 case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
154 logmsg = string_append(logmsg, &size, &ptr, 1,
155 "public key record (currently?) unavailable]");
156 break;
157
158 case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
159 logmsg = string_append(logmsg, &size, &ptr, 1,
160 "overlong public key record]");
161 break;
162
163 case PDKIM_VERIFY_INVALID_PUBKEY_PARSING:
164 logmsg = string_append(logmsg, &size, &ptr, 1,
165 "syntax error in public key record]");
166 break;
167
168 default:
169 logmsg = string_append(logmsg, &size, &ptr, 1,
170 "unspecified problem]");
171 }
80a47a2c 172 break;
1cfe5c1c
JH
173
174 case PDKIM_VERIFY_FAIL:
175 logmsg =
176 string_append(logmsg, &size, &ptr, 1, "[verification failed - ");
177 switch (sig->verify_ext_status)
178 {
179 case PDKIM_VERIFY_FAIL_BODY:
180 logmsg = string_append(logmsg, &size, &ptr, 1,
181 "body hash mismatch (body probably modified in transit)]");
182 break;
183
184 case PDKIM_VERIFY_FAIL_MESSAGE:
185 logmsg = string_append(logmsg, &size, &ptr, 1,
186 "signature did not verify (headers probably modified in transit)]");
187 break;
188
189 default:
190 logmsg = string_append(logmsg, &size, &ptr, 1, "unspecified reason]");
191 }
80a47a2c 192 break;
1cfe5c1c
JH
193
194 case PDKIM_VERIFY_PASS:
195 logmsg =
196 string_append(logmsg, &size, &ptr, 1, "[verification succeeded]");
80a47a2c
TK
197 break;
198 }
199
1cfe5c1c
JH
200 logmsg[ptr] = '\0';
201 log_write(0, LOG_MAIN, "DKIM: %s", logmsg);
80a47a2c 202
1cfe5c1c
JH
203 /* Build a colon-separated list of signing domains (and identities, if present) in dkim_signers */
204
205 dkim_signers = string_append(dkim_signers,
206 &dkim_signers_size,
207 &dkim_signers_ptr, 2, sig->domain, ":");
208
209 if (sig->identity)
9e5d6b55 210 dkim_signers = string_append(dkim_signers,
1cfe5c1c
JH
211 &dkim_signers_size,
212 &dkim_signers_ptr, 2, sig->identity, ":");
80a47a2c 213
1cfe5c1c 214 /* Process next signature */
80a47a2c
TK
215 }
216
1cfe5c1c
JH
217/* NULL-terminate and chop the last colon from the domain list */
218
219if (dkim_signers)
220 {
221 dkim_signers[dkim_signers_ptr] = '\0';
222 if (Ustrlen(dkim_signers) > 0)
223 dkim_signers[Ustrlen(dkim_signers) - 1] = '\0';
0d2b278d 224 }
80a47a2c
TK
225}
226
227
1cfe5c1c
JH
228void
229dkim_exim_acl_setup(uschar * id)
230{
231pdkim_signature * sig;
232uschar * cmp_val;
233
234
235dkim_cur_sig = NULL;
236dkim_cur_signer = id;
237
238if (dkim_disable_verify || !id || !dkim_verify_ctx)
239 return;
240
241/* Find signature to run ACL on */
242
243for (sig = dkim_signatures; sig; sig = sig->next)
244 if ( (cmp_val = Ustrchr(id, '@') != NULL ? US sig->identity : US sig->domain)
245 && strcmpic(cmp_val, id) == 0
246 )
247 {
248 dkim_cur_sig = sig;
249
250 /* The "dkim_domain" and "dkim_selector" expansion variables have
251 related globals, since they are used in the signing code too.
252 Instead of inventing separate names for verification, we set
253 them here. This is easy since a domain and selector is guaranteed
254 to be in a signature. The other dkim_* expansion items are
255 dynamically fetched from dkim_cur_sig at expansion time (see
256 function below). */
257
258 dkim_signing_domain = US sig->domain;
259 dkim_signing_selector = US sig->selector;
260 return;
80a47a2c 261 }
80a47a2c
TK
262}
263
264
1cfe5c1c
JH
265static uschar *
266dkim_exim_expand_defaults(int what)
267{
268switch (what)
269 {
270 case DKIM_ALGO: return US"";
271 case DKIM_BODYLENGTH: return US"9999999999999";
272 case DKIM_CANON_BODY: return US"";
273 case DKIM_CANON_HEADERS: return US"";
274 case DKIM_COPIEDHEADERS: return US"";
275 case DKIM_CREATED: return US"0";
276 case DKIM_EXPIRES: return US"9999999999999";
277 case DKIM_HEADERNAMES: return US"";
278 case DKIM_IDENTITY: return US"";
279 case DKIM_KEY_GRANULARITY: return US"*";
280 case DKIM_KEY_SRVTYPE: return US"*";
281 case DKIM_KEY_NOTES: return US"";
282 case DKIM_KEY_TESTING: return US"0";
283 case DKIM_NOSUBDOMAINS: return US"0";
284 case DKIM_VERIFY_STATUS: return US"none";
285 case DKIM_VERIFY_REASON: return US"";
286 default: return US"";
287 }
288}
80a47a2c 289
80a47a2c 290
1cfe5c1c
JH
291uschar *
292dkim_exim_expand_query(int what)
293{
294if (!dkim_verify_ctx || dkim_disable_verify || !dkim_cur_sig)
295 return dkim_exim_expand_defaults(what);
296
297switch (what)
298 {
299 case DKIM_ALGO:
300 switch (dkim_cur_sig->algo)
301 {
302 case PDKIM_ALGO_RSA_SHA1: return US"rsa-sha1";
303 case PDKIM_ALGO_RSA_SHA256:
304 default: return US"rsa-sha256";
da5dfc3a 305 }
1cfe5c1c
JH
306
307 case DKIM_BODYLENGTH:
308 return dkim_cur_sig->bodylength >= 0
309 ? string_sprintf(OFF_T_FMT, (LONGLONG_T) dkim_cur_sig->bodylength)
310 : dkim_exim_expand_defaults(what);
311
312 case DKIM_CANON_BODY:
313 switch (dkim_cur_sig->canon_body)
314 {
315 case PDKIM_CANON_RELAXED: return US"relaxed";
316 case PDKIM_CANON_SIMPLE:
317 default: return US"simple";
80a47a2c 318 }
1cfe5c1c
JH
319
320 case DKIM_CANON_HEADERS:
321 switch (dkim_cur_sig->canon_headers)
322 {
323 case PDKIM_CANON_RELAXED: return US"relaxed";
324 case PDKIM_CANON_SIMPLE:
325 default: return US"simple";
326 }
327
328 case DKIM_COPIEDHEADERS:
329 return dkim_cur_sig->copiedheaders
330 ? US dkim_cur_sig->copiedheaders : dkim_exim_expand_defaults(what);
331
332 case DKIM_CREATED:
333 return dkim_cur_sig->created > 0
334 ? string_sprintf("%llu", dkim_cur_sig->created)
335 : dkim_exim_expand_defaults(what);
336
337 case DKIM_EXPIRES:
338 return dkim_cur_sig->expires > 0
339 ? string_sprintf("%llu", dkim_cur_sig->expires)
340 : dkim_exim_expand_defaults(what);
341
342 case DKIM_HEADERNAMES:
343 return dkim_cur_sig->headernames
344 ? US dkim_cur_sig->headernames : dkim_exim_expand_defaults(what);
345
346 case DKIM_IDENTITY:
347 return dkim_cur_sig->identity
348 ? US dkim_cur_sig->identity : dkim_exim_expand_defaults(what);
349
350 case DKIM_KEY_GRANULARITY:
351 return dkim_cur_sig->pubkey
352 ? dkim_cur_sig->pubkey->granularity
353 ? US dkim_cur_sig->pubkey->granularity
354 : dkim_exim_expand_defaults(what)
355 : dkim_exim_expand_defaults(what);
356
357 case DKIM_KEY_SRVTYPE:
358 return dkim_cur_sig->pubkey
359 ? dkim_cur_sig->pubkey->srvtype
360 ? US dkim_cur_sig->pubkey->srvtype
361 : dkim_exim_expand_defaults(what)
362 : dkim_exim_expand_defaults(what);
363
364 case DKIM_KEY_NOTES:
365 return dkim_cur_sig->pubkey
366 ? dkim_cur_sig->pubkey->notes
367 ? US dkim_cur_sig->pubkey->notes
368 : dkim_exim_expand_defaults(what)
369 : dkim_exim_expand_defaults(what);
370
371 case DKIM_KEY_TESTING:
372 return dkim_cur_sig->pubkey
373 ? dkim_cur_sig->pubkey->testing
374 ? US"1"
375 : dkim_exim_expand_defaults(what)
376 : dkim_exim_expand_defaults(what);
377
378 case DKIM_NOSUBDOMAINS:
379 return dkim_cur_sig->pubkey
380 ? dkim_cur_sig->pubkey->no_subdomaining
381 ? US"1"
382 : dkim_exim_expand_defaults(what)
383 : dkim_exim_expand_defaults(what);
384
385 case DKIM_VERIFY_STATUS:
386 switch (dkim_cur_sig->verify_status)
387 {
388 case PDKIM_VERIFY_INVALID: return US"invalid";
389 case PDKIM_VERIFY_FAIL: return US"fail";
390 case PDKIM_VERIFY_PASS: return US"pass";
391 case PDKIM_VERIFY_NONE:
392 default: return US"none";
80a47a2c 393 }
80a47a2c 394
1cfe5c1c
JH
395 case DKIM_VERIFY_REASON:
396 switch (dkim_cur_sig->verify_ext_status)
397 {
398 case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
399 return US"pubkey_unavailable";
400 case PDKIM_VERIFY_INVALID_PUBKEY_PARSING: return US"pubkey_syntax";
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
JH
565
566 ctx = pdkim_init_sign(PDKIM_INPUT_SMTP,
567 (char *) dkim_signing_domain,
568 (char *) dkim_signing_selector,
569 (char *) dkim_private_key_expanded);
3b957582 570#ifdef PDKIM_DEBUG
1cfe5c1c 571 pdkim_set_debug_stream(ctx, debug_file);
3b957582 572#endif
1cfe5c1c
JH
573 pdkim_set_optional(ctx,
574 (char *) dkim_sign_headers_expanded,
575 NULL,
576 pdkim_canon,
577 pdkim_canon, -1, PDKIM_ALGO_RSA_SHA256, 0, 0);
578
579 lseek(dkim_fd, 0, SEEK_SET);
580
581 while ((sread = read(dkim_fd, &buf, 4096)) > 0)
582 if (pdkim_feed(ctx, buf, sread) != PDKIM_OK)
583 {
80a47a2c
TK
584 rc = NULL;
585 goto CLEANUP;
1cfe5c1c
JH
586 }
587
588 /* Handle failed read above. */
589 if (sread == -1)
590 {
591 debug_printf("DKIM: Error reading -K file.\n");
592 save_errno = errno;
593 rc = NULL;
594 goto CLEANUP;
80a47a2c 595 }
80a47a2c 596
1cfe5c1c
JH
597 if ((pdkim_rc = pdkim_feed_finish(ctx, &signature)) != PDKIM_OK)
598 {
599 log_write(0, LOG_MAIN|LOG_PANIC, "DKIM: signing failed (RC %d)", pdkim_rc);
600 rc = NULL;
601 goto CLEANUP;
a8e1eeba 602 }
80a47a2c 603
1cfe5c1c
JH
604 sigbuf = string_append(sigbuf, &sigsize, &sigptr, 2,
605 US signature->signature_header, US"\r\n");
80a47a2c 606
1cfe5c1c
JH
607 pdkim_free_ctx(ctx);
608 ctx = NULL;
80a47a2c 609 }
a8e1eeba 610
1cfe5c1c
JH
611if (sigbuf)
612 {
613 sigbuf[sigptr] = '\0';
614 rc = sigbuf;
615 }
616else
617 rc = US"";
618
619CLEANUP:
620if (ctx)
621 pdkim_free_ctx(ctx);
622store_pool = old_pool;
623errno = save_errno;
624return rc;
93f2d376 625}
80a47a2c
TK
626
627#endif