// kitsockset.cpp
//
// KIT AIM client
//
// For copyright and license, see accompanying documentation

extern "C"
{
#include <string.h>
#include <unistd.h>
#include <errno.h>
}

#include "kitsockset.h"

// SOCKS 5 (RFC 1928)
bool socksConnect(int sock, const QString &host, unsigned short port,
                  bool do1929, const QString &name, const QString &password)
{
	if(sock < 0) return false;
	int bytes;
	
	// send the server what methods I accept
	unsigned char methods[4] = {5, 2, 0, 0};
	if(do1929) methods[3] = 2;
	do
	{
		bytes = ::write(sock, methods, 4);
		if(bytes > -1 && bytes < 4) return false;
		if(bytes == -1) ERRNO_SWITCH(break, return false);
	} while(bytes < 4);

	// receive from server what method to use
	unsigned char methodsResponse[2];
	do
	{
		bytes = ::read(sock, (void *)methodsResponse, 2);
		if(bytes > -1 && bytes < 2) return false;
		if(bytes == -1) ERRNO_SWITCH(break, return false);
	} while(bytes < 2);
	if(5 != methodsResponse[0]) return false;
	switch(methodsResponse[1])
	{
		// no authentication
		case 0:
			// no method-specific subnegotiation
			break;
		// username/password (RFC 1929)
		case 2:
		{
			// send credentials
			unsigned char nLen = name.length() > 255 ? 255 : name.length();
			unsigned char pLen = password.length() > 255 ? 255 : password.length();
			unsigned char *login = new unsigned char[nLen + pLen + 3];
			login[0] = 1;
			login[1] = nLen;
			strncpy((char *)(login + 2), name.local8Bit(), nLen);
			login[nLen + 2] = pLen;
			strncpy((char *)(login + nLen + 3), password.local8Bit(), pLen);
			bytes = ::write(sock, login, nLen + pLen + 3);
			delete login;
			while(bytes < nLen + pLen + 3)
			{
				if(bytes > -1) return false;
				ERRNO_SWITCH(break, return false);
				bytes = ::write(sock, login, nLen + pLen + 3);
			}

			// find out whether we're allowed
			unsigned char reply[2];
			do
			{
				bytes = ::read(sock, reply, 2);
				if(bytes > -1 && bytes < 2) return false;
				if(bytes == -1) ERRNO_SWITCH(break, return false);
			} while(bytes < 2);
			if(1 != reply[0]) return false;
			if(0 != reply[1]) return false;
		}
			break;
		default:
			return false;
	}

	// request connection
	unsigned char hLen = host.length() > 255 ? 255 : host.length();
	unsigned char *request = new unsigned char[hLen + 7];
	request[0] = 5;
	request[1] = 1;
	request[2] = 0;
	request[3] = 3;
	request[4] = hLen;
	strncpy((char *)(request + 5), host.local8Bit(), hLen);
	*(unsigned short *)(request + hLen + 5) = htons(port);
	bytes = ::write(sock, request, hLen + 7);
	delete request;
	while(bytes < hLen + 7)
	{
		if(bytes > -1) return false;
		ERRNO_SWITCH(break, return false);
		bytes = ::write(sock, request, hLen + 7);
	}

	// and the last server reply
	unsigned char replyHeader[4];
	do
	{
		bytes = ::read(sock, replyHeader, 4);
		if(bytes > -1 && bytes < 4) return false;
		if(bytes == -1) ERRNO_SWITCH(break, return false);
	} while(bytes < 4);
	if(5 != replyHeader[0]) return false;
	if(0 != replyHeader[1]) return false;
	if(0 != replyHeader[2]) return false;
	// I don't care about the address we're bound to right now,
	// But I need to get it out of the pipe
	switch(replyHeader[3])
	{
		// IPV4 address
		case 1:
		{
			unsigned char replyBody[6];
			if(6 != ::read(sock, replyBody, 6)) return false;
		}
			break;
		// Domain name
		case 3:
		{
			unsigned char nameLength;
			if(1 != ::read(sock, &nameLength, 1)) return false;
			unsigned char *replyBody = new unsigned char[nameLength + 2];
			int w = ::read(sock, replyBody, nameLength + 2);
			delete replyBody;
			if( (nameLength + 2) != w ) return false;
		}
			break;
		default:
			return false;
	}
	return true;
}
KitSOCKSet::KitSOCKSet(const QString &host, unsigned short port,
                       const QString &socksHost, unsigned short socksPort,
                       bool auth, const QString &un, const QString &pd,
                       int to)
	: KitSocket(socksHost, socksPort, to),
	wantedHost(host),
	wantedPort(port),
	username(un),
	password(pd),
	do1929(auth)
{
}
KitSOCKSet::~KitSOCKSet()
{
}
bool KitSOCKSet::connect(void)
{
	if(connectSocket() &&
	socksConnect(sock(), wantedHost, wantedPort, do1929, username, password) &&
	connectSFLAP())
		return true;
	disconnect();
	return false;
}

#include "kitsockset.moc"
