//
// File : socks.c
//
// manage socks protocol (4 and 5)
//
// by Loïc Berthelot, CryoNetworks, june 2001
// $ LB



#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>

#include <sys/types.h>
#include <sys/socket.h>

#include "../../kernel/mmemory.h"
#include "../../kernel/include/socks.h"







/***************************************************************************/
/*                                                                         */
/*        SOCKS 4  PROTOCOL  CODES                                         */
/*                                                                         */
/***************************************************************************/

#define SOCKS4_REQ_GRANTED 90 // proxy socks 4 has accepted request from client




/***************************************************************************/
/*                                                                         */
/*        SOCKS 5  PROTOCOL  CODES                                         */
/*                                                                         */
/***************************************************************************/

#define SOCKS5_PROTO_VER               0x05
#define SOCKS5_AUTH_PROTO_VER          0x01
#define SOCKS5_AUTH_METHOD_NO_AUTH     0x00
#define SOCKS5_AUTH_METHOD_UNAME_PWORD 0x02
#define SOCKS5_AUTH_METHOD_ERROR       0xFF
#define SOCKS5_AUTH_GRANTED            0x00
#define SOCKS5_COMMAND_CONNECT         0x01
#define SOCKS5_IP_4BYTES               0x01
#define SOCKS5_REQ_GRANTED             0x00






/***************************************************************************/
/*                                                                         */
/*        SOCKS  GLOBAL VARIABLES                                          */
/*                                                                         */
/***************************************************************************/


static int socksVer = 4;               // socks protocol version <=> 4 or 5
static int socks5Authentication = 0;   // socks 5 authentication   <=> 1 if needed, 0 if not
static char socks5Uname [255];         // socks 5 authentication username
static char socks5Pword [255];         // socks 5 authentication password






/***************************************************************************/
/*                                                                         */
/*        SOCKS  ERROR MESSAGES                                            */
/*        check socks.h to see the corresponding error codes               */
/*                                                                         */
/***************************************************************************/

typedef struct SocksError
{
  char* classMsg;
  char  nbErrorMsg;
  char* errorMsg[10];
} SocksError;

SocksError SOCKS_ERRORCLASS_MSG [NB_SOCKS_ERROR] = 
{
	{"proxy socks SUCCESS : the socket is connected to the server", 0, NULL}, 
	{"proxy socks ERROR   : there is no proxysocks defined!", 0, NULL},
	{"proxy socks ERROR   : the proxysocks version is not defined!", 0, NULL},
	{"proxy socks ERROR   : could not connect socket to proxy", 0, NULL},
	{"proxy socks ERROR   : SOCKS 4 : could not send the command request", 0, NULL},
	{"proxy socks ERROR   : SOCKS 4 : could not receive acknowledgement of the command request from proxy", 0, NULL},
	{
		"proxy socks ERROR   : SOCKS 4 : proxy socks has rejected the command request", 
		4, 
		{
			"request granted",
			"request rejected or failed",
			"request rejected because SOCKS server cannot connect to identd on the client",
			"request rejected because the client program and identd report different user-ids"
		}
	},
	{"proxy socks ERROR   : SOCKS 5 : could not send request for authentication methods enumeration", 0, NULL},
	{"proxy socks ERROR   : SOCKS 5 : could not receive authentication methods enumeration from proxy", 0, NULL},
	{
		"proxy socks ERROR   : SOCKS 5 : unable to manage authentication method the proxy socks asked", 
		6, 
		{
			"NO AUTHENTICATION REQUIRED", 
			"GSSAPI AUTHENTICATION",
			"USERNAME / PASSWORD AUTHENTICATION",
			"IANA ASSIGNED AUTHENTICATION",
			"PRIVATE METHOD AUTHENTICATION",
			"NO ACCEPTABLE METHOD"
		}
	},
	{"proxy socks ERROR   : SOCKS 5 : access rejected. Authentication needed", 0, NULL},
	{"proxy socks ERROR   : SOCKS 5 : could not send authentication to proxy", 0, NULL},
	{"proxy socks ERROR   : SOCKS 5 : could not receive aknowledgement of the authentication from proxy", 0, NULL},
	{"proxy socks ERROR   : SOCKS 5 : authentication failed", 0, NULL},
	{"proxy socks ERROR   : SOCKS 5 : could not send the command request", 0, NULL},
	{"proxy socks ERROR   : SOCKS 5 : could not receive the aknowledgement of the command request from proxy", 0, NULL},
	{
		"proxy socks ERROR   : SOCKS 5 : proxy socks has been unable to process the command request", 
		10, 
		{
			"command succdeeded",
			"general SOCKS server failure",
			"connection not allowed by ruleset",
			"Network unreachable",
			"Host unreachable",
			"Connection refused",
			"TTL expired",
			"Command not supported",
			"Address type not supported",
			"...[unassigned] error code..."
		}
	}
};










