FreeBSD: TLS: preload configuration items
[exim.git] / src / src / tls.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* Copyright (c) The Exim Maintainers 2020 */
7 /* See the file NOTICE for conditions of use and distribution. */
8
9 /* This module provides TLS (aka SSL) support for Exim. The code for OpenSSL is
10 based on a patch that was originally contributed by Steve Haslam. It was
11 adapted from stunnel, a GPL program by Michal Trojnara. The code for GNU TLS is
12 based on a patch contributed by Nikos Mavrogiannopoulos. Because these packages
13 are so very different, the functions for each are kept in separate files. The
14 relevant file is #included as required, after any any common functions.
15
16 No cryptographic code is included in Exim. All this module does is to call
17 functions from the OpenSSL or GNU TLS libraries. */
18
19
20 #include "exim.h"
21 #include "transports/smtp.h"
22
23 #if !defined(DISABLE_TLS) && !defined(USE_OPENSSL) && !defined(USE_GNUTLS)
24 # error One of USE_OPENSSL or USE_GNUTLS must be defined for a TLS build
25 #endif
26
27
28 #if defined(MACRO_PREDEF) && !defined(DISABLE_TLS)
29 # include "macro_predef.h"
30 # ifdef USE_GNUTLS
31 #  include "tls-gnu.c"
32 # else
33 #  include "tls-openssl.c"
34 # endif
35 #endif
36
37 #ifndef MACRO_PREDEF
38
39 static void tls_per_lib_daemon_init(void);
40 static void tls_per_lib_daemon_tick(void);
41 static void tls_server_creds_init(void);
42 static void tls_server_creds_invalidate(void);
43 static void tls_client_creds_init(transport_instance *, BOOL);
44 static void tls_client_creds_invalidate(transport_instance *);
45 static void tls_daemon_creds_reload(void);
46 static BOOL opt_set_and_noexpand(const uschar *);
47 static BOOL opt_unset_or_noexpand(const uschar *);
48
49
50
51 /* This module is compiled only when it is specifically requested in the
52 build-time configuration. However, some compilers don't like compiling empty
53 modules, so keep them happy with a dummy when skipping the rest. Make it
54 reference itself to stop picky compilers complaining that it is unused, and put
55 in a dummy argument to stop even pickier compilers complaining about infinite
56 loops. */
57
58 #ifdef DISABLE_TLS
59 static void dummy(int x) { dummy(x-1); }
60 #else   /* most of the rest of the file */
61
62 const exim_tlslib_state null_tls_preload = {0};
63
64 /* Static variables that are used for buffering data by both sets of
65 functions and the common functions below.
66
67 We're moving away from this; GnuTLS is already using a state, which
68 can switch, so we can do TLS callouts during ACLs. */
69
70 static const int ssl_xfer_buffer_size = 4096;
71 #ifdef USE_OPENSSL
72 static uschar *ssl_xfer_buffer = NULL;
73 static int ssl_xfer_buffer_lwm = 0;
74 static int ssl_xfer_buffer_hwm = 0;
75 static int ssl_xfer_eof = FALSE;
76 static BOOL ssl_xfer_error = FALSE;
77 #endif
78
79 #ifdef EXIM_HAVE_KEVENT
80 # define KEV_SIZE 16    /* Eight file,dir pairs */
81 static struct kevent kev[KEV_SIZE];
82 static int kev_used = 0;
83 #endif
84
85 /*************************************************
86 *       Expand string; give error on failure     *
87 *************************************************/
88
89 /* If expansion is forced to fail, set the result NULL and return TRUE.
90 Other failures return FALSE. For a server, an SMTP response is given.
91
92 Arguments:
93   s         the string to expand; if NULL just return TRUE
94   name      name of string being expanded (for error)
95   result    where to put the result
96
97 Returns:    TRUE if OK; result may still be NULL after forced failure
98 */
99
100 static BOOL
101 expand_check(const uschar *s, const uschar *name, uschar **result, uschar ** errstr)
102 {
103 if (!s)
104   *result = NULL;
105 else if (  !(*result = expand_string(US s)) /* need to clean up const more */
106         && !f.expand_string_forcedfail
107         )
108   {
109   *errstr = US"Internal error";
110   log_write(0, LOG_MAIN|LOG_PANIC, "expansion of %s failed: %s", name,
111     expand_string_message);
112   return FALSE;
113   }
114 return TRUE;
115 }
116
117
118 #if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
119 /* Add the directory for a filename to the inotify handle, creating that if
120 needed.  This is enough to see changes to files in that dir.
121 Return boolean success.
122
123 The word "system" fails, which is on the safe side as we don't know what
124 directory it implies nor if the TLS library handles a watch for us.
125
126 The string "system,cache" is recognised and explicitly accepted without
127 setting a watch.  This permits the system CA bundle to be cached even though
128 we have no way to tell when it gets modified by an update.
129 The call chain for OpenSSL uses a (undocumented) call into the library
130 to discover the actual file.  We don't know what GnuTLS uses.
131
132 A full set of caching including the CAs takes 35ms output off of the
133 server tls_init() (GnuTLS, Fedora 32, 2018-class x86_64 laptop hardware).
134 */
135 static BOOL
136 tls_set_one_watch(const uschar * filename)
137 # ifdef EXIM_HAVE_INOTIFY
138 {
139 uschar * s;
140
141 if (Ustrcmp(filename, "system,cache") == 0) return TRUE;
142
143 if (!(s = Ustrrchr(filename, '/'))) return FALSE;
144 s = string_copyn(filename, s - filename);       /* mem released by tls_set_watch */
145 DEBUG(D_tls) debug_printf("watch dir '%s'\n", s);
146
147 /*XXX unclear what effect symlinked files will have for inotify */
148
149 if (inotify_add_watch(tls_watch_fd, CCS s,
150       IN_ONESHOT | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF
151       | IN_MOVED_FROM | IN_MOVED_TO | IN_MOVE_SELF) >= 0)
152   return TRUE;
153 DEBUG(D_tls) debug_printf("add_watch: %s\n", strerror(errno));
154 return FALSE;
155 }
156 # endif
157 # ifdef EXIM_HAVE_KEVENT
158 {
159 uschar * s;
160 int fd1, fd2, i, cnt = 0;
161 struct stat sb;
162
163 if (Ustrcmp(filename, "system,cache") == 0) return TRUE;
164
165 for (;;)
166   {
167   if (!(s = Ustrrchr(filename, '/'))) return FALSE;
168   if ((lstat(filename, &sb)) < 0) return FALSE;
169   if (kev_used > KEV_SIZE-2) return FALSE;
170
171   /* The dir open will fail if there is a symlink on the path. Fine; it's too
172   much effort to handle all possible cases; just refuse the preload. */
173
174   if ((fd2 = open(s, O_RDONLY | O_NOFOLLOW)) < 0) return FALSE;
175
176   if (!S_ISLNK(sb.st_mode))
177     {
178     if ((fd1 = open(filename, O_RDONLY | O_NOFOLLOW)) < 0) return FALSE;
179     DEBUG(D_tls) debug_printf("watch file '%s'\n", filename);
180     EV_SET(&kev[++kev_used],
181         (uintptr_t)fd1,
182         EVFILT_VNODE,
183         EV_ADD | EV_ENABLE | EV_ONESHOT,
184         NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND
185         | NOTE_ATTRIB | NOTE_RENAME | NOTE_REVOKE,
186         0,
187         NULL);
188     cnt++;
189     }
190   DEBUG(D_tls) debug_printf("watch dir  '%s'\n", s);
191   EV_SET(&kev[++kev_used],
192         (uintptr_t)fd2,
193         EVFILT_VNODE,
194         EV_ADD | EV_ENABLE | EV_ONESHOT,
195         NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND
196         | NOTE_ATTRIB | NOTE_RENAME | NOTE_REVOKE,
197         0,
198         NULL);
199   cnt++;
200
201   if (!(S_ISLNK(sb.st_mode))) break;
202
203   s = store_get(1024, FALSE);
204   if ((i = readlink(filename, s, 1024)) < 0) return FALSE;
205   filename = s;
206   *(s += i) = '\0';
207   store_release_above(s+1);
208   }
209
210 if (kevent(tls_watch_fd, &kev[kev_used-cnt], cnt, NULL, 0, NULL) >= 0)
211   return TRUE;
212 DEBUG(D_tls) debug_printf("add_watch: %d, %s\n", strerror(errno));
213 return FALSE;
214 }
215 # endif /*EXIM_HAVE_KEVENT*/
216
217
218 /* Create an inotify facility if needed.
219 Then set watches on the dir containing the given file or (optionally)
220 list of files.  Return boolean success. */
221
222 static BOOL
223 tls_set_watch(const uschar * filename, BOOL list)
224 {
225 rmark r;
226 BOOL rc = FALSE;
227
228 if (!filename || !*filename) return TRUE;
229 if (Ustrncmp(filename, "system", 6) == 0) return TRUE;
230
231 DEBUG(D_tls) debug_printf("tls_set_watch: '%s'\n", filename);
232
233 if (  tls_watch_fd < 0
234 # ifdef EXIM_HAVE_INOTIFY
235    && (tls_watch_fd = inotify_init1(O_CLOEXEC)) < 0
236 # endif
237 # ifdef EXIM_HAVE_KEVENT
238    && (tls_watch_fd = kqueue()) < 0
239 # endif
240    )
241     {
242     DEBUG(D_tls) debug_printf("inotify_init: %s\n", strerror(errno));
243     return FALSE;
244     }
245
246 r = store_mark();
247
248 if (list)
249   {
250   int sep = 0;
251   for (uschar * s; s = string_nextinlist(&filename, &sep, NULL, 0); )
252     if (!(rc = tls_set_one_watch(s))) break;
253   }
254 else
255   rc = tls_set_one_watch(filename);
256
257 store_reset(r);
258 if (!rc) DEBUG(D_tls) debug_printf("tls_set_watch() fail on '%s': %s\n", filename, strerror(errno));
259 return rc;
260 }
261
262
263 void
264 tls_watch_discard_event(int fd)
265 {
266 #ifdef EXIM_HAVE_INOTIFY
267 (void) read(fd, big_buffer, big_buffer_size);
268 #endif
269 #ifdef EXIM_HAVE_KEVENT
270 struct kevent kev;
271 struct timespec t = {0};
272 (void) kevent(fd, NULL, 0, &kev, 1, &t);
273 #endif
274 }
275
276 /* Called, after a delay for multiple file ops to get done, from
277 the daemon when any of the watches added (above) fire.
278
279 Dump the set of watches and arrange to reload cached creds (which
280 will set up new watches). */
281
282 static void
283 tls_watch_triggered(void)
284 {
285 DEBUG(D_tls) debug_printf("watch triggered\n");
286
287 tls_daemon_creds_reload();
288 }
289 #endif  /*EXIM_HAVE_INOTIFY*/
290
291
292 void
293 tls_client_creds_reload(BOOL watch)
294 {
295 for(transport_instance * t = transports; t; t = t->next)
296   if (Ustrcmp(t->driver_name, "smtp") == 0)
297     {
298     tls_client_creds_invalidate(t);
299     tls_client_creds_init(t, watch);
300     }
301 }
302
303
304 void
305 tls_watch_invalidate(void)
306 {
307 if (tls_watch_fd < 0) return;
308
309 #ifdef EXIM_HAVE_KEVENT
310 /* Close the files we had open for kevent */
311 for (int fd, i = 0; i < kev_used; i++)
312   {
313   (void) close((int) kev[i].ident);
314   kev[i].ident = (uintptr_t)-1;
315   }
316 kev_used = 0;
317 #endif
318
319 close(tls_watch_fd);
320 tls_watch_fd = -1;
321 }
322
323
324 static void
325 tls_daemon_creds_reload(void)
326 {
327 #ifdef EXIM_HAVE_KEVENT
328 tls_watch_invalidate();
329 #endif
330
331 tls_server_creds_invalidate();
332 tls_server_creds_init();
333
334 tls_client_creds_reload(TRUE);
335 }
336
337
338 /* Utility predicates for use by the per-library code */
339 static BOOL
340 opt_set_and_noexpand(const uschar * opt)
341 { return opt && *opt && Ustrchr(opt, '$') == NULL; }
342
343 static BOOL
344 opt_unset_or_noexpand(const uschar * opt)
345 { return !opt || Ustrchr(opt, '$') == NULL; }
346
347
348
349 /* Called every time round the daemon loop */
350
351 void
352 tls_daemon_tick(void)
353 {
354 tls_per_lib_daemon_tick();
355 #if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
356 if (tls_watch_trigger_time && time(NULL) >= tls_watch_trigger_time + 5)
357   {
358   tls_watch_trigger_time = 0;
359   tls_watch_triggered();
360   }
361 #endif
362 }
363
364 /* Called once at daemon startup */
365
366 void
367 tls_daemon_init(void)
368 {
369 tls_per_lib_daemon_init();
370 }
371
372
373 /*************************************************
374 *        Timezone environment flipping           *
375 *************************************************/
376
377 static uschar *
378 to_tz(uschar * tz)
379 {
380 uschar * old = US getenv("TZ");
381 (void) setenv("TZ", CCS tz, 1);
382 tzset();
383 return old;
384 }
385
386 static void
387 restore_tz(uschar * tz)
388 {
389 if (tz)
390   (void) setenv("TZ", CCS tz, 1);
391 else
392   (void) os_unsetenv(US"TZ");
393 tzset();
394 }
395
396 /*************************************************
397 *        Many functions are package-specific     *
398 *************************************************/
399
400 #ifdef USE_GNUTLS
401 # include "tls-gnu.c"
402 # include "tlscert-gnu.c"
403 # define ssl_xfer_buffer (state_server.xfer_buffer)
404 # define ssl_xfer_buffer_lwm (state_server.xfer_buffer_lwm)
405 # define ssl_xfer_buffer_hwm (state_server.xfer_buffer_hwm)
406 # define ssl_xfer_eof (state_server.xfer_eof)
407 # define ssl_xfer_error (state_server.xfer_error)
408 #endif
409
410 #ifdef USE_OPENSSL
411 # include "tls-openssl.c"
412 # include "tlscert-openssl.c"
413 #endif
414
415
416
417 /*************************************************
418 *           TLS version of ungetc                *
419 *************************************************/
420
421 /* Puts a character back in the input buffer. Only ever
422 called once.
423 Only used by the server-side TLS.
424
425 Arguments:
426   ch           the character
427
428 Returns:       the character
429 */
430
431 int
432 tls_ungetc(int ch)
433 {
434 ssl_xfer_buffer[--ssl_xfer_buffer_lwm] = ch;
435 return ch;
436 }
437
438
439
440 /*************************************************
441 *           TLS version of feof                  *
442 *************************************************/
443
444 /* Tests for a previous EOF
445 Only used by the server-side TLS.
446
447 Arguments:     none
448 Returns:       non-zero if the eof flag is set
449 */
450
451 int
452 tls_feof(void)
453 {
454 return (int)ssl_xfer_eof;
455 }
456
457
458
459 /*************************************************
460 *              TLS version of ferror             *
461 *************************************************/
462
463 /* Tests for a previous read error, and returns with errno
464 restored to what it was when the error was detected.
465 Only used by the server-side TLS.
466
467 >>>>> Hmm. Errno not handled yet. Where do we get it from?  >>>>>
468
469 Arguments:     none
470 Returns:       non-zero if the error flag is set
471 */
472
473 int
474 tls_ferror(void)
475 {
476 return (int)ssl_xfer_error;
477 }
478
479
480 /*************************************************
481 *           TLS version of smtp_buffered         *
482 *************************************************/
483
484 /* Tests for unused chars in the TLS input buffer.
485 Only used by the server-side TLS.
486
487 Arguments:     none
488 Returns:       TRUE/FALSE
489 */
490
491 BOOL
492 tls_smtp_buffered(void)
493 {
494 return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm;
495 }
496
497
498 #endif  /*DISABLE_TLS*/
499
500 void
501 tls_modify_variables(tls_support * dest_tsp)
502 {
503 modify_variable(US"tls_bits",                 &dest_tsp->bits);
504 modify_variable(US"tls_certificate_verified", &dest_tsp->certificate_verified);
505 modify_variable(US"tls_cipher",               &dest_tsp->cipher);
506 modify_variable(US"tls_peerdn",               &dest_tsp->peerdn);
507 #ifdef USE_OPENSSL
508 modify_variable(US"tls_sni",                  &dest_tsp->sni);
509 #endif
510 }
511
512
513 #ifndef DISABLE_TLS
514 /************************************************
515 *       TLS certificate name operations         *
516 ************************************************/
517
518 /* Convert an rfc4514 DN to an exim comma-sep list.
519 Backslashed commas need to be replaced by doublecomma
520 for Exim's list quoting.  We modify the given string
521 inplace.
522 */
523
524 static void
525 dn_to_list(uschar * dn)
526 {
527 for (uschar * cp = dn; *cp; cp++)
528   if (cp[0] == '\\' && cp[1] == ',')
529     *cp++ = ',';
530 }
531
532
533 /* Extract fields of a given type from an RFC4514-
534 format Distinguished Name.  Return an Exim list.
535 NOTE: We modify the supplied dn string during operation.
536
537 Arguments:
538         dn      Distinguished Name string
539         mod     list containing optional output list-sep and
540                 field selector match, comma-separated
541 Return:
542         allocated string with list of matching fields,
543         field type stripped
544 */
545
546 uschar *
547 tls_field_from_dn(uschar * dn, const uschar * mod)
548 {
549 int insep = ',';
550 uschar outsep = '\n';
551 uschar * ele;
552 uschar * match = NULL;
553 int len;
554 gstring * list = NULL;
555
556 while ((ele = string_nextinlist(&mod, &insep, NULL, 0)))
557   if (ele[0] != '>')
558     match = ele;        /* field tag to match */
559   else if (ele[1])
560     outsep = ele[1];    /* nondefault output separator */
561
562 dn_to_list(dn);
563 insep = ',';
564 len = match ? Ustrlen(match) : -1;
565 while ((ele = string_nextinlist(CUSS &dn, &insep, NULL, 0)))
566   if (  !match
567      || Ustrncmp(ele, match, len) == 0 && ele[len] == '='
568      )
569     list = string_append_listele(list, outsep, ele+len+1);
570 return string_from_gstring(list);
571 }
572
573
574 /* Compare a domain name with a possibly-wildcarded name. Wildcards
575 are restricted to a single one, as the first element of patterns
576 having at least three dot-separated elements.  Case-independent.
577 Return TRUE for a match
578 */
579 static BOOL
580 is_name_match(const uschar * name, const uschar * pat)
581 {
582 uschar * cp;
583 return *pat == '*'              /* possible wildcard match */
584   ?    *++pat == '.'            /* starts star, dot              */
585     && !Ustrchr(++pat, '*')     /* has no more stars             */
586     && Ustrchr(pat, '.')        /* and has another dot.          */
587     && (cp = Ustrchr(name, '.'))/* The name has at least one dot */
588     && strcmpic(++cp, pat) == 0 /* and we only compare after it. */
589   :    !Ustrchr(pat+1, '*')
590     && strcmpic(name, pat) == 0;
591 }
592
593 /* Compare a list of names with the dnsname elements
594 of the Subject Alternate Name, if any, and the
595 Subject otherwise.
596
597 Arguments:
598         namelist names to compare
599         cert     certificate
600
601 Returns:
602         TRUE/FALSE
603 */
604
605 BOOL
606 tls_is_name_for_cert(const uschar * namelist, void * cert)
607 {
608 uschar * altnames = tls_cert_subject_altname(cert, US"dns");
609 uschar * subjdn;
610 uschar * certname;
611 int cmp_sep = 0;
612 uschar * cmpname;
613
614 if ((altnames = tls_cert_subject_altname(cert, US"dns")))
615   {
616   int alt_sep = '\n';
617   while ((cmpname = string_nextinlist(&namelist, &cmp_sep, NULL, 0)))
618     {
619     const uschar * an = altnames;
620     while ((certname = string_nextinlist(&an, &alt_sep, NULL, 0)))
621       if (is_name_match(cmpname, certname))
622         return TRUE;
623     }
624   }
625
626 else if ((subjdn = tls_cert_subject(cert, NULL)))
627   {
628   int sn_sep = ',';
629
630   dn_to_list(subjdn);
631   while ((cmpname = string_nextinlist(&namelist, &cmp_sep, NULL, 0)))
632     {
633     const uschar * sn = subjdn;
634     while ((certname = string_nextinlist(&sn, &sn_sep, NULL, 0)))
635       if (  *certname++ == 'C'
636          && *certname++ == 'N'
637          && *certname++ == '='
638          && is_name_match(cmpname, certname)
639          )
640         return TRUE;
641     }
642   }
643 return FALSE;
644 }
645
646
647 /* Environment cleanup: The GnuTLS library uses SSLKEYLOGFILE in the environment
648 and writes a file by that name.  Our OpenSSL code does the same, using keying
649 info from the library API.
650 The GnuTLS support only works if exim is run by root, not taking advantage of
651 the setuid bit.
652 You can use either the external environment (modulo the keep_environment config)
653 or the add_environment config option for SSLKEYLOGFILE; the latter takes
654 precedence.
655
656 If the path is absolute, require it starts with the spooldir; otherwise delete
657 the env variable.  If relative, prefix the spooldir.
658 */
659 void
660 tls_clean_env(void)
661 {
662 uschar * path = US getenv("SSLKEYLOGFILE");
663 if (path)
664   if (!*path)
665     unsetenv("SSLKEYLOGFILE");
666   else if (*path != '/')
667     {
668     DEBUG(D_tls)
669       debug_printf("prepending spooldir to  env SSLKEYLOGFILE\n");
670     setenv("SSLKEYLOGFILE", CCS string_sprintf("%s/%s", spool_directory, path), 1);
671     }
672   else if (Ustrncmp(path, spool_directory, Ustrlen(spool_directory)) != 0)
673     {
674     DEBUG(D_tls)
675       debug_printf("removing env SSLKEYLOGFILE=%s: not under spooldir\n", path);
676     unsetenv("SSLKEYLOGFILE");
677     }
678 }
679
680 /*************************************************
681 *       Drop privs for checking TLS config      *
682 *************************************************/
683
684 /* We want to validate TLS options during readconf, but do not want to be
685 root when we call into the TLS library, in case of library linkage errors
686 which cause segfaults; before this check, those were always done as the Exim
687 runtime user and it makes sense to continue with that.
688
689 Assumes:  tls_require_ciphers has been set, if it will be
690           exim_user has been set, if it will be
691           exim_group has been set, if it will be
692
693 Returns:  bool for "okay"; false will cause caller to immediately exit.
694 */
695
696 BOOL
697 tls_dropprivs_validate_require_cipher(BOOL nowarn)
698 {
699 const uschar *errmsg;
700 pid_t pid;
701 int rc, status;
702 void (*oldsignal)(int);
703
704 /* If TLS will never be used, no point checking ciphers */
705
706 if (  !tls_advertise_hosts
707    || !*tls_advertise_hosts
708    || Ustrcmp(tls_advertise_hosts, ":") == 0
709    )
710   return TRUE;
711 else if (!nowarn && !tls_certificate)
712   log_write(0, LOG_MAIN,
713     "Warning: No server certificate defined; will use a selfsigned one.\n"
714     " Suggested action: either install a certificate or change tls_advertise_hosts option");
715
716 oldsignal = signal(SIGCHLD, SIG_DFL);
717
718 fflush(NULL);
719 if ((pid = exim_fork(US"cipher-validate")) < 0)
720   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "fork failed for TLS check");
721
722 if (pid == 0)
723   {
724   /* in some modes, will have dropped privilege already */
725   if (!geteuid())
726     exim_setugid(exim_uid, exim_gid, FALSE,
727         US"calling tls_validate_require_cipher");
728
729   if ((errmsg = tls_validate_require_cipher()))
730     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
731         "tls_require_ciphers invalid: %s", errmsg);
732   fflush(NULL);
733   exim_underbar_exit(EXIT_SUCCESS);
734   }
735
736 do {
737   rc = waitpid(pid, &status, 0);
738 } while (rc < 0 && errno == EINTR);
739
740 DEBUG(D_tls)
741   debug_printf("tls_validate_require_cipher child %d ended: status=0x%x\n",
742       (int)pid, status);
743
744 signal(SIGCHLD, oldsignal);
745
746 return status == 0;
747 }
748
749
750
751
752 #endif  /*!DISABLE_TLS*/
753 #endif  /*!MACRO_PREDEF*/
754
755 /* vi: aw ai sw=2
756 */
757 /* End of tls.c */