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