/***********************************************************************/
/* autoroute.c by truff (truff@projet7.org)                            */ 
/*                                                                     */
/* Autoroute is a network utility that permits to redirect             */
/* a TCP connexion through a proxy chain.                              */
/* HTTP and socks 5 proxies are supported                              */
/*                                                                     */
/* compil with: gcc -o autoroute autoroute.c                           */
/*                                                                     */
/* To use autoroute you need to create a file contening the proxy      */
/* list. The file must be like this:                                   */
/* ip_proxy1 port_proxy1 type_proxy1                                   */
/* ip_proxy2 port_proxy2 type_proxy2                                   */
/* ......... ........... ...........                                   */
/*                                                                     */
/* Proxy types are:                                                    */
/* SOCKS 5 : 0                                                         */
/* HTTP    : 1                                                         */
/*                                                                     */
/* Special thanks to hotfyre for his help                              */
/* Greetz goes to all projet7's members and all open coderz            */ 
/*                                                                     */
/* Visit our lab at www.projet7.org                                    */
/*                                                                     */
/***********************************************************************/




#include <stdio.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/time.h>
#include <fcntl.h>

#define     VERSION        "0.2"
#define     LISTEN_PORT    2000   /* Change this to the port u want to be listening on */
#define     SOCKS          0
#define     HTTP           1


int SocksConnect (int sock, long adress, short port, char type);
int HttpConnect (int sock, long adress, short port, char type);
int GetSocksError (char error);
int IsAuthNeeded (char method);
void DisplayHelp (char *progname);

