
/* http://www.anticode.com  for the latest exploits, tools and documents! */

/* sbouncer v0.0.4b by Stok 27 Feb 1999

 * ChangeLog:
 *            v0.0.4b - Fixed some small compilation bugs - Thanks diab.
 *            v0.0.4  - SOCKS5 username/password authentication
 *            v0.0.3  - Implemented non-authenticated SOCKS5 support
 *            v0.0.2  - The rewrite
 *
 * This is a complete rewrite of the patched-irc-bouncer-thingy
 * I wrote some years ago.
 *
 * Usage: sbouncer locallistenport sockshost desthost destport [-4 | -u username:password]
 *
 * Examples: 
 * ./sbouncer 9999 wingate.blaha.com www.fbi.gov 23 ; telnet localhost 9999
 * ./sbouncer 8888 socks4proxy.blaha.com www.whitehouse.gov 80 -4 ; telnet localhost 8888
 * ./sbouncer 8888 passwordprotectedwingate.blaha.com www.c5.org 80 -u loser:wimp ; telnet localhost 8888
 *
 * Official distsite - http://www.c5.org
 *
 */

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <netinet/in.h>

#define BUFSIZE 512
#define SOCKSPORT 1080

const char portclosed[] = "socks: Destination port closed (?)\n";
const char sockshostdown[] = "The socks host seems to be down\n";
const char permdeny[] = "socks: Permission denied\n";
const char timedout[] = "socks: Connection timeout\n";


