/*	WebDownloader for X-Window
 *	Copyright (C) 1999-2002 Koshelev Maxim
 *	This Program is free but not GPL!!! You can't modify it
 *	without agreement with author. You can't distribute modified
 *	program but you can distribute unmodified program.
 *
 *	This program 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.
 */

#include "socks.h"
#include "dbc.h"
#include <sys/types.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
enum D4X_SOCKS_AUTH{
	SOCKS_AUTH_NONE=0,
	SOCKS_AUTH_GSSAPI,
	SOCKS_AUTH_USER
};

#define SOCKS_AUTH_FAILED 0xFF

enum D4X_SOCKS_CMDS{
	SOCKS_CMD_UNKNOWN=0,
	SOCKS_CMD_CONNECT,
	SOCKS_CMD_BIND,
	SOCKS_CMD_UDP
};

enum D4X_SOCKS_ATYPES{
	SOCKS_ATYPE_UNKNOWN=0,
	SOCKS_ATYPE_IPV4,
	SOCKS_ATYPE_RESERVED,
	SOCKS_ATYPE_DOMAIN,
	SOCKS_ATYPE_IPV6
};

enum D4X_SOCKS_REPLIES{
	SOCKS_REP_SUCCESS,
	SOCKS_REP_FAILURE,
	SOCKS_REP_FORBIDEN,
	SOCKS_REP_NETFAILED,
	SOCKS_REP_HOSTFAILED,
	SOCKS_REP_REFUSED,
	SOCKS_REP_EXPIRED,
	SOCKS_REP_NOTSUPPORTED,
	SOCKS_REP_WRONGTYPE
};

#define SOCKS_VERSION 5

/****************************************************************/

tSocksSocket::tSocksSocket():tSocket(){
	socks_port=0;
};

tSocksSocket::tSocksSocket(char *host,unsigned short int port,
			   char *use,char *pas):tSocket(){
	user.set(use);
	pass.set(pas);
	socks_host.set(host);
	socks_port=port;
};

tSocksSocket::~tSocksSocket(){
};

void tSocksSocket::socks_init(){
	DBC_RETURN_IF_FAIL(socks_host.get()!=NULL);
	if (tSocket::open_port(socks_host.get(),socks_port)){
		down();
		return;
	};
		
	if (user.get()==NULL || pass.get()==NULL){
		unsigned char request[]={SOCKS_VERSION,1,SOCKS_AUTH_NONE};
		write(fd,request,sizeof(request));
	}else{
		unsigned char request[]={SOCKS_VERSION,2,
				       SOCKS_AUTH_NONE,
				       SOCKS_AUTH_USER};
		write(fd,request,sizeof(request));
	};
	if (read(fd,socks_buf,2)<2){
		down();
		return;
	};
	if (socks_buf[1]==SOCKS_AUTH_NONE)
		return; //successfull initiated
	if (socks_buf[1]==SOCKS_AUTH_USER && user.get() && pass.get()){
		unsigned char tmp=1;
		write(fd,&tmp,sizeof(tmp));
		tmp=strlen(user.get());
		write(fd,&tmp,sizeof(tmp));
		write(fd,user.get(),tmp);
		tmp=strlen(pass.get());
		write(fd,&tmp,sizeof(tmp));
		write(fd,pass.get(),tmp);
		if (read(fd,&socks_buf,2)<2){
			down();
			return;
		};
		if (socks_buf[1]==0)
			return;
	};
	down(); //failed to connect
};

int tSocksSocket::socks_connect_reply(){
	int repsize=read(fd,socks_buf,10);
	if (repsize<10){
		down();
		return(SOCKET_CANT_CONNECT);
	};
	switch(socks_buf[1]){
	case SOCKS_REP_SUCCESS:{
		break;
	};
	case SOCKS_REP_FAILURE:
	case SOCKS_REP_NETFAILED:
	case SOCKS_REP_HOSTFAILED:
		return(SOCKET_UNKNOWN_HOST);
	default:
		return(SOCKET_CANT_CONNECT);
	};		
	return 0;
};