/***************************************************************************/
/*                                                                         */
/*     SOCKS 5    I N T E R N A L         B O D Y                          */
/*                                                                         */
/*                fills communication packet as SOCKS 5 protocol specifies */
/*                                                                         */
/***************************************************************************/





/*************************************************/
/* scol v 4                                      */
/*                                               */
/* int SOCKS5packet_req_auth_methods (char* pkt) */
/*                                               */
/* fills communication packet to ask proxy which */
/* authentication method the client has to use   */
/*                                               */
/* returns the size of the packet                */
/*                                               */
/*************************************************/

int SOCKS5packet_req_auth_methods (char* pkt)
{
	// socks protocol version
	*(pkt)   = (char)SOCKS5_PROTO_VER;

	// 2 authentication methods managed by client
	*(pkt+1) = (char)0x02;

	// first one : no authentication
	*(pkt+2) = (char)SOCKS5_AUTH_METHOD_NO_AUTH;

	// seconde one : authentication username / password
	*(pkt+3) = (char)SOCKS5_AUTH_METHOD_UNAME_PWORD;

	// socks5 packets have to be non-NULL terminated
	return 4;
}






/****************************************************/
/* scol v 4                                         */
/*                                                  */
/* int SOCKS5packet_ack_auth_methods (void)         */
/*                                                  */
/* returns the size of the data in which proxysocks */
/* acknowledge the authentication methods demand    */
/*                                                  */
/****************************************************/

#define SOCKS5packet_ack_auth_methods() (2)






/*************************************************/
/* scol v 4                                      */
/*                                               */
/* int SOCKS5packet_req_auth (char* pkt)         */
/*                                               */
/* fills communication packet to username and    */
/* password to proxy                             */
/*                                               */
/* returns the size of the packet                */
/*                                               */
/*************************************************/

int SOCKS5packet_req_auth (char* pkt)
{
int unameSize = strlen (socks5Uname);
int pwordSize = strlen (socks5Pword);

	// socks 5 authentication protocol version
	*(pkt) = (char)SOCKS5_AUTH_PROTO_VER;

	// username size
	*(pkt+1) = (char)unameSize;

	// username
	memcpy (pkt+2, socks5Uname, unameSize);

	//password size
	*(pkt+2+unameSize) = (char)pwordSize;

	//password
	memcpy (pkt+2+unameSize+1, socks5Pword, pwordSize);

	return 2 + unameSize + 1 + pwordSize;
}







/****************************************************/
/* scol v 4                                         */
/*                                                  */
/* int SOCKS5packet_ack_auth_methods (void)         */
/*                                                  */
/* returns the size of the data in which proxysocks */
/* acknowledge the authentication request           */
/*                                                  */
/****************************************************/

#define SOCKS5packet_ack_auth() (2)









/********************************************************/
/* scol v 4                                             */
/*                                                      */
/* int SOCKS5packet_req_auth (char* pkt,                */
/*                            unsigned long INETaddr,   */ 
/*                            unsigned short TCPIPport) */
/*                                                      */
/* fills communication packet to send the               */
/* connect request to proxy                             */
/*                                                      */
/* returns the size of the packet                       */
/*                                                      */
/********************************************************/

int SOCKS5packet_req_connect (char* pkt, unsigned long INETaddr, unsigned short TCPIPport)
{
	// socks 5 protocol version
	*(pkt) = (char)SOCKS5_PROTO_VER;

	// command to make proxy socks execute
	// here, it's a 'connec' to server
	*(pkt+1) = (char)SOCKS5_COMMAND_CONNECT;

	// reserved. must be 0
	*(pkt+2) = (char)0;

	// format of server address 
	// here, it's a 4 bytes IP
	*(pkt+3) = (char)SOCKS5_IP_4BYTES;

	// server address
	*(unsigned long*)(pkt+4) = INETaddr;

	// port number (must be in TCP network byte order)
	*(unsigned short*)(pkt+8) = TCPIPport;

	return 10;
}