int
main (int argc, char **argv)
{
  int listensocket, insocket, outsocket;
  short listenport, destport;
  struct hostent *socks_he, *dest_he = 0;
  struct sockaddr_in listen_sa, socks_sa;
  int sopts = 1, maxfd;
  char buffer[BUFSIZE];
  int length = 0, error = 0, use_userpass = 0;
  char username[256], password[256];
#ifdef MYDEBUG
  int cnt;
#endif
  int socks5 = 1, socks4 = 0;
  struct timeval tv;
  fd_set rfds;

  if (argc < 5)
    {
      printf ("Usage: %s locallistenport sockshost desthost destport [-4 | -u username:password]\n", argv[0]);
      printf ("       -4 = socks4, default is socks5\n");
      exit (1);
    }

  if (argc >= 6)
    {
      if (strcmp (argv[5], "-4") == 0)
	{
	  socks4 = 1;
	  socks5 = 0;
	}
      else if (strcmp (argv[5], "-u") == 0 && argc >= 7)
	{
	  use_userpass = 1;
	  strncpy (username, argv[6], (void *) index (argv[6], ':') - (void *) argv[6]);
	  username[strlen (username)] = 0;
	  strncpy (password, index (argv[6], ':') + 1, strlen (argv[6]) - strlen (username));
	  password[strlen (password)] = 0;
	}
      else
	{
	  printf ("Usage: %s locallistenport sockshost desthost destport [-4]\n", argv[0]);
	  exit (1);
	}
    }

  if ((socks_he = gethostbyname (argv[2])) == NULL)
    {
      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;
  if (socks4)
    {
      if ((dest_he = gethostbyname (argv[3])) == NULL)
	{
	  herror ("gethostbyname");
	  exit (1);
	}
    }

  /* no need for errorchecking. only fools mess these up */
  listenport = atoi (argv[1]);
  destport = atoi (argv[4]);

  listensocket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
  setsockopt (listensocket, SOL_SOCKET, SO_REUSEADDR, (void *) &sopts, sizeof (int));

  memset (&listen_sa, 0, sizeof (struct sockaddr_in));

  listen_sa.sin_port = htons (listenport);
  listen_sa.sin_addr.s_addr = htonl (INADDR_ANY);

  socks_sa.sin_port = htons (SOCKSPORT);

  if ((bind (listensocket, (struct sockaddr *) &listen_sa, sizeof (struct sockaddr_in))) == -1)
    {
      perror ("bind");
      exit (1);
    }
  if ((listen (listensocket, 1)) == -1)
    {
      perror ("listen");
      exit (1);
    }

  /* background stuff */
  switch (fork ())
    {
    case -1:
      perror ("fork");
      exit (1);
      break;
    case 0:
#ifndef MYDEBUG
      close (STDIN_FILENO);
      close (STDOUT_FILENO);
      close (STDERR_FILENO);
#endif
      if (setsid () == -1)
	{
	  perror ("setsid");
	  exit (1);
	}
      break;
    default:
      return 0;
    }

  insocket = accept (listensocket, NULL, 0);
  if (insocket == -1)
    {
      perror ("accept");
      exit (1);
    }
  close (listensocket);
  outsocket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if ((connect (outsocket, (struct sockaddr *) &socks_sa, sizeof (struct sockaddr_in))) == -1)
    {
      send (insocket, sockshostdown, sizeof (sockshostdown), 0);
      exit (1);
    }

  if (socks4)
    {
      snprintf (buffer, sizeof (buffer), "\x04\x01%c%c%c%c%c%c", (destport >> 8) & 0xFF, destport & 0xFF,	/* <-- port */
		(char) dest_he->h_addr[0], (char) dest_he->h_addr[1], (char) dest_he->h_addr[2], (char) dest_he->h_addr[3]);	/* <-- ip# */
      /* errorchecking sucks */
      send (outsocket, buffer, 9, 0);

#ifdef MYDEBUG
      for (length = 0; length < 8; length++)
	printf ("%02X:", (unsigned char) buffer[length]);
      printf ("\n");
      for (length = 0; length < 8; length++)
	if (buffer[length] > 'A' && buffer[length] < 'z')
	  printf (" %c:", (unsigned char) buffer[length]);
	else
	  printf (" *:");
      printf ("\n");
#endif
    }
  else if (socks5)
    {
      /* socks authentication negotiation - rfc1928 */
      snprintf (buffer, sizeof (buffer), "\x05\x01%c", 0);
      if (use_userpass)
	buffer[2] = 0x02;
      send (outsocket, buffer, 3, 0);
      FD_ZERO (&rfds);
      FD_SET (outsocket, &rfds);
      tv.tv_sec = 20;
      tv.tv_usec = 0;
      select (outsocket + 1, &rfds, NULL, NULL, &tv);
      if (!FD_ISSET (outsocket, &rfds))
	{
	  send (insocket, timedout, sizeof (timedout), 0);
	  exit (1);
	}

      length = recv (outsocket, buffer, 100, 0);
#ifdef MYDEBUG
      for (cnt = 0; cnt < length; cnt++)
	printf ("%02X:", (unsigned char) buffer[cnt]);
      printf ("\n");
#endif

      /* username/password subnegotiation - rfc1929 */
      if (use_userpass)
	{
	  length = snprintf (buffer, sizeof (buffer), "\x01%c%s%c%s", strlen (username), username, strlen (password), password);
	  send (outsocket, buffer, length, 0);
	  FD_ZERO (&rfds);
	  FD_SET (outsocket, &rfds);
	  tv.tv_sec = 20;
	  tv.tv_usec = 0;
	  select (outsocket + 1, &rfds, NULL, NULL, &tv);
	  if (!FD_ISSET (outsocket, &rfds))
	    {
	      send (insocket, timedout, sizeof (timedout), 0);
	      exit (1);
	    }
	  recv (outsocket, buffer, 2, 0);
	  if (buffer[1] != 0x00)
	    {
	      snprintf (buffer, sizeof (buffer), "Username/password invalid\n");
	      send (insocket, buffer, strlen (buffer), 0);
	      exit (1);
	    }
	}

      /* socks request - rfc1928 */
      length = snprintf (buffer, sizeof (buffer), "\x05\x01%c\x03%c%s%c%c", 0, strlen (argv[3]), argv[3],
			 (destport >> 8) & 0xFF, destport & 0xFF);
#ifdef MYDEBUG
      for (cnt = 0; cnt < length; cnt++)
	printf ("%02X:", (unsigned char) buffer[cnt]);
      printf ("\n");
#endif
      /* errorchecking sucks */
      send (outsocket, buffer, length, 0);
    }

  FD_ZERO (&rfds);
  FD_SET (outsocket, &rfds);
  tv.tv_sec = 20;
  tv.tv_usec = 0;
  select (outsocket + 1, &rfds, NULL, NULL, &tv);
  if (!FD_ISSET (outsocket, &rfds))
    {
      send (insocket, timedout, sizeof (timedout), 0);
      exit (1);
    }

  /* recieve responce. did we succeed? */
  if (socks4)
    {
      if ((recv (outsocket, buffer, 8, 0)) < 8)
	{
	  send (insocket, permdeny, sizeof (permdeny), 0);
	  exit (1);
	}
    }
  else if (socks5)
    {
      if ((length = recv (outsocket, buffer, length, 0)) <= 0)
	{
	  send (insocket, permdeny, sizeof (permdeny), 0);
	  exit (1);
	}
#ifdef MYDEBUG
      for (cnt = 0; cnt < length; cnt++)
	printf ("%02X:", (unsigned char) buffer[cnt]);
      printf ("\n");
#endif
    }

  /* handle errors etc */
  if (socks4)
    {
      if (buffer[1] != 0x5A)
	{
	  send (insocket, portclosed, sizeof (portclosed), 0);
	  exit (1);
	}
#ifdef MYDEBUG
      for (length = 0; length < 8; length++)
	printf ("%02X:", (unsigned char) buffer[length]);
      printf ("\n");
      for (length = 0; length < 8; length++)
	if (buffer[length] > 'A' && buffer[length] < 'z')
	  printf (" %c:", (unsigned char) buffer[length]);
	else
	  printf (" *:");
      printf ("\n");
#endif
    }
  else if (socks5)
    {
      switch (buffer[1])
	{
	case 0x00:
	  break;
	case 0x01:
	  snprintf (buffer, sizeof (buffer), "socks: General SOCKS server failure\n");
	  error = 1;
	  break;
	case 0x02:
	  snprintf (buffer, sizeof (buffer), "socks: Permission denied\n");
	  error = 1;
	  break;
	case 0x03:
	case 0x04:
	  snprintf (buffer, sizeof (buffer), "socks: Net/Host unreachable\n");
	  error = 1;
	  break;
	case 0x05:
	  snprintf (buffer, sizeof (buffer), "socks: Connection refused\n");
	  error = 1;
	  break;
	default:
	  snprintf (buffer, sizeof (buffer), "socks: Error %d\n", buffer[1]);
	  error = 1;
	  break;
	}
      if (error)
	{
	  send (insocket, buffer, strlen (buffer), 0);
	  exit (1);
	}
    }

  maxfd = insocket > outsocket ? insocket : outsocket;

  /* main loop */
  while (1)
    {
      FD_ZERO (&rfds);
      FD_SET (insocket, &rfds);
      FD_SET (outsocket, &rfds);
      select (maxfd + 1, &rfds, NULL, NULL, NULL);
      if (FD_ISSET (insocket, &rfds))
	{
	  length = recv (insocket, 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 (insocket, buffer, length, 0)) == -1)
	    break;
	}
    }

  exit (0);
}
