cadf7bcf5332a3299d2c2fc4739b794c8ab3c69a
[shibboleth/cpp-xmltooling.git] / xmltooling / XMLToolingConfig.cpp
1 /*
2  *  Copyright 2001-2009 Internet2
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /**
18  * XMLToolingConfig.cpp
19  *
20  * Library configuration.
21  */
22
23 #include "internal.h"
24 #include "exceptions.h"
25 #include "logging.h"
26 #include "XMLToolingConfig.h"
27 #include "encryption/Encryption.h"
28 #include "encryption/Encrypter.h"
29 #include "impl/UnknownElement.h"
30 #include "io/HTTPResponse.h"
31 #include "security/TrustEngine.h"
32 #include "security/OpenSSLCryptoX509CRL.h"
33 #include "security/CredentialResolver.h"
34 #include "security/KeyInfoResolver.h"
35 #include "signature/KeyInfo.h"
36 #include "signature/Signature.h"
37 #include "soap/SOAP.h"
38 #include "util/NDC.h"
39 #include "util/PathResolver.h"
40 #include "util/ReplayCache.h"
41 #include "util/StorageService.h"
42 #include "util/TemplateEngine.h"
43 #include "util/Threads.h"
44 #include "util/URLEncoder.h"
45 #include "validation/ValidatorSuite.h"
46
47 #ifdef HAVE_DLFCN_H
48 # include <dlfcn.h>
49 #endif
50
51 #include <stdexcept>
52 #if defined(XMLTOOLING_LOG4SHIB)
53 # include <log4shib/PropertyConfigurator.hh>
54 # include <log4shib/OstreamAppender.hh>
55 #elif defined(XMLTOOLING_LOG4CPP)
56 # include <log4cpp/PropertyConfigurator.hh>
57 # include <log4cpp/OstreamAppender.hh>
58 #endif
59 #include <xercesc/util/PlatformUtils.hpp>
60 #include <xercesc/util/XMLUniDefs.hpp>
61 #ifndef XMLTOOLING_NO_XMLSEC
62 # include <curl/curl.h>
63 # include <openssl/err.h>
64 # include <xsec/framework/XSECProvider.hpp>
65 #endif
66
67 using namespace soap11;
68 using namespace xmltooling::logging;
69 using namespace xmltooling;
70 using namespace std;
71
72 using xercesc::XMLPlatformUtils;
73
74 DECL_XMLTOOLING_EXCEPTION_FACTORY(XMLParserException,xmltooling);
75 DECL_XMLTOOLING_EXCEPTION_FACTORY(XMLObjectException,xmltooling);
76 DECL_XMLTOOLING_EXCEPTION_FACTORY(MarshallingException,xmltooling);
77 DECL_XMLTOOLING_EXCEPTION_FACTORY(UnmarshallingException,xmltooling);
78 DECL_XMLTOOLING_EXCEPTION_FACTORY(UnknownElementException,xmltooling);
79 DECL_XMLTOOLING_EXCEPTION_FACTORY(UnknownAttributeException,xmltooling);
80 DECL_XMLTOOLING_EXCEPTION_FACTORY(UnknownExtensionException,xmltooling);
81 DECL_XMLTOOLING_EXCEPTION_FACTORY(ValidationException,xmltooling);
82 DECL_XMLTOOLING_EXCEPTION_FACTORY(IOException,xmltooling);
83
84 #ifndef XMLTOOLING_NO_XMLSEC
85 using namespace xmlencryption;
86 using namespace xmlsignature;
87     DECL_XMLTOOLING_EXCEPTION_FACTORY(XMLSecurityException,xmltooling);
88     DECL_XMLTOOLING_EXCEPTION_FACTORY(SignatureException,xmlsignature);
89     DECL_XMLTOOLING_EXCEPTION_FACTORY(EncryptionException,xmlencryption);
90 #endif
91
92 namespace xmltooling {
93     static XMLToolingInternalConfig g_config;
94 #ifndef XMLTOOLING_NO_XMLSEC
95     static vector<Mutex*> g_openssl_locks;
96
97     extern "C" void openssl_locking_callback(int mode,int n,const char *file,int line)
98     {
99         if (mode & CRYPTO_LOCK)
100             g_openssl_locks[n]->lock();
101         else
102             g_openssl_locks[n]->unlock();
103     }
104
105 # ifndef WIN32
106     extern "C" unsigned long openssl_thread_id(void)
107     {
108         return (unsigned long)(pthread_self());
109     }
110 # endif
111 #endif
112
113 #ifdef WIN32
114     BOOL LogEvent(
115         LPCSTR  lpUNCServerName,
116         WORD  wType,
117         DWORD  dwEventID,
118         PSID  lpUserSid,
119         LPCSTR  message)
120     {
121         LPCSTR  messages[] = {message, NULL};
122
123         HANDLE hElog = RegisterEventSource(lpUNCServerName, "OpenSAML XMLTooling Library");
124         BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
125         return (DeregisterEventSource(hElog) && res);
126     }
127 #endif
128 }
129
130 XMLToolingConfig& XMLToolingConfig::getConfig()
131 {
132     return g_config;
133 }
134
135 XMLToolingInternalConfig& XMLToolingInternalConfig::getInternalConfig()
136 {
137     return g_config;
138 }
139
140 #ifndef XMLTOOLING_NO_XMLSEC
141 XMLToolingConfig::XMLToolingConfig()
142     : m_keyInfoResolver(NULL), m_replayCache(NULL), m_pathResolver(NULL), m_templateEngine(NULL), m_urlEncoder(NULL), clock_skew_secs(180)
143 #else
144 XMLToolingConfig::XMLToolingConfig()
145     : m_pathResolver(NULL), m_templateEngine(NULL), m_urlEncoder(NULL), clock_skew_secs(180)
146 #endif
147 {
148 }
149
150 XMLToolingConfig::~XMLToolingConfig()
151 {
152 }
153
154 bool XMLToolingInternalConfig::log_config(const char* config)
155 {
156     try {
157         if (!config || !*config)
158             config=getenv("XMLTOOLING_LOG_CONFIG");
159         if (!config || !*config)
160             config="WARN";
161
162         bool level=false;
163         Category& root = Category::getRoot();
164         if (!strcmp(config,"DEBUG")) {
165             root.setPriority(Priority::DEBUG);
166             level=true;
167         }
168         else if (!strcmp(config,"INFO")) {
169             root.setPriority(Priority::INFO);
170             level=true;
171         }
172         else if (!strcmp(config,"NOTICE")) {
173             root.setPriority(Priority::NOTICE);
174             level=true;
175         }
176         else if (!strcmp(config,"WARN")) {
177             root.setPriority(Priority::WARN);
178             level=true;
179         }
180         else if (!strcmp(config,"ERROR")) {
181             root.setPriority(Priority::ERROR);
182             level=true;
183         }
184         else if (!strcmp(config,"CRIT")) {
185             root.setPriority(Priority::CRIT);
186             level=true;
187         }
188         else if (!strcmp(config,"ALERT")) {
189             root.setPriority(Priority::ALERT);
190             level=true;
191         }
192         else if (!strcmp(config,"EMERG")) {
193             root.setPriority(Priority::EMERG);
194             level=true;
195         }
196         else if (!strcmp(config,"FATAL")) {
197             root.setPriority(Priority::FATAL);
198             level=true;
199         }
200         if (level) {
201             root.setAppender(new OstreamAppender("default",&cerr));
202         }
203         else {
204             string path(config);
205             PropertyConfigurator::configure(m_pathResolver ? m_pathResolver->resolve(path, PathResolver::XMLTOOLING_CFG_FILE) : path);
206         }
207     }
208     catch (const ConfigureFailure& e) {
209         string msg = string("failed to configure logging: ") + e.what();
210         Category::getInstance(XMLTOOLING_LOGCAT".Logging").crit(msg);
211 #ifdef WIN32
212         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
213 #endif
214         return false;
215     }
216
217     return true;
218 }
219
220 #ifndef XMLTOOLING_LITE
221 const KeyInfoResolver* XMLToolingConfig::getKeyInfoResolver() const
222 {
223     return m_keyInfoResolver;
224 }
225
226 ReplayCache* XMLToolingConfig::getReplayCache() const
227 {
228     return m_replayCache;
229 }
230
231 void XMLToolingConfig::setKeyInfoResolver(xmltooling::KeyInfoResolver *keyInfoResolver)
232 {
233     delete m_keyInfoResolver;
234     m_keyInfoResolver = keyInfoResolver;
235 }
236
237 void XMLToolingConfig::setReplayCache(ReplayCache* replayCache)
238 {
239     delete m_replayCache;
240     m_replayCache = replayCache;
241 }
242 #endif
243
244 PathResolver* XMLToolingConfig::getPathResolver() const
245 {
246     return m_pathResolver;
247 }
248
249 TemplateEngine* XMLToolingConfig::getTemplateEngine() const
250 {
251     return m_templateEngine;
252 }
253
254 const URLEncoder* XMLToolingConfig::getURLEncoder() const
255 {
256     return m_urlEncoder;
257 }
258
259 void XMLToolingConfig::setPathResolver(PathResolver* pathResolver)
260 {
261     delete m_pathResolver;
262     m_pathResolver = pathResolver;
263 }
264
265 void XMLToolingConfig::setTemplateEngine(TemplateEngine* templateEngine)
266 {
267     delete m_templateEngine;
268     m_templateEngine = templateEngine;
269 }
270
271 void XMLToolingConfig::setURLEncoder(URLEncoder* urlEncoder)
272 {
273     delete m_urlEncoder;
274     m_urlEncoder = urlEncoder;
275 }
276
277 bool XMLToolingInternalConfig::init()
278 {
279 #ifdef _DEBUG
280     xmltooling::NDC ndc("init");
281 #endif
282     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".XMLToolingConfig");
283     try {
284         log.debug("library initialization started");
285
286 #ifndef XMLTOOLING_NO_XMLSEC
287         if (curl_global_init(CURL_GLOBAL_ALL)) {
288             log.fatal("failed to initialize libcurl, OpenSSL, or Winsock");
289             return false;
290         }
291         log.debug("libcurl %s initialization complete", LIBCURL_VERSION);
292 #endif
293
294         XMLPlatformUtils::Initialize();
295         log.debug("Xerces %s initialization complete", XERCES_FULLVERSIONDOT);
296
297 #ifndef XMLTOOLING_NO_XMLSEC
298         XSECPlatformUtils::Initialise();
299         m_xsecProvider=new XSECProvider();
300         log.debug("XML-Security %s initialization complete", XSEC_FULLVERSIONDOT);
301 #endif
302
303         m_parserPool=new ParserPool();
304         m_validatingPool=new ParserPool(true,true);
305         m_lock=XMLPlatformUtils::makeMutex();
306
307         // Load catalogs from path.
308         if (!catalog_path.empty()) {
309             char* catpath=strdup(catalog_path.c_str());
310             char* sep=NULL;
311             char* start=catpath;
312             while (start && *start) {
313                 sep=strchr(start,PATH_SEPARATOR_CHAR);
314                 if (sep)
315                     *sep=0;
316                 auto_ptr_XMLCh temp(start);
317                 m_validatingPool->loadCatalog(temp.get());
318                 start = sep ? sep + 1 : NULL;
319             }
320             free(catpath);
321         }
322
323         // default registrations
324         XMLObjectBuilder::registerDefaultBuilder(new UnknownElementBuilder());
325
326         registerSOAPClasses();
327
328         REGISTER_XMLTOOLING_EXCEPTION_FACTORY(XMLParserException,xmltooling);
329         REGISTER_XMLTOOLING_EXCEPTION_FACTORY(XMLObjectException,xmltooling);
330         REGISTER_XMLTOOLING_EXCEPTION_FACTORY(MarshallingException,xmltooling);
331         REGISTER_XMLTOOLING_EXCEPTION_FACTORY(UnmarshallingException,xmltooling);
332         REGISTER_XMLTOOLING_EXCEPTION_FACTORY(UnknownElementException,xmltooling);
333         REGISTER_XMLTOOLING_EXCEPTION_FACTORY(UnknownAttributeException,xmltooling);
334         REGISTER_XMLTOOLING_EXCEPTION_FACTORY(ValidationException,xmltooling);
335         REGISTER_XMLTOOLING_EXCEPTION_FACTORY(IOException,xmltooling);
336
337 #ifndef XMLTOOLING_NO_XMLSEC
338         XMLObjectBuilder::registerBuilder(QName(xmlconstants::XMLSIG_NS,Signature::LOCAL_NAME),new SignatureBuilder());
339         REGISTER_XMLTOOLING_EXCEPTION_FACTORY(XMLSecurityException,xmltooling);
340         REGISTER_XMLTOOLING_EXCEPTION_FACTORY(SignatureException,xmlsignature);
341         REGISTER_XMLTOOLING_EXCEPTION_FACTORY(EncryptionException,xmlencryption);
342         registerKeyInfoClasses();
343         registerEncryptionClasses();
344         registerKeyInfoResolvers();
345         registerCredentialResolvers();
346         registerTrustEngines();
347         registerXMLAlgorithms();
348         registerSOAPTransports();
349         initSOAPTransports();
350         registerStorageServices();
351         m_keyInfoResolver = KeyInfoResolverManager.newPlugin(INLINE_KEYINFO_RESOLVER,NULL);
352 #endif
353
354         m_pathResolver = new PathResolver();
355         m_urlEncoder = new URLEncoder();
356
357         HTTPResponse::getAllowedSchemes().push_back("https");
358         HTTPResponse::getAllowedSchemes().push_back("http");
359
360         // Register xml:id as an ID attribute.
361         static const XMLCh xmlid[] = UNICODE_LITERAL_2(i,d);
362         AttributeExtensibleXMLObject::registerIDAttribute(QName(xmlconstants::XML_NS, xmlid));
363     }
364     catch (const xercesc::XMLException&) {
365         log.fatal("caught exception while initializing Xerces");
366 #ifndef XMLTOOLING_NO_XMLSEC
367         curl_global_cleanup();
368 #endif
369         return false;
370     }
371
372 #ifndef XMLTOOLING_NO_XMLSEC
373     // Set up OpenSSL locking.
374     for (int i=0; i<CRYPTO_num_locks(); i++)
375         g_openssl_locks.push_back(Mutex::create());
376     CRYPTO_set_locking_callback(openssl_locking_callback);
377 # ifndef WIN32
378     CRYPTO_set_id_callback(openssl_thread_id);
379 # endif
380 #endif
381
382     log.info("%s library initialization complete", PACKAGE_STRING);
383     return true;
384 }
385
386 void XMLToolingInternalConfig::term()
387 {
388 #ifndef XMLTOOLING_NO_XMLSEC
389     CRYPTO_set_locking_callback(NULL);
390     for_each(g_openssl_locks.begin(), g_openssl_locks.end(), xmltooling::cleanup<Mutex>());
391     g_openssl_locks.clear();
392 #endif
393
394     SchemaValidators.destroyValidators();
395     XMLObjectBuilder::destroyBuilders();
396     XMLToolingException::deregisterFactories();
397     AttributeExtensibleXMLObject::deregisterIDAttributes();
398
399 #ifndef XMLTOOLING_NO_XMLSEC
400     StorageServiceManager.deregisterFactories();
401     termSOAPTransports();
402     SOAPTransportManager.deregisterFactories();
403     TrustEngineManager.deregisterFactories();
404     CredentialResolverManager.deregisterFactories();
405     KeyInfoResolverManager.deregisterFactories();
406     m_algorithmMap.clear();
407
408     delete m_keyInfoResolver;
409     m_keyInfoResolver = NULL;
410
411     delete m_replayCache;
412     m_replayCache = NULL;
413 #endif
414
415     delete m_pathResolver;
416     m_pathResolver = NULL;
417
418     delete m_templateEngine;
419     m_templateEngine = NULL;
420
421     delete m_urlEncoder;
422     m_urlEncoder = NULL;
423
424     for (vector<void*>::reverse_iterator i=m_libhandles.rbegin(); i!=m_libhandles.rend(); i++) {
425 #if defined(WIN32)
426         FARPROC fn=GetProcAddress(static_cast<HMODULE>(*i),"xmltooling_extension_term");
427         if (fn)
428             fn();
429         FreeLibrary(static_cast<HMODULE>(*i));
430 #elif defined(HAVE_DLFCN_H)
431         void (*fn)()=(void (*)())dlsym(*i,"xmltooling_extension_term");
432         if (fn)
433             fn();
434         dlclose(*i);
435 #else
436 # error "Don't know about dynamic loading on this platform!"
437 #endif
438     }
439     m_libhandles.clear();
440
441     delete m_parserPool;
442     m_parserPool=NULL;
443     delete m_validatingPool;
444     m_validatingPool=NULL;
445
446 #ifndef XMLTOOLING_NO_XMLSEC
447     delete m_xsecProvider;
448     m_xsecProvider=NULL;
449     XSECPlatformUtils::Terminate();
450 #endif
451
452     XMLPlatformUtils::closeMutex(m_lock);
453     m_lock=NULL;
454     XMLPlatformUtils::Terminate();
455
456 #ifndef XMLTOOLING_NO_XMLSEC
457     curl_global_cleanup();
458 #endif
459 #ifdef _DEBUG
460     xmltooling::NDC ndc("term");
461 #endif
462    Category::getInstance(XMLTOOLING_LOGCAT".XMLToolingConfig").info("%s library shutdown complete", PACKAGE_STRING);
463 }
464
465 Lockable* XMLToolingInternalConfig::lock()
466 {
467     xercesc::XMLPlatformUtils::lockMutex(m_lock);
468     return this;
469 }
470
471 void XMLToolingInternalConfig::unlock()
472 {
473     xercesc::XMLPlatformUtils::unlockMutex(m_lock);
474 }
475
476 bool XMLToolingInternalConfig::load_library(const char* path, void* context)
477 {
478 #ifdef _DEBUG
479     xmltooling::NDC ndc("LoadLibrary");
480 #endif
481     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".XMLToolingConfig");
482     log.info("loading extension: %s", path);
483
484     Locker locker(this);
485
486     string resolved(path);
487     m_pathResolver->resolve(resolved, PathResolver::XMLTOOLING_LIB_FILE);
488
489 #if defined(WIN32)
490     HMODULE handle=NULL;
491     for (string::iterator i = resolved.begin(); i != resolved.end(); ++i)
492         if (*i == '/')
493             *i = '\\';
494
495     UINT em=SetErrorMode(SEM_FAILCRITICALERRORS);
496     try {
497         handle=LoadLibraryEx(resolved.c_str(),NULL,LOAD_WITH_ALTERED_SEARCH_PATH);
498         if (!handle)
499              handle=LoadLibraryEx(resolved.c_str(),NULL,0);
500         if (!handle)
501             throw runtime_error(string("unable to load extension library: ") + resolved);
502         FARPROC fn=GetProcAddress(handle,"xmltooling_extension_init");
503         if (!fn)
504             throw runtime_error(string("unable to locate xmltooling_extension_init entry point: ") + resolved);
505         if (reinterpret_cast<int(*)(void*)>(fn)(context)!=0)
506             throw runtime_error(string("detected error in xmltooling_extension_init: ") + resolved);
507         SetErrorMode(em);
508     }
509     catch(exception&) {
510         if (handle)
511             FreeLibrary(handle);
512         SetErrorMode(em);
513         throw;
514     }
515
516 #elif defined(HAVE_DLFCN_H)
517     void* handle=dlopen(resolved.c_str(),RTLD_LAZY);
518     if (!handle)
519         throw runtime_error(string("unable to load extension library '") + resolved + "': " + dlerror());
520     int (*fn)(void*)=(int (*)(void*))(dlsym(handle,"xmltooling_extension_init"));
521     if (!fn) {
522         dlclose(handle);
523         throw runtime_error(
524             string("unable to locate xmltooling_extension_init entry point in '") + resolved + "': " +
525                 (dlerror() ? dlerror() : "unknown error")
526             );
527     }
528     try {
529         if (fn(context)!=0)
530             throw runtime_error(string("detected error in xmltooling_extension_init in ") + resolved);
531     }
532     catch(exception&) {
533         if (handle)
534             dlclose(handle);
535         throw;
536     }
537 #else
538 # error "Don't know about dynamic loading on this platform!"
539 #endif
540     m_libhandles.push_back(handle);
541     log.info("loaded extension: %s", resolved.c_str());
542     return true;
543 }
544
545 #ifndef XMLTOOLING_NO_XMLSEC
546 void xmltooling::log_openssl()
547 {
548     const char* file;
549     const char* data;
550     int flags,line;
551
552     unsigned long code=ERR_get_error_line_data(&file,&line,&data,&flags);
553     while (code) {
554         Category& log=Category::getInstance("OpenSSL");
555         log.errorStream() << "error code: " << code << " in " << file << ", line " << line << logging::eol;
556         if (data && (flags & ERR_TXT_STRING))
557             log.errorStream() << "error data: " << data << logging::eol;
558         code=ERR_get_error_line_data(&file,&line,&data,&flags);
559     }
560 }
561
562 XSECCryptoX509CRL* XMLToolingInternalConfig::X509CRL() const
563 {
564     return new OpenSSLCryptoX509CRL();
565 }
566
567 void XMLToolingInternalConfig::registerXMLAlgorithms()
568 {
569     registerXMLAlgorithm(DSIGConstants::s_unicodeStrURIRSA_MD5, "RSA", 0);
570     registerXMLAlgorithm(DSIGConstants::s_unicodeStrURIRSA_SHA1, "RSA", 0);
571     registerXMLAlgorithm(DSIGConstants::s_unicodeStrURIRSA_SHA224, "RSA", 0);
572     registerXMLAlgorithm(DSIGConstants::s_unicodeStrURIRSA_SHA256, "RSA", 0);
573     registerXMLAlgorithm(DSIGConstants::s_unicodeStrURIRSA_SHA384, "RSA", 0);
574     registerXMLAlgorithm(DSIGConstants::s_unicodeStrURIRSA_SHA512, "RSA", 0);
575
576     registerXMLAlgorithm(DSIGConstants::s_unicodeStrURIRSA_1_5, "RSA", 0);
577     registerXMLAlgorithm(DSIGConstants::s_unicodeStrURIRSA_OAEP_MGFP1, "RSA", 0);
578
579     registerXMLAlgorithm(DSIGConstants::s_unicodeStrURIDSA_SHA1, "DSA", 0);
580
581     registerXMLAlgorithm(DSIGConstants::s_unicodeStrURIHMAC_SHA1, "HMAC", 0);
582     registerXMLAlgorithm(DSIGConstants::s_unicodeStrURIHMAC_SHA224, "HMAC", 0);
583     registerXMLAlgorithm(DSIGConstants::s_unicodeStrURIHMAC_SHA256, "HMAC", 0);
584     registerXMLAlgorithm(DSIGConstants::s_unicodeStrURIHMAC_SHA384, "HMAC", 0);
585     registerXMLAlgorithm(DSIGConstants::s_unicodeStrURIHMAC_SHA512, "HMAC", 0);
586
587     registerXMLAlgorithm(DSIGConstants::s_unicodeStrURI3DES_CBC, "DESede", 192);
588     registerXMLAlgorithm(DSIGConstants::s_unicodeStrURIKW_3DES, "DESede", 192);
589
590     registerXMLAlgorithm(DSIGConstants::s_unicodeStrURIAES128_CBC, "AES", 128);
591     registerXMLAlgorithm(DSIGConstants::s_unicodeStrURIKW_AES128, "AES", 128);
592
593     registerXMLAlgorithm(DSIGConstants::s_unicodeStrURIAES192_CBC, "AES", 192);
594     registerXMLAlgorithm(DSIGConstants::s_unicodeStrURIKW_AES192, "AES", 192);
595
596     registerXMLAlgorithm(DSIGConstants::s_unicodeStrURIAES256_CBC, "AES", 256);
597     registerXMLAlgorithm(DSIGConstants::s_unicodeStrURIKW_AES256, "AES", 256);
598 }
599 #endif
600
601 #ifdef WIN32
602
603 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
604 {
605     if (fdwReason == DLL_THREAD_DETACH || fdwReason == DLL_PROCESS_DETACH)
606         ThreadKey::onDetach();
607     return TRUE;
608 }
609
610 #endif