/****************************************************/
/* scol v 4                                         */
/*                                                  */
/* int SOCKS5packet_ack_auth_methods (void)         */
/*                                                  */
/* returns the size of the data in which proxysocks */
/* acknowledge the authentication request           */
/*                                                  */
/****************************************************/

#define SOCKS5packet_ack_connect() (10)


















/***************************************************************************/
/*                                                                         */
/*     SOCKS    I N T E R N A L         B O D Y                            */
/*                                                                         */
/***************************************************************************/






int SOCKSerror (SOCKET sock, int errorClass, char errorCode)
{
char* buf;
int msgSize;


	msgSize = strlen (SOCKS_ERRORCLASS_MSG [errorClass].classMsg);

	if (errorCode != (char)NULL)
		if (SOCKS_ERRORCLASS_MSG [errorClass].nbErrorMsg > errorCode)
			msgSize += strlen (SOCKS_ERRORCLASS_MSG [errorClass].errorMsg [errorCode]);
		
	buf = (char*) malloc ((msgSize + 32) * sizeof(char));

	sprintf (buf, "%s\n", SOCKS_ERRORCLASS_MSG [errorClass].classMsg);

	if (errorCode != (char)NULL)
		if (SOCKS_ERRORCLASS_MSG [errorClass].nbErrorMsg > errorCode)
		{
			strcat (buf, "\t : ");
			strcat (buf, SOCKS_ERRORCLASS_MSG [errorClass].errorMsg [errorCode]);
			strcat (buf, "\n");
		}

	MMechostr (1, "%s", buf);


	if (buf)
	{
		free (buf);
		buf = NULL;
	}
	return errorClass;	
}







/*********************************************************************/
/* scol v 4                                                          */
/*                                                                   */
/* int SOCKSconnect_PROTO5 (SOCKET, unsigned long, unsigned short)   */
/*                                                                   */
/* connect the socket 'sock' to a server, through a proxy socks,     */
/* SOCKS5 protocol.                                                  */
/* server's address is given as inet adress,                         */
/* and port is given as TCP/IP byte order.                           */
/*                                                                   */
/* returns 0 if success, non-zero code if fail. error codes are      */
/* defined in socks.h                                                */
/*                                                                   */
/*********************************************************************/

