/*
 * Hace una peticion BIND a un proxy socks y la redirige a la maquina que
 * le decimos por el puerto que le decimos
 *  V 0.1 dreyer
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <resolv.h>
#include <string.h>
#include <getopt.h>

#define BUFSIZE 512
#define SOCKSPORT 1080
#define SOCKS4 4
#define SOCKS5 5

char portclosed[] = "socks: Destination port closed (?)\n";
char sockshostdown[] = "The socks host seems to be down\n";
char permdeny[] = "socks: Permission denied\n";
char timedout[] = "socks: Connection timeout\n";
int outsocket,bouncesocket;
fd_set rfds;
int verbose=0;


void warning (char *dest, char *msg)
{ 
      printf("Error:%s:%s\n",dest,msg);
}

int bind_socks4 (char *hostpermit, short portpermit,char *dest) {
  char buffer[BUFSIZE];
  struct timeval tv;
  struct hostent *host_he;
  short myport;
  int myhost;

  if ((host_he=gethostbyname(hostpermit))==NULL)
  {
      warning(hostpermit,"gethostbyname");
      exit(-1);
  }
  snprintf (buffer, sizeof (buffer), "\x04\x02%c%c%c%c%c%c", (portpermit >> 8) & 0xFF, portpermit & 0xFF, (char) host_he->h_addr[0], (char) host_he->h_addr[1], (char) host_he->h_addr[2], (char) host_he->h_addr[3]);    /* <-- ip# */
  send (outsocket, buffer, 9, 0);
  
  FD_ZERO (&rfds);
  FD_SET (outsocket, &rfds);
  tv.tv_sec = 30;
  tv.tv_usec = 0;
  select (outsocket + 1, &rfds, NULL, NULL, &tv);
  if (!FD_ISSET (outsocket, &rfds))
    {
      warning(dest,timedout);
      exit(-1);
    }
  if ((recv (outsocket, buffer, 8, 0)) < 8)
        {
          warning(dest,permdeny);
          exit (-1);
        }
  if (buffer[1] != 0x5A)
        {
          warning(dest,portclosed);
          exit (-1);
        }
      memcpy((char *)&myport,&buffer[2],2);
      memcpy((char *)&myhost,&buffer[4],4);
      myport=ntohs(myport);
      myhost=ntohl(myhost);
      printf("Server is waiting on %s:%d X-overflow: %d\n",
              inet_ntoa(*((struct in_addr *)&myhost)),myport,myport+59536);   
      printf("Htons: %d\n",htons(myport));

     // Ahora a por el segundo reply cuando conecte el application server
      FD_ZERO (&rfds);
      FD_SET (outsocket, &rfds);
      tv.tv_sec = 60;
      tv.tv_usec = 0;
      select (outsocket + 1, &rfds, NULL, NULL, &tv);
      if (!FD_ISSET (outsocket, &rfds))
      {
          warning(dest,timedout);
          exit(-1);
      }
      if ((recv (outsocket, buffer, 8, 0)) < 8)
      {
          warning(dest,permdeny);
          exit (-1);
      }
      if (buffer[1] != 0x5A)
      {
          warning(dest,portclosed);
          exit (-1);
      }
      return 1;
}

void uso (char *name)
{
  fprintf(stderr, "SOCKS binder by dreyer ;)\n");
  fprintf(stderr, "Uso: %s hostpermit bouncehost bounceport sockshost socksport\n"
  , name);
  exit(-1);
     
}

int main (int argc, char *argv[])
{
  short localport, destport;
  struct hostent *socks_he,*bounce_he;
  struct sockaddr_in socks_sa,bounce_sa;
  char buffer[BUFSIZE];
  char *chain_host,*localhost,*hostpermit;
  int maxfd,length;


  if (argc!=6) uso(argv[0]);

  hostpermit =argv[1];
  localhost=argv[2];
  localport = atoi (argv[3]);
  chain_host=argv[4];
  destport = atoi (argv[5]);


  if ((socks_he = gethostbyname (chain_host)) == NULL)
    {
      printf ("Procesando '%s' ",chain_host);
      herror ("gethostbyname");
      exit (1);
    } 
  memset (&socks_sa, 0, sizeof (struct sockaddr_in));
  memcpy (&socks_sa.sin_addr.s_addr, socks_he->h_addr_list[0], socks_he->h_length);
  socks_sa.sin_family = AF_INET;
  socks_sa.sin_port = htons (destport);
  outsocket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if ((connect (outsocket, (struct sockaddr *) &socks_sa, sizeof (struct sockaddr_in))) == -1)
    {
      printf("Error:%s:%s\n",chain_host,sockshostdown);
      exit (1);
    }


  if ((bounce_he=gethostbyname (localhost)) == NULL)
    {
      printf ("Procesando '%s' ",localhost);
      herror ("gethostbyname");
      exit (1);
    }
  memset (&bounce_sa, 0, sizeof (struct sockaddr_in));
  memcpy (&bounce_sa.sin_addr.s_addr, bounce_he->h_addr_list[0], bounce_he->h_length);
  bounce_sa.sin_family = AF_INET;
  bounce_sa.sin_port = htons (localport);
  bouncesocket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if ((connect (bouncesocket, (struct sockaddr *) &bounce_sa, sizeof (struct sockaddr_in))) == -1)
    {
      printf("Error conectando con %s\n",localhost);
      exit (1);
    }

     printf("Doing bind to %s on %d with server %s\n",localhost,localport,chain_host);
     bind_socks4(hostpermit,0,chain_host); 
       /* background */
  switch (fork ())
    {
    case -1:
      perror ("fork");
      exit (1);
      break;
    case 0:
      close (STDIN_FILENO);
      close (STDOUT_FILENO);
      close (STDERR_FILENO);
      if (setsid () == -1)
        {
          perror ("setsid");
          exit (1);
        }
      break;
    default:
      return 0;
    }
      /* main loop */
  maxfd = bouncesocket > outsocket ? bouncesocket : outsocket;
  while (1)
    {
      FD_ZERO (&rfds);
      FD_SET (bouncesocket, &rfds);
      FD_SET (outsocket, &rfds);
      select (maxfd + 1, &rfds, NULL, NULL, NULL);
      if (FD_ISSET (bouncesocket, &rfds))
        {
          length = recv (bouncesocket, buffer, sizeof (buffer), 0);
          if (length == -1 || length == 0)
            break;
          if ((send (outsocket, buffer, length, 0)) == -1)
            break;
        }
      if (FD_ISSET (outsocket, &rfds))
        {
          length = recv (outsocket, buffer, sizeof (buffer), 0);
          if (length == -1 || length == 0)
            break;
          if ((send (bouncesocket, buffer, length, 0)) == -1)
            break;
        }
    }
     exit (0);
}

