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