Remove dashes from all options F-Ticks.* for consistency.
[libradsec.git] / tlscommon.c
1 /*
2  * Copyright (C) 2006-2009 Stig Venaas <venaas@uninett.no>
3  * Copyright (C) 2010 NORDUnet A/S
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  */
9
10 #if defined(RADPROT_TLS) || defined(RADPROT_DTLS)
11 #include <signal.h>
12 #include <sys/socket.h>
13 #include <netinet/in.h>
14 #include <netdb.h>
15 #include <string.h>
16 #include <unistd.h>
17 #include <limits.h>
18 #ifdef SYS_SOLARIS9
19 #include <fcntl.h>
20 #endif
21 #include <sys/time.h>
22 #include <sys/types.h>
23 #include <sys/select.h>
24 #include <ctype.h>
25 #include <sys/wait.h>
26 #include <arpa/inet.h>
27 #include <regex.h>
28 #include <libgen.h>
29 #include <pthread.h>
30 #include <openssl/ssl.h>
31 #include <openssl/rand.h>
32 #include <openssl/err.h>
33 #include <openssl/md5.h>
34 #include <openssl/x509v3.h>
35 #include "debug.h"
36 #include "list.h"
37 #include "hash.h"
38 #include "util.h"
39 #include "hostport.h"
40 #include "radsecproxy.h"
41
42 static struct hash *tlsconfs = NULL;
43
44 static int pem_passwd_cb(char *buf, int size, int rwflag, void *userdata) {
45     int pwdlen = strlen(userdata);
46     if (rwflag != 0 || pwdlen > size) /* not for decryption or too large */
47         return 0;
48     memcpy(buf, userdata, pwdlen);
49     return pwdlen;
50 }
51
52 static int verify_cb(int ok, X509_STORE_CTX *ctx) {
53     char *buf = NULL;
54     X509 *err_cert;
55     int err, depth;
56
57     err_cert = X509_STORE_CTX_get_current_cert(ctx);
58     err = X509_STORE_CTX_get_error(ctx);
59     depth = X509_STORE_CTX_get_error_depth(ctx);
60
61     if (depth > MAX_CERT_DEPTH) {
62         ok = 0;
63         err = X509_V_ERR_CERT_CHAIN_TOO_LONG;
64         X509_STORE_CTX_set_error(ctx, err);
65     }
66
67     if (!ok) {
68         if (err_cert)
69             buf = X509_NAME_oneline(X509_get_subject_name(err_cert), NULL, 0);
70         debug(DBG_WARN, "verify error: num=%d:%s:depth=%d:%s", err, X509_verify_cert_error_string(err), depth, buf ? buf : "");
71         free(buf);
72         buf = NULL;
73
74         switch (err) {
75         case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
76             if (err_cert) {
77                 buf = X509_NAME_oneline(X509_get_issuer_name(err_cert), NULL, 0);
78                 if (buf) {
79                     debug(DBG_WARN, "\tIssuer=%s", buf);
80                     free(buf);
81                     buf = NULL;
82                 }
83             }
84             break;
85         case X509_V_ERR_CERT_NOT_YET_VALID:
86         case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
87             debug(DBG_WARN, "\tCertificate not yet valid");
88             break;
89         case X509_V_ERR_CERT_HAS_EXPIRED:
90             debug(DBG_WARN, "Certificate has expired");
91             break;
92         case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
93             debug(DBG_WARN, "Certificate no longer valid (after notAfter)");
94             break;
95         case X509_V_ERR_NO_EXPLICIT_POLICY:
96             debug(DBG_WARN, "No Explicit Certificate Policy");
97             break;
98         }
99     }
100 #ifdef DEBUG
101     printf("certificate verify returns %d\n", ok);
102 #endif
103     return ok;
104 }
105
106 #ifdef DEBUG
107 static void ssl_info_callback(const SSL *ssl, int where, int ret) {
108     const char *s;
109     int w;
110
111     w = where & ~SSL_ST_MASK;
112
113     if (w & SSL_ST_CONNECT)
114         s = "SSL_connect";
115     else if (w & SSL_ST_ACCEPT)
116         s = "SSL_accept";
117     else
118         s = "undefined";
119
120     if (where & SSL_CB_LOOP)
121         debug(DBG_DBG, "%s:%s\n", s, SSL_state_string_long(ssl));
122     else if (where & SSL_CB_ALERT) {
123         s = (where & SSL_CB_READ) ? "read" : "write";
124         debug(DBG_DBG, "SSL3 alert %s:%s:%s\n", s, SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret));
125     }
126     else if (where & SSL_CB_EXIT) {
127         if (ret == 0)
128             debug(DBG_DBG, "%s:failed in %s\n", s, SSL_state_string_long(ssl));
129         else if (ret < 0)
130             debug(DBG_DBG, "%s:error in %s\n", s, SSL_state_string_long(ssl));
131     }
132 }
133 #endif
134
135 static X509_VERIFY_PARAM *createverifyparams(char **poids) {
136     X509_VERIFY_PARAM *pm;
137     ASN1_OBJECT *pobject;
138     int i;
139
140     pm = X509_VERIFY_PARAM_new();
141     if (!pm)
142         return NULL;
143
144     for (i = 0; poids[i]; i++) {
145         pobject = OBJ_txt2obj(poids[i], 0);
146         if (!pobject) {
147             X509_VERIFY_PARAM_free(pm);
148             return NULL;
149         }
150         X509_VERIFY_PARAM_add0_policy(pm, pobject);
151     }
152
153     X509_VERIFY_PARAM_set_flags(pm, X509_V_FLAG_POLICY_CHECK | X509_V_FLAG_EXPLICIT_POLICY);
154     return pm;
155 }
156
157 static int tlsaddcacrl(SSL_CTX *ctx, struct tls *conf) {
158     STACK_OF(X509_NAME) *calist;
159     X509_STORE *x509_s;
160     unsigned long error;
161
162     if (!SSL_CTX_load_verify_locations(ctx, conf->cacertfile, conf->cacertpath)) {
163         while ((error = ERR_get_error()))
164             debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL));
165         debug(DBG_ERR, "tlsaddcacrl: Error updating TLS context %s", conf->name);
166         return 0;
167     }
168
169     calist = conf->cacertfile ? SSL_load_client_CA_file(conf->cacertfile) : NULL;
170     if (!conf->cacertfile || calist) {
171         if (conf->cacertpath) {
172             if (!calist)
173                 calist = sk_X509_NAME_new_null();
174             if (!SSL_add_dir_cert_subjects_to_stack(calist, conf->cacertpath)) {
175                 sk_X509_NAME_free(calist);
176                 calist = NULL;
177             }
178         }
179     }
180     if (!calist) {
181         while ((error = ERR_get_error()))
182             debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL));
183         debug(DBG_ERR, "tlsaddcacrl: Error adding CA subjects in TLS context %s", conf->name);
184         return 0;
185     }
186     ERR_clear_error(); /* add_dir_cert_subj returns errors on success */
187     SSL_CTX_set_client_CA_list(ctx, calist);
188
189     SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb);
190     SSL_CTX_set_verify_depth(ctx, MAX_CERT_DEPTH + 1);
191
192     if (conf->crlcheck || conf->vpm) {
193         x509_s = SSL_CTX_get_cert_store(ctx);
194         if (conf->crlcheck)
195             X509_STORE_set_flags(x509_s, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
196         if (conf->vpm)
197             X509_STORE_set1_param(x509_s, conf->vpm);
198     }
199
200     debug(DBG_DBG, "tlsaddcacrl: updated TLS context %s", conf->name);
201     return 1;
202 }
203
204 static SSL_CTX *tlscreatectx(uint8_t type, struct tls *conf) {
205     SSL_CTX *ctx = NULL;
206     unsigned long error;
207     long sslversion = SSLeay();
208
209     switch (type) {
210 #ifdef RADPROT_TLS
211     case RAD_TLS:
212         ctx = SSL_CTX_new(TLSv1_method());
213 #ifdef DEBUG
214         SSL_CTX_set_info_callback(ctx, ssl_info_callback);
215 #endif
216         break;
217 #endif
218 #ifdef RADPROT_DTLS
219     case RAD_DTLS:
220         ctx = SSL_CTX_new(DTLSv1_method());
221 #ifdef DEBUG
222         SSL_CTX_set_info_callback(ctx, ssl_info_callback);
223 #endif
224         SSL_CTX_set_read_ahead(ctx, 1);
225         break;
226 #endif
227     }
228     if (!ctx) {
229         debug(DBG_ERR, "tlscreatectx: Error initialising SSL/TLS in TLS context %s", conf->name);
230         return NULL;
231     }
232
233     if (sslversion < 0x00908100L ||
234         (sslversion >= 0x10000000L && sslversion < 0x10000020L)) {
235         debug(DBG_WARN, "%s: %s seems to be of a version with a "
236               "certain security critical bug (fixed in OpenSSL 0.9.8p and "
237               "1.0.0b).  Disabling OpenSSL session caching for context %p.",
238               __func__, SSLeay_version(SSLEAY_VERSION), ctx);
239         SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
240     }
241
242     if (conf->certkeypwd) {
243         SSL_CTX_set_default_passwd_cb_userdata(ctx, conf->certkeypwd);
244         SSL_CTX_set_default_passwd_cb(ctx, pem_passwd_cb);
245     }
246     if (!SSL_CTX_use_certificate_chain_file(ctx, conf->certfile) ||
247         !SSL_CTX_use_PrivateKey_file(ctx, conf->certkeyfile, SSL_FILETYPE_PEM) ||
248         !SSL_CTX_check_private_key(ctx)) {
249         while ((error = ERR_get_error()))
250             debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL));
251         debug(DBG_ERR, "tlscreatectx: Error initialising SSL/TLS in TLS context %s", conf->name);
252         SSL_CTX_free(ctx);
253         return NULL;
254     }
255
256     if (conf->policyoids) {
257         if (!conf->vpm) {
258             conf->vpm = createverifyparams(conf->policyoids);
259             if (!conf->vpm) {
260                 debug(DBG_ERR, "tlscreatectx: Failed to add policyOIDs in TLS context %s", conf->name);
261                 SSL_CTX_free(ctx);
262                 return NULL;
263             }
264         }
265     }
266
267     if (!tlsaddcacrl(ctx, conf)) {
268         if (conf->vpm) {
269             X509_VERIFY_PARAM_free(conf->vpm);
270             conf->vpm = NULL;
271         }
272         SSL_CTX_free(ctx);
273         return NULL;
274     }
275
276     debug(DBG_DBG, "tlscreatectx: created TLS context %s", conf->name);
277     return ctx;
278 }
279
280 struct tls *tlsgettls(char *alt1, char *alt2) {
281     struct tls *t;
282
283     t = hash_read(tlsconfs, alt1, strlen(alt1));
284     if (!t)
285         t = hash_read(tlsconfs, alt2, strlen(alt2));
286     return t;
287 }
288
289 SSL_CTX *tlsgetctx(uint8_t type, struct tls *t) {
290     struct timeval now;
291
292     if (!t)
293         return NULL;
294     gettimeofday(&now, NULL);
295
296     switch (type) {
297 #ifdef RADPROT_TLS
298     case RAD_TLS:
299         if (t->tlsexpiry && t->tlsctx) {
300             if (t->tlsexpiry < now.tv_sec) {
301                 t->tlsexpiry = now.tv_sec + t->cacheexpiry;
302                 tlsaddcacrl(t->tlsctx, t);
303             }
304         }
305         if (!t->tlsctx) {
306             t->tlsctx = tlscreatectx(RAD_TLS, t);
307             if (t->cacheexpiry)
308                 t->tlsexpiry = now.tv_sec + t->cacheexpiry;
309         }
310         return t->tlsctx;
311 #endif
312 #ifdef RADPROT_DTLS
313     case RAD_DTLS:
314         if (t->dtlsexpiry && t->dtlsctx) {
315             if (t->dtlsexpiry < now.tv_sec) {
316                 t->dtlsexpiry = now.tv_sec + t->cacheexpiry;
317                 tlsaddcacrl(t->dtlsctx, t);
318             }
319         }
320         if (!t->dtlsctx) {
321             t->dtlsctx = tlscreatectx(RAD_DTLS, t);
322             if (t->cacheexpiry)
323                 t->dtlsexpiry = now.tv_sec + t->cacheexpiry;
324         }
325         return t->dtlsctx;
326 #endif
327     }
328     return NULL;
329 }
330
331 X509 *verifytlscert(SSL *ssl) {
332     X509 *cert;
333     unsigned long error;
334
335     if (SSL_get_verify_result(ssl) != X509_V_OK) {
336         debug(DBG_ERR, "verifytlscert: basic validation failed");
337         while ((error = ERR_get_error()))
338             debug(DBG_ERR, "verifytlscert: TLS: %s", ERR_error_string(error, NULL));
339         return NULL;
340     }
341
342     cert = SSL_get_peer_certificate(ssl);
343     if (!cert)
344         debug(DBG_ERR, "verifytlscert: failed to obtain certificate");
345     return cert;
346 }
347
348 static int subjectaltnameaddr(X509 *cert, int family, struct in6_addr *addr) {
349     int loc, i, l, n, r = 0;
350     char *v;
351     X509_EXTENSION *ex;
352     STACK_OF(GENERAL_NAME) *alt;
353     GENERAL_NAME *gn;
354
355     debug(DBG_DBG, "subjectaltnameaddr");
356
357     loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1);
358     if (loc < 0)
359         return r;
360
361     ex = X509_get_ext(cert, loc);
362     alt = X509V3_EXT_d2i(ex);
363     if (!alt)
364         return r;
365
366     n = sk_GENERAL_NAME_num(alt);
367     for (i = 0; i < n; i++) {
368         gn = sk_GENERAL_NAME_value(alt, i);
369         if (gn->type != GEN_IPADD)
370             continue;
371         r = -1;
372         v = (char *)ASN1_STRING_data(gn->d.ia5);
373         l = ASN1_STRING_length(gn->d.ia5);
374         if (((family == AF_INET && l == sizeof(struct in_addr)) || (family == AF_INET6 && l == sizeof(struct in6_addr)))
375             && !memcmp(v, &addr, l)) {
376             r = 1;
377             break;
378         }
379     }
380     GENERAL_NAMES_free(alt);
381     return r;
382 }
383
384 static int subjectaltnameregexp(X509 *cert, int type, char *exact,  regex_t *regex) {
385     int loc, i, l, n, r = 0;
386     char *s, *v;
387     X509_EXTENSION *ex;
388     STACK_OF(GENERAL_NAME) *alt;
389     GENERAL_NAME *gn;
390
391     debug(DBG_DBG, "subjectaltnameregexp");
392
393     loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1);
394     if (loc < 0)
395         return r;
396
397     ex = X509_get_ext(cert, loc);
398     alt = X509V3_EXT_d2i(ex);
399     if (!alt)
400         return r;
401
402     n = sk_GENERAL_NAME_num(alt);
403     for (i = 0; i < n; i++) {
404         gn = sk_GENERAL_NAME_value(alt, i);
405         if (gn->type != type)
406             continue;
407         r = -1;
408         v = (char *)ASN1_STRING_data(gn->d.ia5);
409         l = ASN1_STRING_length(gn->d.ia5);
410         if (l <= 0)
411             continue;
412 #ifdef DEBUG
413         printfchars(NULL, gn->type == GEN_DNS ? "dns" : "uri", NULL, v, l);
414 #endif
415         if (exact) {
416             if (memcmp(v, exact, l))
417                 continue;
418         } else {
419             s = stringcopy((char *)v, l);
420             if (!s) {
421                 debug(DBG_ERR, "malloc failed");
422                 continue;
423             }
424             if (regexec(regex, s, 0, NULL, 0)) {
425                 free(s);
426                 continue;
427             }
428             free(s);
429         }
430         r = 1;
431         break;
432     }
433     GENERAL_NAMES_free(alt);
434     return r;
435 }
436
437 static int cnregexp(X509 *cert, char *exact, regex_t *regex) {
438     int loc, l;
439     char *v, *s;
440     X509_NAME *nm;
441     X509_NAME_ENTRY *e;
442     ASN1_STRING *t;
443
444     nm = X509_get_subject_name(cert);
445     loc = -1;
446     for (;;) {
447         loc = X509_NAME_get_index_by_NID(nm, NID_commonName, loc);
448         if (loc == -1)
449             break;
450         e = X509_NAME_get_entry(nm, loc);
451         t = X509_NAME_ENTRY_get_data(e);
452         v = (char *) ASN1_STRING_data(t);
453         l = ASN1_STRING_length(t);
454         if (l < 0)
455             continue;
456         if (exact) {
457             if (l == strlen(exact) && !strncasecmp(exact, v, l))
458                 return 1;
459         } else {
460             s = stringcopy((char *)v, l);
461             if (!s) {
462                 debug(DBG_ERR, "malloc failed");
463                 continue;
464             }
465             if (regexec(regex, s, 0, NULL, 0)) {
466                 free(s);
467                 continue;
468             }
469             free(s);
470             return 1;
471         }
472     }
473     return 0;
474 }
475
476 /* this is a bit sloppy, should not always accept match to any */
477 int certnamecheck(X509 *cert, struct list *hostports) {
478     struct list_node *entry;
479     struct hostportres *hp;
480     int r;
481     uint8_t type = 0; /* 0 for DNS, AF_INET for IPv4, AF_INET6 for IPv6 */
482     struct in6_addr addr;
483
484     for (entry = list_first(hostports); entry; entry = list_next(entry)) {
485         hp = (struct hostportres *)entry->data;
486         if (hp->prefixlen != 255) {
487             /* we disable the check for prefixes */
488             return 1;
489         }
490         if (inet_pton(AF_INET, hp->host, &addr))
491             type = AF_INET;
492         else if (inet_pton(AF_INET6, hp->host, &addr))
493             type = AF_INET6;
494         else
495             type = 0;
496
497         r = type ? subjectaltnameaddr(cert, type, &addr) : subjectaltnameregexp(cert, GEN_DNS, hp->host, NULL);
498         if (r) {
499             if (r > 0) {
500                 debug(DBG_DBG, "certnamecheck: Found subjectaltname matching %s %s", type ? "address" : "host", hp->host);
501                 return 1;
502             }
503             debug(DBG_WARN, "certnamecheck: No subjectaltname matching %s %s", type ? "address" : "host", hp->host);
504         } else {
505             if (cnregexp(cert, hp->host, NULL)) {
506                 debug(DBG_DBG, "certnamecheck: Found cn matching host %s", hp->host);
507                 return 1;
508             }
509             debug(DBG_WARN, "certnamecheck: cn not matching host %s", hp->host);
510         }
511     }
512     return 0;
513 }
514
515 int verifyconfcert(X509 *cert, struct clsrvconf *conf) {
516     if (conf->certnamecheck) {
517         if (!certnamecheck(cert, conf->hostports)) {
518             debug(DBG_WARN, "verifyconfcert: certificate name check failed");
519             return 0;
520         }
521         debug(DBG_WARN, "verifyconfcert: certificate name check ok");
522     }
523     if (conf->certcnregex) {
524         if (cnregexp(cert, NULL, conf->certcnregex) < 1) {
525             debug(DBG_WARN, "verifyconfcert: CN not matching regex");
526             return 0;
527         }
528         debug(DBG_DBG, "verifyconfcert: CN matching regex");
529     }
530     if (conf->certuriregex) {
531         if (subjectaltnameregexp(cert, GEN_URI, NULL, conf->certuriregex) < 1) {
532             debug(DBG_WARN, "verifyconfcert: subjectaltname URI not matching regex");
533             return 0;
534         }
535         debug(DBG_DBG, "verifyconfcert: subjectaltname URI matching regex");
536     }
537     return 1;
538 }
539
540 int conftls_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) {
541     struct tls *conf;
542     long int expiry = LONG_MIN;
543
544     debug(DBG_DBG, "conftls_cb called for %s", block);
545
546     conf = malloc(sizeof(struct tls));
547     if (!conf) {
548         debug(DBG_ERR, "conftls_cb: malloc failed");
549         return 0;
550     }
551     memset(conf, 0, sizeof(struct tls));
552
553     if (!getgenericconfig(cf, block,
554                           "CACertificateFile", CONF_STR, &conf->cacertfile,
555                           "CACertificatePath", CONF_STR, &conf->cacertpath,
556                           "CertificateFile", CONF_STR, &conf->certfile,
557                           "CertificateKeyFile", CONF_STR, &conf->certkeyfile,
558                           "CertificateKeyPassword", CONF_STR, &conf->certkeypwd,
559                           "CacheExpiry", CONF_LINT, &expiry,
560                           "CRLCheck", CONF_BLN, &conf->crlcheck,
561                           "PolicyOID", CONF_MSTR, &conf->policyoids,
562                           NULL
563             )) {
564         debug(DBG_ERR, "conftls_cb: configuration error in block %s", val);
565         goto errexit;
566     }
567     if (!conf->certfile || !conf->certkeyfile) {
568         debug(DBG_ERR, "conftls_cb: TLSCertificateFile and TLSCertificateKeyFile must be specified in block %s", val);
569         goto errexit;
570     }
571     if (!conf->cacertfile && !conf->cacertpath) {
572         debug(DBG_ERR, "conftls_cb: CA Certificate file or path need to be specified in block %s", val);
573         goto errexit;
574     }
575     if (expiry != LONG_MIN) {
576         if (expiry < 0) {
577             debug(DBG_ERR, "error in block %s, value of option CacheExpiry is %ld, may not be negative", val, expiry);
578             goto errexit;
579         }
580         conf->cacheexpiry = expiry;
581     }
582
583     conf->name = stringcopy(val, 0);
584     if (!conf->name) {
585         debug(DBG_ERR, "conftls_cb: malloc failed");
586         goto errexit;
587     }
588
589     if (!tlsconfs)
590         tlsconfs = hash_create();
591     if (!hash_insert(tlsconfs, val, strlen(val), conf)) {
592         debug(DBG_ERR, "conftls_cb: malloc failed");
593         goto errexit;
594     }
595     if (!tlsgetctx(RAD_TLS, conf))
596         debug(DBG_ERR, "conftls_cb: error creating ctx for TLS block %s", val);
597     debug(DBG_DBG, "conftls_cb: added TLS block %s", val);
598     return 1;
599
600 errexit:
601     free(conf->cacertfile);
602     free(conf->cacertpath);
603     free(conf->certfile);
604     free(conf->certkeyfile);
605     free(conf->certkeypwd);
606     freegconfmstr(conf->policyoids);
607     free(conf);
608     return 0;
609 }
610
611 int addmatchcertattr(struct clsrvconf *conf) {
612     char *v;
613     regex_t **r;
614
615     if (!strncasecmp(conf->matchcertattr, "CN:/", 4)) {
616         r = &conf->certcnregex;
617         v = conf->matchcertattr + 4;
618     } else if (!strncasecmp(conf->matchcertattr, "SubjectAltName:URI:/", 20)) {
619         r = &conf->certuriregex;
620         v = conf->matchcertattr + 20;
621     } else
622         return 0;
623     if (!*v)
624         return 0;
625     /* regexp, remove optional trailing / if present */
626     if (v[strlen(v) - 1] == '/')
627         v[strlen(v) - 1] = '\0';
628     if (!*v)
629         return 0;
630
631     *r = malloc(sizeof(regex_t));
632     if (!*r) {
633         debug(DBG_ERR, "malloc failed");
634         return 0;
635     }
636     if (regcomp(*r, v, REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
637         free(*r);
638         *r = NULL;
639         debug(DBG_ERR, "failed to compile regular expression %s", v);
640         return 0;
641     }
642     return 1;
643 }
644 #else
645 /* Just to makes file non-empty, should rather avoid compiling this file when not needed */
646 static void tlsdummy() {
647 }
648 #endif
649
650 /* Local Variables: */
651 /* c-file-style: "stroustrup" */
652 /* End: */