/*
 * everybuddy
 *
 * Copyright (C) 1998-1999, Torrey Searle
 * proxy featured by Seb C.
 *
 * 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.
 *
 * 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.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

/* this is a little piece of code to handle proxy connection */
/* it is intended to : 1st handle http proxy, using the CONNECT command
 , 2nd provide an easy way to add socks support */

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

#include "config.h"
#include "libproxy.h"
static int proxy_inited=0;
int proxy_type = PROXY_NONE;
char *proxy_host;
int proxy_port = 3128;
static char *proxy_realhost = NULL;
static char debug_buff[256];
#define debug_print printf

#ifndef HAVE_GETHOSTBYNAME2
#define gethostbyname2(x, y) gethostbyname(x)
#endif

typedef struct _udp_connection
{
	int fd;
	struct sockaddr * host;
	struct _udp_connection * next;
} udp_connection;

static udp_connection * server_list = NULL;

static struct sockaddr * get_server( int fd )
{
	udp_connection * node = NULL;
	printf("looking for connection matching %d", fd);
	for ( node = server_list; node; node = node->next )
	{
		if(fd == node->fd )
		{
			printf("match found\n");
			return node->host;
		}
	}
	printf("no match found\n");
	return NULL;
}

static int connect_address(unsigned int addy, unsigned short port)
{
        int fd;
    struct sockaddr_in sin;

    sin.sin_addr.s_addr = addy;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(port);

    fd = socket(AF_INET, SOCK_STREAM, 0);

    if (fd > -1)
    {
        if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) > -1)
        {
        	return fd;
        }
		else
		{
			perror("connect:" );
		}

    }
	else
	{
		printf("unable to create socket\n");
	}
    return -1;
}

/* Call this function if you want to free memory when leaving */
void proxy_freemem() {
    /* dummy function to avoid possibly memory leak */
    if (proxy_host != NULL)
        free(proxy_host);
    if (proxy_realhost != NULL)
        free(proxy_realhost);
    return;
}

/* this function is useless if you do use an external method to set 
   the proxy setting, but it does yet allow to test the code without 
   having to code the interface */
static void proxy_autoinit() {
    if (getenv("HTTPPROXY") != NULL) {
        proxy_host=(char *)strdup(getenv("HTTPPROXY"));
        if (proxy_host != NULL) {
            proxy_port=atoi(getenv("HTTPPROXYPORT"));
	    proxy_type=PROXY_HTTP;
            if (!proxy_port)
                proxy_port=3128;
#ifdef HAVE_ATEXIT
	    /* if you do not have atexit, it means that if you don't call
	       proxy_freemem when leaving, you'll have mem leaks */
            atexit(proxy_freemem);
#endif
        }
    }
	else
	{
		proxy_type=PROXY_NONE;
	}
    proxy_inited=1;
    return;
}

/* external function to use to set the proxy settings */
int proxy_set_proxy(int type,char *host,int port) {
    proxy_type=type;
    if (type != PROXY_NONE) {
    proxy_port=0;
    proxy_host=(char *)strdup(host);
    if (proxy_host != NULL) 
	proxy_port=port;
    if (proxy_port == 0)
	proxy_port=3128;
#ifdef HAVE_ATEXIT
    /* if you do not have atexit, it means that if you don't call
       proxy_freemem when leaving, you'll have mem leaks */
    atexit(proxy_freemem);
#endif
    }
    proxy_inited=1;
    return(0);
}

/* this code is borrowed from cvs 1.10 */
static int
proxy_recv_line (int sock, char **resultp)
{
    int c;
    char *result;
    size_t input_index = 0;
    size_t result_size = 80;

    result = (char *) malloc (result_size);

    while (1)
    {
	char ch;
	if (recv (sock, &ch, 1, 0) < 0)
	    fprintf (stderr, "recv() error from  proxy server\n");
	c = ch;

	if (c == EOF)
	{
	    free (result);

	    /* It's end of file.  */
	    fprintf(stderr, "end of file from  server\n");
	}

	if (c == '\012')
	    break;

	result[input_index++] = c;
	while (input_index + 1 >= result_size)
	{
	    result_size *= 2;
	    result = (char *) realloc (result, result_size);
	}
    }

    if (resultp)
	*resultp = result;

    /* Terminate it just for kicks, but we *can* deal with embedded NULs.  */
    result[input_index] = '\0';

    if (resultp == NULL)
	free (result);
    return input_index;
}