int
main (int argc, char **argv)
{

  int socklocal, sockremote, sockservice;
  int nbrsocks = 0;
  FILE *fdconf;
  int maxfd, longueur;
  int sopts = 1;
  int i = 0;
  char buffer[512];
  fd_set readfs;
  struct sockaddr_in adresse_in, adresse_out;
  struct timeval tv;
  short sock_port[16];
  long sock_addr[16];
  short type[16];
  char *opt_list = "i:p:f:n:hv";
  int option;
  long ipserv;
  short portserv;
  char *conffile = "/etc/autoroot.conf";

  opterr = 0;

  while ((option = getopt (argc, argv, opt_list)) != -1)
    {
      switch (option)
	{
	case 'i':
	  ipserv = inet_addr(optarg);
	  break;
	case 'p':
	  portserv = atoi(optarg);
	  break;
	case 'f':
	  conffile = optarg;
	  break;
	case 'n':
	  nbrsocks = atoi(optarg);
	  break;
	case 'h':
	  DisplayHelp (argv[0]);
	  break;
	case 'v':
	  fprintf (stdout, "Autoroute %s by truff\n", VERSION);
	  fprintf (stdout, "http://www.projet7.org/\n");
	  exit (0);
	case '?':
	  fprintf (stderr, "Unknown argument : %c\n", optopt);
	  fprintf (stderr, "For help, use %s -h\n", argv[0]);
	  exit (1);
	}
    }

  if (optind != argc)
    {
      fprintf (stderr, "Bad arguments :\n");
      while (optind != argc)
	fprintf (stderr, " %s\n", argv[optind++]);
      exit (1);
    }


  if ((fdconf = fopen (conffile, "r")) == NULL)
    {
      perror ("Opening config file");
      exit (0);
    }



  /* Reading config file whith the proxy list */
  do
    {
      memset (buffer, 0, sizeof (buffer));
      fgets (buffer, sizeof (buffer), fdconf);
      sock_addr[i] = inet_addr (strtok (buffer, " "));
      sock_port[i] = atoi (strtok (NULL, " "));
      type[i] = atoi (strtok (NULL, "\n"));
      i++;
    }
  while (i < nbrsocks); 
 
  fclose (fdconf);


  /* Setting up the listening socket */
  if ((socklocal = socket (AF_INET, SOCK_STREAM, 0)) == -1)
    {
      perror ("Socket creation");
      exit (0);
    }

  if (setsockopt(socklocal, SOL_SOCKET, SO_REUSEADDR, &sopts, sizeof (int)) == -1)
    {
      perror ("Could not Setsockopt");
      exit (0);
    }

  memset (&adresse_in, 0, sizeof (struct sockaddr_in));
  adresse_in.sin_family = AF_INET;
  adresse_in.sin_port = htons (LISTEN_PORT);
  adresse_in.sin_addr.s_addr = htonl (INADDR_ANY);
  if (bind (socklocal, (struct sockaddr *)&adresse_in, sizeof (struct sockaddr_in)) == -1)
    {
      perror ("Bind");
      close (socklocal);
      exit (0);
    }

  if (listen (socklocal, 1) == -1)
    {
      perror ("Listen");
      exit (0);
    }

  if ((sockservice = accept (socklocal, NULL, 0)) == -1)
    {
      perror ("Accept");
      exit (0);
    }


  /* Connexion to the first proxy */
  close (socklocal);
  sockremote = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
  memset (&adresse_out, 0, sizeof (struct sockaddr_in));
  adresse_out.sin_family = AF_INET;
  adresse_out.sin_port = htons (sock_port[0]);
  adresse_out.sin_addr.s_addr = sock_addr[0];
  printf ("%s\t%d", inet_ntoa (sock_addr[0]), sock_port[0]);
  if ((connect(sockremote, (struct sockaddr *) &adresse_out, sizeof (struct sockaddr_in))) == -1)
    {
      perror ("Connecting to proxy");
      exit (0);
    }

  printf ("\t[Connected]\n");


  /* Connecting to the others proxies */
  if (nbrsocks > 1)
    {
      for (i = 1; i < nbrsocks; i++)
	{
	  switch (type[i - 1])
	    {
	    case SOCKS:
	      if (SocksConnect (sockremote, sock_addr[i], sock_port[i], 0))
		exit (1);
	      break;
	    case HTTP:
	      if (HttpConnect (sockremote, sock_addr[i], sock_port[i], 0))
		exit (1);
	      break;
	    }
	}
    }


  /* Connecting to the destination server */
  switch (type[nbrsocks - 1])
    {
    case SOCKS:
      if (SocksConnect (sockremote, ipserv, portserv, 1))
	exit (1);
      printf ("\t[Connected]\n");
      break;
    case HTTP:
      if (HttpConnect (sockremote, ipserv, portserv, 0))
	exit (1);
      break;
    }



  /* Send and receive loop */
  maxfd = sockservice > sockremote ? sockservice : sockremote;
  while (1)
    {

      FD_ZERO (&readfs);
      FD_SET (sockremote, &readfs);
      FD_SET (sockservice, &readfs);
      select (maxfd + 1, &readfs, NULL, NULL, NULL);
      if (FD_ISSET (sockservice, &readfs))
	{
	  longueur = recv (sockservice, buffer, sizeof (buffer), 0);
	  if ((longueur == 0) || (longueur == 1))
	    break;
	  if ((send (sockremote, buffer, longueur, 0)) == -1)
	    break;
	}

      if (FD_ISSET (sockremote, &readfs))
	{
	  longueur = recv (sockremote, buffer, sizeof (buffer), 0);
	  if ((longueur == 0) || (longueur == 1))
	    break;
	  if ((send (sockservice, buffer, longueur, 0)) == -1)
	    break;
	}
    }

  return 0;
}



