/***************************************************************************
                          libproxychains.c  -  description
                             -------------------
    begin                : Tue May 14 2002
    copyright          :  netcreature (C) 2002
    email                 : netcreature@users.sourceforge.net
 ***************************************************************************/
 /*     GPL */
/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
#define _GNU_SOURCE
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <memory.h>
#include <errno.h>
#include <netdb.h>

#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/fcntl.h>
#include <dlfcn.h>


#include "core.h"

#define     satosin(x)      ((struct sockaddr_in *) &(x))
#define     SOCKADDR(x)     (satosin(x)->sin_addr.s_addr)
#define     SOCKADDR_2(x)     (satosin(x)->sin_addr)
#define     SOCKPORT(x)     (satosin(x)->sin_port)
#define     SOCKFAMILY(x)     (satosin(x)->sin_family)
#define     MAX_CHAIN 30*1024

int tcp_read_time_out;
int tcp_connect_time_out;
chain_type proxychains_ct;
proxy_data proxychains_pd[MAX_CHAIN];
int proxychains_proxy_count = 0;
int proxychains_got_chain_data = 0;
int proxychains_max_chain = 1;
int proxychains_quiet_mode = 0;
int proxychains_resolver = 0;
static int init_l = 0;
static inline void get_chain_data(
	proxy_data *pd,
	unsigned int *proxy_count,
	chain_type *ct);

static void init_lib()
{
//	proxychains_write_log("ProxyChains-"VERSION
//			" (http://proxychains.sf.net)\n");
	
	get_chain_data(proxychains_pd,&proxychains_proxy_count,&proxychains_ct);
	true_connect = (connect_t) dlsym(RTLD_NEXT, "connect");

	if (!true_connect) {
		fprintf(stderr, "Cannot load symbol 'connect' %s\n", dlerror());
		exit(1);
	} else {
//		PDEBUG( "loaded symbol 'connect'"
//		" real addr %p  wrapped addr %p\n",
//		true_connect, connect);
	}
	true_gethostbyname = (gethostbyname_t) 
		dlsym(RTLD_NEXT, "gethostbyname");

	if (!true_gethostbyname) {
		fprintf(stderr, "Cannot load symbol 'gethostbyname' %s\n", 
				dlerror());
		exit(1);
	} else {
//		PDEBUG( "loaded symbol 'gethostbyname'"
//		" real addr %p  wrapped addr %p\n",
//		true_gethostbyname, gethostbyname);
	}
	true_getaddrinfo = (getaddrinfo_t) 
		dlsym(RTLD_NEXT, "getaddrinfo");

	if (!true_getaddrinfo) {
		fprintf(stderr, "Cannot load symbol 'getaddrinfo' %s\n", 
				dlerror());
		exit(1);
	} else {
//		PDEBUG( "loaded symbol 'getaddrinfo'"
//			" real addr %p  wrapped addr %p\n",
//			true_getaddrinfo, getaddrinfo);
	}
	true_freeaddrinfo = (freeaddrinfo_t) 
		dlsym(RTLD_NEXT, "freeaddrinfo");

	if (!true_freeaddrinfo) {
		fprintf(stderr, "Cannot load symbol 'freeaddrinfo' %s\n", 
				dlerror());
		exit(1);
	} else {
//		PDEBUG( "loaded symbol 'freeaddrinfo'"
//			" real addr %p  wrapped addr %p\n",
//			true_freeaddrinfo, freeaddrinfo);
	}
	true_gethostbyaddr = (gethostbyaddr_t) 
		dlsym(RTLD_NEXT, "gethostbyaddr");

	if (!true_gethostbyaddr) {
		fprintf(stderr, "Cannot load symbol 'gethostbyaddr' %s\n", 
				dlerror());
		exit(1);
	} else {
//		PDEBUG( "loaded symbol 'gethostbyaddr'"
//			" real addr %p  wrapped addr %p\n",
//			true_gethostbyaddr, gethostbyaddr);
	}
	true_getnameinfo = (getnameinfo_t) 
		dlsym(RTLD_NEXT, "getnameinfo");

	if (!true_getnameinfo) {
		fprintf(stderr, "Cannot load symbol 'getnameinfo' %s\n", 
				dlerror());
		exit(1);
	} else {
//		PDEBUG( "loaded symbol 'getnameinfo'"
//			" real addr %p  wrapped addr %p\n",
//			true_getnameinfo, getnameinfo);
	}
	init_l = 1;
}