int proxy_recv( int s, void * buff, int len, unsigned int flags )
{
	int type;
	int size;

	
	getsockopt( s, SOL_SOCKET, SO_TYPE, &type, &size );
	if( proxy_type != PROXY_SOCKS5 || type == SOCK_STREAM )
	{
		return recv(s, buff, len, flags );
	}
	else
	{
		char * tmpbuff = g_new0(char, len+10);
		int mylen;
		mylen = recv(s, tmpbuff, len+10, flags);
		memcpy( buff, tmpbuff+10, len);
		g_free(tmpbuff);

		return mylen - 9;
	}
	return 0;
}
		


int proxy_send(int  s,  const  void *msg, int len, unsigned int
		       flags)
{
	int type;
	int size;

	printf("proxy_send\n");
	
	getsockopt( s, SOL_SOCKET, SO_TYPE, &type, &size );
	if( proxy_type != PROXY_SOCKS5 || type == SOCK_STREAM)
	{
		if( type != SOCK_DGRAM )
			printf("type TCP\n");
		else
			printf("type UDP\n");

		return send(s, msg, len, flags);
	}
	else
	{
		char * buff = g_new0(gchar, len+10);
		struct sockaddr * serv_addr = get_server( s );
		int ret;

		printf("type UDP %s %d\n", 
				inet_ntoa((((struct sockaddr_in *)serv_addr)->sin_addr)),
				ntohs(((struct sockaddr_in *)serv_addr)->sin_port) );

		buff[3] = 0x01;
		
		memcpy(buff+4, &(((struct sockaddr_in *)serv_addr)->sin_addr),4 );
		memcpy((buff+8), &(((struct sockaddr_in *)serv_addr)->sin_port), 2);
		memcpy((buff+10), msg, len );


		ret = send(s, buff, len+10, flags);
		g_free(buff);
		return ret;

		
	}
	return 0;
}

struct hostent *proxy_gethostbyname(char *host)
{
    if (!proxy_inited)
	proxy_autoinit();
    
    if (proxy_type == PROXY_NONE || proxy_type == PROXY_SOCKS5)
	return (gethostbyname(host));

    if (proxy_realhost != NULL)
	g_free(proxy_realhost);

    /* we keep the real host name for the Connect command */
    proxy_realhost = (char *) strdup(host);
        
    return (gethostbyname(proxy_host));
    
}

struct hostent *proxy_gethostbyname2(char *host,int type)
{
    if (!proxy_inited)
	proxy_autoinit();

    if (proxy_type == PROXY_NONE || proxy_type == PROXY_SOCKS5)
	return (gethostbyname2(host,type));

    if (proxy_realhost != NULL)
	g_free(proxy_realhost);

    /* we keep the real host name for the Connect command */
    proxy_realhost = (char *) strdup(host);

    return (gethostbyname2(proxy_host,type));

}

int socks4_connect(int  sock, struct sockaddr *serv_addr, int addrlen )
{
   struct sockaddr_in sa;
   unsigned char packet[12];
   struct hostent * hp;

   if (!(hp = gethostbyname(proxy_host)))
      return -1;
   
   /* Clear the structure and setup for SOCKS4 open */  
   bzero(&sa, sizeof(sa));
   sa.sin_family = AF_INET;
   sa.sin_port = htons (proxy_port);
   bcopy(hp->h_addr, (char *) &sa.sin_addr, hp->h_length);

   /* Connect to the SOCKS4 port and send a connect packet */
   if (connect(sock, (struct sockaddr *) &sa, sizeof (sa))!=-1)
   {
      unsigned short realport;
      
      if (!(hp = gethostbyname(proxy_realhost)))
      {
         return -1;
      }
      realport = ntohs(((struct sockaddr_in *)serv_addr)->sin_port);
      packet[0] = 4;  /* Version number */
      packet[1] = 1;
      packet[2] = (((unsigned short) realport) >> 8);
      packet[3] = (((unsigned short) realport) & 0xff);
      packet[4] = (unsigned char) (hp->h_addr_list[0])[0];
      packet[5] = (unsigned char) (hp->h_addr_list[0])[1];
      packet[6] = (unsigned char) (hp->h_addr_list[0])[2];
      packet[7] = (unsigned char) (hp->h_addr_list[0])[3];
      packet[8] = 0;
      if (write(sock, packet, 9) == 9)
      {
         bzero(packet, sizeof(packet));
         /* Check response - return as SOCKS4 if its valid */
         if (read(sock, packet, 9)>=4 && packet[1] == 90)
         {
            return 0;
         }
      }
      close(sock);
   }
   
   return -1;
}

