PageRenderTime 82ms CodeModel.GetById 19ms app.highlight 58ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/llmessage/llproxy.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 546 lines | 311 code | 72 blank | 163 comment | 44 complexity | 5a5d8e21245f26b6d90a05ad5eec6a45 MD5 | raw file
  1/**
  2 * @file llproxy.cpp
  3 * @brief UDP and HTTP proxy communications
  4 *
  5 * $LicenseInfo:firstyear=2011&license=viewerlgpl$
  6 * Second Life Viewer Source Code
  7 * Copyright (C) 2011, Linden Research, Inc.
  8 * 
  9 * This library is free software; you can redistribute it and/or
 10 * modify it under the terms of the GNU Lesser General Public
 11 * License as published by the Free Software Foundation;
 12 * version 2.1 of the License only.
 13 * 
 14 * This library is distributed in the hope that it will be useful,
 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 17 * Lesser General Public License for more details.
 18 * 
 19 * You should have received a copy of the GNU Lesser General Public
 20 * License along with this library; if not, write to the Free Software
 21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 22 * 
 23 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 24 * $/LicenseInfo$
 25 */
 26
 27#include "linden_common.h"
 28
 29#include "llproxy.h"
 30
 31#include <string>
 32#include <curl/curl.h>
 33
 34#include "llapr.h"
 35#include "llcurl.h"
 36#include "llhost.h"
 37
 38// Static class variable instances
 39
 40// We want this to be static to avoid excessive indirection on every
 41// incoming packet just to do a simple bool test. The getter for this
 42// member is also static
 43bool LLProxy::sUDPProxyEnabled = false;
 44
 45// Some helpful TCP static functions.
 46static apr_status_t tcp_blocking_handshake(LLSocket::ptr_t handle, char * dataout, apr_size_t outlen, char * datain, apr_size_t maxinlen); // Do a TCP data handshake
 47static LLSocket::ptr_t tcp_open_channel(LLHost host); // Open a TCP channel to a given host
 48static void tcp_close_channel(LLSocket::ptr_t* handle_ptr); // Close an open TCP channel
 49
 50LLProxy::LLProxy():
 51		mHTTPProxyEnabled(false),
 52		mProxyMutex(NULL),
 53		mUDPProxy(),
 54		mTCPProxy(),
 55		mHTTPProxy(),
 56		mProxyType(LLPROXY_SOCKS),
 57		mAuthMethodSelected(METHOD_NOAUTH),
 58		mSocksUsername(),
 59		mSocksPassword()
 60{
 61}
 62
 63LLProxy::~LLProxy()
 64{
 65	stopSOCKSProxy();
 66	disableHTTPProxy();
 67}
 68
 69/**
 70 * @brief Open the SOCKS 5 TCP control channel.
 71 *
 72 * Perform a SOCKS 5 authentication and UDP association with the proxy server.
 73 *
 74 * @param proxy The SOCKS 5 server to connect to.
 75 * @return SOCKS_OK if successful, otherwise a socks error code from llproxy.h.
 76 */
 77S32 LLProxy::proxyHandshake(LLHost proxy)
 78{
 79	S32 result;
 80
 81	/* SOCKS 5 Auth request */
 82	socks_auth_request_t  socks_auth_request;
 83	socks_auth_response_t socks_auth_response;
 84
 85	socks_auth_request.version		= SOCKS_VERSION;				// SOCKS version 5
 86	socks_auth_request.num_methods	= 1;							// Sending 1 method.
 87	socks_auth_request.methods		= getSelectedAuthMethod();		// Send only the selected method.
 88
 89	result = tcp_blocking_handshake(mProxyControlChannel,
 90									static_cast<char*>(static_cast<void*>(&socks_auth_request)),
 91									sizeof(socks_auth_request),
 92									static_cast<char*>(static_cast<void*>(&socks_auth_response)),
 93									sizeof(socks_auth_response));
 94	if (result != APR_SUCCESS)
 95	{
 96		LL_WARNS("Proxy") << "SOCKS authentication request failed, error on TCP control channel : " << result << LL_ENDL;
 97		stopSOCKSProxy();
 98		return SOCKS_CONNECT_ERROR;
 99	}
100
101	if (socks_auth_response.method == AUTH_NOT_ACCEPTABLE)
102	{
103		LL_WARNS("Proxy") << "SOCKS 5 server refused all our authentication methods." << LL_ENDL;
104		stopSOCKSProxy();
105		return SOCKS_NOT_ACCEPTABLE;
106	}
107
108	/* SOCKS 5 USERNAME/PASSWORD authentication */
109	if (socks_auth_response.method == METHOD_PASSWORD)
110	{
111		// The server has requested a username/password combination
112		std::string socks_username(getSocksUser());
113		std::string socks_password(getSocksPwd());
114		U32 request_size = socks_username.size() + socks_password.size() + 3;
115		char * password_auth = new char[request_size];
116		password_auth[0] = 0x01;
117		password_auth[1] = socks_username.size();
118		memcpy(&password_auth[2], socks_username.c_str(), socks_username.size());
119		password_auth[socks_username.size() + 2] = socks_password.size();
120		memcpy(&password_auth[socks_username.size() + 3], socks_password.c_str(), socks_password.size());
121
122		authmethod_password_reply_t password_reply;
123
124		result = tcp_blocking_handshake(mProxyControlChannel,
125										password_auth,
126										request_size,
127										static_cast<char*>(static_cast<void*>(&password_reply)),
128										sizeof(password_reply));
129		delete[] password_auth;
130
131		if (result != APR_SUCCESS)
132		{
133			LL_WARNS("Proxy") << "SOCKS authentication failed, error on TCP control channel : " << result << LL_ENDL;
134			stopSOCKSProxy();
135			return SOCKS_CONNECT_ERROR;
136		}
137
138		if (password_reply.status != AUTH_SUCCESS)
139		{
140			LL_WARNS("Proxy") << "SOCKS authentication failed" << LL_ENDL;
141			stopSOCKSProxy();
142			return SOCKS_AUTH_FAIL;
143		}
144	}
145
146	/* SOCKS5 connect request */
147
148	socks_command_request_t  connect_request;
149	socks_command_response_t connect_reply;
150
151	connect_request.version		= SOCKS_VERSION;         // SOCKS V5
152	connect_request.command		= COMMAND_UDP_ASSOCIATE; // Associate UDP
153	connect_request.reserved	= FIELD_RESERVED;
154	connect_request.atype		= ADDRESS_IPV4;
155	connect_request.address		= htonl(0); // 0.0.0.0
156	connect_request.port		= htons(0); // 0
157	// "If the client is not in possession of the information at the time of the UDP ASSOCIATE,
158	//  the client MUST use a port number and address of all zeros. RFC 1928"
159
160	result = tcp_blocking_handshake(mProxyControlChannel,
161									static_cast<char*>(static_cast<void*>(&connect_request)),
162									sizeof(connect_request),
163									static_cast<char*>(static_cast<void*>(&connect_reply)),
164									sizeof(connect_reply));
165	if (result != APR_SUCCESS)
166	{
167		LL_WARNS("Proxy") << "SOCKS connect request failed, error on TCP control channel : " << result << LL_ENDL;
168		stopSOCKSProxy();
169		return SOCKS_CONNECT_ERROR;
170	}
171
172	if (connect_reply.reply != REPLY_REQUEST_GRANTED)
173	{
174		LL_WARNS("Proxy") << "Connection to SOCKS 5 server failed, UDP forward request not granted" << LL_ENDL;
175		stopSOCKSProxy();
176		return SOCKS_UDP_FWD_NOT_GRANTED;
177	}
178
179	mUDPProxy.setPort(ntohs(connect_reply.port)); // reply port is in network byte order
180	mUDPProxy.setAddress(proxy.getAddress());
181	// The connection was successful. We now have the UDP port to send requests that need forwarding to.
182	LL_INFOS("Proxy") << "SOCKS 5 UDP proxy connected on " << mUDPProxy << LL_ENDL;
183
184	return SOCKS_OK;
185}
186
187/**
188 * @brief Initiates a SOCKS 5 proxy session.
189 *
190 * Performs basic checks on host to verify that it is a valid address. Opens the control channel
191 * and then negotiates the proxy connection with the server. Closes any existing SOCKS
192 * connection before proceeding. Also disables an HTTP proxy if it is using SOCKS as the proxy.
193 *
194 *
195 * @param host Socks server to connect to.
196 * @return SOCKS_OK if successful, otherwise a SOCKS error code defined in llproxy.h.
197 */
198S32 LLProxy::startSOCKSProxy(LLHost host)
199{
200	if (host.isOk())
201	{
202		mTCPProxy = host;
203	}
204	else
205	{
206		return SOCKS_INVALID_HOST;
207	}
208
209	// Close any running SOCKS connection.
210	stopSOCKSProxy();
211
212	mProxyControlChannel = tcp_open_channel(mTCPProxy);
213	if (!mProxyControlChannel)
214	{
215		return SOCKS_HOST_CONNECT_FAILED;
216	}
217
218	S32 status = proxyHandshake(mTCPProxy);
219
220	if (status != SOCKS_OK)
221	{
222		// Shut down the proxy if any of the above steps failed.
223		stopSOCKSProxy();
224	}
225	else
226	{
227		// Connection was successful.
228		sUDPProxyEnabled = true;
229	}
230
231	return status;
232}
233
234/**
235 * @brief Stop using the SOCKS 5 proxy.
236 *
237 * This will stop sending UDP packets through the SOCKS 5 proxy
238 * and will also stop the HTTP proxy if it is configured to use SOCKS.
239 * The proxy control channel will also be disconnected.
240 */
241void LLProxy::stopSOCKSProxy()
242{
243	sUDPProxyEnabled = false;
244
245	// If the SOCKS proxy is requested to stop and we are using that for HTTP as well
246	// then we must shut down any HTTP proxy operations. But it is allowable if web
247	// proxy is being used to continue proxying HTTP.
248
249	if (LLPROXY_SOCKS == getHTTPProxyType())
250	{
251		disableHTTPProxy();
252	}
253
254	if (mProxyControlChannel)
255	{
256		tcp_close_channel(&mProxyControlChannel);
257	}
258}
259
260/**
261 * @brief Set the proxy's SOCKS authentication method to none.
262 */
263void LLProxy::setAuthNone()
264{
265	LLMutexLock lock(&mProxyMutex);
266
267	mAuthMethodSelected = METHOD_NOAUTH;
268}
269
270/**
271 * @brief Set the proxy's SOCKS authentication method to password.
272 *
273 * Check whether the lengths of the supplied username
274 * and password conform to the lengths allowed by the
275 * SOCKS protocol.
276 *
277 * @param 	username The SOCKS username to send.
278 * @param 	password The SOCKS password to send.
279 * @return  Return true if applying the settings was successful. No changes are made if false.
280 *
281 */
282bool LLProxy::setAuthPassword(const std::string &username, const std::string &password)
283{
284	if (username.length() > SOCKSMAXUSERNAMELEN || password.length() > SOCKSMAXPASSWORDLEN ||
285			username.length() < SOCKSMINUSERNAMELEN || password.length() < SOCKSMINPASSWORDLEN)
286	{
287		LL_WARNS("Proxy") << "Invalid SOCKS 5 password or username length." << LL_ENDL;
288		return false;
289	}
290
291	LLMutexLock lock(&mProxyMutex);
292
293	mAuthMethodSelected = METHOD_PASSWORD;
294	mSocksUsername      = username;
295	mSocksPassword      = password;
296
297	return true;
298}
299
300/**
301 * @brief Enable the HTTP proxy for either SOCKS or HTTP.
302 *
303 * Check the supplied host to see if it is a valid IP and port.
304 *
305 * @param httpHost Proxy server to connect to.
306 * @param type Is the host a SOCKS or HTTP proxy.
307 * @return Return true if applying the setting was successful. No changes are made if false.
308 */
309bool LLProxy::enableHTTPProxy(LLHost httpHost, LLHttpProxyType type)
310{
311	if (!httpHost.isOk())
312	{
313		LL_WARNS("Proxy") << "Invalid SOCKS 5 Server" << LL_ENDL;
314		return false;
315	}
316
317	LLMutexLock lock(&mProxyMutex);
318
319	mHTTPProxy        = httpHost;
320	mProxyType        = type;
321
322	mHTTPProxyEnabled = true;
323
324	return true;
325}
326
327/**
328 * @brief Enable the HTTP proxy without changing the proxy settings.
329 *
330 * This should not be called unless the proxy has already been set up.
331 *
332 * @return Return true only if the current settings are valid and the proxy was enabled.
333 */
334bool LLProxy::enableHTTPProxy()
335{
336	bool ok;
337
338	LLMutexLock lock(&mProxyMutex);
339
340	ok = (mHTTPProxy.isOk());
341	if (ok)
342	{
343		mHTTPProxyEnabled = true;
344	}
345
346	return ok;
347}
348
349/**
350 * @brief Disable the HTTP proxy.
351 */
352void LLProxy::disableHTTPProxy()
353{
354	LLMutexLock lock(&mProxyMutex);
355
356	mHTTPProxyEnabled = false;
357}
358
359/**
360 * @brief Get the currently selected HTTP proxy type
361 */
362LLHttpProxyType LLProxy::getHTTPProxyType() const
363{
364	LLMutexLock lock(&mProxyMutex);
365	return mProxyType;
366}
367
368/**
369 * @brief Get the SOCKS 5 password.
370 */
371std::string LLProxy::getSocksPwd() const
372{
373	LLMutexLock lock(&mProxyMutex);
374	return mSocksPassword;
375}
376
377/**
378 * @brief Get the SOCKS 5 username.
379 */
380std::string LLProxy::getSocksUser() const
381{
382	LLMutexLock lock(&mProxyMutex);
383	return mSocksUsername;
384}
385
386/**
387 * @brief Get the currently selected SOCKS 5 authentication method.
388 *
389 * @return Returns either none or password.
390 */
391LLSocks5AuthType LLProxy::getSelectedAuthMethod() const
392{
393	LLMutexLock lock(&mProxyMutex);
394	return mAuthMethodSelected;
395}
396
397/**
398 * @brief Stop the LLProxy and make certain that any APR pools and classes are deleted before terminating APR.
399 *
400 * Deletes the LLProxy singleton, destroying the APR pool used by the control channel as well as .
401 */
402//static
403void LLProxy::cleanupClass()
404{
405	getInstance()->stopSOCKSProxy();
406	deleteSingleton();
407}
408
409void LLProxy::applyProxySettings(LLCurlEasyRequest* handle)
410{
411	applyProxySettings(handle->getEasy());
412}
413
414void LLProxy::applyProxySettings(LLCurl::Easy* handle)
415{
416	applyProxySettings(handle->getCurlHandle());
417}
418
419/**
420 * @brief Apply proxy settings to a CuRL request if an HTTP proxy is enabled.
421 *
422 * This method has been designed to be safe to call from
423 * any thread in the viewer.  This allows requests in the
424 * texture fetch thread to be aware of the proxy settings.
425 * When the HTTP proxy is enabled, the proxy mutex will
426 * be locked every time this method is called.
427 *
428 * @param handle A pointer to a valid CURL request, before it has been performed.
429 */
430void LLProxy::applyProxySettings(CURL* handle)
431{
432	// Do a faster unlocked check to see if we are supposed to proxy.
433	if (mHTTPProxyEnabled)
434	{
435		// We think we should proxy, lock the proxy mutex.
436		LLMutexLock lock(&mProxyMutex);
437		// Now test again to verify that the proxy wasn't disabled between the first check and the lock.
438		if (mHTTPProxyEnabled)
439		{
440			LLCurlFF::check_easy_code(curl_easy_setopt(handle, CURLOPT_PROXY, mHTTPProxy.getIPString().c_str()));
441			LLCurlFF::check_easy_code(curl_easy_setopt(handle, CURLOPT_PROXYPORT, mHTTPProxy.getPort()));
442
443			if (mProxyType == LLPROXY_SOCKS)
444			{
445				LLCurlFF::check_easy_code(curl_easy_setopt(handle, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5));
446				if (mAuthMethodSelected == METHOD_PASSWORD)
447				{
448					std::string auth_string = mSocksUsername + ":" + mSocksPassword;
449					LLCurlFF::check_easy_code(curl_easy_setopt(handle, CURLOPT_PROXYUSERPWD, auth_string.c_str()));
450				}
451			}
452			else
453			{
454				LLCurlFF::check_easy_code(curl_easy_setopt(handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP));
455			}
456		}
457	}
458}
459
460/**
461 * @brief Send one TCP packet and receive one in return.
462 *
463 * This operation is done synchronously with a 1000ms timeout. Therefore, it should not be used when a blocking
464 * operation would impact the operation of the viewer.
465 *
466 * @param handle_ptr 	Pointer to a connected LLSocket of type STREAM_TCP.
467 * @param dataout		Data to send.
468 * @param outlen		Length of dataout.
469 * @param datain		Buffer for received data. Undefined if return value is not APR_SUCCESS.
470 * @param maxinlen		Maximum possible length of received data.  Short reads are allowed.
471 * @return 				Indicates APR status code of exchange. APR_SUCCESS if exchange was successful, -1 if invalid data length was received.
472 */
473static apr_status_t tcp_blocking_handshake(LLSocket::ptr_t handle, char * dataout, apr_size_t outlen, char * datain, apr_size_t maxinlen)
474{
475	apr_socket_t* apr_socket = handle->getSocket();
476	apr_status_t rv = APR_SUCCESS;
477
478	apr_size_t expected_len = outlen;
479
480	handle->setBlocking(1000);
481
482  	rv = apr_socket_send(apr_socket, dataout, &outlen);
483	if (APR_SUCCESS != rv)
484	{
485		LL_WARNS("Proxy") << "Error sending data to proxy control channel, status: " << rv << LL_ENDL;
486		ll_apr_warn_status(rv);
487	}
488	else if (expected_len != outlen)
489	{
490		LL_WARNS("Proxy") << "Incorrect data length sent. Expected: " << expected_len <<
491				" Sent: " << outlen << LL_ENDL;
492		rv = -1;
493	}
494
495	if (APR_SUCCESS == rv)
496	{
497		expected_len = maxinlen;
498		rv = apr_socket_recv(apr_socket, datain, &maxinlen);
499		if (rv != APR_SUCCESS)
500		{
501			LL_WARNS("Proxy") << "Error receiving data from proxy control channel, status: " << rv << LL_ENDL;
502			ll_apr_warn_status(rv);
503		}
504		else if (expected_len < maxinlen)
505		{
506			LL_WARNS("Proxy") << "Incorrect data length received. Expected: " << expected_len <<
507					" Received: " << maxinlen << LL_ENDL;
508			rv = -1;
509		}
510	}
511
512	handle->setNonBlocking();
513
514	return rv;
515}
516
517/**
518 * @brief Open a LLSocket and do a blocking connect to the chosen host.
519 *
520 * Checks for a successful connection, and makes sure the connection is closed if it fails.
521 *
522 * @param host		The host to open the connection to.
523 * @return			The created socket.  Will evaluate as NULL if the connection is unsuccessful.
524 */
525static LLSocket::ptr_t tcp_open_channel(LLHost host)
526{
527	LLSocket::ptr_t socket = LLSocket::create(NULL, LLSocket::STREAM_TCP);
528	bool connected = socket->blockingConnect(host);
529	if (!connected)
530	{
531		tcp_close_channel(&socket);
532	}
533
534	return socket;
535}
536
537/**
538 * @brief Close the socket.
539 *
540 * @param handle_ptr The handle of the socket being closed. A pointer-to-pointer to avoid increasing the use count.
541 */
542static void tcp_close_channel(LLSocket::ptr_t* handle_ptr)
543{
544	LL_DEBUGS("Proxy") << "Resetting proxy LLSocket handle, use_count == " << handle_ptr->use_count() << LL_ENDL;
545	handle_ptr->reset();
546}