/***************************************/
/* Socks 5 Connect request             */
/***************************************/
int
SocksConnect (int sock, long adress, short port, char type)
{

  int longueur;
  char buffer[512];
  fd_set fds;
  struct timeval tv;
  char *ch_addr;

  ch_addr = (char *) inet_ntoa (adress);
  printf ("%s\t%d", ch_addr, port);

  snprintf (buffer, sizeof (buffer), "\x05\x01%c", 0);
  send (sock, buffer, 3, 0);
  
  FD_ZERO (&fds);
  FD_SET (sock, &fds);
  tv.tv_sec = 20;
  tv.tv_usec = 0;
  select (sock + 1, &fds, NULL, NULL, &tv);
  if (!FD_ISSET (sock, &fds))
    {
      printf ("\tTime out\n");
      exit (0);
    }

  longueur = recv (sock, buffer, 100, 0);
  if (type == 0)
    {
      if (IsAuthNeeded (buffer[1]))
	return 1;
    }


  /* Socks 5 connect request whithout auth (RFC 1928) */
  longueur =
    snprintf (buffer, sizeof (buffer), "\x05\x01%c\x03%c%s%c%c", 0,
	      strlen (ch_addr), ch_addr, (port >> 8) & 0xFF, port & 0xFF);
  send (sock, buffer, longueur, 0);
  recv (sock, buffer, longueur, 0);
  if (type == 0)
    {
      if (GetSocksError (buffer[1])) 
	return 1;
    }

  return 0;
}



/***************************************/
/* Tests auth method for the socks 5   */
/***************************************/
int
IsAuthNeeded (char method)
{
  switch (method)
    {
    case 0x0:
      return 0;
      break;
    case 0x1:
      return 0;
      break;
    case 0x2:
      printf ("\tUsername/Password required\n");
      return 1;
      break;
    default:
      printf ("\tAuth method not implemented\n");
      return 1;
      break;
    }
}




/***************************************/
/* Errot handling for the socks 5      */
/***************************************/
int
GetSocksError (char error)
{
  switch (error)
    {
    case 0x0:
      printf ("\t[Connected]\n");
      return 0;
      break;
    case 0x1:
      printf ("\tgeneral SOCKS server failure\n");
      return 1;
      break;
    case 0x2:
      printf ("\tconnection not allowed by ruleset\n");
      return 1;
      break;
    case 0x3:
      printf ("\tNetwork unreachable\n");
      return 1;
      break;
    case 0x4:
      printf ("\tHost unreachable\n");
      return 1;
      break;
    case 0x5:
      printf ("\tConnection refused\n");
      return 1;
      break;
    case 0x6:
      printf ("\tTTL expired\n");
      return 1;
      break;
    case 0x7:
      printf ("\tCommand not supported\n");
      return 1;
      break;
    case 0x8:
      printf ("\tAddress type not supported\n");
      return 1;
      break;
    default:
      printf ("\tErreur non reconnue\n");
      return 1;
      break;
    }
}



/***************************************/
/* HTTP proxy Connect request          */
/***************************************/
int
HttpConnect (int sock, long adress, short port, char type)
{

  int longueur;
  char buffer[512];
  fd_set fds;
  struct timeval tv;
  char *ch_addr;
  
  ch_addr = (char *) inet_ntoa (adress);
  printf ("%s\t%d", ch_addr, port);

  longueur =
    snprintf (buffer, sizeof (buffer),
	      "CONNECT %s:%d HTTP/1.0\n\n", ch_addr, port);
  send (sock, buffer, longueur, 0);

  FD_ZERO (&fds);
  FD_SET (sock, &fds);
  tv.tv_sec = 20;
  tv.tv_usec = 0;
  select (sock + 1, &fds, NULL, NULL, &tv);
  if (!FD_ISSET (sock, &fds))
    {
      printf ("\tTime out\n");
      exit (0);
    }

  longueur = recv (sock, buffer, 100, 0);
  printf ("\t[Connected]\n");
  return 0;
}



/***************************************/
/* General Help                        */
/***************************************/
void
DisplayHelp (char *progname)
{
  fprintf (stderr, "Syntaxe : %s [option]\n", progname);
  fprintf (stderr, "Options :\n");
  fprintf (stderr, "-h		Display this help menu\n");
  fprintf (stderr, "-i		IP of the server you want to connect to\n");
  fprintf (stderr, "-p		Port of the server you want to connect to\n");
  fprintf (stderr,
	   "-f		Configuration file to use. If not specified /etc/autoroot.conf will be used\n");
  fprintf (stderr, "-n          How many proxies to use\n");
  exit (0);
}