int socks5_connect(int  sockfd, struct sockaddr *serv_addr, int addrlen )
{
   int i;
   int s =0;
   int type;
   int size;
   int tcplink = 0;
   struct sockaddr_in bind_addr;
   struct sockaddr_in sin;
   struct hostent * hostinfo;
   char buff[255];

   getsockopt( sockfd, SOL_SOCKET, SO_TYPE, &type, &size );
   printf(" %d %d %d \n", SOCK_STREAM, SOCK_DGRAM, type );

   bind_addr.sin_addr.s_addr = INADDR_ANY;
   bind_addr.sin_family = PF_INET;
   bind_addr.sin_port = 0;

   if( type == SOCK_DGRAM )
   {
	   size = sizeof(bind_addr);
	   if(bind(sockfd, (struct sockaddr*)&bind_addr, sizeof(bind_addr)) < 0 )
	   {
		   perror("bind");
		   exit(0);
	   }
	   getsockname( sockfd, (struct sockaddr*)&bind_addr, &size );
   }
   hostinfo = gethostbyname (proxy_host);
   if (hostinfo == NULL) 
   {
	   fprintf (stderr, "Unknown host %s.\n", proxy_host);
	   return (-1);
   }

   /* connect to the proxy server */
   sin.sin_addr.s_addr = atol(hostinfo->h_addr);
   sin.sin_family = AF_INET;
   sin.sin_port = htons(proxy_port);

   if( type != SOCK_DGRAM )
   {
      s = connect(sockfd, (struct sockaddr *)&sin, sizeof(sin)); 
	   if( s < 0 )
		   return s;
   }
   else
   {
	   printf("Connecting to %x on %u\n", inet_addr(proxy_host), proxy_port );
	   tcplink = connect_address( inet_addr(proxy_host), proxy_port );
	   printf("We got socket %d\n", tcplink);
   }

   buff[0] = 0x05;  //use socks v5
   buff[1] = 0x01;  //we support only one method (no authenticaton)
   buff[2] = 0x00;  //we support the method type "no authentication"

   if( type != SOCK_DGRAM )
   {
	   write( sockfd, buff, 3 );

	   read( sockfd, buff, 2 );
   }
   else
   {
	   write( tcplink, buff, 3 );
	   read( tcplink, buff, 2 );
	   fprintf(stderr, "We got a response back from the SOCKS server\n");
   }


   if( buff[1] != 0x00 )
   {
	   fprintf(stderr, "No Acceptable Methods");
	   exit(0);
   }

   buff[0] = 0x05; //use socks5
   buff[1] = ((type == SOCK_STREAM) ? 0x01 : 0x03); //connect
   buff[2] = 0x00; //reserved
   buff[3] = 0x01; //ipv4 address
   if( type != SOCK_DGRAM )
   {
	   memcpy(buff+4, &(((struct sockaddr_in *)serv_addr)->sin_addr),4 );
	   memcpy((buff+8), &(((struct sockaddr_in *)serv_addr)->sin_port), 2);
   }
   else
   {
	   buff[4] = 0x00;
	   buff[5] = 0x00;
	   buff[6] = 0x00;
	   buff[7] = 0x00;
	   memcpy((buff+8), &(((struct sockaddr_in *)&bind_addr)->sin_port), 2);
	   printf("UDP port %s %d\n", inet_ntoa(((struct sockaddr_in *)serv_addr)->sin_addr), 
			   ntohs(((struct sockaddr_in *)serv_addr)->sin_port));
	   for( i = 0; i < 8; i++ )
	   {
		   printf("%03d ", buff[i] );
	   }
	   printf("%d", ntohs(*(unsigned short *)&buff[8]));
	   printf("\n");
   }

   if( type != SOCK_DGRAM )
   {
	   write( sockfd, buff, 10 );
	   read(sockfd, buff, 10);
   }
   else
   {
	   write( tcplink, buff, 10 );
	   read( tcplink, buff, 10);
	   fprintf(stderr, "We got another response from the server!\n");
   }

   //	buff[1] = 0;


   if( buff[1] == 0x00 )
   {
	   for( i = 0; i < 8; i++ )
	   {
		   printf("%03d ", buff[i] );
	   }
	   printf("%d", ntohs(*(unsigned short *)&buff[8]));
	   printf("\n");
	   if( type == SOCK_DGRAM )
	   {
		   udp_connection * conn = g_new0(udp_connection, 1);
		   struct sockaddr * host = g_new0(struct sockaddr, 1);
		   memcpy(host, serv_addr, sizeof(struct sockaddr_in));
		   conn->next = server_list;
		   conn->fd = sockfd;
		   conn->host = host;
		   server_list = conn;
		   printf("adding UDP connection %s %d on fd %d\n", 
			   inet_ntoa((((struct sockaddr_in *)serv_addr)->sin_addr)),
			   ntohs(((struct sockaddr_in *)serv_addr)->sin_port),
			   sockfd );


		   memcpy( &sin.sin_addr, buff+4, 4);
		   memcpy( &sin.sin_port, buff+8, 2);

    	   sin.sin_family = AF_INET;
		   printf( "trying to connect to %s on port %d \n", 
				   inet_ntoa(sin.sin_addr), ntohs(sin.sin_port) );
         s = connect(sockfd, (struct sockaddr *)&sin, sizeof(sin)); 
	   }

	   printf("libproxy; SOCKS5 connection on %d\n", sockfd );
	   return  s;
   }
   else
   {
	   for( i = 0; i < 8; i++ )
	   {
		   printf("%03d ", buff[i] );
	   }
	   printf("%d", ntohs(*(unsigned short *)&buff[8]));
	   printf("\n");
	   fprintf(stderr, "SOCKS error number %d\n", buff[1] );
	   close(sockfd);
	   exit(0);
	   return -1;
   }
}

