summaryrefslogtreecommitdiff
path: root/indra/llmessage/llproxy.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llmessage/llproxy.cpp')
-rw-r--r--indra/llmessage/llproxy.cpp551
1 files changed, 551 insertions, 0 deletions
diff --git a/indra/llmessage/llproxy.cpp b/indra/llmessage/llproxy.cpp
new file mode 100644
index 0000000000..3f4a6accbf
--- /dev/null
+++ b/indra/llmessage/llproxy.cpp
@@ -0,0 +1,551 @@
+/**
+ * @file llproxy.cpp
+ * @brief UDP and HTTP proxy communications
+ *
+ * $LicenseInfo:firstyear=2011&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2011, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "linden_common.h"
+
+#include "llproxy.h"
+
+#include <string>
+#include <curl/curl.h>
+
+#include "llapr.h"
+#include "llcurl.h"
+#include "llhost.h"
+
+// Static class variable instances
+
+// We want this to be static to avoid excessive indirection on every
+// incoming packet just to do a simple bool test. The getter for this
+// member is also static
+bool LLProxy::sUDPProxyEnabled = false;
+
+// Some helpful TCP static functions.
+static S32 tcp_handshake(LLSocket::ptr_t handle, char * dataout, apr_size_t outlen, char * datain, apr_size_t maxinlen); // Do a TCP data handshake
+static LLSocket::ptr_t tcp_open_channel(apr_pool_t* pool, LLHost host); // Open a TCP channel to a given host
+static void tcp_close_channel(LLSocket::ptr_t* handle_ptr); // Close an open TCP channel
+
+LLProxy::LLProxy():
+ mHTTPProxyEnabled(false),
+ mProxyMutex(0),
+ mUDPProxy(),
+ mTCPProxy(),
+ mPool(gAPRPoolp),
+ mHTTPProxy(),
+ mProxyType(LLPROXY_SOCKS),
+ mAuthMethodSelected(METHOD_NOAUTH),
+ mSocksUsername(),
+ mSocksPassword()
+{
+}
+
+LLProxy::~LLProxy()
+{
+ stopSOCKSProxy();
+ sUDPProxyEnabled = false;
+ mHTTPProxyEnabled = false;
+}
+
+/**
+ * @brief Open the SOCKS 5 TCP control channel.
+ *
+ * Perform a SOCKS 5 authentication and UDP association to the proxy server.
+ *
+ * @param proxy The SOCKS 5 server to connect to.
+ * @return SOCKS_OK if successful, otherwise a socks error code from llproxy.h.
+ */
+S32 LLProxy::proxyHandshake(LLHost proxy)
+{
+ S32 result;
+
+ /* SOCKS 5 Auth request */
+ socks_auth_request_t socks_auth_request;
+ socks_auth_response_t socks_auth_response;
+
+ socks_auth_request.version = SOCKS_VERSION; // SOCKS version 5
+ socks_auth_request.num_methods = 1; // Sending 1 method.
+ socks_auth_request.methods = getSelectedAuthMethod(); // Send only the selected method.
+
+ result = tcp_handshake(mProxyControlChannel, (char*)&socks_auth_request, sizeof(socks_auth_request), (char*)&socks_auth_response, sizeof(socks_auth_response));
+ if (result != APR_SUCCESS)
+ {
+ LL_WARNS("Proxy") << "SOCKS authentication request failed, error on TCP control channel : " << result << LL_ENDL;
+ stopSOCKSProxy();
+ return SOCKS_CONNECT_ERROR;
+ }
+
+ if (socks_auth_response.method == AUTH_NOT_ACCEPTABLE)
+ {
+ LL_WARNS("Proxy") << "SOCKS 5 server refused all our authentication methods" << LL_ENDL;
+ stopSOCKSProxy();
+ return SOCKS_NOT_ACCEPTABLE;
+ }
+
+ // SOCKS 5 USERNAME/PASSWORD authentication
+ if (socks_auth_response.method == METHOD_PASSWORD)
+ {
+ // The server has requested a username/password combination
+ std::string socks_username(getSocksUser());
+ std::string socks_password(getSocksPwd());
+ U32 request_size = socks_username.size() + socks_password.size() + 3;
+ char * password_auth = new char[request_size];
+ password_auth[0] = 0x01;
+ password_auth[1] = socks_username.size();
+ memcpy(&password_auth[2], socks_username.c_str(), socks_username.size());
+ password_auth[socks_username.size() + 2] = socks_password.size();
+ memcpy(&password_auth[socks_username.size()+3], socks_password.c_str(), socks_password.size());
+
+ authmethod_password_reply_t password_reply;
+
+ result = tcp_handshake(mProxyControlChannel, password_auth, request_size, (char*)&password_reply, sizeof(password_reply));
+ delete[] password_auth;
+
+ if (result != APR_SUCCESS)
+ {
+ LL_WARNS("Proxy") << "SOCKS authentication failed, error on TCP control channel : " << result << LL_ENDL;
+ stopSOCKSProxy();
+ return SOCKS_CONNECT_ERROR;
+ }
+
+ if (password_reply.status != AUTH_SUCCESS)
+ {
+ LL_WARNS("Proxy") << "SOCKS authentication failed" << LL_ENDL;
+ stopSOCKSProxy();
+ return SOCKS_AUTH_FAIL;
+ }
+ }
+
+ /* SOCKS5 connect request */
+
+ socks_command_request_t connect_request;
+ socks_command_response_t connect_reply;
+
+ connect_request.version = SOCKS_VERSION; // SOCKS V5
+ connect_request.command = COMMAND_UDP_ASSOCIATE; // Associate UDP
+ connect_request.reserved = FIELD_RESERVED;
+ connect_request.atype = ADDRESS_IPV4;
+ connect_request.address = htonl(0); // 0.0.0.0
+ connect_request.port = htons(0); // 0
+ // "If the client is not in possession of the information at the time of the UDP ASSOCIATE,
+ // the client MUST use a port number and address of all zeros. RFC 1928"
+
+ result = tcp_handshake(mProxyControlChannel, (char*)&connect_request, sizeof(connect_request), (char*)&connect_reply, sizeof(connect_reply));
+ if (result != APR_SUCCESS)
+ {
+ LL_WARNS("Proxy") << "SOCKS connect request failed, error on TCP control channel : " << result << LL_ENDL;
+ stopSOCKSProxy();
+ return SOCKS_CONNECT_ERROR;
+ }
+
+ if (connect_reply.reply != REPLY_REQUEST_GRANTED)
+ {
+ LL_WARNS("Proxy") << "Connection to SOCKS 5 server failed, UDP forward request not granted" << LL_ENDL;
+ stopSOCKSProxy();
+ return SOCKS_UDP_FWD_NOT_GRANTED;
+ }
+
+ mUDPProxy.setPort(ntohs(connect_reply.port)); // reply port is in network byte order
+ mUDPProxy.setAddress(proxy.getAddress());
+ // The connection was successful. We now have the UDP port to send requests that need forwarding to.
+ LL_INFOS("Proxy") << "SOCKS 5 UDP proxy connected on " << mUDPProxy << LL_ENDL;
+ return SOCKS_OK;
+}
+
+/**
+ * @brief Initiates a SOCKS 5 proxy session.
+ *
+ * Performs basic checks on host to verify that it is a valid address. Opens the control channel
+ * and then negotiates the proxy connection with the server.
+ *
+ *
+ * @param host Socks server to connect to.
+ * @return SOCKS_OK if successful, otherwise a SOCKS error code defined in llproxy.h.
+ */
+S32 LLProxy::startSOCKSProxy(LLHost host)
+{
+ S32 status = SOCKS_OK;
+
+ if (host.isOk())
+ {
+ mTCPProxy = host;
+ }
+ else
+ {
+ status = SOCKS_INVALID_HOST;
+ }
+
+ if (mProxyControlChannel && status == SOCKS_OK)
+ {
+ tcp_close_channel(&mProxyControlChannel);
+ }
+
+ if (status == SOCKS_OK)
+ {
+ mProxyControlChannel = tcp_open_channel(mPool, mTCPProxy);
+ if (!mProxyControlChannel)
+ {
+ status = SOCKS_HOST_CONNECT_FAILED;
+ }
+ }
+
+ if (status == SOCKS_OK)
+ {
+ status = proxyHandshake(mTCPProxy);
+ }
+ if (status == SOCKS_OK)
+ {
+ sUDPProxyEnabled = true;
+ }
+ else
+ {
+ stopSOCKSProxy();
+ }
+ return status;
+}
+
+/**
+ * @brief Stop using the SOCKS 5 proxy.
+ *
+ * This will stop sending UDP packets through the SOCKS 5 proxy
+ * and will also stop the HTTP proxy if it is configured to use SOCKS.
+ * The proxy control channel will also be disconnected.
+ */
+void LLProxy::stopSOCKSProxy()
+{
+ sUDPProxyEnabled = false;
+
+ // If the SOCKS proxy is requested to stop and we are using that for HTTP as well
+ // then we must shut down any HTTP proxy operations. But it is allowable if web
+ // proxy is being used to continue proxying HTTP.
+
+ if (LLPROXY_SOCKS == getHTTPProxyType())
+ {
+ void disableHTTPProxy();
+ }
+
+ if (mProxyControlChannel)
+ {
+ tcp_close_channel(&mProxyControlChannel);
+ }
+}
+
+/**
+ * @brief Set the proxy's SOCKS authentication method to none.
+ */
+void LLProxy::setAuthNone()
+{
+ LLMutexLock lock(&mProxyMutex);
+
+ mAuthMethodSelected = METHOD_NOAUTH;
+}
+
+/**
+ * @brief Set the proxy's SOCKS authentication method to password.
+ *
+ * Check whether the lengths of the supplied username
+ * and password conform to the lengths allowed by the
+ * SOCKS protocol.
+ *
+ * @param username The SOCKS username to send.
+ * @param password The SOCKS password to send.
+ * @return Return true if applying the settings was successful. No changes are made if false.
+ *
+ */
+bool LLProxy::setAuthPassword(const std::string &username, const std::string &password)
+{
+ if (username.length() > SOCKSMAXUSERNAMELEN || password.length() > SOCKSMAXPASSWORDLEN ||
+ username.length() < SOCKSMINUSERNAMELEN || password.length() < SOCKSMINPASSWORDLEN)
+ {
+ LL_WARNS("Proxy") << "Invalid SOCKS 5 password or username length." << LL_ENDL;
+ return false;
+ }
+
+ LLMutexLock lock(&mProxyMutex);
+
+ mAuthMethodSelected = METHOD_PASSWORD;
+ mSocksUsername = username;
+ mSocksPassword = password;
+
+ return true;
+}
+
+/**
+ * @brief Enable the HTTP proxy for either SOCKS or HTTP.
+ *
+ * Check the supplied host to see if it is a valid IP and port.
+ *
+ * @param httpHost Proxy server to connect to.
+ * @param type Is the host a SOCKS or HTTP proxy.
+ * @return Return true if applying the setting was successful. No changes are made if false.
+ */
+bool LLProxy::enableHTTPProxy(LLHost httpHost, LLHttpProxyType type)
+{
+ if (!httpHost.isOk())
+ {
+ LL_WARNS("Proxy") << "Invalid SOCKS 5 Server" << LL_ENDL;
+ return false;
+ }
+
+ LLMutexLock lock(&mProxyMutex);
+
+ mHTTPProxy = httpHost;
+ mProxyType = type;
+
+ mHTTPProxyEnabled = true;
+
+ return true;
+}
+
+/**
+ * @brief Enable the HTTP proxy without changing the proxy settings.
+ *
+ * This should not be called unless the proxy has already been set up.
+ *
+ * @return Return true only if the current settings are valid and the proxy was enabled.
+ */
+bool LLProxy::enableHTTPProxy()
+{
+ bool ok;
+
+ LLMutexLock lock(&mProxyMutex);
+
+ ok = (mHTTPProxy.isOk());
+ if (ok)
+ {
+ mHTTPProxyEnabled = true;
+ }
+
+ return ok;
+}
+
+/**
+ * @brief Disable the HTTP proxy.
+ */
+void LLProxy::disableHTTPProxy()
+{
+ LLMutexLock lock(&mProxyMutex);
+
+ mHTTPProxyEnabled = false;
+}
+
+/**
+ * @brief Get the HTTP proxy address and port
+ */
+//
+LLHost LLProxy::getHTTPProxy() const
+{
+ LLMutexLock lock(&mProxyMutex);
+ return mHTTPProxy;
+}
+
+/**
+ * @brief Get the currently selected HTTP proxy type
+ */
+LLHttpProxyType LLProxy::getHTTPProxyType() const
+{
+ LLMutexLock lock(&mProxyMutex);
+ return mProxyType;
+}
+
+/**
+ * @brief Get the SOCKS 5 password.
+ */
+std::string LLProxy::getSocksPwd() const
+{
+ LLMutexLock lock(&mProxyMutex);
+ return mSocksPassword;
+}
+
+/**
+ * @brief Get the SOCKS 5 username.
+ */
+std::string LLProxy::getSocksUser() const
+{
+ LLMutexLock lock(&mProxyMutex);
+ return mSocksUsername;
+}
+
+/**
+ * @brief Get the currently selected SOCKS 5 authentication method.
+ *
+ * @return Returns either none or password.
+ */
+LLSocks5AuthType LLProxy::getSelectedAuthMethod() const
+{
+ LLMutexLock lock(&mProxyMutex);
+ return mAuthMethodSelected;
+}
+
+/**
+ * @brief Stop the LLProxy and make certain that any APR pools and classes are deleted before terminating APR.
+ *
+ * Deletes the LLProxy singleton, destroying the APR pool used by the control channel as well as .
+ */
+//static
+void LLProxy::cleanupClass()
+{
+ getInstance()->stopSOCKSProxy();
+ deleteSingleton();
+}
+
+void LLProxy::applyProxySettings(LLCurlEasyRequest* handle)
+{
+ applyProxySettings(handle->getEasy());
+}
+
+void LLProxy::applyProxySettings(LLCurl::Easy* handle)
+{
+ applyProxySettings(handle->getCurlHandle());
+}
+
+/**
+ * @brief Apply proxy settings to a CuRL request if an HTTP proxy is enabled.
+ *
+ * This method has been designed to be safe to call from
+ * any thread in the viewer. This allows requests in the
+ * texture fetch thread to be aware of the proxy settings.
+ * When the HTTP proxy is enabled, the proxy mutex will
+ * be locked every time this method is called.
+ *
+ * @param handle A pointer to a valid CURL request, before it has been performed.
+ */
+void LLProxy::applyProxySettings(CURL* handle)
+{
+ // Do a faster unlocked check to see if we are supposed to proxy.
+ if (mHTTPProxyEnabled)
+ {
+ // We think we should proxy, lock the proxy mutex.
+ LLMutexLock lock(&mProxyMutex);
+ // Now test again to verify that the proxy wasn't disabled between the first check and the lock.
+ if (mHTTPProxyEnabled)
+ {
+ check_curl_code(curl_easy_setopt(handle, CURLOPT_PROXY, mHTTPProxy.getIPString().c_str()));
+ check_curl_code(curl_easy_setopt(handle, CURLOPT_PROXYPORT, mHTTPProxy.getPort()));
+
+ if (mProxyType == LLPROXY_SOCKS)
+ {
+ check_curl_code(curl_easy_setopt(handle, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5));
+ if (mAuthMethodSelected == METHOD_PASSWORD)
+ {
+ std::string auth_string = mSocksUsername + ":" + mSocksPassword;
+ check_curl_code(curl_easy_setopt(handle, CURLOPT_PROXYUSERPWD, auth_string.c_str()));
+ }
+ }
+ else
+ {
+ check_curl_code(curl_easy_setopt(handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP));
+ }
+ }
+ }
+}
+
+/**
+ * @brief Send one TCP packet and receive one in return.
+ *
+ * This operation is done synchronously with a 1000ms timeout. Therefore, it should not be used when a blocking
+ * operation would impact the operation of the viewer.
+ *
+ * @param handle_ptr Pointer to a connected LLSocket of type STREAM_TCP.
+ * @param dataout Data to send.
+ * @param outlen Length of dataout.
+ * @param datain Buffer for received data. Undefined if return value is not APR_SUCCESS.
+ * @param maxinlen Maximum possible length of received data. Short reads are allowed.
+ * @return Indicates APR status code of exchange. APR_SUCCESS if exchange was successful, -1 if invalid data length was received.
+ */
+static S32 tcp_handshake(LLSocket::ptr_t handle, char * dataout, apr_size_t outlen, char * datain, apr_size_t maxinlen)
+{
+ apr_socket_t* apr_socket = handle->getSocket();
+ apr_status_t rv = APR_SUCCESS;
+
+ apr_size_t expected_len = outlen;
+
+ handle->setBlocking(1000);
+
+ rv = apr_socket_send(apr_socket, dataout, &outlen);
+ if (APR_SUCCESS != rv)
+ {
+ LL_WARNS("Proxy") << "Error sending data to proxy control channel, status: " << rv << LL_ENDL;
+ ll_apr_warn_status(rv);
+ }
+ else if (expected_len != outlen)
+ {
+ LL_WARNS("Proxy") << "Incorrect data length sent. Expected: " << expected_len <<
+ " Sent: " << outlen << LL_ENDL;
+ rv = -1;
+ }
+
+ if (APR_SUCCESS == rv)
+ {
+ expected_len = maxinlen;
+ rv = apr_socket_recv(apr_socket, datain, &maxinlen);
+ if (rv != APR_SUCCESS)
+ {
+ LL_WARNS("Proxy") << "Error receiving data from proxy control channel, status: " << rv << LL_ENDL;
+ ll_apr_warn_status(rv);
+ }
+ else if (expected_len < maxinlen)
+ {
+ LL_WARNS("Proxy") << "Incorrect data length received. Expected: " << expected_len <<
+ " Received: " << maxinlen << LL_ENDL;
+ rv = -1;
+ }
+ }
+
+ handle->setNonBlocking();
+
+ return rv;
+}
+
+/**
+ * @brief Open a LLSocket and do a blocking connect to the chosen host.
+ *
+ * Checks for a successful connection, and makes sure the connection is closed if it fails.
+ *
+ * @param pool APR pool to pass into the LLSocket.
+ * @param host The host to open the connection to.
+ * @return The created socket. Will evaluate as NULL if the connection is unsuccessful.
+ */
+static LLSocket::ptr_t tcp_open_channel(apr_pool_t* pool, LLHost host)
+{
+ LLSocket::ptr_t socket = LLSocket::create(pool, LLSocket::STREAM_TCP);
+ bool connected = socket->blockingConnect(host);
+ if (!connected)
+ {
+ tcp_close_channel(&socket);
+ }
+
+ return socket;
+}
+
+/**
+ * @brief Close the socket.
+ *
+ * @param handle_ptr A pointer-to-pointer to avoid increasing the use count.
+ */
+static void tcp_close_channel(LLSocket::ptr_t* handle_ptr)
+{
+ LL_DEBUGS("Proxy") << "Resetting proxy LLSocket handle, use_count == " << handle_ptr->use_count() << LL_ENDL;
+ handle_ptr->reset();
+}