int tSocksSocket::open_port(char *host, guint16 port) {
	DBC_RETVAL_IF_FAIL(host!=NULL,SOCKET_CANT_CONNECT);
	socks_init();
	if (fd<0) return(SOCKET_CANT_CONNECT);
	socks_buf[0]=SOCKS_VERSION;
	socks_buf[1]=SOCKS_CMD_CONNECT;
	socks_buf[2]=0;
	socks_buf[3]=SOCKS_ATYPE_DOMAIN;
	write(fd,socks_buf,4);
	unsigned char tmp=strlen(host);
	write(fd,&tmp,1);
	write(fd,host,tmp);
	port=htons(port);
	write(fd,&port,2);
	return(socks_connect_reply());
};

int tSocksSocket::open_port(guint32 host,guint16 port) {
//	port=htons(port);
//	host=htonl(host);
	socks_init();
	if (fd<0) return(SOCKET_CANT_CONNECT);
	socks_buf[0]=SOCKS_VERSION;
	socks_buf[1]=SOCKS_CMD_CONNECT;
	socks_buf[2]=0;
	socks_buf[3]=SOCKS_ATYPE_IPV4;
	write(fd,socks_buf,4);
	write(fd,&host,4);
	write(fd,&port,2);
	return(socks_connect_reply());
};

unsigned int tSocksSocket::get_addr(){
	return(bnd_host);
};

unsigned short int tSocksSocket::get_port(){
	return(bnd_port);
};

int tSocksSocket::socks_bind_reply(){
	int repsize=read(fd,socks_buf,10);
	if (repsize<10){
		down();
		return(SOCKET_CANT_CONNECT);
	};
	switch(socks_buf[1]){
	case SOCKS_REP_SUCCESS:{
		break;
	};
	case SOCKS_REP_FAILURE:
	case SOCKS_REP_NETFAILED:
	case SOCKS_REP_HOSTFAILED:
		return(SOCKET_UNKNOWN_HOST);
	default:
		return(SOCKET_CANT_CONNECT);
	};
	memcpy(&bnd_host,socks_buf+4,4);
	memcpy(&bnd_port,socks_buf+8,2);
	bnd_host=ntohl(bnd_host);
	bnd_port=ntohs(bnd_port);
	return 0;
};

int tSocksSocket::accepting(char *host){
	DBC_RETVAL_IF_FAIL(host!=NULL,-1);
	int repsize=read(fd,socks_buf,10);
	if (repsize<10){
		down();
		return(-1);
	};
	if (socks_buf[1]!=SOCKS_REP_SUCCESS){
		down();
		return(-1);
	};
	return(0);
};

int tSocksSocket::open_any(char * host){
	DBC_RETVAL_IF_FAIL(host!=NULL,SOCKET_CANT_CONNECT);
	socks_init();
	if (fd<0) return(SOCKET_CANT_CONNECT);
	socks_buf[0]=SOCKS_VERSION;
	socks_buf[1]=SOCKS_CMD_BIND;
	socks_buf[2]=0;
	socks_buf[3]=SOCKS_ATYPE_DOMAIN;
	write(fd,socks_buf,4);
	unsigned char tmp=strlen(host);
	write(fd,&tmp,1);
	write(fd,host,tmp);
	guint16 port=0;
	write(fd,&port,2);
	return(socks_bind_reply());
};

int tSocksSocket::open_any(guint32 host) {
	host=htonl(host);
	socks_init();
	if (fd<0) return(SOCKET_CANT_CONNECT);
	socks_buf[0]=SOCKS_VERSION;
	socks_buf[1]=SOCKS_CMD_BIND;
	socks_buf[2]=0;
	socks_buf[3]=SOCKS_ATYPE_IPV4;
	write(fd,socks_buf,4);
//	guint32 me=htonl(tSocket::get_addr());
//	write(fd,&me,4);
/*
	char a[6]={0,0,0,0,0,0};
	write(fd,a,6);
*/
	write(fd,&host,4);
	guint16 port=0;
	write(fd,&port,2);
	return(socks_bind_reply());
};