int SOCKSconnect_PROTO5 (SOCKET sock, unsigned long INETaddr, unsigned short TCPIPport)
{
char pkt[1024];
int  pktSize = 0;
char value;
struct sockaddr_in ina;



	ina.sin_family = PF_INET;
	ina.sin_port = htons ((unsigned short)proxyPort);
	ina.sin_addr.s_addr = proxyIp;


	// contact proxy socks
	if ( (connect (sock,(struct sockaddr*)&ina, sizeof (ina))) != 0)
		return SOCKSerror (sock, SOCKS_ERROR_CONNECT_PROXY, (char)NULL);




    /*********************************************************************/
	/*  SOCKS 5 AUTHENTICATION METHODS                                   */
	/*********************************************************************/

	// client asks proxy which authentication method is applying to him
	pktSize = SOCKS5packet_req_auth_methods (&pkt[0]);
	if (send (sock, pkt, pktSize, 0) <= 0)
		return SOCKSerror (sock, SOCKS5_ERROR_REQ_AUTH_METHODS, (char)NULL);


	// proxy transmit authentication method needed
	pktSize = SOCKS5packet_ack_auth_methods ();
    if (recv (sock, pkt, pktSize, 0) <= 0)
		return SOCKSerror (sock, SOCKS5_ERROR_ACK_AUTH_METHODS, (char)NULL);



	// check if we're able to manage the authentication method
	// the proxy requires
	value = pkt[1];
	if ( 
		 // the protocole version is not good
		 (pkt[0] != SOCKS5_PROTO_VER)         ||

		 // there is no authentication method for this client
		 (value == SOCKS5_AUTH_METHOD_ERROR)  ||
         (
		   // the authentication method is not "NO AUTHENTICATION" method
		   (value != SOCKS5_AUTH_METHOD_NO_AUTH)     && 

		   // the authentication method is not "USERNAME / PASSWORD" method
		   (value != SOCKS5_AUTH_METHOD_UNAME_PWORD)
		 )
	   )
	{
		return SOCKSerror (sock, SOCKS5_ERROR_AUTH_METHODS, pkt[1]);
	}




    /*********************************************************************/
	/*  SOCKS 5 AUTHENTICATION USERNAME / PASSWORD                       */
	/*********************************************************************/


	// if proxy requires username and password, give it to him
	if (value == SOCKS5_AUTH_METHOD_UNAME_PWORD)
	{
		// maybe the client has not required a username/password authentication
		if (!socks5Authentication)
			return SOCKSerror (sock, SOCKS5_ERROR_AUTH_REQUIRED, (char)NULL);


		// otherwize, send required data
		pktSize = SOCKS5packet_req_auth (&pkt[0]);
		if (send (sock, pkt, pktSize, 0) <= 0)
			return SOCKSerror (sock, SOCKS5_ERROR_REQ_AUTH, (char)NULL);


		// proxy tells client if he's authorized or not
		pktSize = SOCKS5packet_ack_auth ();
		if (recv (sock, pkt, pktSize, 0) <= 0)
			return SOCKSerror (sock, SOCKS5_ERROR_ACK_AUTH, (char)NULL);


		// check the proxy answer
		if (
			 // the auth protocole version is not good
			 (pkt[0] != SOCKS5_AUTH_PROTO_VER) ||

			 // the authentication has been rejected
		     (pkt[1] != SOCKS5_AUTH_GRANTED)
			)
		{
			return SOCKSerror (sock, SOCKS5_ERROR_AUTH_REJECTED, (char)NULL);
		}

	}




    /*********************************************************************/
	/*  SOCKS 5 'CONNEXION TO SERVER' REQUEST TO PROXY                   */
	/*********************************************************************/


	// finally, send the 'connect to server' command to proxy
	pktSize = SOCKS5packet_req_connect (&pkt[0], INETaddr, TCPIPport);
	if (send (sock, pkt, pktSize, 0) <= 0)
		return SOCKSerror (sock, SOCKS5_ERROR_REQ_COMMAND, (char)NULL);

	// and check what proxy tells about that
	pktSize = SOCKS5packet_ack_connect ();
	if (recv (sock, pkt, pktSize, 0) <= 0)
		return SOCKSerror (sock, SOCKS5_ERROR_ACK_COMMAND, (char)NULL);

	if (
		  // the protocole version is not good
		  (pkt[0] != SOCKS5_PROTO_VER) ||

		  // the connect request to server has not succeeded
		  (pkt[1] != SOCKS5_REQ_GRANTED)
	   )
	{
		return SOCKSerror (sock, SOCKS5_ERROR_COMMAND, pkt[1]);
	}




	return SOCKS_SUCCESS;
}
















/*********************************************************************/
/* scol v 4                                                          */
/*                                                                   */
/* int SOCKSconnect_PROTO4 (SOCKET, unsigned long, unsigned short )  */
/*                                                                   */
/* connect the socket 'sock' to a server, through a proxy socks,     */
/* SOCKS4 protocol.                                                  */
/* server's address is given as inet adress,                         */
/* and port is given as TCP/IP byte order.                           */
/*                                                                   */
/* returns 0 if success, non-zero code if fail. error codes are      */
/* defined in socks.h                                                */
/*                                                                   */
/*********************************************************************/

int SOCKSconnect_PROTO4 (SOCKET sock, unsigned long INETaddr, unsigned short TCPIPport)
{
char pkt[1024];  // buffer  : communication packet 
SOCKADDR_IN ina;


	ina.sin_family = PF_INET;
	ina.sin_port = htons((unsigned short)proxyPort);
	ina.sin_addr.s_addr = proxyIp;

	// contact proxy socks
	if ( (connect (sock,(struct sockaddr*)&ina, sizeof (ina))) != 0)
		return SOCKSerror (sock, SOCKS_ERROR_CONNECT_PROXY, (char)NULL);


	// fill the packet buffer as socks 4 protocol
	pkt[0]=4;
	pkt[1]=1;
	*(short*)(pkt+2) = TCPIPport;
	*(long*)(pkt+4) = INETaddr;
	*(pkt+8) = '\0';
	


	// send packet to proxy
	if (send (sock, pkt, 9, 0) <= 0)
		return SOCKSerror (sock, SOCKS4_ERROR_REQ_COMMAND, (char)NULL);



	// recv acknowledge from proxy
	if (recv (sock, pkt, 8, 0) <= 0)
		return SOCKSerror (sock, SOCKS4_ERROR_ACK_COMMAND, (char)NULL);



	// check the result
	if (pkt[1] != SOCKS4_REQ_GRANTED)
		return SOCKSerror (sock, SOCKS4_ERROR_REQ_REJECTED, (char)((int)pkt[1]-SOCKS4_REQ_GRANTED));

	return SOCKS_SUCCESS;
}






















