DKIM: Project coding standards
[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
1cfe5c1c
JH
234dkim_cur_sig = NULL;
235dkim_cur_signer = id;
236
237if (dkim_disable_verify || !id || !dkim_verify_ctx)
238 return;
239
240/* Find signature to run ACL on */
241
242for (sig = dkim_signatures; sig; sig = sig->next)
243 if ( (cmp_val = Ustrchr(id, '@') != NULL ? US sig->identity : US sig->domain)
244 && strcmpic(cmp_val, id) == 0
245 )
246 {
247 dkim_cur_sig = sig;
248
249 /* The "dkim_domain" and "dkim_selector" expansion variables have
250 related globals, since they are used in the signing code too.
251 Instead of inventing separate names for verification, we set
252 them here. This is easy since a domain and selector is guaranteed
253 to be in a signature. The other dkim_* expansion items are
254 dynamically fetched from dkim_cur_sig at expansion time (see
255 function below). */
256
257 dkim_signing_domain = US sig->domain;
258 dkim_signing_selector = US sig->selector;
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";
399 case PDKIM_VERIFY_INVALID_PUBKEY_PARSING: return US"pubkey_syntax";
400 case PDKIM_VERIFY_FAIL_BODY: return US"bodyhash_mismatch";
401 case PDKIM_VERIFY_FAIL_MESSAGE: return US"signature_incorrect";
402 }
80a47a2c 403
1cfe5c1c
JH
404 default:
405 return US"";
80a47a2c
TK
406 }
407}
408
409
55414b25 410uschar *
1cfe5c1c
JH
411dkim_exim_sign(int dkim_fd, uschar * dkim_private_key,
412 const uschar * dkim_domain, uschar * dkim_selector,
413 uschar * dkim_canon, uschar * dkim_sign_headers)
55414b25 414{
1cfe5c1c
JH
415int sep = 0;
416uschar *seen_items = NULL;
417int seen_items_size = 0;
418int seen_items_offset = 0;
419uschar itembuf[256];
420uschar *dkim_canon_expanded;
421uschar *dkim_sign_headers_expanded;
422uschar *dkim_private_key_expanded;
423pdkim_ctx *ctx = NULL;
424uschar *rc = NULL;
425uschar *sigbuf = NULL;
426int sigsize = 0;
427int sigptr = 0;
428pdkim_signature *signature;
429int pdkim_canon;
430int pdkim_rc;
431int sread;
432char buf[4096];
433int save_errno = 0;
434int old_pool = store_pool;
435
436store_pool = POOL_MAIN;
437
438if (!(dkim_domain = expand_cstring(dkim_domain)))
439 {
440 /* expansion error, do not send message. */
441 log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
442 "dkim_domain: %s", expand_string_message);
443 rc = NULL;
444 goto CLEANUP;
445 }
446
447/* Set $dkim_domain expansion variable to each unique domain in list. */
448
449while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep,
450 itembuf, sizeof(itembuf))))
451 {
452 if (!dkim_signing_domain || dkim_signing_domain[0] == '\0')
453 continue;
454
455 /* Only sign once for each domain, no matter how often it
456 appears in the expanded list. */
457
458 if (seen_items)
459 {
460 const uschar *seen_items_list = seen_items;
461 if (match_isinlist(dkim_signing_domain,
462 &seen_items_list, 0, NULL, NULL, MCL_STRING, TRUE,
463 NULL) == OK)
464 continue;
465
466 seen_items =
467 string_append(seen_items, &seen_items_size, &seen_items_offset, 1, ":");
468 }
469
470 seen_items =
471 string_append(seen_items, &seen_items_size, &seen_items_offset, 1,
472 dkim_signing_domain);
473 seen_items[seen_items_offset] = '\0';
474
475 /* Set up $dkim_selector expansion variable. */
476
477 if (!(dkim_signing_selector = expand_string(dkim_selector)))
478 {
479 log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
480 "dkim_selector: %s", expand_string_message);
481 rc = NULL;
482 goto CLEANUP;
483 }
484
485 /* Get canonicalization to use */
486
487 dkim_canon_expanded = dkim_canon ? expand_string(dkim_canon) : US"relaxed";
488 if (!dkim_canon_expanded)
489 {
80a47a2c 490 /* expansion error, do not send message. */
1cfe5c1c
JH
491 log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
492 "dkim_canon: %s", expand_string_message);
80a47a2c
TK
493 rc = NULL;
494 goto CLEANUP;
1cfe5c1c 495 }
80a47a2c 496
1cfe5c1c
JH
497 if (Ustrcmp(dkim_canon_expanded, "relaxed") == 0)
498 pdkim_canon = PDKIM_CANON_RELAXED;
499 else if (Ustrcmp(dkim_canon_expanded, "simple") == 0)
500 pdkim_canon = PDKIM_CANON_SIMPLE;
501 else
502 {
503 log_write(0, LOG_MAIN,
504 "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",
505 dkim_canon_expanded);
506 pdkim_canon = PDKIM_CANON_RELAXED;
a8e1eeba 507 }
1cfe5c1c
JH
508
509 dkim_sign_headers_expanded = NULL;
510 if (dkim_sign_headers)
511 if (!(dkim_sign_headers_expanded = expand_string(dkim_sign_headers)))
512 {
513 log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
514 "dkim_sign_headers: %s", expand_string_message);
a8e1eeba
MH
515 rc = NULL;
516 goto CLEANUP;
1cfe5c1c
JH
517 }
518 /* else pass NULL, which means default header list */
519
520 /* Get private key to use. */
521
522 if (!(dkim_private_key_expanded = expand_string(dkim_private_key)))
523 {
524 log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
525 "dkim_private_key: %s", expand_string_message);
526 rc = NULL;
527 goto CLEANUP;
a8e1eeba 528 }
80a47a2c 529
1cfe5c1c
JH
530 if ( Ustrlen(dkim_private_key_expanded) == 0
531 || Ustrcmp(dkim_private_key_expanded, "0") == 0
532 || Ustrcmp(dkim_private_key_expanded, "false") == 0
533 )
534 continue; /* don't sign, but no error */
535
536 if (dkim_private_key_expanded[0] == '/')
537 {
538 int privkey_fd = 0;
539
540 /* Looks like a filename, load the private key. */
541
542 memset(big_buffer, 0, big_buffer_size);
543 privkey_fd = open(CS dkim_private_key_expanded, O_RDONLY);
544 if (privkey_fd < 0)
545 {
546 log_write(0, LOG_MAIN | LOG_PANIC, "unable to open "
547 "private key file for reading: %s",
548 dkim_private_key_expanded);
80a47a2c
TK
549 rc = NULL;
550 goto CLEANUP;
db4d0902 551 }
80a47a2c 552
1cfe5c1c
JH
553 if (read(privkey_fd, big_buffer, big_buffer_size - 2) < 0)
554 {
555 log_write(0, LOG_MAIN|LOG_PANIC, "unable to read private key file: %s",
556 dkim_private_key_expanded);
cf745305
TK
557 rc = NULL;
558 goto CLEANUP;
1ac6b2e7 559 }
80a47a2c 560
1cfe5c1c
JH
561 (void) close(privkey_fd);
562 dkim_private_key_expanded = big_buffer;
a8e1eeba 563 }
1cfe5c1c
JH
564
565 ctx = pdkim_init_sign(PDKIM_INPUT_SMTP,
566 (char *) dkim_signing_domain,
567 (char *) dkim_signing_selector,
568 (char *) dkim_private_key_expanded);
3b957582 569#ifdef PDKIM_DEBUG
1cfe5c1c 570 pdkim_set_debug_stream(ctx, debug_file);
3b957582 571#endif
1cfe5c1c
JH
572 pdkim_set_optional(ctx,
573 (char *) dkim_sign_headers_expanded,
574 NULL,
575 pdkim_canon,
576 pdkim_canon, -1, PDKIM_ALGO_RSA_SHA256, 0, 0);
577
578 lseek(dkim_fd, 0, SEEK_SET);
579
580 while ((sread = read(dkim_fd, &buf, 4096)) > 0)
581 if (pdkim_feed(ctx, buf, sread) != PDKIM_OK)
582 {
80a47a2c
TK
583 rc = NULL;
584 goto CLEANUP;
1cfe5c1c
JH
585 }
586
587 /* Handle failed read above. */
588 if (sread == -1)
589 {
590 debug_printf("DKIM: Error reading -K file.\n");
591 save_errno = errno;
592 rc = NULL;
593 goto CLEANUP;
80a47a2c 594 }
80a47a2c 595
1cfe5c1c
JH
596 if ((pdkim_rc = pdkim_feed_finish(ctx, &signature)) != PDKIM_OK)
597 {
598 log_write(0, LOG_MAIN|LOG_PANIC, "DKIM: signing failed (RC %d)", pdkim_rc);
599 rc = NULL;
600 goto CLEANUP;
a8e1eeba 601 }
80a47a2c 602
1cfe5c1c
JH
603 sigbuf = string_append(sigbuf, &sigsize, &sigptr, 2,
604 US signature->signature_header, US"\r\n");
80a47a2c 605
1cfe5c1c
JH
606 pdkim_free_ctx(ctx);
607 ctx = NULL;
80a47a2c 608 }
a8e1eeba 609
1cfe5c1c
JH
610if (sigbuf)
611 {
612 sigbuf[sigptr] = '\0';
613 rc = sigbuf;
614 }
615else
616 rc = US"";
617
618CLEANUP:
619if (ctx)
620 pdkim_free_ctx(ctx);
621store_pool = old_pool;
622errno = save_errno;
623return rc;
93f2d376 624}
80a47a2c
TK
625
626#endif