Skip to content
Navigation Menu
Toggle navigation
Sign in
In this repository
All GitHub Enterprise
↵
Jump to
↵
No suggested jump to results
In this repository
All GitHub Enterprise
↵
Jump to
↵
In this organization
All GitHub Enterprise
↵
Jump to
↵
In this repository
All GitHub Enterprise
↵
Jump to
↵
Sign in
Reseting focus
You signed in with another tab or window.
Reload
to refresh your session.
You signed out in another tab or window.
Reload
to refresh your session.
You switched accounts on another tab or window.
Reload
to refresh your session.
Dismiss alert
{{ message }}
mariux64
/
radsecproxy
Public
Notifications
You must be signed in to change notification settings
Fork
0
Star
0
Code
Issues
0
Pull requests
0
Actions
Projects
0
Security
Insights
Additional navigation options
Code
Issues
Pull requests
Actions
Projects
Security
Insights
Files
a0d7b97
packaging
tests
tools
.gitignore
AUTHORS
ChangeLog
INSTALL
LICENSE
Makefile.am
NEWS
README
THANKS
acinclude.m4
autogen.sh
catgconf.c
configure.ac
debug.c
debug.h
develdoc.txt
dtls.c
dtls.h
fticks.c
fticks.h
fticks_hashmac.c
fticks_hashmac.h
gconfig.c
gconfig.h
hash.c
hash.h
hostport.c
hostport.h
list.c
list.h
main.c
radmsg.c
radmsg.h
radsecproxy-hash.1
radsecproxy-hash.c
radsecproxy.1
radsecproxy.c
radsecproxy.conf-example
radsecproxy.conf.5.xml
radsecproxy.h
tcp.c
tcp.h
tls.c
tls.h
tlscommon.c
tlscommon.h
tlv11.c
tlv11.h
udp.c
udp.h
util.c
util.h
Breadcrumbs
radsecproxy
/
tlscommon.c
Blame
Blame
Latest commit
History
History
723 lines (648 loc) · 18.9 KB
Breadcrumbs
radsecproxy
/
tlscommon.c
Top
File metadata and controls
Code
Blame
723 lines (648 loc) · 18.9 KB
Raw
/* Copyright (c) 2007-2009, UNINETT AS * Copyright (c) 2010-2011,2015-2016, NORDUnet A/S */ /* See LICENSE for licensing information. */ #if defined(RADPROT_TLS) || defined(RADPROT_DTLS) #include <signal.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <string.h> #include <unistd.h> #include <limits.h> #ifdef SYS_SOLARIS9 #include <fcntl.h> #endif #include <sys/time.h> #include <sys/types.h> #include <ctype.h> #include <sys/wait.h> #include <arpa/inet.h> #include <regex.h> #include <libgen.h> #include <pthread.h> #include <openssl/ssl.h> #include <openssl/rand.h> #include <openssl/err.h> #include <openssl/md5.h> #include <openssl/x509v3.h> #include "debug.h" #include "hash.h" #include "util.h" #include "hostport.h" #include "radsecproxy.h" static struct hash *tlsconfs = NULL; /* callbacks for making OpenSSL < 1.1 thread safe */ #if OPENSSL_VERSION_NUMBER < 0x10100000 static pthread_mutex_t *ssl_locks = NULL; unsigned long ssl_thread_id() { return (unsigned long)pthread_self(); } void ssl_locking_callback(int mode, int type, const char *file, int line) { if (mode & CRYPTO_LOCK) pthread_mutex_lock(&ssl_locks[type]); else pthread_mutex_unlock(&ssl_locks[type]); } #endif void sslinit() { #if OPENSSL_VERSION_NUMBER < 0x10100000 int i; SSL_library_init(); ssl_locks = OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t)); if (!ssl_locks) debugx(1, DBG_ERR, "malloc failed"); for (i = 0; i < CRYPTO_num_locks(); i++) { pthread_mutex_init(&ssl_locks[i], NULL); } CRYPTO_set_id_callback(ssl_thread_id); CRYPTO_set_locking_callback(ssl_locking_callback); #else OPENSSL_init_ssl(0, NULL); #endif SSL_load_error_strings(); } static int pem_passwd_cb(char *buf, int size, int rwflag, void *userdata) { int pwdlen = strlen(userdata); if (rwflag != 0 || pwdlen > size) /* not for decryption or too large */ return 0; memcpy(buf, userdata, pwdlen); return pwdlen; } static int verify_cb(int ok, X509_STORE_CTX *ctx) { char *buf = NULL; X509 *err_cert; int err, depth; err_cert = X509_STORE_CTX_get_current_cert(ctx); err = X509_STORE_CTX_get_error(ctx); depth = X509_STORE_CTX_get_error_depth(ctx); if (depth > MAX_CERT_DEPTH) { ok = 0; err = X509_V_ERR_CERT_CHAIN_TOO_LONG; X509_STORE_CTX_set_error(ctx, err); } if (!ok) { if (err_cert) buf = X509_NAME_oneline(X509_get_subject_name(err_cert), NULL, 0); debug(DBG_WARN, "verify error: num=%d:%s:depth=%d:%s", err, X509_verify_cert_error_string(err), depth, buf ? buf : ""); free(buf); buf = NULL; switch (err) { case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: if (err_cert) { buf = X509_NAME_oneline(X509_get_issuer_name(err_cert), NULL, 0); if (buf) { debug(DBG_WARN, "\tIssuer=%s", buf); free(buf); buf = NULL; } } break; case X509_V_ERR_CERT_NOT_YET_VALID: case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: debug(DBG_WARN, "\tCertificate not yet valid"); break; case X509_V_ERR_CERT_HAS_EXPIRED: debug(DBG_WARN, "Certificate has expired"); break; case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: debug(DBG_WARN, "Certificate no longer valid (after notAfter)"); break; case X509_V_ERR_NO_EXPLICIT_POLICY: debug(DBG_WARN, "No Explicit Certificate Policy"); break; } } #ifdef DEBUG printf("certificate verify returns %d\n", ok); #endif return ok; } #ifdef DEBUG static void ssl_info_callback(const SSL *ssl, int where, int ret) { const char *s; int w; w = where & ~SSL_ST_MASK; if (w & SSL_ST_CONNECT) s = "SSL_connect"; else if (w & SSL_ST_ACCEPT) s = "SSL_accept"; else s = "undefined"; if (where & SSL_CB_LOOP) debug(DBG_DBG, "%s:%s\n", s, SSL_state_string_long(ssl)); else if (where & SSL_CB_ALERT) { s = (where & SSL_CB_READ) ? "read" : "write"; debug(DBG_DBG, "SSL3 alert %s:%s:%s\n", s, SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); } else if (where & SSL_CB_EXIT) { if (ret == 0) debug(DBG_DBG, "%s:failed in %s\n", s, SSL_state_string_long(ssl)); else if (ret < 0) debug(DBG_DBG, "%s:error in %s\n", s, SSL_state_string_long(ssl)); } } #endif static X509_VERIFY_PARAM *createverifyparams(char **poids) { X509_VERIFY_PARAM *pm; ASN1_OBJECT *pobject; int i; pm = X509_VERIFY_PARAM_new(); if (!pm) return NULL; for (i = 0; poids[i]; i++) { pobject = OBJ_txt2obj(poids[i], 0); if (!pobject) { X509_VERIFY_PARAM_free(pm); return NULL; } X509_VERIFY_PARAM_add0_policy(pm, pobject); } X509_VERIFY_PARAM_set_flags(pm, X509_V_FLAG_POLICY_CHECK | X509_V_FLAG_EXPLICIT_POLICY); return pm; } static int tlsaddcacrl(SSL_CTX *ctx, struct tls *conf) { STACK_OF(X509_NAME) *calist; X509_STORE *x509_s; unsigned long error; SSL_CTX_set_cert_store(ctx, X509_STORE_new()); if (!SSL_CTX_load_verify_locations(ctx, conf->cacertfile, conf->cacertpath)) { while ((error = ERR_get_error())) debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL)); debug(DBG_ERR, "tlsaddcacrl: Error updating TLS context %s", conf->name); return 0; } calist = conf->cacertfile ? SSL_load_client_CA_file(conf->cacertfile) : NULL; if (!conf->cacertfile || calist) { if (conf->cacertpath) { if (!calist) calist = sk_X509_NAME_new_null(); if (!SSL_add_dir_cert_subjects_to_stack(calist, conf->cacertpath)) { sk_X509_NAME_free(calist); calist = NULL; } } } if (!calist) { while ((error = ERR_get_error())) debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL)); debug(DBG_ERR, "tlsaddcacrl: Error adding CA subjects in TLS context %s", conf->name); return 0; } ERR_clear_error(); /* add_dir_cert_subj returns errors on success */ SSL_CTX_set_client_CA_list(ctx, calist); SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb); SSL_CTX_set_verify_depth(ctx, MAX_CERT_DEPTH + 1); if (conf->crlcheck || conf->vpm) { x509_s = SSL_CTX_get_cert_store(ctx); if (conf->crlcheck) X509_STORE_set_flags(x509_s, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); if (conf->vpm) X509_STORE_set1_param(x509_s, conf->vpm); } debug(DBG_DBG, "tlsaddcacrl: updated TLS context %s", conf->name); return 1; } static SSL_CTX *tlscreatectx(uint8_t type, struct tls *conf) { SSL_CTX *ctx = NULL; unsigned long error; long sslversion = SSLeay(); switch (type) { #ifdef RADPROT_TLS case RAD_TLS: #if OPENSSL_VERSION_NUMBER >= 0x10100000 /* TLS_method() was introduced in OpenSSL 1.1.0. */ ctx = SSL_CTX_new(TLS_method()); #else /* No TLS_method(), use SSLv23_method() and disable SSLv2 and SSLv3. */ ctx = SSL_CTX_new(SSLv23_method()); SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); #endif #ifdef DEBUG SSL_CTX_set_info_callback(ctx, ssl_info_callback); #endif break; #endif #ifdef RADPROT_DTLS case RAD_DTLS: #if OPENSSL_VERSION_NUMBER >= 0x10002000 /* DTLS_method() seems to have been introduced in OpenSSL 1.0.2. */ ctx = SSL_CTX_new(DTLS_method()); #else ctx = SSL_CTX_new(DTLSv1_method()); #endif #ifdef DEBUG SSL_CTX_set_info_callback(ctx, ssl_info_callback); #endif SSL_CTX_set_read_ahead(ctx, 1); break; #endif } if (!ctx) { debug(DBG_ERR, "tlscreatectx: Error initialising SSL/TLS in TLS context %s", conf->name); return NULL; } if (sslversion < 0x00908100L || (sslversion >= 0x10000000L && sslversion < 0x10000020L)) { debug(DBG_WARN, "%s: %s seems to be of a version with a " "certain security critical bug (fixed in OpenSSL 0.9.8p and " "1.0.0b). Disabling OpenSSL session caching for context %p.", __func__, SSLeay_version(SSLEAY_VERSION), ctx); SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); } if (conf->certkeypwd) { SSL_CTX_set_default_passwd_cb_userdata(ctx, conf->certkeypwd); SSL_CTX_set_default_passwd_cb(ctx, pem_passwd_cb); } if (!SSL_CTX_use_certificate_chain_file(ctx, conf->certfile) || !SSL_CTX_use_PrivateKey_file(ctx, conf->certkeyfile, SSL_FILETYPE_PEM) || !SSL_CTX_check_private_key(ctx)) { while ((error = ERR_get_error())) debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL)); debug(DBG_ERR, "tlscreatectx: Error initialising SSL/TLS in TLS context %s", conf->name); SSL_CTX_free(ctx); return NULL; } if (conf->policyoids) { if (!conf->vpm) { conf->vpm = createverifyparams(conf->policyoids); if (!conf->vpm) { debug(DBG_ERR, "tlscreatectx: Failed to add policyOIDs in TLS context %s", conf->name); SSL_CTX_free(ctx); return NULL; } } } if (!tlsaddcacrl(ctx, conf)) { if (conf->vpm) { X509_VERIFY_PARAM_free(conf->vpm); conf->vpm = NULL; } SSL_CTX_free(ctx); return NULL; } debug(DBG_DBG, "tlscreatectx: created TLS context %s", conf->name); return ctx; } struct tls *tlsgettls(char *alt1, char *alt2) { struct tls *t; t = hash_read(tlsconfs, alt1, strlen(alt1)); if (!t) t = hash_read(tlsconfs, alt2, strlen(alt2)); return t; } SSL_CTX *tlsgetctx(uint8_t type, struct tls *t) { struct timeval now; if (!t) return NULL; gettimeofday(&now, NULL); switch (type) { #ifdef RADPROT_TLS case RAD_TLS: if (t->tlsexpiry && t->tlsctx) { if (t->tlsexpiry < now.tv_sec) { t->tlsexpiry = now.tv_sec + t->cacheexpiry; tlsaddcacrl(t->tlsctx, t); } } if (!t->tlsctx) { t->tlsctx = tlscreatectx(RAD_TLS, t); if (t->cacheexpiry) t->tlsexpiry = now.tv_sec + t->cacheexpiry; } return t->tlsctx; #endif #ifdef RADPROT_DTLS case RAD_DTLS: if (t->dtlsexpiry && t->dtlsctx) { if (t->dtlsexpiry < now.tv_sec) { t->dtlsexpiry = now.tv_sec + t->cacheexpiry; tlsaddcacrl(t->dtlsctx, t); } } if (!t->dtlsctx) { t->dtlsctx = tlscreatectx(RAD_DTLS, t); if (t->cacheexpiry) t->dtlsexpiry = now.tv_sec + t->cacheexpiry; } return t->dtlsctx; #endif } return NULL; } void tlsreloadcrls() { struct tls *conf; struct hash_entry *entry; struct timeval now; gettimeofday(&now, NULL); for (entry = hash_first(tlsconfs); entry; entry = hash_next(entry)) { conf = (struct tls *)entry->data; #ifdef RADPROT_TLS if (conf->tlsctx) { if (conf->tlsexpiry) conf->tlsexpiry = now.tv_sec + conf->cacheexpiry; tlsaddcacrl(conf->tlsctx, conf); } #endif #ifdef RADPROT_DTLS if (conf->dtlsctx) { if (conf->dtlsexpiry) conf->dtlsexpiry = now.tv_sec + conf->cacheexpiry; tlsaddcacrl(conf->dtlsctx, conf); } #endif } } X509 *verifytlscert(SSL *ssl) { X509 *cert; unsigned long error; if (SSL_get_verify_result(ssl) != X509_V_OK) { debug(DBG_ERR, "verifytlscert: basic validation failed"); while ((error = ERR_get_error())) debug(DBG_ERR, "verifytlscert: TLS: %s", ERR_error_string(error, NULL)); return NULL; } cert = SSL_get_peer_certificate(ssl); if (!cert) debug(DBG_ERR, "verifytlscert: failed to obtain certificate"); return cert; } static int subjectaltnameaddr(X509 *cert, int family, struct in6_addr *addr) { int loc, i, l, n, r = 0; char *v; X509_EXTENSION *ex; STACK_OF(GENERAL_NAME) *alt; GENERAL_NAME *gn; debug(DBG_DBG, "subjectaltnameaddr"); loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1); if (loc < 0) return r; ex = X509_get_ext(cert, loc); alt = X509V3_EXT_d2i(ex); if (!alt) return r; n = sk_GENERAL_NAME_num(alt); for (i = 0; i < n; i++) { gn = sk_GENERAL_NAME_value(alt, i); if (gn->type != GEN_IPADD) continue; r = -1; v = (char *)ASN1_STRING_get0_data(gn->d.ia5); l = ASN1_STRING_length(gn->d.ia5); if (((family == AF_INET && l == sizeof(struct in_addr)) || (family == AF_INET6 && l == sizeof(struct in6_addr))) && !memcmp(v, &addr, l)) { r = 1; break; } } GENERAL_NAMES_free(alt); return r; } static int subjectaltnameregexp(X509 *cert, int type, char *exact, regex_t *regex) { int loc, i, l, n, r = 0; char *s, *v; X509_EXTENSION *ex; STACK_OF(GENERAL_NAME) *alt; GENERAL_NAME *gn; debug(DBG_DBG, "subjectaltnameregexp"); loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1); if (loc < 0) return r; ex = X509_get_ext(cert, loc); alt = X509V3_EXT_d2i(ex); if (!alt) return r; n = sk_GENERAL_NAME_num(alt); for (i = 0; i < n; i++) { gn = sk_GENERAL_NAME_value(alt, i); if (gn->type != type) continue; r = -1; v = (char *)ASN1_STRING_get0_data(gn->d.ia5); l = ASN1_STRING_length(gn->d.ia5); if (l <= 0) continue; #ifdef DEBUG printfchars(NULL, gn->type == GEN_DNS ? "dns" : "uri", NULL, (uint8_t *) v, l); #endif if (exact) { if (memcmp(v, exact, l)) continue; } else { s = stringcopy((char *)v, l); if (!s) { debug(DBG_ERR, "malloc failed"); continue; } if (regexec(regex, s, 0, NULL, 0)) { free(s); continue; } free(s); } r = 1; break; } GENERAL_NAMES_free(alt); return r; } static int cnregexp(X509 *cert, char *exact, regex_t *regex) { int loc, l; char *v, *s; X509_NAME *nm; X509_NAME_ENTRY *e; ASN1_STRING *t; nm = X509_get_subject_name(cert); loc = -1; for (;;) { loc = X509_NAME_get_index_by_NID(nm, NID_commonName, loc); if (loc == -1) break; e = X509_NAME_get_entry(nm, loc); t = X509_NAME_ENTRY_get_data(e); v = (char *) ASN1_STRING_get0_data(t); l = ASN1_STRING_length(t); if (l < 0) continue; if (exact) { if (l == strlen(exact) && !strncasecmp(exact, v, l)) return 1; } else { s = stringcopy((char *)v, l); if (!s) { debug(DBG_ERR, "malloc failed"); continue; } if (regexec(regex, s, 0, NULL, 0)) { free(s); continue; } free(s); return 1; } } return 0; } /* this is a bit sloppy, should not always accept match to any */ int certnamecheck(X509 *cert, struct list *hostports) { struct list_node *entry; struct hostportres *hp; int r; uint8_t type = 0; /* 0 for DNS, AF_INET for IPv4, AF_INET6 for IPv6 */ struct in6_addr addr; for (entry = list_first(hostports); entry; entry = list_next(entry)) { hp = (struct hostportres *)entry->data; if (hp->prefixlen != 255) { /* we disable the check for prefixes */ return 1; } if (inet_pton(AF_INET, hp->host, &addr)) type = AF_INET; else if (inet_pton(AF_INET6, hp->host, &addr)) type = AF_INET6; else type = 0; r = type ? subjectaltnameaddr(cert, type, &addr) : subjectaltnameregexp(cert, GEN_DNS, hp->host, NULL); if (r) { if (r > 0) { debug(DBG_DBG, "certnamecheck: Found subjectaltname matching %s %s", type ? "address" : "host", hp->host); return 1; } debug(DBG_WARN, "certnamecheck: No subjectaltname matching %s %s", type ? "address" : "host", hp->host); } else { if (cnregexp(cert, hp->host, NULL)) { debug(DBG_DBG, "certnamecheck: Found cn matching host %s", hp->host); return 1; } debug(DBG_WARN, "certnamecheck: cn not matching host %s", hp->host); } } return 0; } int verifyconfcert(X509 *cert, struct clsrvconf *conf) { if (conf->certnamecheck) { if (!certnamecheck(cert, conf->hostports)) { debug(DBG_WARN, "verifyconfcert: certificate name check failed"); return 0; } debug(DBG_WARN, "verifyconfcert: certificate name check ok"); } if (conf->certcnregex) { if (cnregexp(cert, NULL, conf->certcnregex) < 1) { debug(DBG_WARN, "verifyconfcert: CN not matching regex"); return 0; } debug(DBG_DBG, "verifyconfcert: CN matching regex"); } if (conf->certuriregex) { if (subjectaltnameregexp(cert, GEN_URI, NULL, conf->certuriregex) < 1) { debug(DBG_WARN, "verifyconfcert: subjectaltname URI not matching regex"); return 0; } debug(DBG_DBG, "verifyconfcert: subjectaltname URI matching regex"); } return 1; } int conftls_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) { struct tls *conf; long int expiry = LONG_MIN; debug(DBG_DBG, "conftls_cb called for %s", block); conf = malloc(sizeof(struct tls)); if (!conf) { debug(DBG_ERR, "conftls_cb: malloc failed"); return 0; } memset(conf, 0, sizeof(struct tls)); if (!getgenericconfig(cf, block, "CACertificateFile", CONF_STR, &conf->cacertfile, "CACertificatePath", CONF_STR, &conf->cacertpath, "CertificateFile", CONF_STR, &conf->certfile, "CertificateKeyFile", CONF_STR, &conf->certkeyfile, "CertificateKeyPassword", CONF_STR, &conf->certkeypwd, "CacheExpiry", CONF_LINT, &expiry, "CRLCheck", CONF_BLN, &conf->crlcheck, "PolicyOID", CONF_MSTR, &conf->policyoids, NULL )) { debug(DBG_ERR, "conftls_cb: configuration error in block %s", val); goto errexit; } if (!conf->certfile || !conf->certkeyfile) { debug(DBG_ERR, "conftls_cb: TLSCertificateFile and TLSCertificateKeyFile must be specified in block %s", val); goto errexit; } if (!conf->cacertfile && !conf->cacertpath) { debug(DBG_ERR, "conftls_cb: CA Certificate file or path need to be specified in block %s", val); goto errexit; } if (expiry != LONG_MIN) { if (expiry < 0) { debug(DBG_ERR, "error in block %s, value of option CacheExpiry is %ld, may not be negative", val, expiry); goto errexit; } conf->cacheexpiry = expiry; } conf->name = stringcopy(val, 0); if (!conf->name) { debug(DBG_ERR, "conftls_cb: malloc failed"); goto errexit; } pthread_mutex_init(&conf->lock, NULL); if (!tlsconfs) tlsconfs = hash_create(); if (!hash_insert(tlsconfs, val, strlen(val), conf)) { debug(DBG_ERR, "conftls_cb: malloc failed"); goto errexit; } if (!tlsgetctx(RAD_TLS, conf)) debug(DBG_ERR, "conftls_cb: error creating ctx for TLS block %s", val); debug(DBG_DBG, "conftls_cb: added TLS block %s", val); return 1; errexit: free(conf->cacertfile); free(conf->cacertpath); free(conf->certfile); free(conf->certkeyfile); free(conf->certkeypwd); freegconfmstr(conf->policyoids); free(conf); return 0; } int addmatchcertattr(struct clsrvconf *conf) { char *v; regex_t **r; if (!strncasecmp(conf->matchcertattr, "CN:/", 4)) { r = &conf->certcnregex; v = conf->matchcertattr + 4; } else if (!strncasecmp(conf->matchcertattr, "SubjectAltName:URI:/", 20)) { r = &conf->certuriregex; v = conf->matchcertattr + 20; } else return 0; if (!*v) return 0; /* regexp, remove optional trailing / if present */ if (v[strlen(v) - 1] == '/') v[strlen(v) - 1] = '\0'; if (!*v) return 0; *r = malloc(sizeof(regex_t)); if (!*r) { debug(DBG_ERR, "malloc failed"); return 0; } if (regcomp(*r, v, REG_EXTENDED | REG_ICASE | REG_NOSUB)) { free(*r); *r = NULL; debug(DBG_ERR, "failed to compile regular expression %s", v); return 0; } return 1; } #else /* Just to makes file non-empty, should rather avoid compiling this file when not needed */ static void tlsdummy() { } #endif /* Local Variables: */ /* c-file-style: "stroustrup" */ /* End: */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
You can’t perform that action at this time.