int http_tunnel_init(int sockfd, struct sockaddr *serv_addr, int addrlen )
{
	/* step two : do  proxy tunneling init */
	char cmd[80];
	char *inputline;
	unsigned short realport=ntohs(((struct sockaddr_in *)serv_addr)->sin_port);
   
	sprintf(cmd,"CONNECT %s:%d HTTP/1.1\n\r\n\r",proxy_realhost,realport);
#ifndef DEBUG
	sprintf(debug_buff,"<%s>\n",cmd);
	debug_print(debug_buff);
#endif
	if (send(sockfd,cmd,strlen(cmd),0)<0)
		return(-1);
	if (proxy_recv_line(sockfd,&inputline) < 0)
		return(-1);
#ifndef DEBUG
	sprintf(debug_buff,"<%s>\n",inputline);
	debug_print(debug_buff);
#endif
	if (memcmp("HTTP/1.0 200 Connection established",inputline,35))
		if (memcmp("HTTP/1.1 200 Connection established",inputline,35)) {
		   free(inputline);
		   return(-1);
		}

	while (strlen(inputline)>1) {
		free(inputline);
		if (proxy_recv_line(sockfd,&inputline) < 0) {
		   return(-1);
		}
#ifndef DEBUG
		sprintf(debug_buff,"<%s>\n",inputline);
		debug_print(debug_buff);
#endif
	}
	free(inputline);
	        
	return 0;
}

int http_connect(int sockfd, struct sockaddr *serv_addr, int addrlen )
{
	/* do the  tunneling */
	/* step one : connect to  proxy */
   struct sockaddr_in name;
   int ret;
	struct hostent *hostinfo;
	unsigned short shortport = proxy_port;

	memset (&name, 0, sizeof (name));
	name.sin_family = AF_INET;
	name.sin_port = htons (shortport);
	hostinfo = gethostbyname (proxy_host);
	if (hostinfo == NULL) {
		fprintf (stderr, "Unknown host %s.\n", proxy_host);
		return (-1);
	}
	name.sin_addr = *(struct in_addr *) hostinfo->h_addr;
#ifndef DEBUG
	sprintf(debug_buff,"Trying to connect ...\n");
	debug_print(debug_buff);
#endif
	if ((ret = connect(sockfd,(struct sockaddr *)&name,sizeof(name)))<0)
	   return(ret);

   return (http_tunnel_init(sockfd, serv_addr, addrlen));       
}

int proxy_connect(int  sockfd, struct sockaddr *serv_addr, int addrlen )
{
   if (!proxy_inited)
	   proxy_autoinit();

   switch (proxy_type) {
      case PROXY_NONE:    /* No proxy */
	      return (connect(sockfd,serv_addr,addrlen));
	      break;
      case PROXY_HTTP:    /* Http proxy */
         return http_connect(sockfd, serv_addr, addrlen);    
	      break;
      case PROXY_SOCKS4:  /* SOCKS4 proxy */
	      return socks4_connect(sockfd, serv_addr, addrlen);
	      break;
      case PROXY_SOCKS5:  /* SOCKS5 proxy */
	      return socks5_connect(sockfd, serv_addr, addrlen);
	      break;
      default:
	      fprintf(stderr,"Unknown proxy type : %d.\n",proxy_type);
	      break;
   }
   return(-1);
}