static inline void get_chain_data(
			proxy_data *pd,
			unsigned int *proxy_count,
			chain_type *ct)
{


	int count=0,port_n=0,list=0;
	char buff[1024],type[1024],host[1024],user[1024];
	FILE* file;

	if(proxychains_got_chain_data)
	return;

	//Some defaults
	tcp_read_time_out=4*1000;
	tcp_connect_time_out=10*1000;
	*ct=DYNAMIC_TYPE;

	snprintf(buff,256,"%s/.proxychains/proxychains.conf",getenv("HOME"));

	if(!(file=fopen("./proxychains.conf","r")))
	if(!(file=fopen(buff,"r")))
	if(!(file=fopen("/etc/proxychains.conf","r")))
	{
		perror("Can't locate proxychains.conf");
		exit(1);
	}

	while(fgets(buff,sizeof(buff),file)) {
		if(buff[strspn(buff," ")]!='#') {
			if(list) {
				bzero(&pd[count], sizeof(proxy_data));
				pd[count].ps=PLAY_STATE;
				port_n=0;
				sscanf(buff,"%s %s %d %s %s", type,host,&port_n,
					pd[count].user,pd[count].pass);
				pd[count].ip=inet_addr(host);
				pd[count].port=htons((unsigned short)port_n);
				if(!strcmp(type,"http")) {
					pd[count].pt=HTTP_TYPE;
				}else if(!strcmp(type,"socks4")) {
					pd[count].pt=SOCKS4_TYPE;
				}else if(!strcmp(type,"socks5")) {
					pd[count].pt=SOCKS5_TYPE;
				}else continue;
				
				if( pd[count].ip && pd[count].ip!=-1 && port_n)
					if(++count==MAX_CHAIN)
						break;
			 } else {
				if(strstr(buff,"[ProxyList]")) {
					list=1;
				} else if(strstr(buff,"random_chain")) {
					*ct=RANDOM_TYPE;
				} else if(strstr(buff,"strict_chain")) {
					*ct=STRICT_TYPE;
				} else if(strstr(buff,"dynamic_chain")) {
					*ct=DYNAMIC_TYPE;
				}else if(strstr(buff,"tcp_read_time_out")){
					sscanf(buff,"%s %d",user,&tcp_read_time_out) ;
				}else if(strstr(buff,"tcp_connect_time_out")){
					sscanf(buff,"%s %d",user,&tcp_connect_time_out) ;
				}else if(strstr(buff,"chain_len")){
					char *pc;int len;
					pc=strchr(buff,'=');
					len=atoi(++pc);
					proxychains_max_chain=(len?len:1);
				}else if(strstr(buff,"quiet_mode")){
					proxychains_quiet_mode=1;
				}else if(strstr(buff,"proxy_dns")){
					proxychains_resolver=1;
				}
			}
		}
	}
	fclose(file);
	*proxy_count=count;
	proxychains_got_chain_data=1;
}



int connect (int sock, const struct sockaddr *addr, unsigned int len)
{
	int socktype=0,optlen=0,flags=0,ret=0;

	if(!init_l)
		init_lib();
	optlen=sizeof(socktype);
	getsockopt(sock,SOL_SOCKET,SO_TYPE,&socktype,&optlen);
	if (! (SOCKFAMILY(*addr)==AF_INET  && socktype==SOCK_STREAM))
		return true_connect(sock,addr,len);
	flags=fcntl(sock, F_GETFL, 0);
	if(flags & O_NONBLOCK)
	fcntl(sock, F_SETFL, !O_NONBLOCK);
	ret=connect_proxy_chain(
		sock,
		SOCKADDR(*addr),
		SOCKPORT(*addr),
		proxychains_pd,
		proxychains_proxy_count,
		proxychains_ct,
		  proxychains_max_chain );
	fcntl(sock, F_SETFL, flags);
	if(ret!=SUCCESS)
	errno=ECONNREFUSED;
	return ret;
}

struct hostent *gethostbyname(const char *name)
{
	PDEBUG("gethostbyname: %s\n",name);
	if(!init_l)
		init_lib();
	if(proxychains_resolver)
		return proxy_gethostbyname(name);
	else
		return true_gethostbyname(name);
			
	return NULL;
}
int getaddrinfo(const char *node, const char *service,
		const struct addrinfo *hints,
		struct addrinfo **res)
{
	int ret = 0;
	PDEBUG("getaddrinfo: %s %s\n",node ,service);
	if(!init_l)
		init_lib();
	if(proxychains_resolver)
		ret = proxy_getaddrinfo(node, service, hints, res);
	else
		ret = true_getaddrinfo(node, service, hints, res);
			
	return ret;
}
void freeaddrinfo(struct addrinfo *res)
{
	PDEBUG("freeaddrinfo %p \n",res);
	if(!init_l)
		init_lib();
	if(!proxychains_resolver)
		true_freeaddrinfo(res);
	else {
		free(res->ai_addr);
		free(res);
	}
	return;
}

int getnameinfo (const struct sockaddr * sa,
			socklen_t salen, char * host,
			socklen_t hostlen, char * serv,
			socklen_t servlen, unsigned int flags)
{
	int ret = 0;
	if(!init_l)
		init_lib();
	if(!proxychains_resolver) {
		ret = true_getnameinfo(sa,salen,host,hostlen,
				serv,servlen,flags);
	} else {
		if(hostlen) 
			strncpy(host, inet_ntoa(SOCKADDR_2(*sa)),hostlen);
		if(servlen) 
			snprintf(serv, servlen,"%d",ntohs(SOCKPORT(*sa)));
	}
	PDEBUG("getnameinfo: %s %s\n", host, serv);
	return ret;
}
struct hostent *gethostbyaddr (const void *addr, socklen_t len,
		                                      int type)
{
	PDEBUG("TODO: gethostbyaddr hook\n"); 
	if(!init_l)
		init_lib();
	if(!proxychains_resolver)
		return true_gethostbyaddr(addr,len,type);
	return NULL;
}
	
