1 /*
2  * tcpconnect.cpp
3  *
4  *  Created on: 27.02.2010
5  *      Author: ed
6  */
7 
8 #include <sys/select.h>
9 
10 #define LOCAL_DEBUG
11 #include "debug.h"
12 
13 #include "meta.h"
14 #include "tcpconnect.h"
15 
16 #include "acfg.h"
17 #include "caddrinfo.h"
18 #include <signal.h>
19 #include "fileio.h"
20 #include "fileitem.h"
21 #include "cleaner.h"
22 #include <tuple>
23 
24 using namespace std;
25 
26 //#warning FIXME, hack
27 //#define NOCONCACHE
28 
29 #ifdef DEBUG
30 #include <atomic>
31 atomic_int nConCount(0), nDisconCount(0), nReuseCount(0);
32 #endif
33 
34 #ifdef HAVE_SSL
35 #include <openssl/evp.h>
36 #include "openssl/bio.h"
37 #include "openssl/ssl.h"
38 #include "openssl/err.h"
39 #include <openssl/rand.h>
40 #include <openssl/sha.h>
41 #include <openssl/crypto.h>
42 #include <openssl/x509_vfy.h>
43 #include <openssl/x509v3.h>
44 #ifndef HAVE_SSL_HOST_VALIDATION
45 extern "C"
46 {
47 #include "oldssl-workaround/openssl_hostname_validation.h"
48 }
49 #endif
50 #endif
51 
52 namespace acng
53 {
54 
55 std::atomic_uint dl_con_factory::g_nconns(0);
56 dl_con_factory g_tcp_con_factory;
57 
tcpconnect(cfg::tRepoData::IHookHandler * pObserver)58 tcpconnect::tcpconnect(cfg::tRepoData::IHookHandler *pObserver) : m_pStateObserver(pObserver)
59 {
60 	if(cfg::maxdlspeed != cfg::RESERVED_DEFVAL)
61 		dl_con_factory::g_nconns.fetch_add(1);
62 	if(pObserver)
63 		pObserver->OnAccess();
64 }
65 
~tcpconnect()66 tcpconnect::~tcpconnect()
67 {
68 	LOGSTART("tcpconnect::~tcpconnect, terminating outgoing connection class");
69 	Disconnect();
70 	if(cfg::maxdlspeed != cfg::RESERVED_DEFVAL)
71 		dl_con_factory::g_nconns.fetch_add(-1);
72 #ifdef HAVE_SSL
73 	if(m_ctx)
74 	{
75 		SSL_CTX_free(m_ctx);
76 		m_ctx=0;
77 	}
78 #endif
79 	if(m_pStateObserver)
80 	{
81 		m_pStateObserver->OnRelease();
82 		m_pStateObserver=nullptr;
83 
84 	}
85 }
86 
87 /*! \brief Helper to flush data stream contents reliable and close the connection then
88  * DUDES, who write TCP implementations... why can this just not be done easy and reliable? Why do we need hacks like the method below?
89  For details, see: http://blog.netherlabs.nl/articles/2009/01/18/the-ultimate-so_linger-page-or-why-is-my-tcp-not-reliable
90  *
91  */
termsocket(int fd)92 void termsocket(int fd)
93 {
94 	LOGSTART2s("::termsocket", fd);
95 	if (fd < 0)
96 		return;
97 
98 	fcntl(fd, F_SETFL, ~O_NONBLOCK & fcntl(fd, F_GETFL));
99 	::shutdown(fd, SHUT_WR);
100 	char buf[40];
101 	LOG("waiting for peer to react");
102 	while(true)
103 	{
104 		int r=recv(fd, buf, 40, MSG_WAITALL);
105 		if(0 == r)
106 			break;
107 		if(r < 0)
108 		{
109 			if(errno == EINTR)
110 				continue;
111 			break; // XXX error case, actually
112 		}
113 	}
114 
115 	while (0 != ::close(fd))
116 	{
117 		if (errno != EINTR)
118 			break;
119 	};
120 }
121 
connect_timeout(int sockfd,const struct sockaddr * addr,socklen_t addrlen,time_t timeout,bool bAssumeNonBlock)122 static int connect_timeout(int sockfd, const struct sockaddr *addr, socklen_t addrlen, time_t timeout, bool bAssumeNonBlock)
123 {
124 	long stflags;
125 	struct timeval tv;
126 	fd_set wfds;
127 	int res;
128 
129 	tv.tv_sec = timeout;
130 	tv.tv_usec = 0;
131 
132 	if(!bAssumeNonBlock)
133 	{
134 		if ((stflags = fcntl(sockfd, F_GETFL, nullptr)) < 0)
135 			return -1;
136 
137 		// Set to non-blocking mode.
138 		if (fcntl(sockfd, F_SETFL, stflags | O_NONBLOCK) < 0)
139 			return -1;
140 	}
141 	res = connect(sockfd, addr, addrlen);
142 	if (res < 0) {
143 		if (EINPROGRESS == errno)
144 		{
145 			for (;;) {
146 				// Wait for connection.
147 				FD_ZERO(&wfds);
148 				FD_SET(sockfd, &wfds);
149 				res = select(sockfd+1, nullptr, &wfds, nullptr, &tv);
150 				if (res < 0)
151 				{
152 					if (EINTR != errno)
153 						return -1;
154 				}
155 				else if (res > 0)
156 				{
157 					// Socket selected for writing.
158 					int err;
159 					socklen_t optlen = sizeof(err);
160 
161 					if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&err, &optlen) < 0)
162 						return -1;
163 
164 					if (err)
165 					{
166 						errno = err;
167 						return -1;
168 					}
169 
170 					break;
171 				} else {
172 					// Timeout.
173 					errno = ETIMEDOUT;
174 					return -1;
175 				}
176 			}
177 		} else {
178 			return -1;
179 		}
180 	}
181 
182 	if(!bAssumeNonBlock && fcntl(sockfd, F_SETFL, stflags) < 0) // Set back to original mode
183 		return -1;
184 
185 	return 0;
186 }
187 
_Connect(string & sErrorMsg,int timeout)188 inline bool tcpconnect::_Connect(string & sErrorMsg, int timeout)
189 {
190 	LOGSTART2("tcpconnect::_Connect", "hostname: " << m_sHostName);
191 
192 	auto dns = CAddrInfo::CachedResolve(m_sHostName, m_sPort, sErrorMsg);
193 
194 	if(!dns)
195 	{
196 		USRDBG(sErrorMsg);
197 		return false; // sErrorMsg got the info already, no other chance to fix it
198 	}
199 
200 	::signal(SIGPIPE, SIG_IGN);
201 
202 	// always consider first family, afterwards stop when no more specified
203 	for (unsigned i=0; i< _countof(cfg::conprotos) && (0==i || cfg::conprotos[i]!=PF_UNSPEC); ++i)
204 	{
205 		for (auto pInfo = dns->m_addrInfo; pInfo; pInfo = pInfo->ai_next)
206 		{
207 			if (cfg::conprotos[i] != PF_UNSPEC && cfg::conprotos[i] != pInfo->ai_family)
208 				continue;
209 
210 			ldbg("Creating socket for " << m_sHostName);
211 
212 			if (pInfo->ai_socktype != SOCK_STREAM || pInfo->ai_protocol != IPPROTO_TCP)
213 				continue;
214 
215 			Disconnect();
216 
217 			m_conFd = ::socket(pInfo->ai_family, pInfo->ai_socktype, pInfo->ai_protocol);
218 			if (m_conFd < 0)
219 				continue;
220 
221 #ifndef NO_TCP_TUNNING
222 			{
223 				int yes(1);
224 				::setsockopt(m_conFd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
225 				::setsockopt(m_conFd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes));
226 			}
227 #endif
228 			set_nb(m_conFd);
229 			if (acng::connect_timeout(m_conFd, pInfo->ai_addr, pInfo->ai_addrlen, timeout, true) < 0)
230 			{
231 				if(errno==ETIMEDOUT)
232 					sErrorMsg="Connection timeout";
233 #ifndef MINIBUILD
234 				USRDBG(tErrnoFmter("Outgoing connection for ") << m_sHostName << ", Port: " << m_sPort );
235 #endif
236 				continue;
237 			}
238 #ifdef DEBUG
239 			nConCount.fetch_add(1);
240 #endif
241 			ldbg("connect() ok");
242 
243 			return true;
244 		}
245 	}
246 
247 #ifdef MINIBUILD
248 	sErrorMsg = "500 Connection failure";
249 #else
250 	// format the last available error message for the user
251 	sErrorMsg=tErrnoFmter("500 Connection failure: ");
252 #endif
253 	ldbg("Force reconnect, con. failure");
254 	Disconnect();
255 	return false;
256 }
257 
Disconnect()258 void tcpconnect::Disconnect()
259 {
260 	LOGSTART("tcpconnect::_Disconnect");
261 
262 #ifdef DEBUG
263 	nDisconCount.fetch_add(m_conFd >=0);
264 #endif
265 
266 #ifdef HAVE_SSL
267 	if(m_bio)
268 		BIO_free_all(m_bio), m_bio=nullptr;
269 #endif
270 
271 	m_lastFile.reset();
272 
273 	termsocket_quick(m_conFd);
274 }
275 acmutex spareConPoolMx;
276 multimap<tuple<string,string SSL_OPT_ARG(bool) >,
277 		std::pair<tDlStreamHandle, time_t> > spareConPool;
278 
CreateConnected(cmstring & sHostname,cmstring & sPort,mstring & sErrOut,bool * pbSecondHand,cfg::tRepoData::IHookHandler * pStateTracker,bool bSsl,int timeout,bool nocache)279 tDlStreamHandle dl_con_factory::CreateConnected(cmstring &sHostname, cmstring &sPort,
280 		mstring &sErrOut, bool *pbSecondHand, cfg::tRepoData::IHookHandler *pStateTracker
281 		,bool bSsl, int timeout, bool nocache)
282 {
283 	LOGSTART2s("tcpconnect::CreateConnected", "hostname: " << sHostname << ", port: " << sPort
284 			<< (bSsl?" with ssl":" , no ssl"));
285 
286 	tDlStreamHandle p;
287 #ifndef HAVE_SSL
288 	if(bSsl)
289 	{
290 		log::err("E_NOTIMPLEMENTED: SSL");
291 		return p;
292 	}
293 #endif
294 
295 	bool bReused=false;
296 	auto key = make_tuple(sHostname, sPort SSL_OPT_ARG(bSsl) );
297 
298 #ifdef NOCONCACHE
299 	p.reset(new tcpconnect(pStateTracker));
300 	if(p)
301 	{
302 		if(!p->_Connect(sHostname, sPort, sErrOut) || p->GetFD()<0) // failed or worthless
303 			p.reset();
304 	}
305 #else
306 	if(!nocache)
307 	{
308 		// mutex context
309 		lockguard __g(spareConPoolMx);
310 		auto it=spareConPool.find(key);
311 		if(spareConPool.end() != it)
312 		{
313 			p=it->second.first;
314 			spareConPool.erase(it);
315 			bReused = true;
316 			ldbg("got connection " << p.get() << " from the idle pool");
317 
318 			// it was reset in connection recycling, restart now
319 			if(pStateTracker)
320 			{
321 				p->m_pStateObserver = pStateTracker;
322 				pStateTracker->OnAccess();
323 			}
324 #ifdef DEBUG
325 			nReuseCount.fetch_add(1);
326 #endif
327 		}
328 	}
329 #endif
330 
331 	if(!p)
332 	{
333 		p.reset(new tcpconnect(pStateTracker));
334 		if(p)
335 		{
336 			p->m_sHostName=sHostname;
337 			p->m_sPort=sPort;
338 		}
339 
340 		if(!p || !p->_Connect(sErrOut, timeout) || p->GetFD()<0) // failed or worthless
341 			p.reset();
342 #ifdef HAVE_SSL
343 		else if(bSsl)
344 		{
345 			if(!p->SSLinit(sErrOut, sHostname, sPort))
346 			{
347 				p.reset();
348 				LOG("ssl init error");
349 			}
350 		}
351 #endif
352 	}
353 
354 	if(pbSecondHand)
355 		*pbSecondHand = bReused;
356 
357 	return p;
358 }
359 
RecycleIdleConnection(tDlStreamHandle & handle)360 void dl_con_factory::RecycleIdleConnection(tDlStreamHandle & handle)
361 {
362 	if(!handle)
363 		return;
364 
365 	LOGSTART2s("tcpconnect::RecycleIdleConnection", handle->m_sHostName);
366 
367 	if(handle->m_pStateObserver)
368 	{
369 		handle->m_pStateObserver->OnRelease();
370 		handle->m_pStateObserver = nullptr;
371 	}
372 
373 	if(! cfg::persistoutgoing)
374 	{
375 		ldbg("not caching outgoing connections, drop " << handle.get());
376 		handle.reset();
377 		return;
378 	}
379 
380 #ifdef DEBUG
381 	if(check_read_state(handle->GetFD()))
382 	{
383 		acbuf checker;
384 		checker.setsize(300000);
385 		checker.sysread(handle->GetFD());
386 
387 	}
388 #endif
389 
390 	auto& host = handle->GetHostname();
391 	if (!host.empty())
392 	{
393 #ifndef NOCONCACHE
394 		time_t now = GetTime();
395 		lockguard __g(spareConPoolMx);
396 		ldbg("caching connection " << handle.get());
397 
398 		// a DOS?
399 		if (spareConPool.size() < 50)
400 		{
401 			EMPLACE_PAIR_COMPAT(spareConPool, make_tuple(host, handle->GetPort()
402 					SSL_OPT_ARG(handle->m_bio) ), make_pair(handle, now));
403 #ifndef MINIBUILD
404 			g_victor.ScheduleFor(now + TIME_SOCKET_EXPIRE_CLOSE, cleaner::TYPE_EXCONNS);
405 #endif
406 		}
407 #endif
408 	}
409 
410 	handle.reset();
411 }
412 
BackgroundCleanup()413 time_t dl_con_factory::BackgroundCleanup()
414 {
415 	lockguard __g(spareConPoolMx);
416 	time_t now=GetTime();
417 
418 	fd_set rfds;
419 	FD_ZERO(&rfds);
420 	int nMaxFd=0;
421 
422 	// either drop the old ones, or stuff them into a quick select call to find the good sockets
423 	for (auto it = spareConPool.begin(); it != spareConPool.end();)
424 	{
425 		if (now >= (it->second.second + TIME_SOCKET_EXPIRE_CLOSE))
426 			it = spareConPool.erase(it);
427 		else
428 		{
429 			int fd = it->second.first->GetFD();
430 			FD_SET(fd, &rfds);
431 			nMaxFd = max(nMaxFd, fd);
432 			++it;
433 		}
434 	}
435 	// if they have to send something, that must the be the CLOSE signal
436 	struct timeval tv;
437 	tv.tv_sec = 0;
438 	tv.tv_usec = 1;
439 	int r=select(nMaxFd + 1, &rfds, nullptr, nullptr, &tv);
440 	// on error, also do nothing, or stop when r fds are processed
441 	for (auto it = spareConPool.begin(); r>0 && it != spareConPool.end(); r--)
442 	{
443 		if(FD_ISSET(it->second.first->GetFD(), &rfds))
444 			it = spareConPool.erase(it);
445 		else
446 			++it;
447 	}
448 
449 	return spareConPool.empty() ? END_OF_TIME : GetTime()+TIME_SOCKET_EXPIRE_CLOSE/4+1;
450 }
451 
KillLastFile()452 void tcpconnect::KillLastFile()
453 {
454 #ifndef MINIBUILD
455 	tFileItemPtr p = m_lastFile.lock();
456 	if (!p)
457 		return;
458 	p->SetupClean(true);
459 #endif
460 }
461 
dump_status()462 void dl_con_factory::dump_status()
463 {
464 	lockguard __g(spareConPoolMx);
465 	tSS msg;
466 	msg << "TCP connection cache:\n";
467 	for (const auto& x : spareConPool)
468 	{
469 		if(! x.second.first)
470 		{
471 			msg << "[BAD HANDLE] recycle at " << x.second.second << "\n";
472 			continue;
473 		}
474 
475 		msg << x.second.first->m_conFd << ": for "
476 				<< get<0>(x.first) << ":" << get<1>(x.first)
477 				<< ", recycled at " << x.second.second
478 				<< "\n";
479 	}
480 #ifdef DEBUG
481 	msg << "dbg counts, con: " << nConCount.load()
482 			<< " , discon: " << nDisconCount.load()
483 			<< " , reuse: " << nReuseCount.load() << "\n";
484 #endif
485 
486 	log::err(msg);
487 }
488 #ifdef HAVE_SSL
SSLinit(mstring & sErr,cmstring & sHostname,cmstring & sPort)489 bool tcpconnect::SSLinit(mstring &sErr, cmstring &sHostname, cmstring &sPort)
490 {
491 	SSL * ssl(nullptr);
492 	mstring ebuf;
493 
494 	auto withSslError = [&sErr](const char *perr)
495 					{
496 		sErr="500 SSL error: ";
497 		sErr+=(perr?perr:"Generic SSL failure");
498 		return false;
499 					};
500 	auto withLastSslError = [&withSslError]()
501 						{
502 		return withSslError(ERR_reason_error_string(ERR_get_error()));
503 						};
504 	auto withRetCode = [&withSslError, &ssl](int hret)
505 				{
506 		return withSslError(ERR_reason_error_string(SSL_get_error(ssl, hret)));
507 				};
508 
509 	// cleaned up in the destructor on EOL
510 	if(!m_ctx)
511 	{
512 		m_ctx = SSL_CTX_new(SSLv23_client_method());
513 		if (!m_ctx) return withLastSslError();
514 
515 		SSL_CTX_load_verify_locations(m_ctx,
516 				cfg::cafile.empty() ? nullptr : cfg::cafile.c_str(),
517 			cfg::capath.empty() ? nullptr : cfg::capath.c_str());
518 	}
519 
520 	ssl = SSL_new(m_ctx);
521 	if (!m_ctx) return withLastSslError();
522 
523 	// for SNI
524 	SSL_set_tlsext_host_name(ssl, sHostname.c_str());
525 
526 	{
527 #ifdef HAVE_SSL_HOST_VALIDATION
528 		auto param = SSL_get0_param(ssl);
529 		/* Enable automatic hostname checks */
530 		X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
531 		X509_VERIFY_PARAM_set1_host(param, sHostname.c_str(), 0);
532 #endif
533 		/* Configure a non-zero callback if desired */
534 		SSL_set_verify(ssl, SSL_VERIFY_PEER, 0);
535 	}
536 
537 	// mark it connected and prepare for non-blocking mode
538  	SSL_set_connect_state(ssl);
539  	SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY
540  			| SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER
541  			| SSL_MODE_ENABLE_PARTIAL_WRITE);
542 
543  	auto hret=SSL_set_fd(ssl, m_conFd);
544  	if(hret != 1) return withRetCode(hret);
545 
546  	while(true)
547  	{
548  		hret=SSL_connect(ssl);
549  		if(hret == 1 )
550  			break;
551  		if(hret == 0)
552  			return withRetCode(hret);
553 
554 		fd_set rfds, wfds;
555 		FD_ZERO(&rfds);
556 		FD_ZERO(&wfds);
557  		switch(SSL_get_error(ssl, hret))
558  		{
559  		case SSL_ERROR_WANT_READ:
560  			FD_SET(m_conFd, &rfds);
561  			break;
562  		case SSL_ERROR_WANT_WRITE:
563  			FD_SET(m_conFd, &wfds);
564  			break;
565  		default:
566  			return withRetCode(hret);
567  		}
568  		struct timeval tv;
569  		tv.tv_sec = cfg::nettimeout;
570  		tv.tv_usec = 0;
571 		int nReady=select(m_conFd+1, &rfds, &wfds, nullptr, &tv);
572 		if(!nReady) return withSslError("Socket timeout");
573 		if (nReady<0)
574 		{
575 #ifndef MINIBUILD
576 			ebuf=tErrnoFmter("Socket error");
577 			return withSslError(ebuf.c_str());
578 #else
579 			return withSslError("Socket error");
580 #endif
581 		}
582  	}
583 
584  	m_bio = BIO_new(BIO_f_ssl());
585  	if(!m_bio) return withSslError("IO initialization error");
586  	// not sure we need it but maybe the handshake can access this data
587  	BIO_set_conn_hostname(m_bio, sHostname.c_str());
588  	BIO_set_conn_port(m_bio, sPort.c_str());
589 
590  	BIO_set_ssl(m_bio, ssl, BIO_NOCLOSE);
591 
592  	BIO_set_nbio(m_bio, 1);
593 	set_nb(m_conFd);
594 
595 	if(!cfg::nsafriendly)
596 	{
597 		X509* server_cert = nullptr;
598 		hret=SSL_get_verify_result(ssl);
599 		if( hret != X509_V_OK)
600 			return withSslError(X509_verify_cert_error_string(hret));
601 		server_cert = SSL_get_peer_certificate(ssl);
602 		if(server_cert)
603 		{
604 			// XXX: maybe extract the real name to a buffer and report it additionally?
605 			// X509_NAME_oneline(X509_get_subject_name (server_cert), cert_str, sizeof (cert_str));
606 #ifndef HAVE_SSL_HOST_VALIDATION
607 			auto hcResult=validate_hostname(sHostname.c_str(), server_cert);
608 			X509_free(server_cert);
609 			if(hcResult != HostnameValidationResult::MatchFound)
610 				return withSslError("Incorrect remote certificate configuration");
611 #else
612 			X509_free(server_cert);
613 #endif
614 		}
615 		else // The handshake was successful although the server did not provide a certificate
616 			return withSslError("Incompatible remote certificate");
617 	}
618 	return true;
619 }
620 
621 //! Global initialization helper (might be non-reentrant)
globalSslInit()622 void globalSslInit()
623 {
624 	static bool inited=false;
625 	if(inited)
626 		return;
627 	inited = true;
628 	SSL_load_error_strings();
629 	ERR_load_BIO_strings();
630 	ERR_load_crypto_strings();
631 	ERR_load_SSL_strings();
632 	OpenSSL_add_all_algorithms();
633 	SSL_library_init();
634 }
635 
636 #endif
637 
StartTunnel(const tHttpUrl & realTarget,mstring & sError,cmstring * psAuthorization,bool bDoSSL)638 bool tcpconnect::StartTunnel(const tHttpUrl& realTarget, mstring& sError,
639 		cmstring *psAuthorization, bool bDoSSL)
640 {
641 	/*
642 	  CONNECT server.example.com:80 HTTP/1.1
643       Host: server.example.com:80
644       Proxy-Authorization: basic aGVsbG86d29ybGQ=
645 	 */
646 	tSS fmt;
647 	fmt << "CONNECT " << realTarget.sHost << ":" << realTarget.GetPort()
648 			<< " HTTP/1.1\r\nHost: " << realTarget.sHost << ":" << realTarget.GetPort()
649 			<< "\r\n";
650 	if(psAuthorization && !psAuthorization->empty())
651 	{
652 			fmt << "Proxy-Authorization: Basic "
653 					<< EncodeBase64Auth(*psAuthorization) << "\r\n";
654 	}
655 	fmt << "\r\n";
656 
657 	try
658 	{
659 		if (!fmt.send(m_conFd, &sError))
660 			return false;
661 
662 		fmt.clear();
663 		while (true)
664 		{
665 			fmt.setsize(4000);
666 			if (!fmt.recv(m_conFd, &sError))
667 				return false;
668 			if(fmt.freecapa()<=0)
669 			{
670 				sError = "503 Remote proxy error";
671 				return false;
672 			}
673 
674 			header h;
675 			auto n = h.Load(fmt.rptr(), fmt.size());
676 			if(!n)
677 				continue;
678 
679 			auto st = h.getStatus();
680 			if (n <= 0 || st == 404 /* just be sure it doesn't send crap */)
681 			{
682 				sError = "503 Tunnel setup failed";
683 				return false;
684 			}
685 
686 			if (st < 200 || st >= 300)
687 			{
688 				sError = h.frontLine;
689 				return false;
690 			}
691 			break;
692 		}
693 
694 		m_sHostName = realTarget.sHost;
695 		m_sPort = realTarget.GetPort();
696 #ifdef HAVE_SSL
697 		if (bDoSSL && !SSLinit(sError, m_sHostName, m_sPort))
698 		{
699 			m_sHostName.clear();
700 			return false;
701 		}
702 #else
703 		(void) bDoSSL;
704 #endif
705 	}
706 	catch(...)
707 	{
708 		return false;
709 	}
710 	return true;
711 }
712 
713 
714 
715 }
716