/***************************************************************************/
/*                                                                         */
/*         E X T E R N A L         B O D Y                                 */
/*                                                                         */
/***************************************************************************/



// set the protocole version <=> 4 or 5
// returns non-zero value if newValue is not 4 or 5
// returns 0 if ok
int SOCKSsetVersion (int newValue)
{
	if ((newValue != 4) && (newValue != 5))
		return -1;

	socksVer = newValue;
	return 0;
}



// set the authentication flag
// <=> 1 if authentication needed, 0 if not
// returns non-zero value if newValue is not 0 or 1
// returns 0 if ok
int SOCKSsetAuthentication (int newValue)
{
	if ((newValue != 0) && (newValue != 1))
		return -1;

	socks5Authentication = newValue;
	return 0;
}



// set the socks5 username 
// returns 0 if ok, non-zero value if not (username too long)
int SOCKSsetUsername (char* newUname)
{
	if (newUname == NULL)
	{
		memset (socks5Uname, 0, sizeof(socks5Uname));
		return 0;
	}

	if (strlen (newUname) >= sizeof(socks5Uname))
		return -1;

	strcpy (socks5Uname, newUname);
	return 0;
}



// set the socks5 password
// returns 0 if ok, non-zero value if not (password too long)
int SOCKSsetPassword (char* newPword)
{
	if (newPword == NULL)
	{
		memset (socks5Pword, 0, sizeof (socks5Pword));
		return 0;
	}

	if (strlen (newPword) >= sizeof (socks5Pword))
		return -1;

	strcpy (socks5Pword, newPword);
	return 0;
}





/*******************************************************************/
/* scol v 4                                                        */
/*                                                                 */
/* int SOCKSformatMessage (int errorClass, char* buf, int bufSize) */
/*                                                                 */
/* fills the characters buffer with the error message              */
/* corresponding to the errorClass code given as parameter.        */
/*                                                                 */
/* returns 0 if success,                                           */
/* needed size if fail (buffer too small)                          */
/*                                                                 */
/*******************************************************************/

int SOCKSformatMessage (int errorClass, char* buf, int bufSize)
{
int msgSize;

	msgSize = strlen (SOCKS_ERRORCLASS_MSG[errorClass].classMsg);

	if (bufSize <= msgSize)
		return msgSize;

	strcpy (buf, SOCKS_ERRORCLASS_MSG[errorClass].classMsg);
	return 0;
}





/************************************************************************************/
/* scol v 4                                                                         */
/*                                                                                  */
/* int SOCKSconnect (SOCKET sock, unsigned long INETaddr, unsigned short TCPIPport) */
/*                                                                                  */
/* connect the socket 'sock' to a server, addressed by 'addr',                      */
/* on port 'port', through a proxy socks. This function determines                  */
/* the version of socks protocol to use.                                            */
/* server's address is given as inet adress,                                        */
/* and port is given as TCP/IP byte order.                                          */
/*                                                                                  */
/* returns 0 if success, non-zero code if fail. error codes are                     */
/* defined in socks.h                                                               */
/*                                                                                  */
/************************************************************************************/

int SOCKSconnect (SOCKET sock, unsigned long INETaddr, unsigned short TCPIPport)
{

  if (proxyIp == 0)
	  return SOCKS_ERROR_NO_PROXY;

  //check the socks protocol version
  if (socksVer == 4)
	  return SOCKSconnect_PROTO4 (sock, INETaddr, TCPIPport);
  else if (socksVer == 5)
	  return SOCKSconnect_PROTO5 (sock, INETaddr, TCPIPport);
  else return SOCKS_ERROR_VERSION;
}
