/** 
 * @file llsocks5.cpp
 * @brief Socks 5 implementation
 *
 * $LicenseInfo:firstyear=2000&license=viewergpl$
 * 
 * Copyright (c) 2000-2009, Linden Research, Inc.
 * 
 * Second Life Viewer Source Code
 * The source code in this file ("Source Code") is provided by Linden Lab
 * to you under the terms of the GNU General Public License, version 2.0
 * ("GPL"), unless you have obtained a separate licensing agreement
 * ("Other License"), formally executed by you and Linden Lab.  Terms of
 * the GPL can be found in doc/GPL-license.txt in this distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
 * 
 * There are special exceptions to the terms and conditions of the GPL as
 * it is applied to this Source Code. View the full text of the exception
 * in the file doc/FLOSS-exception.txt in this software distribution, or
 * online at
 * http://secondlifegrid.net/programs/open_source/licensing/flossexception
 * 
 * By copying, modifying or distributing this software, you acknowledge
 * that you have read and understood your obligations described above,
 * and agree to abide by those obligations.
 * 
 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
 * COMPLETENESS OR PERFORMANCE.
 * $/LicenseInfo$
 */

#include <string>

#include "linden_common.h"
#include "net.h"
#include "llhost.h"
#include "message.h"
#include "llsocks5.h"

// Static class variable instances

// We want this to be static to avoid excessive indirection on every
// incomming packet just to do a simple bool test. The getter for this
// member is also static
bool LLSocks::sUdpProxyEnabled;
bool LLSocks::sHttpProxyEnabled;

LLSocks::LLSocks()
{
	sUdpProxyEnabled  = false;
	sHttpProxyEnabled = false;
	mNeedUpdate       = false;
}

// Perform a Socks5 authentication and UDP assioacation to the proxy
// specified by proxy, and assiocate UDP port message_port
int LLSocks::proxyHandshake(LLHost proxy, U32 message_port)
{
	int 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     = mAuthMethodSelected; // send only the selected metho

	result = tcp_handshake(hProxyControlChannel, (char*)&socks_auth_request, sizeof(socks_auth_request_t), (char*)&socks_auth_response, sizeof(socks_auth_response_t));
	if (result != 0)
	{
		llwarns << "Socks authentication request failed, error on TCP control channel : " << result << llendl;
		stopProxy();
		return SOCKS_CONNECT_ERROR;
	}
	
	if (socks_auth_response.method == AUTH_NOT_ACCEPTABLE)
	{
		llwarns << "Socks5 server refused all our authentication methods" << llendl;
		stopProxy();
		return SOCKS_NOT_ACCEPTABLE;
	}

	// SOCKS5 USERNAME/PASSWORD authentication
	if (socks_auth_response.method == METHOD_PASSWORD)
	{
		// The server has requested a username/password combination
		U32 request_size = mSocksUsername.size() + mSocksPassword.size() + 3;
		char * password_auth = (char *)malloc(request_size);
		password_auth[0] = 0x01;
		password_auth[1] = mSocksUsername.size();
		memcpy(&password_auth[2],mSocksUsername.c_str(), mSocksUsername.size());
		password_auth[mSocksUsername.size()+2] = mSocksPassword.size();
		memcpy(&password_auth[mSocksUsername.size()+3], mSocksPassword.c_str(), mSocksPassword.size());

		authmethod_password_reply_t password_reply;

		result = tcp_handshake(hProxyControlChannel, password_auth, request_size, (char*)&password_reply, sizeof(authmethod_password_reply_t));
		free (password_auth);

		if (result != 0)
		{
		llwarns << "Socks authentication failed, error on TCP control channel : " << result << llendl;
			stopProxy();
			return SOCKS_CONNECT_ERROR;
		}

		if (password_reply.status != AUTH_SUCCESS)
		{
			llwarns << "Socks authentication failed" << llendl;
			stopProxy();
			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.flag    = FIELD_RESERVED;
	connect_request.atype   = ADDRESS_IPV4;
	connect_request.address = 0; // 0.0.0.0 We are not fussy about address
							     // UDP is promiscious receive for our protocol
	connect_request.port    = 0; // Port must be 0 if you ever want to connect via NAT and your router does port rewrite for you

	result = tcp_handshake(hProxyControlChannel, (char*)&connect_request, sizeof(socks_command_request_t), (char*)&connect_reply, sizeof(socks_command_response_t));
	if (result != 0)
	{
		llwarns << "Socks connect request failed, error on TCP control channel : " << result << llendl;
		stopProxy();
		return SOCKS_CONNECT_ERROR;
	}

	if (connect_reply.reply != REPLY_REQUEST_GRANTED)
	{
		//Something went wrong
		llwarns << "Connection to SOCKS5 server failed, UDP forward request not granted" << llendl;
		stopProxy();
		return SOCKS_UDP_FWD_NOT_GRANTED;
	}

	mUDPProxy.setPort(ntohs(connect_reply.port)); // reply port is in network byte order
	mUDPProxy.setAddress(proxy.getAddress());
	// All good now we have been given the UDP port to send requests that need forwarding.
	llinfos << "Socks 5 UDP proxy connected on " << mUDPProxy << llendl;
	return SOCKS_OK;
}

int LLSocks::startProxy(LLHost proxy, U32 message_port)
{
	int status;

	mTCPProxy   = proxy;
	mNeedUpdate = false;

	stopProxy();
	hProxyControlChannel = tcp_open_channel(proxy);	
	if (hProxyControlChannel == -1)
	{
		return SOCKS_HOST_CONNECT_FAILED;
	}

	status = proxyHandshake(proxy, message_port);	
	if (status == SOCKS_OK)
	{
		sUdpProxyEnabled=true;
	}
	return status;
}

int LLSocks::startProxy(std::string host, U32 port)
{
		mTCPProxy.setHostByName(host);
		mTCPProxy.setPort(port);
		return startProxy(mTCPProxy, (U32)gMessageSystem->mPort);
}

void LLSocks::stopProxy()
{
	sUdpProxyEnabled = false;

	if (hProxyControlChannel)
	{
		tcp_close_channel(hProxyControlChannel);
	}
}

void LLSocks::setAuthNone()
{
	mAuthMethodSelected = METHOD_NOAUTH;
}


void LLSocks::setAuthPassword(std::string username, std::string password)
{
	mAuthMethodSelected = METHOD_PASSWORD;
	mSocksUsername      = username;
	mSocksPassword      = password;
}

void LLSocks::EnableHttpProxy(LLHost httpHost, LLHttpProxyType type)
{ 
	sHttpProxyEnabled = true; 
	mHTTPProxy        = httpHost; 
	mProxyType        = type;
}
